From 9c3e7c679c23e4bf4b60a3516d678c1e48a6291d Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sat, 24 Nov 2018 01:27:33 +0000 Subject: [PATCH 01/10] Implement C code generation with tests --- python/tvm/_ffi/libinfo.py | 60 +++++ python/tvm/_ffi/runtime_ctypes.py | 1 + python/tvm/contrib/cc.py | 2 + python/tvm/module.py | 11 +- src/codegen/build_module.cc | 4 + src/codegen/codegen_c.cc | 9 +- src/codegen/codegen_c_host.cc | 252 +++++++++++++++++++ src/codegen/codegen_c_host.h | 40 +++ src/codegen/codegen_source_base.h | 7 + src/codegen/source_module.cc | 46 ++++ tests/python/unittest/test_codegen_c_host.py | 83 ++++++ 11 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 src/codegen/codegen_c_host.cc create mode 100644 src/codegen/codegen_c_host.h create mode 100644 tests/python/unittest/test_codegen_c_host.py diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py index f911829d38b1..2fdf5aeb132a 100644 --- a/python/tvm/_ffi/libinfo.py +++ b/python/tvm/_ffi/libinfo.py @@ -99,6 +99,66 @@ def find_lib_path(name=None, search_path=None, optional=False): return lib_found +def find_include_path(name=None, search_path=None, optional=False): + """Find header files for C compilation. + + Parameters + ---------- + name : list of str + List of directory names to be searched. + + Returns + ------- + include_path : list(string) + List of all found paths to header files. + """ + ffi_dir = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) + source_dir = os.path.join(ffi_dir, "..", "..", "..") + install_include_dir = os.path.join(ffi_dir, "..", "..", "..", "..") + third_party_dir = os.path.join(source_dir, "3rdparty") + + header_path = [] + + if os.environ.get('TVM_INCLUDE_PATH', None): + header_path.append(os.environ['TVM_INCLUDE_PATH']) + + header_path.append(install_include_dir) + header_path.append(source_dir) + header_path.append(third_party_dir) + + header_path = [os.path.abspath(x) for x in header_path] + if search_path is not None: + if search_path is list: + header_path = header_path + search_path + else: + header_path.append(search_path) + if name is not None: + if isinstance(name, list): + tvm_include_path = [] + for n in name: + tvm_include_path += [os.path.join(p, n) for p in header_path] + else: + tvm_include_path = [os.path.join(p, name) for p in header_path] + dlpack_include_path = [] + else: + tvm_include_path = [os.path.join(p, 'include') for p in header_path] + dlpack_include_path = [os.path.join(p, 'dlpack/include') for p in header_path] + + # try to find include path + include_found = [p for p in tvm_include_path if os.path.exists(p) and os.path.isdir(p)] + include_found += [p for p in dlpack_include_path if os.path.exists(p) and os.path.isdir(p)] + + if not include_found: + message = ('Cannot find the files.\n' + + 'List of candidates:\n' + + str('\n'.join(tvm_include_path + dlpack_include_path))) + if not optional: + raise RuntimeError(message) + return None + + return include_found + + # current version # We use the version of the incoming release for code # that is under development. diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py index b17487559e50..ef5316b5e267 100644 --- a/python/tvm/_ffi/runtime_ctypes.py +++ b/python/tvm/_ffi/runtime_ctypes.py @@ -118,6 +118,7 @@ class TVMContext(ctypes.Structure): 'llvm': 1, 'stackvm': 1, 'cpu': 1, + 'c': 1, 'gpu': 2, 'cuda': 2, 'nvptx': 2, diff --git a/python/tvm/contrib/cc.py b/python/tvm/contrib/cc.py index 0ffa6c420243..4b02db67ef3d 100644 --- a/python/tvm/contrib/cc.py +++ b/python/tvm/contrib/cc.py @@ -7,6 +7,7 @@ from .._ffi.base import py_str from .util import tempdir +from .._ffi.libinfo import find_include_path def create_shared(output, @@ -49,6 +50,7 @@ def _linux_shared(output, objects, options, cc="g++"): cmd += objects if options: cmd += options + cmd += ["-I"+path for path in find_include_path()] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (out, _) = proc.communicate() diff --git a/python/tvm/module.py b/python/tvm/module.py index 1ca09740aff4..48e88edfc624 100644 --- a/python/tvm/module.py +++ b/python/tvm/module.py @@ -6,6 +6,7 @@ from ._ffi.function import ModuleBase, _set_class_module from ._ffi.function import _init_api +from ._ffi.libinfo import find_include_path from .contrib import cc as _cc, tar as _tar, util as _util ProfileResult = namedtuple("ProfileResult", ["mean", "results"]) @@ -97,17 +98,21 @@ def export_library(self, self.save(file_name) return - if self.type_key != "llvm": + if not (self.type_key == "llvm" or self.type_key == "c"): raise ValueError("Module[%s]: Only llvm support export shared" % self.type_key) temp = _util.tempdir() if fcompile is not None and hasattr(fcompile, "object_format"): object_format = fcompile.object_format else: - object_format = "o" + if self.type_key == "llvm": + object_format = "o" + else: + object_format = "cc" path_obj = temp.relpath("lib." + object_format) self.save(path_obj) files = [path_obj] - is_system_lib = self.get_function("__tvm_is_system_module")() + if self.type_key == "llvm": + is_system_lib = self.get_function("__tvm_is_system_module")() if self.imported_modules: path_cc = temp.relpath("devc.cc") with open(path_cc, "w") as f: diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc index 5c0a5e07cd2a..68715eb07dfd 100644 --- a/src/codegen/build_module.cc +++ b/src/codegen/build_module.cc @@ -103,6 +103,10 @@ Target CreateTarget(const std::string& target_name, t->device_type = kDLCPU; } else if (target_name == "ext_dev") { t->device_type = kDLExtDev; + } else if (target_name == "c") { + // TODO: get this to work + t->device_type = kDLCPU; + t->keys_array.push_back(ir::StringImm::make("c")); } else { LOG(ERROR) << "Unknown target name " << target_name; return target::stackvm(); diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc index d902437dd990..d1c87a69aa48 100644 --- a/src/codegen/codegen_c.cc +++ b/src/codegen/codegen_c.cc @@ -187,10 +187,11 @@ std::string CodeGenC::GetStructRef( case intrinsic::kArrNDim: os << "ndim"; break; case intrinsic::kArrTypeCode: os << "dtype.code"; break; case intrinsic::kArrTypeBits: os << "dtype.bits"; break; + case intrinsic::kArrByteOffset: os << "byte_offset"; break; case intrinsic::kArrTypeLanes: os << "dtype.lanes"; break; case intrinsic::kArrDeviceId: os << "ctx.device_id"; break; case intrinsic::kArrDeviceType: os << "ctx.device_type"; break; - default: LOG(FATAL) << "unknown field code"; + default: LOG(FATAL) << "unknown field code" << static_cast(kind); } os << ')'; return os.str(); @@ -834,8 +835,10 @@ void CodeGenC::VisitStmt_(const Evaluate *op) { } } std::string vid = this->PrintExpr(op->value); - this->PrintIndent(); - this->stream << "(void)" << vid << ";\n"; + if (vid != "") { + this->PrintIndent(); + this->stream << "(void)" << vid << ";\n"; + } } void CodeGenC::VisitStmt_(const ProducerConsumer *op) { diff --git a/src/codegen/codegen_c_host.cc b/src/codegen/codegen_c_host.cc new file mode 100644 index 000000000000..93ffa0bf93d0 --- /dev/null +++ b/src/codegen/codegen_c_host.cc @@ -0,0 +1,252 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file codegen_c_host.cc + */ +#include +#include +#include +#include "codegen_c_host.h" +#include "build_common.h" + +namespace tvm { +namespace codegen { + +CodeGenCHost::CodeGenCHost() { + module_name = GetUniqueName("__tvm_module_ctx"); +} + +void CodeGenCHost::Init(bool output_ssa) { + decl_stream << "#include \"tvm/runtime/c_runtime_api.h\"\n"; + decl_stream << "#include \"tvm/runtime/c_backend_api.h\"\n"; + decl_stream << "extern void* " << module_name << " = NULL;\n"; + CodeGenC::Init(output_ssa); +} + +void CodeGenCHost::AddFunction(LoweredFunc f) { + // clear previous generated state. + this->InitFuncState(f); + // skip the first underscore, so SSA variable starts from _1 + GetUniqueName("_"); + GetUniqueName("extern"); + // add to alloc buffer type. + for (const auto & kv : f->handle_data_type) { + RegisterHandleType(kv.first.get(), kv.second.type()); + } + + this->stream << "#ifdef __cplusplus\n"; + this->stream << "extern \"C\"\n"; + this->stream << "#endif\n"; + this->stream << "TVM_DLL int32_t " << f->name << "("; + for (size_t i = 0; i < f->args.size(); ++i) { + Var v = f->args[i]; + std::string vid = AllocVarID(v.get()); + if (i != 0) stream << ", "; + if (v.type().is_handle()) { + auto it = alloc_storage_scope_.find(v.get()); + if (it != alloc_storage_scope_.end()) + PrintStorageScope(it->second, stream); + stream << ' '; + + if (handle_data_type_.count(v.get())) { + PrintType(handle_data_type_.at(v.get()), stream); + } else { + stream << "void"; + } + stream << "*"; + + if (f->is_restricted && restrict_keyword_.length() != 0) { + stream << ' ' << restrict_keyword_; + } + } else { + PrintType(v.type(), stream); + } + stream << ' ' << vid; + } + stream << ") {\n"; + this->PreFunctionBody(f); + int func_scope = this->BeginScope(); + this->PrintStmt(f->body); + this->PrintIndent(); + this->stream << "return 0;\n"; + this->EndScope(func_scope); + this->PrintIndent(); + this->stream << "}\n\n"; +} + +std::string CodeGenCHost::Finish() { + return CodeGenC::Finish(); +} + +void CodeGenCHost::PrintType(Type t, std::ostream& os) { // NOLINT(*) + int lanes = t.lanes(); + if (t.is_handle()) { + CHECK_EQ(lanes, 1) + << "does not support vector types"; + os << "void*"; return; + } + if (t == Bool()) { + os << "bool"; return; + } + bool fail = false; + if (t.is_float()) { + switch (t.bits()) { + case 16: + os << "half"; + break; + case 32: os << "float"; break; + case 64: + os << "double"; + break; + default: fail = true; break; + } + if (!fail && lanes == 1) return; + if (!fail && (lanes >= 2 && lanes <= 16)) { + os << lanes; return; + } + } else if (t.is_uint() || t.is_int()) { + if (t.is_uint()) { + os << 'u'; + } + switch (t.bits()) { + case 8: os << "int8_t"; break; + case 16: os << "int16_t"; break; + case 32: os << "int32_t"; break; + case 64: os << "int64_t"; break; + case 1: os << "int32_t"; break; + default: fail = true; break; + } + if (!fail && lanes == 1) return; + if (!fail && (lanes >= 2 && lanes <= 16)) { + os << lanes; return; + } + } + LOG(FATAL) << "Cannot convert type " << t << " to C type"; +} + +void CodeGenCHost::VisitExpr_(const Broadcast* op, std::ostream& os) { // NOLINT(*) + std::string v = PrintExpr(op->value); + os << "(("; + PrintType(op->type, os); + os << ")("; + for (int i = 0; i < op->lanes; ++i) { + if (i != 0) os << ", "; + os << v; + } + os << "))"; +} + +void CodeGenCHost::PrintGetFuncFromBackend(std::string func_name, std::string packed_func_name) { + this->PrintIndent(); + this->stream << "if (" << packed_func_name << " == NULL) {\n"; + int packed_func_if_scope = this->BeginScope(); + this->PrintIndent(); + this->stream << "if (TVMBackendGetFuncFromEnv(" << module_name + << ", \"" << func_name << "\"" + << ", &" << packed_func_name << ") != 0) {\n"; + int get_func_env_scope = this->BeginScope(); + this->PrintIndent(); + this->stream << "return -1;\n"; + this->EndScope(get_func_env_scope); + this->PrintIndent(); + this->stream << "}\n"; + this->EndScope(packed_func_if_scope); + this->PrintIndent(); + this->stream << "}\n"; +} + +void CodeGenCHost::PrintFuncCall(std::string packed_func_name, int num_args) { + this->PrintIndent(); + std::string ret_val = GetUniqueName("ret_val"); + std::string ret_type_code = GetUniqueName("ret_type_code"); + this->stream << "TVMValue " << ret_val << ";\n"; + this->PrintIndent(); + this->stream << "int " << ret_type_code << ";\n"; + this->PrintIndent(); + this->stream << "if (TVMFuncCall(" << packed_func_name << ", " + << "(TVMValue*) stack_value" << ", " << "(int*) stack_tcode" << ", " + << num_args << ", " << "&" << ret_val << ", " << "&" + << ret_type_code << ") != 0) {\n"; + int func_call_scope = this->BeginScope(); + this->PrintIndent(); + this->stream << "return -1;\n"; + this->EndScope(func_call_scope); + this->PrintIndent(); + this->stream << "}\n"; +} + +void CodeGenCHost::VisitExpr_(const Call *op, std::ostream& os) { // NOLINT(*) + if (op->is_intrinsic(intrinsic::tvm_stack_alloca)) { + std::string stack_name = GetUniqueName("stack"); + const std::string& type = op->args[0].as()->value; + const IntImm* num = op->args[1].as(); + CHECK(num != nullptr); + static_assert(alignof(TVMValue) % alignof(TVMArray) == 0, "invariant"); + size_t unit = sizeof(TVMValue); + size_t size = 0; + if (type == "shape") { + size = (num->value * sizeof(tvm_index_t) + unit - 1) / unit; + } else if (type == "arg_value") { + size = (num->value * sizeof(TVMValue) + unit - 1) / unit; + } else if (type == "arg_tcode") { + size = (num->value * sizeof(int) + unit - 1) / unit; + } else if (type == "array") { + size = (num->value * sizeof(TVMArray) + unit - 1) / unit; + } else { + LOG(FATAL) << "Unknown stack alloca type " << type; + } + this->PrintIndent(); + this->stream << "TVMValue " << stack_name << "[" << size << "];\n"; + os << stack_name; + } else if (op->is_intrinsic(intrinsic::tvm_call_packed_lowered)) { + const StringImm* s = op->args[0].as(); + CHECK(s != nullptr) << "tvm_call_packed_lowered expects first argument as function name"; + int64_t begin = op->args[3].as()->value; + int64_t end = op->args[4].as()->value; + int64_t num_args = end - begin; + CHECK_GE(num_args, 0); + std::string func_name = s->value; + std::string packed_func_name = GetUniqueName(func_name + "_packed"); + decl_stream << "static void* " << packed_func_name << " = NULL;\n"; + this->PrintGetFuncFromBackend(func_name, packed_func_name); + this->PrintFuncCall(packed_func_name, num_args); + } else if (op->is_intrinsic(intrinsic::tvm_throw_last_error)) { + this->PrintIndent(); + this->stream << "return -1;\n"; + } else { + CodeGenC::VisitExpr_(op, os); + } +} + +void CodeGenCHost::VisitStmt_(const AssertStmt *op) { // NOLINT(*) + std::string cond = PrintExpr(op->condition); + PrintIndent(); + stream << "if (!(" << cond << ")) {\n"; + int assert_if_scope = this->BeginScope(); + PrintIndent(); + stream << "TVMAPISetLastError(\"" << op->message.as()->value << "\");\n"; + PrintIndent(); + stream << "return -1;\n"; + this->EndScope(assert_if_scope); + PrintIndent(); + stream << "}\n"; + this->PrintStmt(op->body); +} + +runtime::Module BuildCHost(Array funcs) { + using tvm::runtime::Registry; + bool output_ssa = false; + CodeGenCHost cg; + cg.Init(output_ssa); + for (LoweredFunc f : funcs) { + cg.AddFunction(f); + } + std::string code = cg.Finish(); + return CSourceModuleCreate(code, "c"); +} + +TVM_REGISTER_API("codegen.build_c") +.set_body([](TVMArgs args, TVMRetValue* rv) { + *rv = BuildCHost(args[0]); + }); +} // namespace codegen +} // namespace tvm diff --git a/src/codegen/codegen_c_host.h b/src/codegen/codegen_c_host.h new file mode 100644 index 000000000000..ea962a5b2989 --- /dev/null +++ b/src/codegen/codegen_c_host.h @@ -0,0 +1,40 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file codegen_opencl.h + * \brief Generate OpenCL device code. + */ +#ifndef TVM_CODEGEN_CODEGEN_C_HOST_H_ +#define TVM_CODEGEN_CODEGEN_C_HOST_H_ + +#include +#include +#include +#include "codegen_c.h" + +namespace tvm { +namespace codegen { + +class CodeGenCHost final : public CodeGenC { + public: + CodeGenCHost(); + void Init(bool output_ssa); + void AddFunction(LoweredFunc f); + std::string Finish(); + + void PrintType(Type t, std::ostream& os) final; // NOLINT(*) + + // overload visitor functions + void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*) + void VisitExpr_(const Call *op, std::ostream& os) final; // NOLINT(*) + void VisitStmt_(const AssertStmt *op) final; // NOLINT(*) + + private: + std::string module_name; + void PrintGetFuncFromBackend(std::string func_name, std::string packed_func_name); + void PrintFuncCall(std::string packed_func_name, int num_args); +}; + +} // namespace codegen +} // namespace tvm + +#endif // TVM_CODEGEN_CODEGEN_C_HOST_H_ diff --git a/src/codegen/codegen_source_base.h b/src/codegen/codegen_source_base.h index d2f80a538a33..3fc46c35c7f7 100644 --- a/src/codegen/codegen_source_base.h +++ b/src/codegen/codegen_source_base.h @@ -112,6 +112,13 @@ class CodeGenSourceBase { */ runtime::Module SourceModuleCreate(std::string code, std::string fmt); +/*! + * \brief Create a C source module for viewing and compiling GCC code. + * \param code The code to be viewed. + * \param fmt The code. format. + */ +runtime::Module CSourceModuleCreate(std::string code, std::string fmt); + /*! * \brief Create a source module for viewing and limited saving for device. * \param data The code data to be viewed. diff --git a/src/codegen/source_module.cc b/src/codegen/source_module.cc index c7100e18735e..56facea1567f 100644 --- a/src/codegen/source_module.cc +++ b/src/codegen/source_module.cc @@ -53,6 +53,52 @@ runtime::Module SourceModuleCreate(std::string code, std::string fmt) { return runtime::Module(n); } +// Simulator function +class CSourceModuleNode : public runtime::ModuleNode { + public: + CSourceModuleNode(std::string code, + std::string fmt) + : code_(code), fmt_(fmt) {} + const char* type_key() const { + return "c"; + } + + PackedFunc GetFunction( + const std::string& name, + const std::shared_ptr& sptr_to_self) final { + LOG(FATAL) << "C Source module cannot execute, to get executable module" + << " build TVM with \'" << fmt_ << "\' runtime support"; + return PackedFunc(); + } + + std::string GetSource(const std::string& format) final { + return code_; + } + + void SaveToFile(const std::string& file_name, + const std::string& format) final { + std::string fmt = GetFileFormat(file_name, format); + std::string meta_file = GetMetaFilePath(file_name); + if (fmt == "cc") { + CHECK_NE(code_.length(), 0); + SaveBinaryToFile(file_name, code_); + } else { + CHECK_EQ(fmt, fmt_) + << "Can only save to format=" << fmt_; + } + } + + protected: + std::string code_; + std::string fmt_; +}; + +runtime::Module CSourceModuleCreate(std::string code, std::string fmt) { + std::shared_ptr n = + std::make_shared(code, fmt); + return runtime::Module(n); +} + // supports limited save without cross compile class DeviceSourceModuleNode final : public runtime::ModuleNode { public: diff --git a/tests/python/unittest/test_codegen_c_host.py b/tests/python/unittest/test_codegen_c_host.py new file mode 100644 index 000000000000..31f43b20b766 --- /dev/null +++ b/tests/python/unittest/test_codegen_c_host.py @@ -0,0 +1,83 @@ +import tvm +import numpy as np +from tvm._ffi.libinfo import find_include_path + +def test_add(): + nn = 1024 + n = tvm.convert(nn) + A = tvm.placeholder((n,), name='A') + B = tvm.placeholder((n,), name='B') + C = tvm.compute(A.shape, lambda *i: A(*i) + B(*i), name='C') + s = tvm.create_schedule(C.op) + + def check_c(): + f1 = tvm.lower(s, [A, B, C], name="fadd") + fsplits = [x for x in tvm.ir_pass.SplitHostDevice(f1)] + fsplits[0] = tvm.ir_pass.LowerTVMBuiltin(fsplits[0]) + mhost = tvm.codegen.build_module(fsplits[0], "c") + mhost.export_library("mod.so") + m = tvm.module.load("mod.so") + fadd = m['fadd'] + ctx = tvm.cpu(0) + # launch the kernel. + n = nn + a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx) + b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx) + c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx) + fadd(a, b, c) + tvm.testing.assert_allclose( + c.asnumpy(), a.asnumpy() + b.asnumpy()) + check_c() + +def test_add_pipeline(): + nn = 1024 + n = tvm.convert(nn) + A = tvm.placeholder((n,), name='A') + B = tvm.placeholder((n,), name='B') + AA = tvm.compute((n,), lambda *i: A(*i), name='A') + BB = tvm.compute((n,), lambda *i: B(*i), name='B') + T = tvm.compute(A.shape, lambda *i: AA(*i) + BB(*i), name='T') + C = tvm.compute(A.shape, lambda *i: T(*i), name='C') + s = tvm.create_schedule(C.op) + xo, xi = s[C].split(C.op.axis[0], factor=4) + xo1, xo2 = s[C].split(xo, factor=13) + s[C].parallel(xo2) + s[C].pragma(xo1, "parallel_launch_point") + s[C].pragma(xo2, "parallel_stride_pattern") + s[C].pragma(xo2, "parallel_barrier_when_finish") + s[C].vectorize(xi) + + def check_llvm(): + if not tvm.module.enabled("llvm"): + return + # Specifically allow offset to test codepath when offset is available + Ab = tvm.decl_buffer( + A.shape, A.dtype, + elem_offset=tvm.var('Aoffset'), + offset_factor=8, + name='A') + binds = {A : Ab} + # BUILD and invoke the kernel. + f1 = tvm.lower(s, [A,B,C], name="fadd_pipeline") + fsplits = [x for x in tvm.ir_pass.SplitHostDevice(f1)] + fsplits[0] = tvm.ir_pass.LowerTVMBuiltin(fsplits[0]) + mhost = tvm.codegen.build_module(fsplits[0], "c") + mhost.export_library("mod.so") + m = tvm.module.load("mod.so") + fadd = m["fadd_pipeline"] + ctx = tvm.cpu(0) + # launch the kernel. + n = nn + a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx) + b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx) + c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx) + fadd(a, b, c) + tvm.testing.assert_allclose( + c.asnumpy(), a.asnumpy() + b.asnumpy()) + + with tvm.build_config(offset_factor=4): + check_llvm() + +if __name__ == "__main__": + test_add() + test_add_pipeline() From 433f4cece532fb1fb72c33ce22cfa0e852e5362b Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sat, 24 Nov 2018 01:44:10 +0000 Subject: [PATCH 02/10] Code cleanup --- src/codegen/build_module.cc | 4 ---- src/codegen/codegen_c.cc | 2 +- tests/python/unittest/test_codegen_c_host.py | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc index 68715eb07dfd..5c0a5e07cd2a 100644 --- a/src/codegen/build_module.cc +++ b/src/codegen/build_module.cc @@ -103,10 +103,6 @@ Target CreateTarget(const std::string& target_name, t->device_type = kDLCPU; } else if (target_name == "ext_dev") { t->device_type = kDLExtDev; - } else if (target_name == "c") { - // TODO: get this to work - t->device_type = kDLCPU; - t->keys_array.push_back(ir::StringImm::make("c")); } else { LOG(ERROR) << "Unknown target name " << target_name; return target::stackvm(); diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc index d1c87a69aa48..e29cff172393 100644 --- a/src/codegen/codegen_c.cc +++ b/src/codegen/codegen_c.cc @@ -191,7 +191,7 @@ std::string CodeGenC::GetStructRef( case intrinsic::kArrTypeLanes: os << "dtype.lanes"; break; case intrinsic::kArrDeviceId: os << "ctx.device_id"; break; case intrinsic::kArrDeviceType: os << "ctx.device_type"; break; - default: LOG(FATAL) << "unknown field code" << static_cast(kind); + default: LOG(FATAL) << "unknown field code"; } os << ')'; return os.str(); diff --git a/tests/python/unittest/test_codegen_c_host.py b/tests/python/unittest/test_codegen_c_host.py index 31f43b20b766..ca53d595cd5d 100644 --- a/tests/python/unittest/test_codegen_c_host.py +++ b/tests/python/unittest/test_codegen_c_host.py @@ -47,7 +47,7 @@ def test_add_pipeline(): s[C].pragma(xo2, "parallel_barrier_when_finish") s[C].vectorize(xi) - def check_llvm(): + def check_c(): if not tvm.module.enabled("llvm"): return # Specifically allow offset to test codepath when offset is available @@ -76,7 +76,7 @@ def check_llvm(): c.asnumpy(), a.asnumpy() + b.asnumpy()) with tvm.build_config(offset_factor=4): - check_llvm() + check_c() if __name__ == "__main__": test_add() From f10e3a114e96e9ef11a20a18614a262fc3a434b6 Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sat, 24 Nov 2018 01:27:33 +0000 Subject: [PATCH 03/10] Implement C code generation with tests --- python/tvm/_ffi/libinfo.py | 60 +++++ python/tvm/_ffi/runtime_ctypes.py | 1 + python/tvm/contrib/cc.py | 2 + python/tvm/module.py | 11 +- src/codegen/build_module.cc | 4 + src/codegen/codegen_c.cc | 9 +- src/codegen/codegen_c_host.cc | 252 +++++++++++++++++++ src/codegen/codegen_c_host.h | 40 +++ src/codegen/codegen_source_base.h | 7 + src/codegen/source_module.cc | 46 ++++ tests/python/unittest/test_codegen_c_host.py | 83 ++++++ 11 files changed, 509 insertions(+), 6 deletions(-) create mode 100644 src/codegen/codegen_c_host.cc create mode 100644 src/codegen/codegen_c_host.h create mode 100644 tests/python/unittest/test_codegen_c_host.py diff --git a/python/tvm/_ffi/libinfo.py b/python/tvm/_ffi/libinfo.py index f911829d38b1..2fdf5aeb132a 100644 --- a/python/tvm/_ffi/libinfo.py +++ b/python/tvm/_ffi/libinfo.py @@ -99,6 +99,66 @@ def find_lib_path(name=None, search_path=None, optional=False): return lib_found +def find_include_path(name=None, search_path=None, optional=False): + """Find header files for C compilation. + + Parameters + ---------- + name : list of str + List of directory names to be searched. + + Returns + ------- + include_path : list(string) + List of all found paths to header files. + """ + ffi_dir = os.path.dirname(os.path.abspath(os.path.expanduser(__file__))) + source_dir = os.path.join(ffi_dir, "..", "..", "..") + install_include_dir = os.path.join(ffi_dir, "..", "..", "..", "..") + third_party_dir = os.path.join(source_dir, "3rdparty") + + header_path = [] + + if os.environ.get('TVM_INCLUDE_PATH', None): + header_path.append(os.environ['TVM_INCLUDE_PATH']) + + header_path.append(install_include_dir) + header_path.append(source_dir) + header_path.append(third_party_dir) + + header_path = [os.path.abspath(x) for x in header_path] + if search_path is not None: + if search_path is list: + header_path = header_path + search_path + else: + header_path.append(search_path) + if name is not None: + if isinstance(name, list): + tvm_include_path = [] + for n in name: + tvm_include_path += [os.path.join(p, n) for p in header_path] + else: + tvm_include_path = [os.path.join(p, name) for p in header_path] + dlpack_include_path = [] + else: + tvm_include_path = [os.path.join(p, 'include') for p in header_path] + dlpack_include_path = [os.path.join(p, 'dlpack/include') for p in header_path] + + # try to find include path + include_found = [p for p in tvm_include_path if os.path.exists(p) and os.path.isdir(p)] + include_found += [p for p in dlpack_include_path if os.path.exists(p) and os.path.isdir(p)] + + if not include_found: + message = ('Cannot find the files.\n' + + 'List of candidates:\n' + + str('\n'.join(tvm_include_path + dlpack_include_path))) + if not optional: + raise RuntimeError(message) + return None + + return include_found + + # current version # We use the version of the incoming release for code # that is under development. diff --git a/python/tvm/_ffi/runtime_ctypes.py b/python/tvm/_ffi/runtime_ctypes.py index b17487559e50..ef5316b5e267 100644 --- a/python/tvm/_ffi/runtime_ctypes.py +++ b/python/tvm/_ffi/runtime_ctypes.py @@ -118,6 +118,7 @@ class TVMContext(ctypes.Structure): 'llvm': 1, 'stackvm': 1, 'cpu': 1, + 'c': 1, 'gpu': 2, 'cuda': 2, 'nvptx': 2, diff --git a/python/tvm/contrib/cc.py b/python/tvm/contrib/cc.py index 0ffa6c420243..4b02db67ef3d 100644 --- a/python/tvm/contrib/cc.py +++ b/python/tvm/contrib/cc.py @@ -7,6 +7,7 @@ from .._ffi.base import py_str from .util import tempdir +from .._ffi.libinfo import find_include_path def create_shared(output, @@ -49,6 +50,7 @@ def _linux_shared(output, objects, options, cc="g++"): cmd += objects if options: cmd += options + cmd += ["-I"+path for path in find_include_path()] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (out, _) = proc.communicate() diff --git a/python/tvm/module.py b/python/tvm/module.py index 1ca09740aff4..48e88edfc624 100644 --- a/python/tvm/module.py +++ b/python/tvm/module.py @@ -6,6 +6,7 @@ from ._ffi.function import ModuleBase, _set_class_module from ._ffi.function import _init_api +from ._ffi.libinfo import find_include_path from .contrib import cc as _cc, tar as _tar, util as _util ProfileResult = namedtuple("ProfileResult", ["mean", "results"]) @@ -97,17 +98,21 @@ def export_library(self, self.save(file_name) return - if self.type_key != "llvm": + if not (self.type_key == "llvm" or self.type_key == "c"): raise ValueError("Module[%s]: Only llvm support export shared" % self.type_key) temp = _util.tempdir() if fcompile is not None and hasattr(fcompile, "object_format"): object_format = fcompile.object_format else: - object_format = "o" + if self.type_key == "llvm": + object_format = "o" + else: + object_format = "cc" path_obj = temp.relpath("lib." + object_format) self.save(path_obj) files = [path_obj] - is_system_lib = self.get_function("__tvm_is_system_module")() + if self.type_key == "llvm": + is_system_lib = self.get_function("__tvm_is_system_module")() if self.imported_modules: path_cc = temp.relpath("devc.cc") with open(path_cc, "w") as f: diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc index c5c14d711df7..7fbefa37ea0e 100644 --- a/src/codegen/build_module.cc +++ b/src/codegen/build_module.cc @@ -103,6 +103,10 @@ Target CreateTarget(const std::string& target_name, t->device_type = kDLCPU; } else if (target_name == "ext_dev") { t->device_type = kDLExtDev; + } else if (target_name == "c") { + // TODO: get this to work + t->device_type = kDLCPU; + t->keys_array.push_back(ir::StringImm::make("c")); } else { LOG(ERROR) << "Unknown target name " << target_name; return target::stackvm(); diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc index d902437dd990..d1c87a69aa48 100644 --- a/src/codegen/codegen_c.cc +++ b/src/codegen/codegen_c.cc @@ -187,10 +187,11 @@ std::string CodeGenC::GetStructRef( case intrinsic::kArrNDim: os << "ndim"; break; case intrinsic::kArrTypeCode: os << "dtype.code"; break; case intrinsic::kArrTypeBits: os << "dtype.bits"; break; + case intrinsic::kArrByteOffset: os << "byte_offset"; break; case intrinsic::kArrTypeLanes: os << "dtype.lanes"; break; case intrinsic::kArrDeviceId: os << "ctx.device_id"; break; case intrinsic::kArrDeviceType: os << "ctx.device_type"; break; - default: LOG(FATAL) << "unknown field code"; + default: LOG(FATAL) << "unknown field code" << static_cast(kind); } os << ')'; return os.str(); @@ -834,8 +835,10 @@ void CodeGenC::VisitStmt_(const Evaluate *op) { } } std::string vid = this->PrintExpr(op->value); - this->PrintIndent(); - this->stream << "(void)" << vid << ";\n"; + if (vid != "") { + this->PrintIndent(); + this->stream << "(void)" << vid << ";\n"; + } } void CodeGenC::VisitStmt_(const ProducerConsumer *op) { diff --git a/src/codegen/codegen_c_host.cc b/src/codegen/codegen_c_host.cc new file mode 100644 index 000000000000..93ffa0bf93d0 --- /dev/null +++ b/src/codegen/codegen_c_host.cc @@ -0,0 +1,252 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file codegen_c_host.cc + */ +#include +#include +#include +#include "codegen_c_host.h" +#include "build_common.h" + +namespace tvm { +namespace codegen { + +CodeGenCHost::CodeGenCHost() { + module_name = GetUniqueName("__tvm_module_ctx"); +} + +void CodeGenCHost::Init(bool output_ssa) { + decl_stream << "#include \"tvm/runtime/c_runtime_api.h\"\n"; + decl_stream << "#include \"tvm/runtime/c_backend_api.h\"\n"; + decl_stream << "extern void* " << module_name << " = NULL;\n"; + CodeGenC::Init(output_ssa); +} + +void CodeGenCHost::AddFunction(LoweredFunc f) { + // clear previous generated state. + this->InitFuncState(f); + // skip the first underscore, so SSA variable starts from _1 + GetUniqueName("_"); + GetUniqueName("extern"); + // add to alloc buffer type. + for (const auto & kv : f->handle_data_type) { + RegisterHandleType(kv.first.get(), kv.second.type()); + } + + this->stream << "#ifdef __cplusplus\n"; + this->stream << "extern \"C\"\n"; + this->stream << "#endif\n"; + this->stream << "TVM_DLL int32_t " << f->name << "("; + for (size_t i = 0; i < f->args.size(); ++i) { + Var v = f->args[i]; + std::string vid = AllocVarID(v.get()); + if (i != 0) stream << ", "; + if (v.type().is_handle()) { + auto it = alloc_storage_scope_.find(v.get()); + if (it != alloc_storage_scope_.end()) + PrintStorageScope(it->second, stream); + stream << ' '; + + if (handle_data_type_.count(v.get())) { + PrintType(handle_data_type_.at(v.get()), stream); + } else { + stream << "void"; + } + stream << "*"; + + if (f->is_restricted && restrict_keyword_.length() != 0) { + stream << ' ' << restrict_keyword_; + } + } else { + PrintType(v.type(), stream); + } + stream << ' ' << vid; + } + stream << ") {\n"; + this->PreFunctionBody(f); + int func_scope = this->BeginScope(); + this->PrintStmt(f->body); + this->PrintIndent(); + this->stream << "return 0;\n"; + this->EndScope(func_scope); + this->PrintIndent(); + this->stream << "}\n\n"; +} + +std::string CodeGenCHost::Finish() { + return CodeGenC::Finish(); +} + +void CodeGenCHost::PrintType(Type t, std::ostream& os) { // NOLINT(*) + int lanes = t.lanes(); + if (t.is_handle()) { + CHECK_EQ(lanes, 1) + << "does not support vector types"; + os << "void*"; return; + } + if (t == Bool()) { + os << "bool"; return; + } + bool fail = false; + if (t.is_float()) { + switch (t.bits()) { + case 16: + os << "half"; + break; + case 32: os << "float"; break; + case 64: + os << "double"; + break; + default: fail = true; break; + } + if (!fail && lanes == 1) return; + if (!fail && (lanes >= 2 && lanes <= 16)) { + os << lanes; return; + } + } else if (t.is_uint() || t.is_int()) { + if (t.is_uint()) { + os << 'u'; + } + switch (t.bits()) { + case 8: os << "int8_t"; break; + case 16: os << "int16_t"; break; + case 32: os << "int32_t"; break; + case 64: os << "int64_t"; break; + case 1: os << "int32_t"; break; + default: fail = true; break; + } + if (!fail && lanes == 1) return; + if (!fail && (lanes >= 2 && lanes <= 16)) { + os << lanes; return; + } + } + LOG(FATAL) << "Cannot convert type " << t << " to C type"; +} + +void CodeGenCHost::VisitExpr_(const Broadcast* op, std::ostream& os) { // NOLINT(*) + std::string v = PrintExpr(op->value); + os << "(("; + PrintType(op->type, os); + os << ")("; + for (int i = 0; i < op->lanes; ++i) { + if (i != 0) os << ", "; + os << v; + } + os << "))"; +} + +void CodeGenCHost::PrintGetFuncFromBackend(std::string func_name, std::string packed_func_name) { + this->PrintIndent(); + this->stream << "if (" << packed_func_name << " == NULL) {\n"; + int packed_func_if_scope = this->BeginScope(); + this->PrintIndent(); + this->stream << "if (TVMBackendGetFuncFromEnv(" << module_name + << ", \"" << func_name << "\"" + << ", &" << packed_func_name << ") != 0) {\n"; + int get_func_env_scope = this->BeginScope(); + this->PrintIndent(); + this->stream << "return -1;\n"; + this->EndScope(get_func_env_scope); + this->PrintIndent(); + this->stream << "}\n"; + this->EndScope(packed_func_if_scope); + this->PrintIndent(); + this->stream << "}\n"; +} + +void CodeGenCHost::PrintFuncCall(std::string packed_func_name, int num_args) { + this->PrintIndent(); + std::string ret_val = GetUniqueName("ret_val"); + std::string ret_type_code = GetUniqueName("ret_type_code"); + this->stream << "TVMValue " << ret_val << ";\n"; + this->PrintIndent(); + this->stream << "int " << ret_type_code << ";\n"; + this->PrintIndent(); + this->stream << "if (TVMFuncCall(" << packed_func_name << ", " + << "(TVMValue*) stack_value" << ", " << "(int*) stack_tcode" << ", " + << num_args << ", " << "&" << ret_val << ", " << "&" + << ret_type_code << ") != 0) {\n"; + int func_call_scope = this->BeginScope(); + this->PrintIndent(); + this->stream << "return -1;\n"; + this->EndScope(func_call_scope); + this->PrintIndent(); + this->stream << "}\n"; +} + +void CodeGenCHost::VisitExpr_(const Call *op, std::ostream& os) { // NOLINT(*) + if (op->is_intrinsic(intrinsic::tvm_stack_alloca)) { + std::string stack_name = GetUniqueName("stack"); + const std::string& type = op->args[0].as()->value; + const IntImm* num = op->args[1].as(); + CHECK(num != nullptr); + static_assert(alignof(TVMValue) % alignof(TVMArray) == 0, "invariant"); + size_t unit = sizeof(TVMValue); + size_t size = 0; + if (type == "shape") { + size = (num->value * sizeof(tvm_index_t) + unit - 1) / unit; + } else if (type == "arg_value") { + size = (num->value * sizeof(TVMValue) + unit - 1) / unit; + } else if (type == "arg_tcode") { + size = (num->value * sizeof(int) + unit - 1) / unit; + } else if (type == "array") { + size = (num->value * sizeof(TVMArray) + unit - 1) / unit; + } else { + LOG(FATAL) << "Unknown stack alloca type " << type; + } + this->PrintIndent(); + this->stream << "TVMValue " << stack_name << "[" << size << "];\n"; + os << stack_name; + } else if (op->is_intrinsic(intrinsic::tvm_call_packed_lowered)) { + const StringImm* s = op->args[0].as(); + CHECK(s != nullptr) << "tvm_call_packed_lowered expects first argument as function name"; + int64_t begin = op->args[3].as()->value; + int64_t end = op->args[4].as()->value; + int64_t num_args = end - begin; + CHECK_GE(num_args, 0); + std::string func_name = s->value; + std::string packed_func_name = GetUniqueName(func_name + "_packed"); + decl_stream << "static void* " << packed_func_name << " = NULL;\n"; + this->PrintGetFuncFromBackend(func_name, packed_func_name); + this->PrintFuncCall(packed_func_name, num_args); + } else if (op->is_intrinsic(intrinsic::tvm_throw_last_error)) { + this->PrintIndent(); + this->stream << "return -1;\n"; + } else { + CodeGenC::VisitExpr_(op, os); + } +} + +void CodeGenCHost::VisitStmt_(const AssertStmt *op) { // NOLINT(*) + std::string cond = PrintExpr(op->condition); + PrintIndent(); + stream << "if (!(" << cond << ")) {\n"; + int assert_if_scope = this->BeginScope(); + PrintIndent(); + stream << "TVMAPISetLastError(\"" << op->message.as()->value << "\");\n"; + PrintIndent(); + stream << "return -1;\n"; + this->EndScope(assert_if_scope); + PrintIndent(); + stream << "}\n"; + this->PrintStmt(op->body); +} + +runtime::Module BuildCHost(Array funcs) { + using tvm::runtime::Registry; + bool output_ssa = false; + CodeGenCHost cg; + cg.Init(output_ssa); + for (LoweredFunc f : funcs) { + cg.AddFunction(f); + } + std::string code = cg.Finish(); + return CSourceModuleCreate(code, "c"); +} + +TVM_REGISTER_API("codegen.build_c") +.set_body([](TVMArgs args, TVMRetValue* rv) { + *rv = BuildCHost(args[0]); + }); +} // namespace codegen +} // namespace tvm diff --git a/src/codegen/codegen_c_host.h b/src/codegen/codegen_c_host.h new file mode 100644 index 000000000000..ea962a5b2989 --- /dev/null +++ b/src/codegen/codegen_c_host.h @@ -0,0 +1,40 @@ +/*! + * Copyright (c) 2017 by Contributors + * \file codegen_opencl.h + * \brief Generate OpenCL device code. + */ +#ifndef TVM_CODEGEN_CODEGEN_C_HOST_H_ +#define TVM_CODEGEN_CODEGEN_C_HOST_H_ + +#include +#include +#include +#include "codegen_c.h" + +namespace tvm { +namespace codegen { + +class CodeGenCHost final : public CodeGenC { + public: + CodeGenCHost(); + void Init(bool output_ssa); + void AddFunction(LoweredFunc f); + std::string Finish(); + + void PrintType(Type t, std::ostream& os) final; // NOLINT(*) + + // overload visitor functions + void VisitExpr_(const Broadcast* op, std::ostream& os) final; // NOLINT(*) + void VisitExpr_(const Call *op, std::ostream& os) final; // NOLINT(*) + void VisitStmt_(const AssertStmt *op) final; // NOLINT(*) + + private: + std::string module_name; + void PrintGetFuncFromBackend(std::string func_name, std::string packed_func_name); + void PrintFuncCall(std::string packed_func_name, int num_args); +}; + +} // namespace codegen +} // namespace tvm + +#endif // TVM_CODEGEN_CODEGEN_C_HOST_H_ diff --git a/src/codegen/codegen_source_base.h b/src/codegen/codegen_source_base.h index d2f80a538a33..3fc46c35c7f7 100644 --- a/src/codegen/codegen_source_base.h +++ b/src/codegen/codegen_source_base.h @@ -112,6 +112,13 @@ class CodeGenSourceBase { */ runtime::Module SourceModuleCreate(std::string code, std::string fmt); +/*! + * \brief Create a C source module for viewing and compiling GCC code. + * \param code The code to be viewed. + * \param fmt The code. format. + */ +runtime::Module CSourceModuleCreate(std::string code, std::string fmt); + /*! * \brief Create a source module for viewing and limited saving for device. * \param data The code data to be viewed. diff --git a/src/codegen/source_module.cc b/src/codegen/source_module.cc index c7100e18735e..56facea1567f 100644 --- a/src/codegen/source_module.cc +++ b/src/codegen/source_module.cc @@ -53,6 +53,52 @@ runtime::Module SourceModuleCreate(std::string code, std::string fmt) { return runtime::Module(n); } +// Simulator function +class CSourceModuleNode : public runtime::ModuleNode { + public: + CSourceModuleNode(std::string code, + std::string fmt) + : code_(code), fmt_(fmt) {} + const char* type_key() const { + return "c"; + } + + PackedFunc GetFunction( + const std::string& name, + const std::shared_ptr& sptr_to_self) final { + LOG(FATAL) << "C Source module cannot execute, to get executable module" + << " build TVM with \'" << fmt_ << "\' runtime support"; + return PackedFunc(); + } + + std::string GetSource(const std::string& format) final { + return code_; + } + + void SaveToFile(const std::string& file_name, + const std::string& format) final { + std::string fmt = GetFileFormat(file_name, format); + std::string meta_file = GetMetaFilePath(file_name); + if (fmt == "cc") { + CHECK_NE(code_.length(), 0); + SaveBinaryToFile(file_name, code_); + } else { + CHECK_EQ(fmt, fmt_) + << "Can only save to format=" << fmt_; + } + } + + protected: + std::string code_; + std::string fmt_; +}; + +runtime::Module CSourceModuleCreate(std::string code, std::string fmt) { + std::shared_ptr n = + std::make_shared(code, fmt); + return runtime::Module(n); +} + // supports limited save without cross compile class DeviceSourceModuleNode final : public runtime::ModuleNode { public: diff --git a/tests/python/unittest/test_codegen_c_host.py b/tests/python/unittest/test_codegen_c_host.py new file mode 100644 index 000000000000..31f43b20b766 --- /dev/null +++ b/tests/python/unittest/test_codegen_c_host.py @@ -0,0 +1,83 @@ +import tvm +import numpy as np +from tvm._ffi.libinfo import find_include_path + +def test_add(): + nn = 1024 + n = tvm.convert(nn) + A = tvm.placeholder((n,), name='A') + B = tvm.placeholder((n,), name='B') + C = tvm.compute(A.shape, lambda *i: A(*i) + B(*i), name='C') + s = tvm.create_schedule(C.op) + + def check_c(): + f1 = tvm.lower(s, [A, B, C], name="fadd") + fsplits = [x for x in tvm.ir_pass.SplitHostDevice(f1)] + fsplits[0] = tvm.ir_pass.LowerTVMBuiltin(fsplits[0]) + mhost = tvm.codegen.build_module(fsplits[0], "c") + mhost.export_library("mod.so") + m = tvm.module.load("mod.so") + fadd = m['fadd'] + ctx = tvm.cpu(0) + # launch the kernel. + n = nn + a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx) + b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx) + c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx) + fadd(a, b, c) + tvm.testing.assert_allclose( + c.asnumpy(), a.asnumpy() + b.asnumpy()) + check_c() + +def test_add_pipeline(): + nn = 1024 + n = tvm.convert(nn) + A = tvm.placeholder((n,), name='A') + B = tvm.placeholder((n,), name='B') + AA = tvm.compute((n,), lambda *i: A(*i), name='A') + BB = tvm.compute((n,), lambda *i: B(*i), name='B') + T = tvm.compute(A.shape, lambda *i: AA(*i) + BB(*i), name='T') + C = tvm.compute(A.shape, lambda *i: T(*i), name='C') + s = tvm.create_schedule(C.op) + xo, xi = s[C].split(C.op.axis[0], factor=4) + xo1, xo2 = s[C].split(xo, factor=13) + s[C].parallel(xo2) + s[C].pragma(xo1, "parallel_launch_point") + s[C].pragma(xo2, "parallel_stride_pattern") + s[C].pragma(xo2, "parallel_barrier_when_finish") + s[C].vectorize(xi) + + def check_llvm(): + if not tvm.module.enabled("llvm"): + return + # Specifically allow offset to test codepath when offset is available + Ab = tvm.decl_buffer( + A.shape, A.dtype, + elem_offset=tvm.var('Aoffset'), + offset_factor=8, + name='A') + binds = {A : Ab} + # BUILD and invoke the kernel. + f1 = tvm.lower(s, [A,B,C], name="fadd_pipeline") + fsplits = [x for x in tvm.ir_pass.SplitHostDevice(f1)] + fsplits[0] = tvm.ir_pass.LowerTVMBuiltin(fsplits[0]) + mhost = tvm.codegen.build_module(fsplits[0], "c") + mhost.export_library("mod.so") + m = tvm.module.load("mod.so") + fadd = m["fadd_pipeline"] + ctx = tvm.cpu(0) + # launch the kernel. + n = nn + a = tvm.nd.array(np.random.uniform(size=n).astype(A.dtype), ctx) + b = tvm.nd.array(np.random.uniform(size=n).astype(B.dtype), ctx) + c = tvm.nd.array(np.zeros(n, dtype=C.dtype), ctx) + fadd(a, b, c) + tvm.testing.assert_allclose( + c.asnumpy(), a.asnumpy() + b.asnumpy()) + + with tvm.build_config(offset_factor=4): + check_llvm() + +if __name__ == "__main__": + test_add() + test_add_pipeline() From c6b8822792492602913b0fcf6d18ea1f6c3d026d Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sat, 24 Nov 2018 01:44:10 +0000 Subject: [PATCH 04/10] Code cleanup --- src/codegen/build_module.cc | 4 ---- src/codegen/codegen_c.cc | 2 +- tests/python/unittest/test_codegen_c_host.py | 4 ++-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/codegen/build_module.cc b/src/codegen/build_module.cc index 7fbefa37ea0e..c5c14d711df7 100644 --- a/src/codegen/build_module.cc +++ b/src/codegen/build_module.cc @@ -103,10 +103,6 @@ Target CreateTarget(const std::string& target_name, t->device_type = kDLCPU; } else if (target_name == "ext_dev") { t->device_type = kDLExtDev; - } else if (target_name == "c") { - // TODO: get this to work - t->device_type = kDLCPU; - t->keys_array.push_back(ir::StringImm::make("c")); } else { LOG(ERROR) << "Unknown target name " << target_name; return target::stackvm(); diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc index d1c87a69aa48..e29cff172393 100644 --- a/src/codegen/codegen_c.cc +++ b/src/codegen/codegen_c.cc @@ -191,7 +191,7 @@ std::string CodeGenC::GetStructRef( case intrinsic::kArrTypeLanes: os << "dtype.lanes"; break; case intrinsic::kArrDeviceId: os << "ctx.device_id"; break; case intrinsic::kArrDeviceType: os << "ctx.device_type"; break; - default: LOG(FATAL) << "unknown field code" << static_cast(kind); + default: LOG(FATAL) << "unknown field code"; } os << ')'; return os.str(); diff --git a/tests/python/unittest/test_codegen_c_host.py b/tests/python/unittest/test_codegen_c_host.py index 31f43b20b766..ca53d595cd5d 100644 --- a/tests/python/unittest/test_codegen_c_host.py +++ b/tests/python/unittest/test_codegen_c_host.py @@ -47,7 +47,7 @@ def test_add_pipeline(): s[C].pragma(xo2, "parallel_barrier_when_finish") s[C].vectorize(xi) - def check_llvm(): + def check_c(): if not tvm.module.enabled("llvm"): return # Specifically allow offset to test codepath when offset is available @@ -76,7 +76,7 @@ def check_llvm(): c.asnumpy(), a.asnumpy() + b.asnumpy()) with tvm.build_config(offset_factor=4): - check_llvm() + check_c() if __name__ == "__main__": test_add() From 156f2478adfc88179230a264c6e06100b9082357 Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sat, 24 Nov 2018 02:29:00 +0000 Subject: [PATCH 05/10] tabs to spaces --- src/codegen/codegen_c_host.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/codegen/codegen_c_host.cc b/src/codegen/codegen_c_host.cc index 93ffa0bf93d0..65fefa168a26 100644 --- a/src/codegen/codegen_c_host.cc +++ b/src/codegen/codegen_c_host.cc @@ -67,7 +67,7 @@ void CodeGenCHost::AddFunction(LoweredFunc f) { int func_scope = this->BeginScope(); this->PrintStmt(f->body); this->PrintIndent(); - this->stream << "return 0;\n"; + this->stream << "return 0;\n"; this->EndScope(func_scope); this->PrintIndent(); this->stream << "}\n\n"; @@ -157,14 +157,14 @@ void CodeGenCHost::PrintGetFuncFromBackend(std::string func_name, std::string pa void CodeGenCHost::PrintFuncCall(std::string packed_func_name, int num_args) { this->PrintIndent(); std::string ret_val = GetUniqueName("ret_val"); - std::string ret_type_code = GetUniqueName("ret_type_code"); - this->stream << "TVMValue " << ret_val << ";\n"; + std::string ret_type_code = GetUniqueName("ret_type_code"); + this->stream << "TVMValue " << ret_val << ";\n"; this->PrintIndent(); - this->stream << "int " << ret_type_code << ";\n"; + this->stream << "int " << ret_type_code << ";\n"; this->PrintIndent(); this->stream << "if (TVMFuncCall(" << packed_func_name << ", " - << "(TVMValue*) stack_value" << ", " << "(int*) stack_tcode" << ", " - << num_args << ", " << "&" << ret_val << ", " << "&" + << "(TVMValue*) stack_value" << ", " << "(int*) stack_tcode" << ", " + << num_args << ", " << "&" << ret_val << ", " << "&" << ret_type_code << ") != 0) {\n"; int func_call_scope = this->BeginScope(); this->PrintIndent(); @@ -203,7 +203,7 @@ void CodeGenCHost::VisitExpr_(const Call *op, std::ostream& os) { // NOLINT(*) int64_t begin = op->args[3].as()->value; int64_t end = op->args[4].as()->value; int64_t num_args = end - begin; - CHECK_GE(num_args, 0); + CHECK_GE(num_args, 0); std::string func_name = s->value; std::string packed_func_name = GetUniqueName(func_name + "_packed"); decl_stream << "static void* " << packed_func_name << " = NULL;\n"; From 5f07c141defd01800806ea8448115b528441d2a7 Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sat, 24 Nov 2018 17:07:07 +0000 Subject: [PATCH 06/10] make lint compliant --- python/tvm/module.py | 1 - src/codegen/codegen_c_host.cc | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/python/tvm/module.py b/python/tvm/module.py index 48e88edfc624..3fe64bdfd30f 100644 --- a/python/tvm/module.py +++ b/python/tvm/module.py @@ -6,7 +6,6 @@ from ._ffi.function import ModuleBase, _set_class_module from ._ffi.function import _init_api -from ._ffi.libinfo import find_include_path from .contrib import cc as _cc, tar as _tar, util as _util ProfileResult = namedtuple("ProfileResult", ["mean", "results"]) diff --git a/src/codegen/codegen_c_host.cc b/src/codegen/codegen_c_host.cc index 65fefa168a26..1caafc3521b4 100644 --- a/src/codegen/codegen_c_host.cc +++ b/src/codegen/codegen_c_host.cc @@ -193,7 +193,7 @@ void CodeGenCHost::VisitExpr_(const Call *op, std::ostream& os) { // NOLINT(*) size = (num->value * sizeof(TVMArray) + unit - 1) / unit; } else { LOG(FATAL) << "Unknown stack alloca type " << type; - } + } this->PrintIndent(); this->stream << "TVMValue " << stack_name << "[" << size << "];\n"; os << stack_name; From 4919c1580ff5cd3975ce7618affdfececc19bb8b Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sun, 25 Nov 2018 19:55:22 +0000 Subject: [PATCH 07/10] update export_library and reserve unique C keywords --- python/tvm/module.py | 4 ++-- src/codegen/codegen_c_host.cc | 35 ++++++++++++++++++++++++++++++++--- src/codegen/codegen_c_host.h | 1 + 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/python/tvm/module.py b/python/tvm/module.py index 3fe64bdfd30f..928780395e59 100644 --- a/python/tvm/module.py +++ b/python/tvm/module.py @@ -106,12 +106,12 @@ def export_library(self, if self.type_key == "llvm": object_format = "o" else: + assert self.type_key == "c" object_format = "cc" path_obj = temp.relpath("lib." + object_format) self.save(path_obj) files = [path_obj] - if self.type_key == "llvm": - is_system_lib = self.get_function("__tvm_is_system_module")() + is_system_lib = self.type_key == "llvm" and self.get_function("__tvm_is_system_module")() if self.imported_modules: path_cc = temp.relpath("devc.cc") with open(path_cc, "w") as f: diff --git a/src/codegen/codegen_c_host.cc b/src/codegen/codegen_c_host.cc index 1caafc3521b4..b7b81e9089db 100644 --- a/src/codegen/codegen_c_host.cc +++ b/src/codegen/codegen_c_host.cc @@ -22,12 +22,41 @@ void CodeGenCHost::Init(bool output_ssa) { CodeGenC::Init(output_ssa); } +void CodeGenCHost::ReserveKeywordsAsUnique() { + GetUniqueName("_"); + GetUniqueName("extern"); + GetUniqueName("void"); + GetUniqueName("int"); + GetUniqueName("float"); + GetUniqueName("double"); + GetUniqueName("char"); + GetUniqueName("unsigned"); + GetUniqueName("short"); + GetUniqueName("long"); + GetUniqueName("if"); + GetUniqueName("else"); + GetUniqueName("switch"); + GetUniqueName("case"); + GetUniqueName("default"); + GetUniqueName("for"); + GetUniqueName("do"); + GetUniqueName("while"); + GetUniqueName("goto"); + GetUniqueName("register"); + GetUniqueName("continue"); + GetUniqueName("break"); + GetUniqueName("typedef"); + GetUniqueName("struct"); + GetUniqueName("enum"); + GetUniqueName("union"); + GetUniqueName("return"); +} + void CodeGenCHost::AddFunction(LoweredFunc f) { // clear previous generated state. this->InitFuncState(f); - // skip the first underscore, so SSA variable starts from _1 - GetUniqueName("_"); - GetUniqueName("extern"); + // reserve keywords + ReserveKeywordsAsUnique(); // add to alloc buffer type. for (const auto & kv : f->handle_data_type) { RegisterHandleType(kv.first.get(), kv.second.type()); diff --git a/src/codegen/codegen_c_host.h b/src/codegen/codegen_c_host.h index ea962a5b2989..dd58207b3fca 100644 --- a/src/codegen/codegen_c_host.h +++ b/src/codegen/codegen_c_host.h @@ -32,6 +32,7 @@ class CodeGenCHost final : public CodeGenC { std::string module_name; void PrintGetFuncFromBackend(std::string func_name, std::string packed_func_name); void PrintFuncCall(std::string packed_func_name, int num_args); + void ReserveKeywordsAsUnique(); }; } // namespace codegen From 76e0b7bf955643cda46e321c07d40077cf2d7033 Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Sun, 25 Nov 2018 22:29:26 +0000 Subject: [PATCH 08/10] move ReserveKeywordsAsUnique to codegen_c --- src/codegen/codegen_c.cc | 34 ++++++++++++++++++++++++++++++++-- src/codegen/codegen_c.h | 1 + src/codegen/codegen_c_host.cc | 30 ------------------------------ src/codegen/codegen_c_host.h | 1 - 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc index e29cff172393..64cad713f3ea 100644 --- a/src/codegen/codegen_c.cc +++ b/src/codegen/codegen_c.cc @@ -22,12 +22,42 @@ void CodeGenC::InitFuncState(LoweredFunc f) { handle_data_type_.clear(); CodeGenSourceBase::ClearFuncState(); } + +void CodeGenC::ReserveKeywordsAsUnique() { + GetUniqueName("_"); + GetUniqueName("extern"); + GetUniqueName("void"); + GetUniqueName("int"); + GetUniqueName("float"); + GetUniqueName("double"); + GetUniqueName("char"); + GetUniqueName("unsigned"); + GetUniqueName("short"); + GetUniqueName("long"); + GetUniqueName("if"); + GetUniqueName("else"); + GetUniqueName("switch"); + GetUniqueName("case"); + GetUniqueName("default"); + GetUniqueName("for"); + GetUniqueName("do"); + GetUniqueName("while"); + GetUniqueName("goto"); + GetUniqueName("register"); + GetUniqueName("continue"); + GetUniqueName("break"); + GetUniqueName("typedef"); + GetUniqueName("struct"); + GetUniqueName("enum"); + GetUniqueName("union"); + GetUniqueName("return"); +} + void CodeGenC::AddFunction(LoweredFunc f) { // clear previous generated state. this->InitFuncState(f); // skip the first underscore, so SSA variable starts from _1 - GetUniqueName("_"); - GetUniqueName("extern"); + ReserveKeywordsAsUnique(); // add to alloc buffer type. for (const auto & kv : f->handle_data_type) { RegisterHandleType(kv.first.get(), kv.second.type()); diff --git a/src/codegen/codegen_c.h b/src/codegen/codegen_c.h index b36e37da54fe..8b7f378a8691 100644 --- a/src/codegen/codegen_c.h +++ b/src/codegen/codegen_c.h @@ -183,6 +183,7 @@ class CodeGenC : std::unordered_map alloc_storage_scope_; /*! \brief the data type of allocated buffers */ std::unordered_map handle_data_type_; + void ReserveKeywordsAsUnique(); private: /*! \brief whether to print in SSA form */ diff --git a/src/codegen/codegen_c_host.cc b/src/codegen/codegen_c_host.cc index b7b81e9089db..9d2f4ed128c4 100644 --- a/src/codegen/codegen_c_host.cc +++ b/src/codegen/codegen_c_host.cc @@ -22,36 +22,6 @@ void CodeGenCHost::Init(bool output_ssa) { CodeGenC::Init(output_ssa); } -void CodeGenCHost::ReserveKeywordsAsUnique() { - GetUniqueName("_"); - GetUniqueName("extern"); - GetUniqueName("void"); - GetUniqueName("int"); - GetUniqueName("float"); - GetUniqueName("double"); - GetUniqueName("char"); - GetUniqueName("unsigned"); - GetUniqueName("short"); - GetUniqueName("long"); - GetUniqueName("if"); - GetUniqueName("else"); - GetUniqueName("switch"); - GetUniqueName("case"); - GetUniqueName("default"); - GetUniqueName("for"); - GetUniqueName("do"); - GetUniqueName("while"); - GetUniqueName("goto"); - GetUniqueName("register"); - GetUniqueName("continue"); - GetUniqueName("break"); - GetUniqueName("typedef"); - GetUniqueName("struct"); - GetUniqueName("enum"); - GetUniqueName("union"); - GetUniqueName("return"); -} - void CodeGenCHost::AddFunction(LoweredFunc f) { // clear previous generated state. this->InitFuncState(f); diff --git a/src/codegen/codegen_c_host.h b/src/codegen/codegen_c_host.h index dd58207b3fca..ea962a5b2989 100644 --- a/src/codegen/codegen_c_host.h +++ b/src/codegen/codegen_c_host.h @@ -32,7 +32,6 @@ class CodeGenCHost final : public CodeGenC { std::string module_name; void PrintGetFuncFromBackend(std::string func_name, std::string packed_func_name); void PrintFuncCall(std::string packed_func_name, int num_args); - void ReserveKeywordsAsUnique(); }; } // namespace codegen From 7b55fa75dea81bf759911b6d80650bd5774d3554 Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Mon, 26 Nov 2018 06:47:04 +0000 Subject: [PATCH 09/10] some documentation and code cleanup --- python/tvm/contrib/cc.py | 2 +- python/tvm/module.py | 2 +- src/codegen/codegen_c.cc | 3 ++- src/codegen/codegen_c.h | 1 + src/codegen/codegen_c_host.cc | 3 ++- src/codegen/codegen_c_host.h | 4 ++-- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/python/tvm/contrib/cc.py b/python/tvm/contrib/cc.py index 4b02db67ef3d..0361f594de6a 100644 --- a/python/tvm/contrib/cc.py +++ b/python/tvm/contrib/cc.py @@ -50,7 +50,7 @@ def _linux_shared(output, objects, options, cc="g++"): cmd += objects if options: cmd += options - cmd += ["-I"+path for path in find_include_path()] + cmd += ["-I" + path for path in find_include_path()] proc = subprocess.Popen( cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) (out, _) = proc.communicate() diff --git a/python/tvm/module.py b/python/tvm/module.py index 928780395e59..cd919722e681 100644 --- a/python/tvm/module.py +++ b/python/tvm/module.py @@ -98,7 +98,7 @@ def export_library(self, return if not (self.type_key == "llvm" or self.type_key == "c"): - raise ValueError("Module[%s]: Only llvm support export shared" % self.type_key) + raise ValueError("Module[%s]: Only llvm and c support export shared" % self.type_key) temp = _util.tempdir() if fcompile is not None and hasattr(fcompile, "object_format"): object_format = fcompile.object_format diff --git a/src/codegen/codegen_c.cc b/src/codegen/codegen_c.cc index 64cad713f3ea..3624dc0403aa 100644 --- a/src/codegen/codegen_c.cc +++ b/src/codegen/codegen_c.cc @@ -24,6 +24,7 @@ void CodeGenC::InitFuncState(LoweredFunc f) { } void CodeGenC::ReserveKeywordsAsUnique() { + // skip the first underscore, so SSA variable starts from _1 GetUniqueName("_"); GetUniqueName("extern"); GetUniqueName("void"); @@ -56,7 +57,7 @@ void CodeGenC::ReserveKeywordsAsUnique() { void CodeGenC::AddFunction(LoweredFunc f) { // clear previous generated state. this->InitFuncState(f); - // skip the first underscore, so SSA variable starts from _1 + // reserve keywords ReserveKeywordsAsUnique(); // add to alloc buffer type. for (const auto & kv : f->handle_data_type) { diff --git a/src/codegen/codegen_c.h b/src/codegen/codegen_c.h index 8b7f378a8691..c9af24a04a3c 100644 --- a/src/codegen/codegen_c.h +++ b/src/codegen/codegen_c.h @@ -183,6 +183,7 @@ class CodeGenC : std::unordered_map alloc_storage_scope_; /*! \brief the data type of allocated buffers */ std::unordered_map handle_data_type_; + /*! \brief reserves common C keywords */ void ReserveKeywordsAsUnique(); private: diff --git a/src/codegen/codegen_c_host.cc b/src/codegen/codegen_c_host.cc index 9d2f4ed128c4..248354dbc339 100644 --- a/src/codegen/codegen_c_host.cc +++ b/src/codegen/codegen_c_host.cc @@ -42,8 +42,9 @@ void CodeGenCHost::AddFunction(LoweredFunc f) { if (i != 0) stream << ", "; if (v.type().is_handle()) { auto it = alloc_storage_scope_.find(v.get()); - if (it != alloc_storage_scope_.end()) + if (it != alloc_storage_scope_.end()) { PrintStorageScope(it->second, stream); + } stream << ' '; if (handle_data_type_.count(v.get())) { diff --git a/src/codegen/codegen_c_host.h b/src/codegen/codegen_c_host.h index ea962a5b2989..eb47a7829e2c 100644 --- a/src/codegen/codegen_c_host.h +++ b/src/codegen/codegen_c_host.h @@ -1,7 +1,7 @@ /*! * Copyright (c) 2017 by Contributors - * \file codegen_opencl.h - * \brief Generate OpenCL device code. + * \file codegen_c_host.h + * \brief Generate C host code. */ #ifndef TVM_CODEGEN_CODEGEN_C_HOST_H_ #define TVM_CODEGEN_CODEGEN_C_HOST_H_ From a3f2c0f56a89c1419914dc7dac494b00cff4bd23 Mon Sep 17 00:00:00 2001 From: Pratyush Patel Date: Wed, 28 Nov 2018 02:45:11 +0000 Subject: [PATCH 10/10] use tvm.contrib.util for tempdir in testcases --- tests/python/unittest/test_codegen_c_host.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/python/unittest/test_codegen_c_host.py b/tests/python/unittest/test_codegen_c_host.py index ca53d595cd5d..00acbeb88fcf 100644 --- a/tests/python/unittest/test_codegen_c_host.py +++ b/tests/python/unittest/test_codegen_c_host.py @@ -1,6 +1,6 @@ import tvm import numpy as np -from tvm._ffi.libinfo import find_include_path +from tvm.contrib import util def test_add(): nn = 1024 @@ -15,8 +15,10 @@ def check_c(): fsplits = [x for x in tvm.ir_pass.SplitHostDevice(f1)] fsplits[0] = tvm.ir_pass.LowerTVMBuiltin(fsplits[0]) mhost = tvm.codegen.build_module(fsplits[0], "c") - mhost.export_library("mod.so") - m = tvm.module.load("mod.so") + temp = util.tempdir() + path_dso = temp.relpath("temp.so") + mhost.export_library(path_dso) + m = tvm.module.load(path_dso) fadd = m['fadd'] ctx = tvm.cpu(0) # launch the kernel. @@ -62,8 +64,10 @@ def check_c(): fsplits = [x for x in tvm.ir_pass.SplitHostDevice(f1)] fsplits[0] = tvm.ir_pass.LowerTVMBuiltin(fsplits[0]) mhost = tvm.codegen.build_module(fsplits[0], "c") - mhost.export_library("mod.so") - m = tvm.module.load("mod.so") + temp = util.tempdir() + path_dso = temp.relpath("temp.so") + mhost.export_library(path_dso) + m = tvm.module.load(path_dso) fadd = m["fadd_pipeline"] ctx = tvm.cpu(0) # launch the kernel.