def with_minrpc(compile_func, server="posix_popen_server", runtime="libtvm"): """Attach the compiler function with minrpc related options. Parameters ---------- compile_func : Union[str, Callable[[str, str, Optional[str]], None]] The compilation function to decorate. server : str The server type. runtime : str The runtime library. Returns ------- fcompile : function The return compilation. """ server_path = find_minrpc_server_libpath(server) runtime_path = libinfo.find_lib_path([runtime, runtime + ".so", runtime + ".dylib"])[0] runtime_dir = os.path.abspath(os.path.dirname(runtime_path)) options = ["-std=c++14"] # Make sure the rpath to the libtvm is set so we can do local tests. # Note that however, this approach won't work on remote. # Always recommend to to link statically. options += ["-Wl,-rpath=" + runtime_dir] options += ["-I" + path for path in libinfo.find_include_path()] fcompile = cc.cross_compiler( compile_func, options=options, add_files=[server_path, runtime_path] ) fcompile.__name__ = "with_minrpc" fcompile.need_system_lib = True return fcompile
def create_micro_lib_base( out_obj_path, in_src_path, toolchain_prefix, device_id, lib_type, options=None, lib_src_paths=None, ): """Compiles code into a binary for the target micro device. Parameters ---------- out_obj_path : str path to generated object file in_src_path : str path to source file toolchain_prefix : str toolchain prefix to be used. For example, a prefix of "riscv64-unknown-elf-" means "riscv64-unknown-elf-gcc" is used as the compiler and "riscv64-unknown-elf-ld" is used as the linker, etc. device_id : str unique identifier for the target device lib_type : micro.LibType whether to compile a MicroTVM runtime or operator library options : List[str] additional options to pass to GCC lib_src_paths : Optional[List[str]] paths to additional source files to be compiled into the library """ # look at these (specifically `strip`): # https://stackoverflow.com/questions/15314581/g-compiler-flag-to-minimize-binary-size base_compile_cmd = [ f"{toolchain_prefix}gcc", "-std=gnu11", "-Wall", "-Wextra", "--pedantic", "-c", "-g", "-nostartfiles", "-nodefaultlibs", "-nostdlib", "-fdata-sections", "-ffunction-sections", ] if options is not None: base_compile_cmd += options src_paths = [] include_paths = find_include_path() + [get_micro_host_driven_dir()] tmp_dir = _util.tempdir() # we need to create a new src file in the operator branch new_in_src_path = in_src_path if lib_type == LibType.RUNTIME: dev_dir = _get_device_source_dir(device_id) print(dev_dir) dev_src_paths = glob.glob(f"{dev_dir}/*.[csS]") # there needs to at least be a utvm_timer.c file assert dev_src_paths assert "utvm_timer.c" in map(os.path.basename, dev_src_paths) src_paths += dev_src_paths elif lib_type == LibType.OPERATOR: # create a temporary copy of the operator source, so we can inject the dev lib # header without modifying the original. temp_src_path = tmp_dir.relpath("temp.c") with open(in_src_path, "r") as f: src_lines = f.read().splitlines() src_lines.insert(0, '#include "utvm_device_dylib_redirect.c"') with open(temp_src_path, "w") as f: f.write("\n".join(src_lines)) new_in_src_path = temp_src_path else: raise RuntimeError("unknown lib type") src_paths += [new_in_src_path] # add any src paths required by the operator if lib_src_paths is not None: src_paths += lib_src_paths # print(f"include paths: {include_paths}") for path in include_paths: base_compile_cmd += ["-I", path] prereq_obj_paths = [] # print(src_paths) for src_path in src_paths: curr_obj_path = tmp_dir.relpath( pathlib.Path(src_path).with_suffix(".o").name) assert curr_obj_path not in prereq_obj_paths prereq_obj_paths.append(curr_obj_path) curr_compile_cmd = base_compile_cmd + [src_path, "-o", curr_obj_path] # TODO(weberlo): make compilation fail if there are any warnings run_cmd(curr_compile_cmd) ld_cmd = [f"{toolchain_prefix}ld", "-relocatable"] ld_cmd += prereq_obj_paths ld_cmd += ["-o", out_obj_path] run_cmd(ld_cmd)
def create_micro_lib_base(out_obj_path, in_src_path, toolchain_prefix, device_id, lib_type, options=None): """Compiles code into a binary for the target micro device. Parameters ---------- out_obj_path : str path to generated object file in_src_path : str path to source file toolchain_prefix : str toolchain prefix to be used. For example, a prefix of "riscv64-unknown-elf-" means "riscv64-unknown-elf-gcc" is used as the compiler and "riscv64-unknown-elf-ld" is used as the linker, etc. device_id : str unique identifier for the target device lib_type : micro.LibType whether to compile a MicroTVM runtime or operator library options : List[str] additional options to pass to GCC """ base_compile_cmd = [ f"{toolchain_prefix}gcc", "-std=c11", "-Wall", "-Wextra", "--pedantic", "-c", "-O0", "-g", "-nostartfiles", "-nodefaultlibs", "-nostdlib", "-fdata-sections", "-ffunction-sections", ] if options is not None: base_compile_cmd += options src_paths = [] include_paths = find_include_path() + [get_micro_host_driven_dir()] tmp_dir = _util.tempdir() # we might transform the src path in one of the branches below new_in_src_path = in_src_path if lib_type == LibType.RUNTIME: dev_dir = _get_device_source_dir(device_id) dev_src_paths = glob.glob(f"{dev_dir}/*.[csS]") # there needs to at least be a utvm_timer.c file assert dev_src_paths assert "utvm_timer.c" in map(os.path.basename, dev_src_paths) src_paths += dev_src_paths elif lib_type == LibType.OPERATOR: # create a temporary copy of the source, so we can inject the dev lib # header without modifying the original. temp_src_path = tmp_dir.relpath("temp.c") with open(in_src_path, "r") as f: src_lines = f.read().splitlines() src_lines.insert(0, "#include \"utvm_device_dylib_redirect.c\"") with open(temp_src_path, "w") as f: f.write("\n".join(src_lines)) new_in_src_path = temp_src_path base_compile_cmd += ["-c"] else: raise RuntimeError("unknown lib type") src_paths += [new_in_src_path] for path in include_paths: base_compile_cmd += ["-I", path] prereq_obj_paths = [] for src_path in src_paths: curr_obj_path = Path(src_path).with_suffix(".o").name assert curr_obj_path not in prereq_obj_paths prereq_obj_paths.append(curr_obj_path) curr_compile_cmd = base_compile_cmd + [src_path, "-o", curr_obj_path] run_cmd(curr_compile_cmd) ld_cmd = [f"{toolchain_prefix}ld", "-relocatable"] ld_cmd += prereq_obj_paths ld_cmd += ["-o", out_obj_path] run_cmd(ld_cmd)
def export_library(self, file_name, fcompile=None, addons=None, workspace_dir=None, **kwargs): """ Export the module and all imported modules into a single device library. This function only works on host LLVM modules, other runtime::Module subclasses will work with this API but they must support implement the save and load mechanisms of modules completely including saving from streams and files. This will pack your non-shared library module into a single shared library which can later be loaded by TVM. Parameters ---------- file_name : str The name of the shared library. fcompile : function(target, file_list, kwargs), optional The compilation function to use create the final library object during export. For example, when fcompile=_cc.create_shared, or when it is not supplied but module is "llvm," this is used to link all produced artifacts into a final dynamic library. This behavior is controlled by the type of object exported. If fcompile has attribute object_format, will compile host library to that format. Otherwise, will use default format "o". workspace_dir : str, optional The path of the directory used to create the intermediate artifacts when exporting the module. If this is not provided a temporary dir will be created. kwargs : dict, optional Additional arguments passed to fcompile Returns ------- result of fcompile() : unknown, optional If the compilation function returns an artifact it would be returned via export_library, if any. """ # NOTE: this function depends on contrib library features # which are only available in when TVM function is available. if _RUNTIME_ONLY: raise RuntimeError("Cannot call export_library in runtime only mode") # Extra dependencies during runtime. from pathlib import Path from tvm.contrib import cc as _cc, tar as _tar, utils as _utils if isinstance(file_name, Path): file_name = str(file_name) if self.type_key == "stackvm": if not file_name.endswith(".stackvm"): raise ValueError( "Module[%s]: can only be saved as stackvm format." "did you build with LLVM enabled?" % self.type_key ) self.save(file_name) return modules = self._collect_dso_modules() if workspace_dir is None: temp = _utils.tempdir() workspace_dir = temp.temp_dir files = addons if addons else [] is_system_lib = False has_c_module = False llvm_target_string = None for index, module in enumerate(modules): if fcompile is not None and hasattr(fcompile, "object_format"): if module.type_key == "c": assert module.format in [ "c", "cc", "cpp", "cu", ], "The module.format needs to be either c, cc, cpp or cu." object_format = module.format has_c_module = True else: object_format = fcompile.object_format else: if module.type_key == "c": if len(module.format) > 0: assert module.format in [ "c", "cc", "cpp", "cu", ], "The module.format needs to be either c, cc, cpp, or cu." object_format = module.format else: object_format = "c" if "cc" in kwargs: if kwargs["cc"] == "nvcc": object_format = "cu" has_c_module = True else: assert module.type_key == "llvm" or module.type_key == "static_library" object_format = "o" path_obj = os.path.join(workspace_dir, f"lib{index}.{object_format}") module.save(path_obj) files.append(path_obj) is_system_lib = ( module.type_key == "llvm" and module.get_function("__tvm_is_system_module")() ) llvm_target_string = ( module.type_key == "llvm" and module.get_function("_get_target_string")() ) if not fcompile: if file_name.endswith(".tar"): fcompile = _tar.tar else: fcompile = _cc.create_shared if llvm_target_string is None and hasattr(fcompile, "get_target_triple"): triple = fcompile.get_target_triple() assert triple, "Target triple should not be empty" llvm_target_string = "llvm -mtriple " + triple if getattr(fcompile, "need_system_lib", False) and not is_system_lib: raise ValueError("%s need --system-lib option" % str(fcompile)) if self.imported_modules: if enabled("llvm") and llvm_target_string: path_obj = os.path.join(workspace_dir, f"devc.{object_format}") m = _ffi_api.ModulePackImportsToLLVM(self, is_system_lib, llvm_target_string) m.save(path_obj) files.append(path_obj) else: path_cc = os.path.join(workspace_dir, "devc.c") with open(path_cc, "w") as f: f.write(_ffi_api.ModulePackImportsToC(self, is_system_lib)) files.append(path_cc) # The imports could contain a c module but the object format could be tar # Thus, it would not recognize the following include paths as options # which are there assuming a c compiler is the fcompile. if has_c_module and not file_name.endswith(".tar"): options = [] if "options" in kwargs: opts = kwargs["options"] options = opts if isinstance(opts, (list, tuple)) else [opts] opts = options + ["-I" + path for path in find_include_path()] kwargs.update({"options": opts}) return fcompile(file_name, files, **kwargs)
def export_library(self, file_name, fcompile=None, **kwargs): """Export the module and its imported device code one library. This function only works on host llvm modules. It will pack all the imported modules Parameters ---------- file_name : str The name of the shared library. fcompile : function(target, file_list, kwargs), optional Compilation function to use create dynamic library. If fcompile has attribute object_format, will compile host library to that format. Otherwise, will use default format "o". kwargs : dict, optional Additional arguments passed to fcompile """ # NOTE: this function depends on contrib library features # which are only available in when TVM function is available. if _RUNTIME_ONLY: raise RuntimeError( "Cannot call export_library in runtime only mode") # Extra dependencies during runtime. from pathlib import Path from tvm.contrib import cc as _cc, tar as _tar, util as _util if isinstance(file_name, Path): file_name = str(file_name) if self.type_key == "stackvm": if not file_name.endswith(".stackvm"): raise ValueError( "Module[%s]: can only be saved as stackvm format." "did you build with LLVM enabled?" % self.type_key) self.save(file_name) return modules = self._collect_dso_modules() temp = _util.tempdir() files = [] is_system_lib = False has_c_module = False llvm_target_triple = None for index, module in enumerate(modules): if fcompile is not None and hasattr(fcompile, "object_format"): object_format = fcompile.object_format else: if module.type_key == "llvm": object_format = "o" else: assert module.type_key == "c" object_format = "cc" has_c_module = True path_obj = temp.relpath("lib" + str(index) + "." + object_format) module.save(path_obj) files.append(path_obj) is_system_lib = (module.type_key == "llvm" and module.get_function("__tvm_is_system_module")()) llvm_target_triple = (module.type_key == "llvm" and module.get_function("_get_target_triple")()) if not fcompile: if file_name.endswith(".tar"): fcompile = _tar.tar else: fcompile = _cc.create_shared if llvm_target_triple is None and hasattr(fcompile, "get_target_triple"): llvm_target_triple = fcompile.get_target_triple() if self.imported_modules: if enabled("llvm") and llvm_target_triple: path_obj = temp.relpath("devc.o") m = _ffi_api.ModulePackImportsToLLVM(self, is_system_lib, llvm_target_triple) m.save(path_obj) files.append(path_obj) else: path_cc = temp.relpath("devc.cc") with open(path_cc, "w") as f: f.write(_ffi_api.ModulePackImportsToC(self, is_system_lib)) files.append(path_cc) if has_c_module: options = [] if "options" in kwargs: opts = kwargs["options"] options = opts if isinstance(opts, (list, tuple)) else [opts] opts = options + ["-I" + path for path in find_include_path()] kwargs.update({'options': opts}) fcompile(file_name, files, **kwargs)
def export_library(self, file_name, fcompile=None, addons=None, workspace_dir=None, **kwargs): """Export the module and its imported device code one library. This function only works on host llvm modules. It will pack all the imported modules Parameters ---------- file_name : str The name of the shared library. fcompile : function(target, file_list, kwargs), optional Compilation function to use create dynamic library. If fcompile has attribute object_format, will compile host library to that format. Otherwise, will use default format "o". workspace_dir : str, optional the path to a directory used to create intermediary artifacts for the process exporting of the library. If this is not provided a temporary dir will be created. kwargs : dict, optional Additional arguments passed to fcompile Returns ------- result of fcompile() : unknown, optional If the compilation function returns an artifact it would be returned via export_library, if any. """ # NOTE: this function depends on contrib library features # which are only available in when TVM function is available. if _RUNTIME_ONLY: raise RuntimeError("Cannot call export_library in runtime only mode") # Extra dependencies during runtime. from pathlib import Path from tvm.contrib import cc as _cc, tar as _tar, utils as _utils if isinstance(file_name, Path): file_name = str(file_name) if self.type_key == "stackvm": if not file_name.endswith(".stackvm"): raise ValueError( "Module[%s]: can only be saved as stackvm format." "did you build with LLVM enabled?" % self.type_key ) self.save(file_name) return modules = self._collect_dso_modules() if workspace_dir is None: temp = _utils.tempdir() workspace_dir = temp.temp_dir files = addons if addons else [] is_system_lib = False has_c_module = False llvm_target_triple = None for index, module in enumerate(modules): if fcompile is not None and hasattr(fcompile, "object_format"): if module.type_key == "c": object_format = "c" has_c_module = True else: object_format = fcompile.object_format else: if module.type_key == "llvm": object_format = "o" else: assert module.type_key == "c" object_format = "c" has_c_module = True path_obj = os.path.join(workspace_dir, f"lib{index}.{object_format}") module.save(path_obj) files.append(path_obj) is_system_lib = ( module.type_key == "llvm" and module.get_function("__tvm_is_system_module")() ) llvm_target_triple = ( module.type_key == "llvm" and module.get_function("_get_target_triple")() ) if not fcompile: if file_name.endswith(".tar"): fcompile = _tar.tar else: fcompile = _cc.create_shared if llvm_target_triple is None and hasattr(fcompile, "get_target_triple"): llvm_target_triple = fcompile.get_target_triple() if getattr(fcompile, "need_system_lib", False) and not is_system_lib: raise ValueError("%s need --system-lib option" % str(fcompile)) if self.imported_modules: if enabled("llvm") and llvm_target_triple: path_obj = os.path.join(workspace_dir, f"devc.{object_format}") m = _ffi_api.ModulePackImportsToLLVM(self, is_system_lib, llvm_target_triple) m.save(path_obj) files.append(path_obj) else: path_cc = os.path.join(workspace_dir, "devc.c") with open(path_cc, "w") as f: f.write(_ffi_api.ModulePackImportsToC(self, is_system_lib)) files.append(path_cc) # The imports could contain a c module but the object format could be tar # Thus, it would not recognize the following include paths as options # which are there assuming a c compiler is the fcompile. if has_c_module and not file_name.endswith(".tar"): options = [] if "options" in kwargs: opts = kwargs["options"] options = opts if isinstance(opts, (list, tuple)) else [opts] opts = options + ["-I" + path for path in find_include_path()] kwargs.update({"options": opts}) return fcompile(file_name, files, **kwargs)