Wire up IREE compilation and runtime in a new backend test.

* Adds python bindings for invoking flow, HAL, and VM lowering pipelines.
* Adds pythong bindings for translating to VM module flatbuffer.
* Adds a new backend_test/iree directory and configure lit to find the IREE python rt bindings.
* Open code a simple_invoke.py that exercises the whole pipeline (need real APIs for a lot of this).
* Fails when invoking the function because I never implemented argument marshaling for scalars :(
* Plenty of stuff to do tomorrow.
pull/1/head
Stella Laurenzo 2020-06-19 00:30:34 -07:00
parent 373878f31f
commit 529873d13c
11 changed files with 282 additions and 7 deletions

View File

@ -58,8 +58,9 @@ if(NPCOMP_ENABLE_IREE)
string(APPEND NPCOMP_TABLEGEN_ARGS "-DNPCOMP_ENABLE_IREE") string(APPEND NPCOMP_TABLEGEN_ARGS "-DNPCOMP_ENABLE_IREE")
if(NPCOMP_IREE_SRCDIR) if(NPCOMP_IREE_SRCDIR)
message(STATUS "Depending on IREE source: ${NPCOMP_IREE_SRCDIR}") message(STATUS "Depending on IREE source: ${NPCOMP_IREE_SRCDIR}")
set(IREE_BUILD_TESTS OFF CACHE BOOL "Override IREE setting") set(IREE_BUILD_TESTS OFF CACHE BOOL "Override IREE setting" FORCE)
set(IREE_BUILD_SAMPLES OFF CACHE BOOL "Override IREE setting") set(IREE_BUILD_SAMPLES OFF CACHE BOOL "Override IREE setting" FORCE)
set(IREE_BUILD_PYTHON_BINDINGS ON CACHE BOOL "Override IREE setting" FORCE)
set(IREE_MLIR_DEP_MODE "DISABLED" CACHE STRING "Override IREE setting") set(IREE_MLIR_DEP_MODE "DISABLED" CACHE STRING "Override IREE setting")
add_subdirectory("${NPCOMP_IREE_SRCDIR}" "iree" EXCLUDE_FROM_ALL) add_subdirectory("${NPCOMP_IREE_SRCDIR}" "iree" EXCLUDE_FROM_ALL)
else() else()
@ -99,3 +100,7 @@ add_subdirectory(pytest)
add_subdirectory(runtime) add_subdirectory(runtime)
add_subdirectory(tools) add_subdirectory(tools)
add_subdirectory(test) add_subdirectory(test)
if(NPCOMP_ENABLE_IREE)
add_subdirectory(backend_test/iree)
endif()

View File

@ -0,0 +1,26 @@
configure_lit_site_cfg(
${CMAKE_CURRENT_SOURCE_DIR}/lit.site.cfg.py.in
${CMAKE_CURRENT_BINARY_DIR}/lit.site.cfg.py
MAIN_CONFIG
${CMAKE_CURRENT_SOURCE_DIR}/lit.cfg.py
)
set(NPCOMP_TEST_DEPENDS
FileCheck count not
npcomp-opt
NPCOMPNativePyExt
# TODO: Fix this so it has an IREE prefix
bindings_python_pyiree_rt_rt
# TODO: Why is this separate?
bindings_python_pyiree_rt_system_api
)
add_lit_testsuite(check-npcomp-backend-iree-lit "Running npcomp IREE tests"
${CMAKE_CURRENT_BINARY_DIR}
DEPENDS ${NPCOMP_TEST_DEPENDS}
)
set_target_properties(check-npcomp-backend-iree-lit PROPERTIES FOLDER "Tests")
add_lit_testsuites(NPCOMP_PYTEST ${CMAKE_CURRENT_SOURCE_DIR} DEPENDS ${NPCOMP_TEST_DEPENDS})
add_dependencies(check-npcomp check-npcomp-backend-iree-lit)

View File

@ -0,0 +1,53 @@
# XFAIL: *
# RUN: %PYTHON %s
from npcomp.compiler.frontend import *
from npcomp.compiler.target import *
# TODO: This should all exist in a high level API somewhere.
from _npcomp import mlir
from _npcomp.backend import iree as ireec
from pyiree import rt
def compile_function(f):
fe = ImportFrontend(target_factory=GenericTarget32)
ir_f = fe.import_global_function(f)
input_m = fe.ir_module
# For easier debugging, split into to pass manager invocations.
pm = mlir.passes.PassManager(input_m.context)
# TOOD: Have an API for this
pm.addPassPipelines(
"basicpy-type-inference", "convert-basicpy-to-std", "canonicalize")
pm.run(input_m)
print("INPUT MODULE:")
print(input_m.to_asm())
# Main IREE compiler.
pm = mlir.passes.PassManager(input_m.context)
ireec.build_flow_transform_pass_pipeline(pm)
ireec.build_hal_transform_pass_pipeline(pm)
ireec.build_vm_transform_pass_pipeline(pm)
pm.run(input_m)
print("VM MODULE:")
print(input_m.to_asm())
# Translate to VM bytecode flatbuffer.
vm_blob = ireec.translate_to_vm_bytecode(input_m)
print("VM BLOB: len =", len(vm_blob))
return vm_blob
def int_add(a: int, b: int):
return a + b
vm_blob = compile_function(int_add)
m = rt.VmModule.from_flatbuffer(vm_blob)
config = rt.Config("vmla")
ctx = rt.SystemContext(config=config)
ctx.add_module(m)
f = ctx.modules.module.int_add
print(f(5, 6))

View File

@ -0,0 +1,80 @@
# -*- Python -*-
import os
import platform
import re
import subprocess
import sys
import tempfile
import lit.formats
import lit.util
from lit.llvm import llvm_config
from lit.llvm.subst import ToolSubst
from lit.llvm.subst import FindTool
# Configuration file for the 'lit' test runner.
# name: The name of this test suite.
config.name = 'NPCOMP_BACKEND_IREE'
config.test_format = lit.formats.ShTest(not llvm_config.use_lit_shell)
# suffixes: A list of file extensions to treat as test files.
config.suffixes = ['.mlir', '.py']
# test_source_root: The root path where tests are located.
config.test_source_root = os.path.dirname(__file__)
# test_exec_root: The root path where tests should be run.
config.test_exec_root = os.path.join(config.npcomp_obj_root, 'backend_test', 'iree')
config.substitutions.append(('%PATH%', config.environment['PATH']))
config.substitutions.append(('%shlibext', config.llvm_shlib_ext))
config.substitutions.append(('%PYTHON', config.python_executable))
llvm_config.with_system_environment(
['HOME', 'INCLUDE', 'LIB', 'NPCOMP_DEBUG', 'TMP', 'TEMP'])
llvm_config.use_default_substitutions()
# excludes: A list of files/directories to exclude from the testsuite. The
# 'Inputs'subdirectories contain auxiliary inputs for various tests in their
# parent directories.
config.excludes = [
'Inputs', 'Examples', 'lit.cfg.py', 'CMakeLists.txt', 'README.txt',
'LICENSE.txt'
]
# test_source_root: The root path where tests are located.
config.test_source_root = os.path.dirname(__file__)
# test_exec_root: The root path where tests should be run.
config.test_exec_root = os.path.join(config.npcomp_obj_root, 'backend_test', 'iree')
config.npcomp_tools_dir = os.path.join(config.npcomp_obj_root, 'tools')
config.npcomp_runtime_shlib = os.path.join(
config.npcomp_obj_root, 'runtime',
'libNPCOMPRuntime' + config.llvm_shlib_ext)
# Tweak the PATH and PYTHONPATH to include the tools dir.
llvm_config.with_environment('PATH', config.llvm_tools_dir, append_path=True)
llvm_config.with_environment('PYTHONPATH', [
os.path.join(config.npcomp_obj_root, "python"),
os.path.join(config.npcomp_obj_root, "python_native"),
os.path.join(config.npcomp_obj_root, "iree", "bindings", "python"),
],
append_path=True)
tool_dirs = [
os.path.join(config.npcomp_tools_dir, 'npcomp-opt'),
os.path.join(config.npcomp_tools_dir, 'npcomp-run-mlir'),
config.llvm_tools_dir,
]
tools = [
'npcomp-opt',
'npcomp-run-mlir',
ToolSubst('%npcomp_runtime_shlib', config.npcomp_runtime_shlib),
]
llvm_config.add_tool_substitutions(tools, tool_dirs)

View File

@ -0,0 +1,49 @@
@LIT_SITE_CFG_IN_HEADER@
import sys
config.host_triple = "@LLVM_HOST_TRIPLE@"
config.target_triple = "@TARGET_TRIPLE@"
config.llvm_src_root = "@LLVM_SOURCE_DIR@"
config.llvm_obj_root = "@LLVM_BINARY_DIR@"
config.llvm_tools_dir = "@LLVM_TOOLS_DIR@"
config.llvm_lib_dir = "@LLVM_LIBRARY_DIR@"
config.llvm_shlib_dir = "@SHLIBDIR@"
config.llvm_shlib_ext = "@SHLIBEXT@"
config.llvm_exe_ext = "@EXEEXT@"
config.lit_tools_dir = "@LLVM_LIT_TOOLS_DIR@"
config.python_executable = "@PYTHON_EXECUTABLE@"
config.gold_executable = "@GOLD_EXECUTABLE@"
config.ld64_executable = "@LD64_EXECUTABLE@"
config.enable_shared = @ENABLE_SHARED@
config.enable_assertions = @ENABLE_ASSERTIONS@
config.targets_to_build = "@TARGETS_TO_BUILD@"
config.native_target = "@LLVM_NATIVE_ARCH@"
config.llvm_bindings = "@LLVM_BINDINGS@".split(' ')
config.host_os = "@HOST_OS@"
config.host_cc = "@HOST_CC@"
config.host_cxx = "@HOST_CXX@"
# Note: ldflags can contain double-quoted paths, so must use single quotes here.
config.host_ldflags = '@HOST_LDFLAGS@'
config.llvm_use_sanitizer = "@LLVM_USE_SANITIZER@"
config.llvm_host_triple = '@LLVM_HOST_TRIPLE@'
config.host_arch = "@HOST_ARCH@"
config.npcomp_src_root = "@CMAKE_SOURCE_DIR@"
config.npcomp_obj_root = "@CMAKE_BINARY_DIR@"
# Support substitution of the tools_dir with user parameters. This is
# used when we can't determine the tool dir at configuration time.
try:
config.llvm_tools_dir = config.llvm_tools_dir % lit_config.params
config.llvm_shlib_dir = config.llvm_shlib_dir % lit_config.params
except KeyError:
e = sys.exc_info()[1]
key, = e.args
lit_config.fatal("unable to find %r parameter, use '--param=%s=VALUE'" % (key,key))
import lit.llvm
lit.llvm.initialize(lit_config, config)
# Let the main config do the real work.
lit_config.load_config(config, "@CMAKE_SOURCE_DIR@/backend_test/iree/lit.cfg.py")

View File

@ -295,6 +295,8 @@ def Basicpy_UnknownCastOp : Basicpy_Op<"unknown_cast", [NoSideEffect]> {
let arguments = (ins AnyType:$operand); let arguments = (ins AnyType:$operand);
let results = (outs AnyType:$result); let results = (outs AnyType:$result);
let assemblyFormat = "operands attr-dict `:` type(operands) `->` type(results)"; let assemblyFormat = "operands attr-dict `:` type(operands) `->` type(results)";
let hasCanonicalizer = 1;
} }
#endif // NPCOMP_DIALECT_BASICPY_IR_BASICPY_OPS #endif // NPCOMP_DIALECT_BASICPY_IR_BASICPY_OPS

View File

@ -31,6 +31,7 @@ public:
/// Defines an "iree" module with backend support definitions. /// Defines an "iree" module with backend support definitions.
void mlir::npcomp::python::defineBackendIREEModule(py::module m) { void mlir::npcomp::python::defineBackendIREEModule(py::module m) {
py::class_<Blob>(m, "Blob", py::buffer_protocol()) py::class_<Blob>(m, "Blob", py::buffer_protocol())
.def("__len__", [](Blob &self) { return self.contents.size(); })
.def_buffer([](Blob &self) -> py::buffer_info { .def_buffer([](Blob &self) -> py::buffer_info {
return py::buffer_info( return py::buffer_info(
static_cast<void *>(&self.contents.front()), // Pointer to buffer static_cast<void *>(&self.contents.front()), // Pointer to buffer

View File

@ -10,6 +10,7 @@
#include "mlir/IR/Builders.h" #include "mlir/IR/Builders.h"
#include "mlir/IR/FunctionImplementation.h" #include "mlir/IR/FunctionImplementation.h"
#include "mlir/IR/OpImplementation.h" #include "mlir/IR/OpImplementation.h"
#include "mlir/IR/PatternMatch.h"
#include "npcomp/Dialect/Basicpy/IR/BasicpyDialect.h" #include "npcomp/Dialect/Basicpy/IR/BasicpyDialect.h"
#include "npcomp/Dialect/Basicpy/IR/BasicpyOpsEnums.cpp.inc" #include "npcomp/Dialect/Basicpy/IR/BasicpyOpsEnums.cpp.inc"
@ -192,6 +193,31 @@ OpFoldResult StrConstantOp::fold(ArrayRef<Attribute> operands) {
return valueAttr(); return valueAttr();
} }
//===----------------------------------------------------------------------===//
// UnknownCastOp
//===----------------------------------------------------------------------===//
namespace {
class ElideIdentityUnknownCast : public OpRewritePattern<UnknownCastOp> {
public:
using OpRewritePattern::OpRewritePattern;
LogicalResult matchAndRewrite(UnknownCastOp op,
PatternRewriter &rewriter) const {
if (op.operand().getType() != op.result().getType())
return failure();
rewriter.replaceOp(op, op.operand());
return success();
}
};
} // namespace
void UnknownCastOp::getCanonicalizationPatterns(
OwningRewritePatternList &patterns, MLIRContext *context) {
patterns.insert<ElideIdentityUnknownCast>(context);
}
#define GET_OP_CLASSES #define GET_OP_CLASSES
#include "npcomp/Dialect/Basicpy/IR/BasicpyOps.cpp.inc" #include "npcomp/Dialect/Basicpy/IR/BasicpyOps.cpp.inc"
} // namespace Basicpy } // namespace Basicpy

View File

@ -224,17 +224,26 @@ void PyDialectHelper::bind(py::module m) {
py::arg("attrs") = llvm::Optional<PyAttribute>()) py::arg("attrs") = llvm::Optional<PyAttribute>())
.def("func_op", .def("func_op",
[](PyDialectHelper &self, const std::string &name, PyType type, [](PyDialectHelper &self, const std::string &name, PyType type,
bool createEntryBlock) { bool createEntryBlock, llvm::Optional<PyAttribute> attrs) {
auto functionType = type.type.dyn_cast_or_null<FunctionType>(); auto functionType = type.type.dyn_cast_or_null<FunctionType>();
if (!functionType) { if (!functionType) {
throw py::raiseValueError("Illegal function type"); throw py::raiseValueError("Illegal function type");
} }
OpBuilder &opBuilder = self.pyOpBuilder.getBuilder(true); OpBuilder &opBuilder = self.pyOpBuilder.getBuilder(true);
Location loc = self.pyOpBuilder.getCurrentLoc(); Location loc = self.pyOpBuilder.getCurrentLoc();
// TODO: Add function and arg/result attributes. // TODO: Dedup attr creation from op().
MutableDictionaryAttr attrList;
if (attrs) {
auto dictAttrs = attrs->attr.dyn_cast<DictionaryAttr>();
if (!dictAttrs) {
throw py::raiseValueError(
"Expected `attrs` to be a DictionaryAttr");
}
attrList = MutableDictionaryAttr(dictAttrs);
}
FuncOp op = FuncOp op =
opBuilder.create<FuncOp>(loc, StringRef(name), functionType, opBuilder.create<FuncOp>(loc, StringRef(name), functionType,
/*attrs=*/ArrayRef<NamedAttribute>()); /*attrs=*/attrList.getAttrs());
if (createEntryBlock) { if (createEntryBlock) {
Block *entryBlock = new Block(); Block *entryBlock = new Block();
entryBlock->addArguments(functionType.getInputs()); entryBlock->addArguments(functionType.getInputs());
@ -245,6 +254,7 @@ void PyDialectHelper::bind(py::module m) {
}, },
py::arg("name"), py::arg("type"), py::arg("name"), py::arg("type"),
py::arg("create_entry_block") = false, py::arg("create_entry_block") = false,
py::arg("attrs") = llvm::Optional<PyAttribute>(),
R"(Creates a new `func` op, optionally creating an entry block. R"(Creates a new `func` op, optionally creating an entry block.
If an entry block is created, the builder will be positioned If an entry block is created, the builder will be positioned
to its start.)") to its start.)")
@ -507,7 +517,10 @@ void PyContext::bind(py::module m) {
return createDenseElementsAttrFromBuffer(&self.context, return createDenseElementsAttrFromBuffer(&self.context,
array_info); array_info);
}, },
py::arg("array")); py::arg("array"))
.def_property_readonly("unit_attr", [](PyContext &self) -> PyAttribute {
return UnitAttr::get(&self.context);
});
} }
PyModuleOp PyContext::parseAsm(const std::string &asm_text) { PyModuleOp PyContext::parseAsm(const std::string &asm_text) {

View File

@ -105,7 +105,12 @@ class ImportFrontend:
h.builder.set_file_line_col(filename_ident, ast_fd.lineno, h.builder.set_file_line_col(filename_ident, ast_fd.lineno,
ast_fd.col_offset) ast_fd.col_offset)
h.builder.insert_before_terminator(ir_m.first_block) h.builder.insert_before_terminator(ir_m.first_block)
ir_f = h.func_op(ast_fd.name, ir_f_type, create_entry_block=True) # TODO: Do not hardcode this IREE attribute.
attrs = ir_c.dictionary_attr({"iree.module.export": ir_c.unit_attr})
ir_f = h.func_op(ast_fd.name,
ir_f_type,
create_entry_block=True,
attrs=attrs)
fctx = FunctionContext(ir_c=ir_c, fctx = FunctionContext(ir_c=ir_c,
ir_f=ir_f, ir_f=ir_f,
ir_h=h, ir_h=h,

View File

@ -0,0 +1,15 @@
// RUN: npcomp-opt -split-input-file %s | npcomp-opt -canonicalize | FileCheck --dump-input=fail %s
// CHECK-LABEL: func @unknown_cast_elide
func @unknown_cast_elide(%arg0 : i32) -> i32 {
// CHECK-NOT: basicpy.unknown_cast
%0 = basicpy.unknown_cast %arg0 : i32 -> i32
return %0 : i32
}
// CHECK-LABEL: func @unknown_cast_preserve
func @unknown_cast_preserve(%arg0 : i32) -> !basicpy.UnknownType {
// CHECK: basicpy.unknown_cast
%0 = basicpy.unknown_cast %arg0 : i32 -> !basicpy.UnknownType
return %0 : !basicpy.UnknownType
}