diff --git a/CMakeLists.txt b/CMakeLists.txt index 42e188a55..46c3c1833 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,8 +58,9 @@ if(NPCOMP_ENABLE_IREE) string(APPEND NPCOMP_TABLEGEN_ARGS "-DNPCOMP_ENABLE_IREE") if(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_SAMPLES 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" FORCE) + set(IREE_BUILD_PYTHON_BINDINGS ON CACHE BOOL "Override IREE setting" FORCE) set(IREE_MLIR_DEP_MODE "DISABLED" CACHE STRING "Override IREE setting") add_subdirectory("${NPCOMP_IREE_SRCDIR}" "iree" EXCLUDE_FROM_ALL) else() @@ -99,3 +100,7 @@ add_subdirectory(pytest) add_subdirectory(runtime) add_subdirectory(tools) add_subdirectory(test) + +if(NPCOMP_ENABLE_IREE) + add_subdirectory(backend_test/iree) +endif() diff --git a/backend_test/iree/CMakeLists.txt b/backend_test/iree/CMakeLists.txt new file mode 100644 index 000000000..121548571 --- /dev/null +++ b/backend_test/iree/CMakeLists.txt @@ -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) diff --git a/backend_test/iree/Sample/simple_invoke.py b/backend_test/iree/Sample/simple_invoke.py new file mode 100644 index 000000000..c9e947eb6 --- /dev/null +++ b/backend_test/iree/Sample/simple_invoke.py @@ -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)) diff --git a/backend_test/iree/lit.cfg.py b/backend_test/iree/lit.cfg.py new file mode 100644 index 000000000..9e6270424 --- /dev/null +++ b/backend_test/iree/lit.cfg.py @@ -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) diff --git a/backend_test/iree/lit.site.cfg.py.in b/backend_test/iree/lit.site.cfg.py.in new file mode 100644 index 000000000..844042253 --- /dev/null +++ b/backend_test/iree/lit.site.cfg.py.in @@ -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") diff --git a/include/npcomp/Dialect/Basicpy/IR/BasicpyOps.td b/include/npcomp/Dialect/Basicpy/IR/BasicpyOps.td index d40baa746..e94b3bceb 100644 --- a/include/npcomp/Dialect/Basicpy/IR/BasicpyOps.td +++ b/include/npcomp/Dialect/Basicpy/IR/BasicpyOps.td @@ -295,6 +295,8 @@ def Basicpy_UnknownCastOp : Basicpy_Op<"unknown_cast", [NoSideEffect]> { let arguments = (ins AnyType:$operand); let results = (outs AnyType:$result); let assemblyFormat = "operands attr-dict `:` type(operands) `->` type(results)"; + + let hasCanonicalizer = 1; } #endif // NPCOMP_DIALECT_BASICPY_IR_BASICPY_OPS diff --git a/lib/Backend/IREE/PythonModule.cpp b/lib/Backend/IREE/PythonModule.cpp index 4c79b39d5..1a9f3dd42 100644 --- a/lib/Backend/IREE/PythonModule.cpp +++ b/lib/Backend/IREE/PythonModule.cpp @@ -31,6 +31,7 @@ public: /// Defines an "iree" module with backend support definitions. void mlir::npcomp::python::defineBackendIREEModule(py::module m) { py::class_(m, "Blob", py::buffer_protocol()) + .def("__len__", [](Blob &self) { return self.contents.size(); }) .def_buffer([](Blob &self) -> py::buffer_info { return py::buffer_info( static_cast(&self.contents.front()), // Pointer to buffer diff --git a/lib/Dialect/Basicpy/IR/BasicpyOps.cpp b/lib/Dialect/Basicpy/IR/BasicpyOps.cpp index 83a675533..0a850d82d 100644 --- a/lib/Dialect/Basicpy/IR/BasicpyOps.cpp +++ b/lib/Dialect/Basicpy/IR/BasicpyOps.cpp @@ -10,6 +10,7 @@ #include "mlir/IR/Builders.h" #include "mlir/IR/FunctionImplementation.h" #include "mlir/IR/OpImplementation.h" +#include "mlir/IR/PatternMatch.h" #include "npcomp/Dialect/Basicpy/IR/BasicpyDialect.h" #include "npcomp/Dialect/Basicpy/IR/BasicpyOpsEnums.cpp.inc" @@ -192,6 +193,31 @@ OpFoldResult StrConstantOp::fold(ArrayRef operands) { return valueAttr(); } +//===----------------------------------------------------------------------===// +// UnknownCastOp +//===----------------------------------------------------------------------===// + +namespace { + +class ElideIdentityUnknownCast : public OpRewritePattern { +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(context); +} + #define GET_OP_CLASSES #include "npcomp/Dialect/Basicpy/IR/BasicpyOps.cpp.inc" } // namespace Basicpy diff --git a/lib/Python/MlirIr.cpp b/lib/Python/MlirIr.cpp index 729a8bbd0..4826e939e 100644 --- a/lib/Python/MlirIr.cpp +++ b/lib/Python/MlirIr.cpp @@ -224,17 +224,26 @@ void PyDialectHelper::bind(py::module m) { py::arg("attrs") = llvm::Optional()) .def("func_op", [](PyDialectHelper &self, const std::string &name, PyType type, - bool createEntryBlock) { + bool createEntryBlock, llvm::Optional attrs) { auto functionType = type.type.dyn_cast_or_null(); if (!functionType) { throw py::raiseValueError("Illegal function type"); } OpBuilder &opBuilder = self.pyOpBuilder.getBuilder(true); 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(); + if (!dictAttrs) { + throw py::raiseValueError( + "Expected `attrs` to be a DictionaryAttr"); + } + attrList = MutableDictionaryAttr(dictAttrs); + } FuncOp op = opBuilder.create(loc, StringRef(name), functionType, - /*attrs=*/ArrayRef()); + /*attrs=*/attrList.getAttrs()); if (createEntryBlock) { Block *entryBlock = new Block(); entryBlock->addArguments(functionType.getInputs()); @@ -245,6 +254,7 @@ void PyDialectHelper::bind(py::module m) { }, py::arg("name"), py::arg("type"), py::arg("create_entry_block") = false, + py::arg("attrs") = llvm::Optional(), R"(Creates a new `func` op, optionally creating an entry block. If an entry block is created, the builder will be positioned to its start.)") @@ -507,7 +517,10 @@ void PyContext::bind(py::module m) { return createDenseElementsAttrFromBuffer(&self.context, 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) { diff --git a/python/npcomp/compiler/frontend.py b/python/npcomp/compiler/frontend.py index 1bf983350..73d666180 100644 --- a/python/npcomp/compiler/frontend.py +++ b/python/npcomp/compiler/frontend.py @@ -105,7 +105,12 @@ class ImportFrontend: h.builder.set_file_line_col(filename_ident, ast_fd.lineno, ast_fd.col_offset) 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, ir_f=ir_f, ir_h=h, diff --git a/test/Dialect/Basicpy/canonicalize.mlir b/test/Dialect/Basicpy/canonicalize.mlir new file mode 100644 index 000000000..3be2ed341 --- /dev/null +++ b/test/Dialect/Basicpy/canonicalize.mlir @@ -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 +}