diff --git a/cmake/modules/StandaloneCrt.cmake b/cmake/modules/StandaloneCrt.cmake index 411d0383faf4..dc1b3b2665f2 100644 --- a/cmake/modules/StandaloneCrt.cmake +++ b/cmake/modules/StandaloneCrt.cmake @@ -45,12 +45,14 @@ if(USE_MICRO) "src/runtime/crt/common *.c -> src/runtime/crt/common" "src/runtime/crt/graph_runtime *.c -> src/runtime/crt/graph_runtime" "src/runtime/crt/graph_runtime_module *.c -> src/runtime/crt/graph_runtime_module" - "src/runtime/crt/host crt_config.h -> src/runtime/crt/host" + "src/runtime/crt/host crt_config.h -> template/host" + "src/runtime/crt/host *.cc -> template/host" "src/runtime/crt/memory *.c -> src/runtime/crt/memory" "src/runtime/crt/utvm_rpc_common *.cc -> src/runtime/crt/utvm_rpc_common" "src/runtime/crt/utvm_rpc_server *.cc -> src/runtime/crt/utvm_rpc_server" "src/runtime/minrpc *.h -> src/runtime/minrpc" "src/support generic_arena.h -> src/support" + "src/runtime/crt crt_config-template.h -> template" ) set(standalone_crt_base "${CMAKE_CURRENT_BINARY_DIR}/standalone_crt") @@ -101,9 +103,7 @@ if(USE_MICRO) endforeach() set(make_common_args - "DLPACK_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/3rdparty/dlpack/include" - "TVM_INCLUDE_DIR=${CMAKE_CURRENT_BINARY_DIR}/standalone_crt/include" - "CRT_CONFIG=src/runtime/crt/host/crt_config.h" + "CRT_CONFIG=template/host/crt_config.h" "BUILD_DIR=${host_build_dir_abspath}" "EXTRA_CFLAGS=-fPIC" "EXTRA_CXXFLAGS=-fPIC" diff --git a/python/tvm/micro/__init__.py b/python/tvm/micro/__init__.py index a6e24343e378..299b143eb5de 100644 --- a/python/tvm/micro/__init__.py +++ b/python/tvm/micro/__init__.py @@ -17,8 +17,8 @@ """MicroTVM module for bare-metal backends""" from .artifact import Artifact -from .build import build_static_runtime, default_options, TVM_ROOT_DIR -from .build import CRT_ROOT_DIR, Workspace +from .build import build_static_runtime, default_options, get_standalone_crt_dir +from .build import get_standalone_crt_lib, Workspace from .compiler import Compiler, DefaultCompiler, Flasher from .debugger import GdbRemoteDebugger from .micro_library import MicroLibrary diff --git a/python/tvm/micro/build.py b/python/tvm/micro/build.py index cad385b9b190..3837d423f8bd 100644 --- a/python/tvm/micro/build.py +++ b/python/tvm/micro/build.py @@ -21,9 +21,11 @@ import logging import os import re +import typing from tvm.contrib import utils from .micro_library import MicroLibrary +from .._ffi import libinfo _LOG = logging.getLogger(__name__) @@ -55,15 +57,62 @@ def path(self): CRT_RUNTIME_LIB_NAMES = ["utvm_rpc_server", "utvm_rpc_common", "common"] -TVM_ROOT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..", "..")) +STANDALONE_CRT_DIR = None -CRT_ROOT_DIR = os.path.join(TVM_ROOT_DIR, "src", "runtime", "crt") +class CrtNotFoundError(Exception): + """Raised when the standalone CRT dirtree cannot be found.""" -RUNTIME_LIB_SRC_DIRS = [os.path.join(CRT_ROOT_DIR, n) for n in CRT_RUNTIME_LIB_NAMES] + [ - os.path.join(TVM_ROOT_DIR, "3rdparty/libcrc/src") -] +def get_standalone_crt_dir() -> str: + """Find the standalone_crt directory. + + Though the C runtime source lives in the tvm tree, it is intended to be distributed with any + binary build of TVM. This source tree is intended to be integrated into user projects to run + models targeted with --runtime=c. + + Returns + ------- + str : + The path to the standalone_crt + """ + global STANDALONE_CRT_DIR + if STANDALONE_CRT_DIR is None: + for path in libinfo.find_lib_path(): + crt_path = os.path.join(os.path.dirname(path), "standalone_crt") + if os.path.isdir(crt_path): + STANDALONE_CRT_DIR = crt_path + break + + else: + raise CrtNotFoundError() + + return STANDALONE_CRT_DIR + + +def get_standalone_crt_lib(name: str) -> str: + """Find a source library directory in the standalone_crt. + + The standalone C runtime is split into various libraries (one per directory underneath + src/runtime/crt). This convenience function returns the full path to one of those libraries + located in get_standalone_crt_dir(). + + Parameters + ---------- + name : str + Name of the library subdirectory underneath src/runtime/crt. + + Returns + ------- + str : + The full path to the the library. + """ + return os.path.join(get_standalone_crt_dir(), "src", "runtime", "crt", name) + + +def get_runtime_libs() -> str: + """Return abspath to all CRT directories which contain source (i.e. not header) files.""" + return [get_standalone_crt_lib(n) for n in CRT_RUNTIME_LIB_NAMES] RUNTIME_SRC_REGEX = re.compile(r"^.*\.cc?$", re.IGNORECASE) @@ -72,52 +121,73 @@ def path(self): _COMMON_CFLAGS = ["-Wall", "-Werror"] -_CRT_DEFAULT_OPTIONS = { - "cflags": ["-std=c11"] + _COMMON_CFLAGS, - "ccflags": ["-std=c++11"] + _COMMON_CFLAGS, - "ldflags": ["-std=c++11"], - "include_dirs": [ - f"{TVM_ROOT_DIR}/include", - f"{TVM_ROOT_DIR}/3rdparty/dlpack/include", - f"{TVM_ROOT_DIR}/3rdparty/libcrc/include", - f"{TVM_ROOT_DIR}/3rdparty/dmlc-core/include", - f"{CRT_ROOT_DIR}/include", - ], -} +def _build_default_compiler_options(standalone_crt_dir: typing.Optional[str] = None) -> str: + """Return a dict containing base compile flags for the CRT under gcc common to . + Parameters + ---------- + standalone_crt_dir : Optional[str] + If given, the path to the standalone_crt + """ + if standalone_crt_dir is None: + standalone_crt_dir = get_standalone_crt_dir() + return { + "cflags": ["-std=c11"] + _COMMON_CFLAGS, + "ccflags": ["-std=c++11"] + _COMMON_CFLAGS, + "ldflags": ["-std=c++11"], + "include_dirs": [os.path.join(standalone_crt_dir, "include")], + } -_CRT_GENERATED_LIB_OPTIONS = copy.copy(_CRT_DEFAULT_OPTIONS) +def default_options(crt_config_include_dir, standalone_crt_dir=None): + """Return default opts passed to Compile commands. + + Parameters + ---------- + crt_config_include_dir : str + Path to a directory containing crt_config.h for the target. This will be appended + to the include path for cflags and ccflags. + standalone_crt_dir : Optional[str] + + Returns + ------- + Dict : + A dictionary containing 3 subkeys, each whose value is _build_default_compiler_options() + plus additional customization. + - "bin_opts" - passed as "options" to Compiler.binary() when building MicroBinary. + - "lib_opts" - passed as "options" to Compiler.library() when building bundled CRT + libraries (or otherwise, non-generated libraries). + - "generated_lib_opts" - passed as "options" to Compiler.library() when building the + generated library. + """ + bin_opts = _build_default_compiler_options(standalone_crt_dir) + bin_opts["include_dirs"].append(crt_config_include_dir) -# Disable due to limitation in the TVM C codegen, which generates lots of local variable -# declarations at the top of generated code without caring whether they're used. -# Example: -# void* arg0 = (((TVMValue*)args)[0].v_handle); -# int32_t arg0_code = ((int32_t*)arg_type_ids)[(0)]; -_CRT_GENERATED_LIB_OPTIONS["cflags"].append("-Wno-unused-variable") -_CRT_GENERATED_LIB_OPTIONS["ccflags"].append("-Wno-unused-variable") + lib_opts = _build_default_compiler_options(standalone_crt_dir) + lib_opts["cflags"] = ["-Wno-error=incompatible-pointer-types"] + lib_opts["include_dirs"].append(crt_config_include_dir) + generated_lib_opts = copy.copy(lib_opts) -# Many TVM-intrinsic operators (i.e. expf, in particular) -_CRT_GENERATED_LIB_OPTIONS["cflags"].append("-fno-builtin") + # Disable due to limitation in the TVM C codegen, which generates lots of local variable + # declarations at the top of generated code without caring whether they're used. + # Example: + # void* arg0 = (((TVMValue*)args)[0].v_handle); + # int32_t arg0_code = ((int32_t*)arg_type_ids)[(0)]; + generated_lib_opts["cflags"].append("-Wno-unused-variable") + generated_lib_opts["ccflags"].append("-Wno-unused-variable") + # Many TVM-intrinsic operators (i.e. expf, in particular) + generated_lib_opts["cflags"].append("-fno-builtin") -def default_options(target_include_dir): - """Return default opts passed to Compile commands.""" - bin_opts = copy.deepcopy(_CRT_DEFAULT_OPTIONS) - bin_opts["include_dirs"].append(target_include_dir) - lib_opts = copy.deepcopy(bin_opts) - lib_opts["cflags"] = ["-Wno-error=incompatible-pointer-types"] - return {"bin_opts": bin_opts, "lib_opts": lib_opts} + return {"bin_opts": bin_opts, "lib_opts": lib_opts, "generated_lib_opts": generated_lib_opts} def build_static_runtime( workspace, compiler, module, - lib_opts=None, - bin_opts=None, - generated_lib_opts=None, + compiler_options, extra_libs=None, ): """Build the on-device runtime, statically linking the given modules. @@ -130,15 +200,11 @@ def build_static_runtime( module : IRModule Module to statically link. - lib_opts : Optional[dict] - The `options` parameter passed to compiler.library(). - - bin_opts : Optional[dict] - The `options` parameter passed to compiler.binary(). - - generated_lib_opts : Optional[dict] - The `options` parameter passed to compiler.library() when compiling the generated TVM C - source module. + compiler_options : dict + The return value of tvm.micro.default_options(), with any keys overridden to inject + compiler options specific to this build. If not given, tvm.micro.default_options() is + used. This dict contains the `options` parameter passed to Compiler.library() and + Compiler.binary() at various stages in the compilation process. extra_libs : Optional[List[MicroLibrary|str]] If specified, extra libraries to be compiled into the binary. If a MicroLibrary, it is @@ -151,18 +217,12 @@ def build_static_runtime( MicroBinary : The compiled runtime. """ - lib_opts = _CRT_DEFAULT_OPTIONS if lib_opts is None else lib_opts - bin_opts = _CRT_DEFAULT_OPTIONS if bin_opts is None else bin_opts - generated_lib_opts = ( - _CRT_GENERATED_LIB_OPTIONS if generated_lib_opts is None else generated_lib_opts - ) - mod_build_dir = workspace.relpath(os.path.join("build", "module")) os.makedirs(mod_build_dir) mod_src_dir = workspace.relpath(os.path.join("src", "module")) libs = [] - for mod_or_src_dir in (extra_libs or []) + RUNTIME_LIB_SRC_DIRS: + for mod_or_src_dir in (extra_libs or []) + get_runtime_libs(): if isinstance(mod_or_src_dir, MicroLibrary): libs.append(mod_or_src_dir) continue @@ -177,7 +237,7 @@ def build_static_runtime( if RUNTIME_SRC_REGEX.match(p): lib_srcs.append(os.path.join(lib_src_dir, p)) - libs.append(compiler.library(lib_build_dir, lib_srcs, lib_opts)) + libs.append(compiler.library(lib_build_dir, lib_srcs, compiler_options["lib_opts"])) mod_src_dir = workspace.relpath(os.path.join("src", "module")) os.makedirs(mod_src_dir) @@ -185,10 +245,12 @@ def build_static_runtime( module.export_library( mod_build_dir, workspace_dir=mod_src_dir, - fcompile=lambda bdir, srcs, **kwargs: compiler.library(bdir, srcs, generated_lib_opts), + fcompile=lambda bdir, srcs, **kwargs: compiler.library( + bdir, srcs, compiler_options["generated_lib_opts"] + ), ) ) runtime_build_dir = workspace.relpath(f"build/runtime") os.makedirs(runtime_build_dir) - return compiler.binary(runtime_build_dir, libs, bin_opts) + return compiler.binary(runtime_build_dir, libs, compiler_options["bin_opts"]) diff --git a/python/tvm/micro/compiler.py b/python/tvm/micro/compiler.py index f59ac8dbc4a0..d0431f42b01d 100644 --- a/python/tvm/micro/compiler.py +++ b/python/tvm/micro/compiler.py @@ -24,7 +24,6 @@ import subprocess import tvm.target -from . import build from . import class_factory from . import debugger from . import transport @@ -291,7 +290,9 @@ def binary(self, output, objects, options=None, link_main=True, main_options=Non args.extend(["-g", "-o", output_abspath]) if link_main: - host_main_srcs = glob.glob(os.path.join(build.CRT_ROOT_DIR, "host", "*.cc")) + host_main_srcs = glob.glob( + os.path.join(tvm.micro.get_standalone_crt_dir(), "template", "host", "*.cc") + ) if main_options: main_lib = self.library(os.path.join(output, "host"), host_main_srcs, main_options) for lib_name in main_lib.library_files: diff --git a/tests/micro/qemu/test_zephyr.py b/tests/micro/qemu/test_zephyr.py index 865c7f88806f..4c8bd5f5dae8 100644 --- a/tests/micro/qemu/test_zephyr.py +++ b/tests/micro/qemu/test_zephyr.py @@ -92,8 +92,7 @@ def _make_session(model, target, zephyr_board, west_cmd, mod): workspace, compiler, mod, - lib_opts=opts["lib_opts"], - bin_opts=opts["bin_opts"], + opts, ) if os.path.exists(prev_build): os.unlink(prev_build) diff --git a/tests/python/unittest/test_crt.py b/tests/python/unittest/test_crt.py index 659d1908096b..3c68b4090309 100644 --- a/tests/python/unittest/test_crt.py +++ b/tests/python/unittest/test_crt.py @@ -50,18 +50,15 @@ def _make_sess_from_op(workspace, op_name, sched, arg_bufs): def _make_session(workspace, mod): compiler = tvm.micro.DefaultCompiler(target=TARGET) - opts = tvm.micro.default_options(os.path.join(tvm.micro.CRT_ROOT_DIR, "host")) + opts = tvm.micro.default_options( + os.path.join(tvm.micro.get_standalone_crt_dir(), "template", "host") + ) micro_binary = tvm.micro.build_static_runtime( - # the x86 compiler *expects* you to give the exact same dictionary for both - # lib_opts and bin_opts. so the library compiler is mutating lib_opts and - # the binary compiler is expecting those mutations to be in bin_opts. - # TODO(weberlo) fix this very bizarre behavior workspace, compiler, mod, - lib_opts=opts["bin_opts"], - bin_opts=opts["bin_opts"], - extra_libs=[os.path.join(tvm.micro.build.CRT_ROOT_DIR, "memory")], + opts, + extra_libs=[tvm.micro.get_standalone_crt_lib("memory")], ) flasher_kw = { diff --git a/tests/python/unittest/test_link_params.py b/tests/python/unittest/test_link_params.py index 52d7a27838d7..80ea11f6d9aa 100644 --- a/tests/python/unittest/test_link_params.py +++ b/tests/python/unittest/test_link_params.py @@ -354,21 +354,18 @@ def test_crt_link_params(): workspace = tvm.micro.Workspace() compiler = tvm.micro.DefaultCompiler(target=target) - opts = tvm.micro.default_options(os.path.join(tvm.micro.CRT_ROOT_DIR, "host")) + opts = tvm.micro.default_options( + os.path.join(tvm.micro.get_standalone_crt_dir(), "template", "host") + ) opts["bin_opts"]["ldflags"].append("-DTVM_HOST_USE_GRAPH_RUNTIME_MODULE") micro_binary = tvm.micro.build_static_runtime( - # the x86 compiler *expects* you to give the exact same dictionary for both - # lib_opts and bin_opts. so the library compiler is mutating lib_opts and - # the binary compiler is expecting those mutations to be in bin_opts. - # TODO(weberlo) fix this very bizarre behavior workspace, compiler, lib, - lib_opts=opts["bin_opts"], - bin_opts=opts["bin_opts"], + compiler_options=opts, extra_libs=[ - os.path.join(tvm.micro.CRT_ROOT_DIR, m) + tvm.micro.get_standalone_crt_lib(m) for m in ("memory", "graph_runtime_module", "graph_runtime") ], ) diff --git a/tests/scripts/task_ci_setup.sh b/tests/scripts/task_ci_setup.sh index f48ed49a2266..17838c58a83c 100755 --- a/tests/scripts/task_ci_setup.sh +++ b/tests/scripts/task_ci_setup.sh @@ -31,3 +31,8 @@ set -o pipefail echo "Addtiional setup in" ${CI_IMAGE_NAME} python3 -m pip install --user tlcpack-sphinx-addon==0.1.4 synr==0.2.1 + +# Rebuild standalone_crt in build/ tree. This file is not currently archived by pack_lib() in +# Jenkinsfile. We expect config.cmake to be present from pack_lib(). +# TODO(areusch): Make pack_lib() pack all the data dependencies of TVM. +(cd build && cmake .. && make standalone_crt) diff --git a/tutorials/micro/micro_tflite.py b/tutorials/micro/micro_tflite.py index c28918380265..15039e2e64e7 100644 --- a/tutorials/micro/micro_tflite.py +++ b/tutorials/micro/micro_tflite.py @@ -207,7 +207,9 @@ # First, compile a static microTVM runtime for the targeted device. In this case, the host simulated # device is used. compiler = tvm.micro.DefaultCompiler(target=TARGET) -opts = tvm.micro.default_options(os.path.join(tvm.micro.CRT_ROOT_DIR, "host")) +opts = tvm.micro.default_options( + os.path.join(tvm.micro.get_standalone_crt_dir(), "template", "host") +) # %% # Compiling for physical hardware @@ -230,18 +232,13 @@ workspace = tvm.micro.Workspace() micro_binary = tvm.micro.build_static_runtime( - # the x86 compiler *expects* you to give the exact same dictionary for both - # lib_opts and bin_opts. so the library compiler is mutating lib_opts and - # the binary compiler is expecting those mutations to be in bin_opts. - # TODO(weberlo) fix this very bizarre behavior workspace, compiler, c_mod, - lib_opts=opts["lib_opts"], - bin_opts=opts["bin_opts"], + opts, # Use the microTVM memory manager. If, in your main.cc, you change TVMPlatformMemoryAllocate and # TVMPlatformMemoryFree to use e.g. malloc() and free(), you can omit this extra library. - extra_libs=[os.path.join(tvm.micro.build.CRT_ROOT_DIR, "memory")], + extra_libs=[tvm.micro.get_standalone_crt_lib("memory")], )