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")
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()

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 results = (outs AnyType:$result);
let assemblyFormat = "operands attr-dict `:` type(operands) `->` type(results)";
let hasCanonicalizer = 1;
}
#endif // NPCOMP_DIALECT_BASICPY_IR_BASICPY_OPS

View File

@ -31,6 +31,7 @@ public:
/// Defines an "iree" module with backend support definitions.
void mlir::npcomp::python::defineBackendIREEModule(py::module m) {
py::class_<Blob>(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<void *>(&self.contents.front()), // Pointer to buffer

View File

@ -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<Attribute> operands) {
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
#include "npcomp/Dialect/Basicpy/IR/BasicpyOps.cpp.inc"
} // namespace Basicpy

View File

@ -224,17 +224,26 @@ void PyDialectHelper::bind(py::module m) {
py::arg("attrs") = llvm::Optional<PyAttribute>())
.def("func_op",
[](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>();
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<DictionaryAttr>();
if (!dictAttrs) {
throw py::raiseValueError(
"Expected `attrs` to be a DictionaryAttr");
}
attrList = MutableDictionaryAttr(dictAttrs);
}
FuncOp op =
opBuilder.create<FuncOp>(loc, StringRef(name), functionType,
/*attrs=*/ArrayRef<NamedAttribute>());
/*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<PyAttribute>(),
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) {

View File

@ -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,

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
}