Commit 0a07411d by Tianqi Chen Committed by ziheng

[JS][WEB][BACKEND] Javascript(webassembly) backend. (#239)

parent 619e529a
...@@ -98,3 +98,5 @@ Win32 ...@@ -98,3 +98,5 @@ Win32
*.dir *.dir
perf perf
nnvm nnvm
*.wasm
.emscripten
...@@ -121,6 +121,31 @@ stage('Build') { ...@@ -121,6 +121,31 @@ stage('Build') {
pack_lib('i386') pack_lib('i386')
} }
} }
},
'web': {
node('emcc') {
ws('workspace/tvm/build-weblib') {
init_git()
sh """
cp make/config.mk .
echo USE_CUDA=0 >> config.mk
echo USE_OPENCL=0 >> config.mk
echo LLVM_CONFIG=llvm-config >> config.mk
echo USE_RPC=0 >> config.mk
"""
sh "${docker_run} emscripten echo testing javascript..."
timeout(time: max_time, unit: 'MINUTES') {
try {
sh "${docker_run} emscripten ./tests/scripts/task_web_build.sh"
} catch (exc) {
echo 'Incremental compilation failed. Fall back to build from scratch'
sh "${docker_run} emscripten make clean"
sh "${docker_run} emscripten ./tests/scripts/task_web_build.sh"
}
}
pack_lib('weblib')
}
}
} }
} }
...@@ -174,6 +199,18 @@ stage('Integration Test') { ...@@ -174,6 +199,18 @@ stage('Integration Test') {
} }
} }
}, },
'web': {
node('emcc') {
ws('workspace/tvm/it-weblib') {
init_git()
unpack_lib('weblib')
sh "${docker_run} emscripten echo testing javascript..."
timeout(time: max_time, unit: 'MINUTES') {
sh "${docker_run} emscripten ./tests/scripts/task_web_test.sh"
}
}
}
},
'docs': { 'docs': {
node('GPU' && 'linux') { node('GPU' && 'linux') {
ws('workspace/tvm/docs-python-gpu') { ws('workspace/tvm/docs-python-gpu') {
......
...@@ -10,11 +10,12 @@ endif ...@@ -10,11 +10,12 @@ endif
include $(config) include $(config)
.PHONY: clean all test doc pylint cpplint lint verilog cython cython2 cython3 .PHONY: clean all test doc pylint cpplint lint verilog cython cython2 cython3 web runtime
BUILD_TARGETS ?= lib/libtvm.so lib/libtvm_runtime.so BUILD_TARGETS ?= lib/libtvm.so lib/libtvm_runtime.so
all: ${BUILD_TARGETS} all: ${BUILD_TARGETS}
runtime: lib/libtvm_runtime.so runtime: lib/libtvm_runtime.so
web: lib/libtvm_web_runtime.js lib/libtvm_web_runtime.bc
ifndef DMLC_CORE_PATH ifndef DMLC_CORE_PATH
DMLC_CORE_PATH = $(ROOTDIR)/dmlc-core DMLC_CORE_PATH = $(ROOTDIR)/dmlc-core
...@@ -52,11 +53,13 @@ RUNTIME_DEP = $(RUNTIME_OBJ) ...@@ -52,11 +53,13 @@ RUNTIME_DEP = $(RUNTIME_OBJ)
# The flags # The flags
LDFLAGS = -pthread -lm -ldl LDFLAGS = -pthread -lm -ldl
CFLAGS = -std=c++11 -Wall -O2\ INCLUDE_FLAGS = -Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include -IHalideIR/src -Itopi/include
-Iinclude -I$(DLPACK_PATH)/include -I$(DMLC_CORE_PATH)/include -IHalideIR/src -Itopi/include -fPIC CFLAGS = -std=c++11 -Wall -O2 $(INCLUDE_FLAGS) -fPIC
LLVM_CFLAGS= -fno-rtti -DDMLC_ENABLE_RTTI=0 LLVM_CFLAGS= -fno-rtti -DDMLC_ENABLE_RTTI=0
FRAMEWORKS = FRAMEWORKS =
OBJCFLAGS = -fno-objc-arc OBJCFLAGS = -fno-objc-arc
EMCC_FLAGS= -s RESERVED_FUNCTION_POINTERS=2 -s NO_EXIT_RUNTIME=1 -DDMLC_LOG_STACK_TRACE=0\
-std=c++11 -Oz $(INCLUDE_FLAGS)
# Dependency specific rules # Dependency specific rules
ifdef CUDA_PATH ifdef CUDA_PATH
...@@ -149,6 +152,15 @@ lib/libtvm_runtime.so: $(RUNTIME_DEP) ...@@ -149,6 +152,15 @@ lib/libtvm_runtime.so: $(RUNTIME_DEP)
@mkdir -p $(@D) @mkdir -p $(@D)
$(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS) $(CXX) $(CFLAGS) $(FRAMEWORKS) -shared -o $@ $(filter %.o %.a, $^) $(LDFLAGS)
lib/libtvm_web_runtime.bc: web/web_runtime.cc
@mkdir -p build/web
@mkdir -p $(@D)
$(CXX) $(CFLAGS) -MM -MT lib/libtvm_web_runtime.bc $< >build/web/web_runtime.d
emcc $(EMCC_FLAGS) -o $@ web/web_runtime.cc
lib/libtvm_web_runtime.js: lib/libtvm_web_runtime.bc
@mkdir -p $(@D)
emcc $(EMCC_FLAGS) -o $@ lib/libtvm_web_runtime.bc
$(LIB_HALIDEIR): LIBHALIDEIR $(LIB_HALIDEIR): LIBHALIDEIR
......
Links to API References
=======================
This page contains links to API references that are build with different doc build system.
* `C++ doyxgen API <doxygen/index.html>`_
* `Javascript jsdoc API <jsdoc/index.html>`_
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
TVM has been developed by community members. TVM has been developed by community members.
Everyone is more than welcome to contribute. It is a way to make the project better and more accessible to more users. Everyone is more than welcome to contribute. It is a way to make the project better and more accessible to more users.
- Please add your name to [CONTRIBUTORS.md](../../CONTRIBUTORS.md) - Please add your name to [CONTRIBUTORS.md](https://github.com/dmlc/tvm/blob/master/CONTRIBUTORS.md)
- Please update [NEWS.md](../../NEWS.md) to add note on your changes to the API or added a new document. - Please update [NEWS.md](https://github.com/dmlc/tvm/blob/master/NEWS.md) to add note on your changes to the API or added a new document.
## Guidelines ## Guidelines
* [Submit Pull Request](#submit-pull-request) * [Submit Pull Request](#submit-pull-request)
...@@ -29,7 +29,7 @@ git rebase upstream/master ...@@ -29,7 +29,7 @@ git rebase upstream/master
it might be good to merge them together(use git rebase then squash) into more meaningful groups. it might be good to merge them together(use git rebase then squash) into more meaningful groups.
* Send the pull request! * Send the pull request!
- Fix the problems reported by automatic checks - Fix the problems reported by automatic checks
- If you are contributing a new module, consider add a testcase in [tests](../tests) - If you are contributing a new module or new function, add a test.
## Git Workflow Howtos ## Git Workflow Howtos
### How to resolve conflict with master ### How to resolve conflict with master
...@@ -87,8 +87,7 @@ The previous two tips requires force push, this is because we altered the path o ...@@ -87,8 +87,7 @@ The previous two tips requires force push, this is because we altered the path o
It is fine to force push to your own fork, as long as the commits changed are only yours. It is fine to force push to your own fork, as long as the commits changed are only yours.
## Testcases ## Testcases
- All the testcases are in [tests](../../tests) - All the testcases are in tests
- We use python nose for python test cases.
## Core Library ## Core Library
- Follow Google C style for C++. - Follow Google C style for C++.
......
...@@ -35,7 +35,7 @@ You can edit `make/config.mk` to change the compile options, and then build by ...@@ -35,7 +35,7 @@ You can edit `make/config.mk` to change the compile options, and then build by
### Building on Windows ### Building on Windows
TVM support build via MSVC using cmake. To build with Visual Studio 2015 use cmake. TVM support build via MSVC using cmake. To build with Visual Studio 2015 use cmake.
Make sure you have a recent version of cmake added to your path and then from the xgboost directory: Make sure you have a recent version of cmake added to your path and then from the tvm directory:
```bash ```bash
mkdir build mkdir build
...@@ -46,8 +46,8 @@ This specifies an out of source build using the MSVC 12 64 bit generator. Open t ...@@ -46,8 +46,8 @@ This specifies an out of source build using the MSVC 12 64 bit generator. Open t
### Customized Building ### Customized Building
The configuration of xgboost can be modified by ```config.mk``` The configuration of tvm can be modified by ```config.mk```
- First copy [make/config.mk](../make/config.mk) to the project root, on which - First copy make/config.mk to the project root, on which
any local modification will be ignored by git, then modify the according flags. any local modification will be ignored by git, then modify the according flags.
- TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM. - TVM optionally depends on LLVM. LLVM is required for CPU codegen that needs LLVM.
- LLVM 4.0 is needed for build with LLVM - LLVM 4.0 is needed for build with LLVM
...@@ -55,7 +55,7 @@ The configuration of xgboost can be modified by ```config.mk``` ...@@ -55,7 +55,7 @@ The configuration of xgboost can be modified by ```config.mk```
## Python Package Installation ## Python Package Installation
The python package is located at [python](../python). The python package is located at python
There are several ways to install the package: There are several ways to install the package:
1. Set the environment variable `PYTHONPATH` to tell python where to find 1. Set the environment variable `PYTHONPATH` to tell python where to find
......
...@@ -3,6 +3,7 @@ TVM Documentation ...@@ -3,6 +3,7 @@ TVM Documentation
Welcome to TVM documentation. Welcome to TVM documentation.
Contents Contents
-------- --------
...@@ -13,6 +14,7 @@ Contents ...@@ -13,6 +14,7 @@ Contents
how_to/install how_to/install
tutorials/index tutorials/index
faq faq
api/python/index
how_to/contribute how_to/contribute
api/python/index
api_links
genindex genindex
...@@ -24,7 +24,12 @@ ...@@ -24,7 +24,12 @@
#define TVM_EXTERN_C #define TVM_EXTERN_C
#endif #endif
/*! \brief TVM_DLL prefix for windows */ #ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#define TVM_DLL EMSCRIPTEN_KEEPALIVE
#endif
#ifndef TVM_DLL
#ifdef _WIN32 #ifdef _WIN32
#ifdef TVM_EXPORTS #ifdef TVM_EXPORTS
#define TVM_DLL __declspec(dllexport) #define TVM_DLL __declspec(dllexport)
...@@ -34,6 +39,7 @@ ...@@ -34,6 +39,7 @@
#else #else
#define TVM_DLL #define TVM_DLL
#endif #endif
#endif
// TVM Runtime is DLPack compatible. // TVM Runtime is DLPack compatible.
#include <dlpack/dlpack.h> #include <dlpack/dlpack.h>
...@@ -331,7 +337,7 @@ TVM_DLL int TVMFuncGetGlobal(const char* name, TVMFunctionHandle* out); ...@@ -331,7 +337,7 @@ TVM_DLL int TVMFuncGetGlobal(const char* name, TVMFunctionHandle* out);
* \param out_array The array of function names. * \param out_array The array of function names.
* \return 0 when success, -1 when failure happens * \return 0 when success, -1 when failure happens
*/ */
TVM_DLL int TVMFuncListGlobalNames(int *out_size, TVM_DLL int TVMFuncListGlobalNames(int* out_size,
const char*** out_array); const char*** out_array);
// Array related apis for quick proptyping // Array related apis for quick proptyping
......
...@@ -28,6 +28,7 @@ class TVMError(Exception): ...@@ -28,6 +28,7 @@ class TVMError(Exception):
"""Error thrown by TVM function""" """Error thrown by TVM function"""
pass pass
def _load_lib(): def _load_lib():
"""Load libary by searching possible path.""" """Load libary by searching possible path."""
lib_path = libinfo.find_lib_path() lib_path = libinfo.find_lib_path()
......
...@@ -5,9 +5,14 @@ import os ...@@ -5,9 +5,14 @@ import os
import platform import platform
def find_lib_path(): def find_lib_path(name=None):
"""Find dynamic library files. """Find dynamic library files.
Parameters
----------
name : list of str
List of names to be found.
Returns Returns
------- -------
lib_path : list(string) lib_path : list(string)
...@@ -30,13 +35,16 @@ def find_lib_path(): ...@@ -30,13 +35,16 @@ def find_lib_path():
elif os.name == "posix" and os.environ.get('LD_LIBRARY_PATH', None): elif os.name == "posix" and os.environ.get('LD_LIBRARY_PATH', None):
dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")]) dll_path.extend([p.strip() for p in os.environ['LD_LIBRARY_PATH'].split(":")])
dll_path = [os.path.abspath(x) for x in dll_path] dll_path = [os.path.abspath(x) for x in dll_path]
if name is not None:
if os.name == 'nt': lib_dll_path = [os.path.join(p, name) for p in dll_path]
lib_dll_path = [os.path.join(p, 'libtvm.dll') for p in dll_path] runtime_dll_path = []
runtime_dll_path = [os.path.join(p, 'libtvm_runtime.dll') for p in dll_path]
else: else:
lib_dll_path = [os.path.join(p, 'libtvm.so') for p in dll_path] if os.name == 'nt':
runtime_dll_path = [os.path.join(p, 'libtvm_runtime.so') for p in dll_path] lib_dll_path = [os.path.join(p, 'libtvm.dll') for p in dll_path]
runtime_dll_path = [os.path.join(p, 'libtvm_runtime.dll') for p in dll_path]
else:
lib_dll_path = [os.path.join(p, 'libtvm.so') for p in dll_path]
runtime_dll_path = [os.path.join(p, 'libtvm_runtime.so') for p in dll_path]
if not use_runtime: if not use_runtime:
# try to find lib_dll_path # try to find lib_dll_path
......
"""Util to invoke emscripten compilers in the system."""
# pylint: disable=invalid-name
from __future__ import absolute_import as _abs
import subprocess
from .._ffi.libinfo import find_lib_path
def create_js(output,
objects,
options=None,
cc="emcc"):
"""Create emscripten javascript library.
Parameters
----------
output : str
The target shared library.
objects : list
List of object files.
options : str
The additional options.
cc : str, optional
The compile string.
"""
cmd = [cc]
cmd += ["-s", "RESERVED_FUNCTION_POINTERS=2"]
cmd += ["-s", "NO_EXIT_RUNTIME=1"]
cmd += ["-Oz"]
cmd += ["-o", output]
objects = [objects] if isinstance(objects, str) else objects
with_runtime = False
for obj in objects:
if obj.find("libtvm_web_runtime.bc") != -1:
with_runtime = True
if not with_runtime:
objects += [find_lib_path("libtvm_web_runtime.bc")[0]]
cmd += objects
if options:
cmd += options
args = ' '.join(cmd)
proc = subprocess.Popen(
args, shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)
(out, _) = proc.communicate()
if proc.returncode != 0:
msg = "Compilation error:\n"
msg += out
raise RuntimeError(msg)
...@@ -37,7 +37,7 @@ void InitializeLLVM() { ...@@ -37,7 +37,7 @@ void InitializeLLVM() {
} }
llvm::TargetMachine* llvm::TargetMachine*
GetLLVMTargetMachine(const std::string& target_str) { GetLLVMTargetMachine(const std::string& target_str, bool allow_null) {
// setup target triple // setup target triple
CHECK(target_str.length() >= 4 && CHECK(target_str.length() >= 4 &&
target_str.substr(0, 4) == "llvm") target_str.substr(0, 4) == "llvm")
...@@ -91,7 +91,10 @@ GetLLVMTargetMachine(const std::string& target_str) { ...@@ -91,7 +91,10 @@ GetLLVMTargetMachine(const std::string& target_str) {
std::string err; std::string err;
const llvm::Target* target = const llvm::Target* target =
llvm::TargetRegistry::lookupTarget(target_triple, err); llvm::TargetRegistry::lookupTarget(target_triple, err);
CHECK(target) << err << " target_triple=" << target_triple; if (target == nullptr) {
CHECK(allow_null) << err << " target_triple=" << target_triple;
return nullptr;
}
// set target option // set target option
llvm::TargetOptions opt; llvm::TargetOptions opt;
opt.LessPreciseFPMADOption = true; opt.LessPreciseFPMADOption = true;
...@@ -110,6 +113,8 @@ GetLLVMTargetMachine(const std::string& target_str) { ...@@ -110,6 +113,8 @@ GetLLVMTargetMachine(const std::string& target_str) {
return tm; return tm;
} }
} // namespace codegen } // namespace codegen
} // namespace tvm } // namespace tvm
#endif // TVM_LLVM_VERSION #endif // TVM_LLVM_VERSION
...@@ -55,11 +55,11 @@ void InitializeLLVM(); ...@@ -55,11 +55,11 @@ void InitializeLLVM();
/*! /*!
* \brief Get target machine from target_str string. * \brief Get target machine from target_str string.
* \param target_str Target string, in format "llvm -target=xxx -mcpu=xxx" * \param target_str Target string, in format "llvm -target=xxx -mcpu=xxx"
* * \param allow_null Whether allow null to be returned.
* \return target machine * \return target machine
*/ */
llvm::TargetMachine* llvm::TargetMachine*
GetLLVMTargetMachine(const std::string& target_str); GetLLVMTargetMachine(const std::string& target_str, bool allow_null = false);
} // namespace codegen } // namespace codegen
} // namespace tvm } // namespace tvm
......
...@@ -194,6 +194,12 @@ TVM_REGISTER_API("module.loadfile_ll") ...@@ -194,6 +194,12 @@ TVM_REGISTER_API("module.loadfile_ll")
n->LoadIR(args[0]); n->LoadIR(args[0]);
*rv = runtime::Module(n); *rv = runtime::Module(n);
}); });
TVM_REGISTER_API("codegen.llvm_target_enabled")
.set_body([](TVMArgs args, TVMRetValue* rv) {
InitializeLLVM();
*rv = (GetLLVMTargetMachine(args[0], true) != nullptr);
});
} // namespace codegen } // namespace codegen
} // namespace tvm } // namespace tvm
#endif // TVM_LLVM_VERSION #endif // TVM_LLVM_VERSION
...@@ -108,12 +108,14 @@ bool RuntimeEnabled(const std::string& target) { ...@@ -108,12 +108,14 @@ bool RuntimeEnabled(const std::string& target) {
f_name = "device_api.metal"; f_name = "device_api.metal";
} else if (target == "stackvm") { } else if (target == "stackvm") {
f_name = "codegen.build_stackvm"; f_name = "codegen.build_stackvm";
} else if (target == "llvm") {
f_name = "codegen.build_llvm";
} else if (target == "rpc") { } else if (target == "rpc") {
f_name = "device_api.rpc"; f_name = "device_api.rpc";
} else if (target == "vpi" || target == "verilog") { } else if (target == "vpi" || target == "verilog") {
f_name = "device_api.vpi"; f_name = "device_api.vpi";
} else if (target.length() >= 4 && target.substr(0, 4) == "llvm") {
const PackedFunc* pf = runtime::Registry::Get("codegen.llvm_target_enabled");
if (pf == nullptr) return false;
return (*pf)(target);
} else { } else {
LOG(FATAL) << "Unknown optional runtime " << target; LOG(FATAL) << "Unknown optional runtime " << target;
} }
......
# For CPU
FROM ubuntu:14.04
RUN apt-get update --fix-missing
COPY install/ubuntu_install_core.sh /install/ubuntu_install_core.sh
RUN bash /install/ubuntu_install_core.sh
COPY install/ubuntu_install_python.sh /install/ubuntu_install_python.sh
RUN bash /install/ubuntu_install_python.sh
COPY install/ubuntu_install_emscripten.sh /install/ubuntu_install_emscripten.sh
RUN bash /install/ubuntu_install_emscripten.sh
RUN cp /root/.emscripten /emsdk-portable/
\ No newline at end of file
...@@ -28,7 +28,14 @@ RUN cd recommonmark; python setup.py install ...@@ -28,7 +28,14 @@ RUN cd recommonmark; python setup.py install
COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh COPY install/ubuntu_install_java.sh /install/ubuntu_install_java.sh
RUN bash /install/ubuntu_install_java.sh RUN bash /install/ubuntu_install_java.sh
COPY install/ubuntu_install_nodejs.sh /install/ubuntu_install_nodejs.sh
RUN bash /install/ubuntu_install_nodejs.sh
# Enable doxygen for c++ doc build
RUN apt-get install -y doxygen graphviz
# Environment variables # Environment variables
ENV PATH=/node_modules/.bin:${PATH}
ENV PATH=/usr/local/nvidia/bin:${PATH} ENV PATH=/usr/local/nvidia/bin:${PATH}
ENV PATH=/usr/clang+llvm-4.0.0-x86_64-linux-gnu-ubuntu-14.04/bin:${PATH} ENV PATH=/usr/clang+llvm-4.0.0-x86_64-linux-gnu-ubuntu-14.04/bin:${PATH}
ENV PATH=/usr/local/cuda/bin:${PATH} ENV PATH=/usr/local/cuda/bin:${PATH}
......
alias make="make -j4"
# Get latest cmake
wget https://cmake.org/files/v3.8/cmake-3.8.2-Linux-x86_64.tar.gz
tar xf cmake-3.8.2-Linux-x86_64.tar.gz
export PATH=/cmake-3.8.2-Linux-x86_64/bin/:${PATH}
wget https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
tar xf emsdk-portable.tar.gz
cd emsdk-portable
./emsdk update
./emsdk install latest
./emsdk activate latest
# Clone and pull latest sdk
./emsdk install clang-incoming-64bit
./emsdk activate clang-incoming-64bit
cd ..
apt-get update && apt-get install -y curl
curl -sL https://deb.nodesource.com/setup_6.x | bash -
apt-get update && apt-get install -y nodejs
npm install eslint jsdoc
...@@ -36,11 +36,11 @@ def test_llvm_add_pipeline(): ...@@ -36,11 +36,11 @@ def test_llvm_add_pipeline():
verify_elf(path, 0x03) verify_elf(path, 0x03)
def build_arm(): def build_arm():
if not tvm.module.enabled("llvm"): target = "llvm -target=armv7-none-linux-gnueabihf"
print("Skip because llvm is not enabled..") if not tvm.module.enabled(target):
print("Skip because %s is not enabled.." % target)
return return
temp = util.tempdir() temp = util.tempdir()
target = "llvm -target=armv7-none-linux-gnueabihf"
f = tvm.build(s, [A, B, C], target) f = tvm.build(s, [A, B, C], target)
path = temp.relpath("myadd.o") path = temp.relpath("myadd.o")
f.save(path) f.save(path)
......
#!/bin/bash #!/bin/bash
mkdir -p docs/_build/html
rm -rf docs/_build/html/jsdoc
rm -rf docs/_build/html/doxygen
# C++ doc
make doc
# JS doc
jsdoc web/tvm_runtime.js web/README.md || exit -1
mv out docs/_build/html/jsdoc || exit -1
mv docs/doxygen docs/_build/html/doxygen || exit -1
cd docs cd docs
PYTHONPATH=../python make html || exit -1 PYTHONPATH=../python make html || exit -1
cd _build/html cd _build/html
......
#!/bin/bash
cp /emsdk-portable/.emscripten ~/.emscripten
source /emsdk-portable/emsdk_env.sh
make -j4
#!/bin/bash
export PYTHONPATH=python
cp /emsdk-portable/.emscripten ~/.emscripten
source /emsdk-portable/emsdk_env.sh
export EM_CONFIG=${HOME}/.emscripten
export EM_CACHE=${HOME}/.emscripten_cache
echo "Build TVM Web runtime..."
make web
echo "Prepare test libraries..."
python tests/web/prepare_test_libs.py || exit -1
echo "Start testing..."
for test in tests/web/test_*.js; do
echo node $test
node $test || exit -1
done
echo "All tests finishes..."
# Prepare test library for js.
import tvm
from tvm.contrib import emscripten
import os
def prepare_test_libs(base_path):
target = "llvm -target=asmjs-unknown-emscripten -system-lib"
if not tvm.module.enabled(target):
raise RuntimeError("Target %s is not enbaled" % target)
n = tvm.var("n")
A = tvm.placeholder((n,), name='A')
B = tvm.compute(A.shape, lambda *i: A(*i) + 1.0, name='B')
s = tvm.create_schedule(B.op)
fadd1 = tvm.build(s, [A, B], target, name="add_one")
obj_path = os.path.join(base_path, "test_add_one.bc")
fadd1.save(obj_path)
emscripten.create_js(os.path.join(base_path, "test_module.js"), obj_path)
if __name__ == "__main__":
curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
prepare_test_libs(os.path.join(curr_path, "../../lib"))
// Load Emscripten Module, need to change path to root/lib
const path = require("path");
process.chdir(path.join(__dirname, "../../lib"));
var Module = require("../../lib/libtvm_web_runtime.js");
// Bootstrap TVMruntime with emscripten module.
const tvm_runtime = require("../../web/tvm_runtime.js");
const tvm = tvm_runtime.create(Module);
// Basic fields.
tvm.assert(tvm.float32 == "float32");
tvm.assert(tvm.listGlobalFuncNames() !== "undefined");
var sysLib = tvm.systemLib();
tvm.assert(typeof sysLib.getFunction !== "undefined");
sysLib.release();
// Test ndarray
function testArrayCopy(dtype, arr) {
var data = [1, 2, 3, 4, 5, 6];
var a = tvm.empty([2, 3], dtype);
a.copyFrom(data);
var ret = a.asArray();
tvm.assert(ret instanceof arr);
tvm.assert(ret.toString() == arr.from(data));
a.release();
}
testArrayCopy("float32", Float32Array);
testArrayCopy("int", Int32Array);
testArrayCopy("int8", Int8Array);
testArrayCopy("uint8", Uint8Array);
testArrayCopy("float64", Float64Array);
// Function registration
tvm.registerFunc("xyz", function(x, y) {
return x + y;
});
// Load Emscripten Module, need to change path to root/lib
const path = require("path");
process.chdir(path.join(__dirname, "../../lib"));
var Module = require("../../lib/test_module.js");
// Bootstrap TVMruntime with emscripten module.
const tvm_runtime = require("../../web/tvm_runtime.js");
const tvm = tvm_runtime.create(Module);
// Load system library
var sysLib = tvm.systemLib();
function randomArray(length, max) {
return Array.apply(null, Array(length)).map(function() {
return Math.random() * max;
});
}
function testAddOne() {
// grab pre-loaded function
var faddOne = sysLib.getFunction("add_one");
tvm.assert(tvm.isPackedFunc(faddOne));
var n = 124;
var A = tvm.empty(n).copyFrom(randomArray(n, 1));
var B = tvm.empty(n);
// call the function.
faddOne(A, B);
// verify
for (var i = 0; i < B.length; ++i) {
tvm.assert(B[i] == A[i] + 1);
}
faddOne.release();
}
testAddOne();
sysLib.release();
console.log("Finish verifying test_module_load");
// Load Emscripten Module, need to change path to root/lib
const path = require("path");
process.chdir(path.join(__dirname, "../../lib"));
var Module = require("../../lib/libtvm_web_runtime.js");
// Bootstrap TVMruntime with emscripten module.
const tvm_runtime = require("../../web/tvm_runtime.js");
const tvm = tvm_runtime.create(Module);
function testGetGlobal() {
var targs = [10, 10.0, "hello"]
tvm.registerFunc("my_packed_func", function () {
tvm.assert(Array.from(arguments).toString() == targs, "assert fail");
return 10
});
var f = tvm.getGlobalFunc("my_packed_func")
tvm.assert(tvm.isPackedFunc(f));
y = f.apply(null, targs);
tvm.assert(y == 10);
f.release();
}
function testReturnFunc() {
function addy(y) {
function add(x) {
return x + y;
}
return add;
}
var myf = tvm.convertFunc(addy);
var f = myf(10);
tvm.assert(tvm.isPackedFunc(f));
tvm.assert(f(11) == 21);
myf.release();
f.release();
}
function testByteArray() {
var a = new Uint8Array(3);
a[0] = 1;
a[1] = 2;
function myfunc(ss){
tvm.assert(ss instanceof Uint8Array);
tvm.assert(ss.toString() == a);
}
f = tvm.convertFunc(myfunc);
f(a);
f.release();
}
testGetGlobal();
testReturnFunc();
testByteArray();
module.exports = {
"env": {
"browser": true,
"node": true,
"es6": true
},
"extends": "eslint:recommended",
"rules": {
"indent": [
"error",
2
],
"linebreak-style": [
"error",
"unix"
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
]
}
};
# TVM WebAssembly and Javascript Backend
This folder contains TVM WebAssembly and Javascript backend through Emscripten.
## Installation
While the LLVM main branch support webassembly as a target. We still need a good runtime with libc and other
system library support. Emscripten toolchain offers that nicely. The general idea is to build TVM against
the fastcomp LLVM backend in the Emscripten project and allow us to generate ```asmjs-unknown-emscripten```
as a backend target.
### Setup Emscripten
Checkout [Emscripten Portable SDK Downloads](https://kripken.github.io/emscripten-site/docs/getting_started/downloads.html)
to download emsdk-portable and unzip it on a local folder. Follow the installation guide from emscripten document.
```bash
./emsdk update
./emsdk install latest
./emsdk activate latest
```
Because we need to compile against the LLVM backend of emscripten, we will need the source and llvm library.
Which can be installed via following command.
```bash
./emsdk install clang-incoming-64bit
./emsdk activate clang-incoming-64bit
```
### Setup Environment Variable
In normal setting, we can setup the necessary environment variable with the following command.
```bash
source /path-to-emsdk-portable/emsdk_env.sh
```
However, this will put emscripten's clang and llvm path ahead of the current system path.
What you can do is to set the path manually, by putting emscripten's path after the PATH like the following ones.
You can get the detailed path by type ```./emsdk activate```
```bash
export PATH=${PATH}:/emsdk-related-path-here
```
### Build TVM with Fastcomp LLVM
To build TVM with Emscripten's Fastcomp LLVM, we can modify the LLVM_CONFIG in ```config.mk```
to point to fastcomp's llvm-config and build TVM normally.
```bash
LLVM_CONFIG = /path/to/emsdk-portable/clang/fastcomp/build_incoming_64/bin/llvm-config
```
### Build TVM Web Runtime
The above command gives us the TVM compiling environment. Now we need to build runtime,
to do so, make sure we set the environment correctly as in previous section and type
```bash
make web
```
This will create ```lib/libtvm_web_runtime.bc``` and ```lib/libtvm_web_runtime.js```.
## Use TVM to Generate Javascript Library
The general idea is to use TVM as normally and set target to be ```llvm -target=asmjs-unknown-emscripten -system-lib```.
The following code snippet from [tests/web/prepare_test_libs.py](https://github.com/dmlc/tvm/tree/master/tests/web/prepare_test_libs.py) demonstrate
the compilation process.
```python
import tvm
from tvm.contrib import emscripten
import os
def prepare_test_libs(base_path):
target = "llvm -target=asmjs-unknown-emscripten -system-lib"
if not tvm.module.enabled(target):
raise RuntimeError("Target %s is not enbaled" % target)
n = tvm.var("n")
A = tvm.placeholder((n,), name='A')
B = tvm.compute(A.shape, lambda *i: A(*i) + 1.0, name='B')
s = tvm.create_schedule(B.op)
fadd1 = tvm.build(s, [A, B], target, name="add_one")
obj_path = os.path.join(base_path, "test_add_one.bc")
fadd1.save(obj_path)
emscripten.create_js(os.path.join(base_path, "test_module.js"), obj_path)
if __name__ == "__main__":
curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
prepare_test_libs(os.path.join(curr_path, "../../lib"))
```
In this workflow, we use TVM to generate a ```.bc``` file and statically link
that with the ```lib/libtvm_web_runtime.bc```(emscripten.create_js will help you do that).
The result js library is a library that contains both TVM runtime and the compiled function.
## Run the Generated Library
The following code snippet from [tests/web/test_module_load.js](https://github.com/dmlc/tvm/tree/master/tests/web/test_module_load.js) demonstrate
how to run the compiled library.
```js
// Load Emscripten Module, need to change path to root/lib
const path = require("path");
process.chdir(path.join(__dirname, "../../lib"));
var Module = require("../../lib/test_module.js");
// Bootstrap TVMruntime with emscripten module.
const tvm_runtime = require("../../web/tvm_runtime.js");
const tvm = tvm_runtime.create(Module);
// Load system library, the compiled functions is registered in sysLib.
var sysLib = tvm.systemLib();
function randomArray(length, max) {
return Array.apply(null, Array(length)).map(function() {
return Math.random() * max;
});
}
function testAddOne() {
// grab pre-loaded function
var faddOne = sysLib.getFunction("add_one");
tvm.assert(tvm.isPackedFunc(faddOne));
var n = 124;
var A = tvm.empty(n).copyFrom(randomArray(n, 1));
var B = tvm.empty(n);
// call the function.
faddOne(A, B);
// verify
for (var i = 0; i < B.length; ++i) {
tvm.assert(B[i] == A[i] + 1);
}
faddOne.release();
}
testAddOne();
sysLib.release();
```
## Notes
- Current example supports static linking, which is the preferred way to get more efficiency
in javascript backend.
- It should also be possible to use Emscripten's dynamic linking to dynamically load modules.
- Take a look at tvm_runtime.js which contains quite a few helper functions
to interact with TVM from javascript.
\ No newline at end of file
/**
* TVM Javascript web runtime library.
*
* @projectname tvm
* @version 0.1
*/
/* eslint no-unused-vars: "off" */
/* eslint no-unexpected-multiline: "off" */
/* eslint indent: "off" */
/**
* TVM Runtime namespace.
* Provide tvm_runtime.create to create a {@link tvm.TVMRuntime}.
*
* @namespace tvm_runtime
*/
var tvm_runtime = tvm_runtime || {};
/**
* TVM root namespace.
* The classes inside this namespace need to be constructed by factory functions.
* Use {@link tvm_runtime}.create to get started.
*
* @namespace tvm
*/
(function() {
/**
* TVMRuntime object for interacting with TVM runtime.
* This object can be constructed using {@link tvm_runtime}.create
*
* @class
* @memberof tvm
*/
function TVMRuntime() {
// Utility function to throw error
function throwError(message) {
if (typeof Error !== "undefined") {
throw new Error(message);
}
throw message;
}
var Module = this.Module;
var Runtime = this.Runtime;
if (typeof Module === "undefined") {
throwError("Emscripten Module is not available");
}
// constants
var SIZEOF_POINTER = 4;
var SIZEOF_SIZE_T = 4;
var SIZEOF_FLOAT = 4;
var SIZEOF_INT = 4;
var SIZEOF_INT8 = 1;
var SIZEOF_INT64 = 8;
var SIZEOF_DOUBLE = 8;
var SIZEOF_TYPE = 4;
var SIZEOF_CTX = SIZEOF_INT + SIZEOF_INT;
var SIZEOF_TVMVALUE = SIZEOF_DOUBLE;
var ARRAY_OFFSET_DATA = 0;
var ARRAY_OFFSET_CTX = ARRAY_OFFSET_DATA + SIZEOF_POINTER;
var ARRAY_OFFSET_DEV_TYPE = ARRAY_OFFSET_CTX;
var ARRAY_OFFSET_DEV_ID = ARRAY_OFFSET_CTX + SIZEOF_INT;
var ARRAY_OFFSET_NDIM = ARRAY_OFFSET_CTX + SIZEOF_CTX;
var ARRAY_OFFSET_DTYPE = ARRAY_OFFSET_NDIM + SIZEOF_INT;
var ARRAY_OFFSET_DTYPE_CODE = ARRAY_OFFSET_DTYPE;
var ARRAY_OFFSET_DTYPE_BITS = ARRAY_OFFSET_DTYPE_CODE + SIZEOF_INT8;
var ARRAY_OFFSET_DTYPE_LANES = ARRAY_OFFSET_DTYPE_BITS + SIZEOF_INT8;
var ARRAY_OFFSET_SHAPE = ARRAY_OFFSET_DTYPE + SIZEOF_TYPE;
var ARRAY_OFFSET_STRIDES = ARRAY_OFFSET_STRIDES + SIZEOF_POINTER;
var ARRAY_OFFSET_BYTE_OFFSET = ARRAY_OFFSET_STRIDES + SIZEOF_POINTER;
// Type codes
var kInt = 0;
var kUInt = 1;
var kFloat = 2;
var kHandle = 3;
var kNull = 4;
var kTVMType = 5;
var kTVMContext = 6;
var kArrayHandle = 7;
var kNodeHandle = 8;
var kModuleHandle = 9;
var kFuncHandle = 10;
var kStr = 11;
var kBytes = 12;
//-----------------------------------------
// TVM CWrap library
// ----------------------------------------
var TVMGetLastError = Module.cwrap(
"TVMGetLastError",
"string", // const char*
[]);
var TVMAPISetLastError = Module.cwrap
("TVMAPISetLastError",
null,
["string" // const char*
]);
var TVMModImport = Module.cwrap
("TVMModImport",
"number",
["number", // TVMModuleHandle mod
"number" // TVMModuleHandle dep
]);
var TVMModGetFunction = Module.cwrap
("TVMModGetFunction",
"number",
["number", // TVMModuleHandle mod
"string", // const char* func_name
"number", // int query_imports
"number" // TVMFunctionHandle *out
]);
var TVMModFree = Module.cwrap
("TVMModFree",
"number",
["number" // TVMModeHandle mod
]);
var TVMFuncFree = Module.cwrap
("TVMFuncFree",
"number",
["number" // TVMFunctionHandle func
]);
var TVMFuncCall = Module.cwrap
("TVMFuncCall",
"number",
["number", // TVMFunctionHandle func
"number", // TVMValue* arg_values
"number", // int* arg_tcodes
"number", // int num_args
"number", // int ret_val
"number" // int ret_type_code
]);
var TVMCFuncSetReturn = Module.cwrap
("TVMCFuncSetReturn",
"number",
["number", // TVMRetValueHandle ret
"number", // TVMValue* value
"number", // int* type_code
"number" // int num_ret
]);
var TVMCbArgToReturn = Module.cwrap
("TVMCbArgToReturn",
"number",
["number", // TVMValue* value
"number" // int code
]);
var TVMFuncCreateFromCFunc = Module.cwrap
("TVMFuncCreateFromCFunc",
"number",
["number", // TVMPackedCFunc func,
"number", // void* resource_handle
"number", // TVMPackedCFuncFinalizer fin
"number" // TVMFunctionHandle *out
]);
var TVMFuncRegisterGlobal = Module.cwrap
("TVMFuncRegisterGlobal",
"number",
["string", // name
"number", // TVMFunctionHandle f
"number" // int override
]);
var TVMFuncGetGlobal = Module.cwrap
("TVMFuncGetGlobal",
"number",
["string", // const char* name
"number" // TVMFunctionHandle* out
]);
var TVMFuncListGlobalNames = Module.cwrap
("TVMFuncListGlobalNames",
"number",
["number", // int* out_size
"number" // const char*** out_array
]);
var TVMArrayAlloc = Module.cwrap
("TVMArrayAlloc",
"number",
["number", // const tvm_index_t* shape
"number", // int ndim
"number", // int dtype_code
"number", // int dtype_bits
"number", // int dtype_lanes
"number", // int device_type
"number", // int device_id
"number" // int TVMArrayHandle* out
]);
var TVMArrayFree = Module.cwrap
("TVMArrayFree",
"number",
["number" // TVMArrayHandle handle
]);
var TVMArrayCopyFromTo = Module.cwrap
("TVMArrayCopyFromTo",
"number",
["number", // TVMArrayHandle from
"number" // TVMArrayHandle to
]);
var TVMArrayCopyFromBytes = Module.cwrap
("TVMArrayCopyFromBytes",
"number",
["number", // TVMArrayHandle handle
"number", // int data
"number" // size_t nbytes
]);
var TVMArrayCopyToBytes = Module.cwrap
("TVMArrayCopyToBytes",
"number",
["number", // TVMArrayHandle handle
"number", // int data
"number" // size_t nbytes
]);
//-----------------------------------------
// Static utility functions
// ----------------------------------------
this.assert = function(condition, message) {
if (!condition) {
message = message || "assert failed";
throwError(message);
}
};
var CHECK = this.assert;
function TVM_CALL(ret) {
if (ret != 0) {
throwError(TVMGetLastError());
}
}
function CInt64ArrayToJS(ptr, size) {
var ret = [];
for (var i = 0; i < size; ++i) {
ret.push(Module.getValue(ptr + i * SIZEOF_INT64, "i64"));
}
return ret;
}
function CStringToJS(ptr) {
var ret = [];
var ch = 1;
while (ch != 0) {
ch = Module.getValue(ptr, "i8");
if (ch != 0) {
ret.push(String.fromCharCode(ch));
}
++ptr;
}
return ret.join("");
}
function CBytesToJS(ptr) {
var data = Module.getValue(ptr, "*");
var size = Module.getValue(ptr + SIZEOF_POINTER, "i32");
var ret = new Uint8Array(new ArrayBuffer(size));
ret.set(new Uint8Array(Module.HEAPU8.buffer, data, size));
return ret;
}
function StringToUint8Array(str) {
var arr = new Uint8Array(str.length);
for(var i = 0; i < str.length; ++i) {
arr[i] = str.charCodeAt(i);
}
return arr;
}
//-----------------------------------------
// Class declarations
// ----------------------------------------
function CBuffer(nbytes) {
this.data = Module._malloc(nbytes);
}
function RefTVMValue() {
this.data = Module._malloc(SIZEOF_TVMVALUE);
}
function TVMArgs(nargs) {
this.nargs = nargs;
this.value = Module._malloc(SIZEOF_TVMVALUE * nargs);
this.tcode = Module._malloc(SIZEOF_INT * nargs);
this.temp = [];
}
function TVMType(code, bits, lanes) {
this.code = code;
this.bits = bits;
this.lanes = lanes;
}
/**
* TVM device context.
* @class
* @memberof tvm
*/
function TVMContext(device_type, device_id) {
this.device_type = device_type;
this.device_id = device_id;
}
/**
* TVM n-dimensional array.
*
* Use {@link tvm.TVMRuntime}.empty to create an instance.
* @class
* @memberof tvm
*/
function NDArray(handle) {
this.handle = handle;
this.ndim = Module.getValue(this.handle + ARRAY_OFFSET_NDIM, "i32");
// shape
var cshape = Module.getValue(this.handle + ARRAY_OFFSET_SHAPE, "*");
this.shape = CInt64ArrayToJS(cshape, this.ndim);
// dtype
var code = Module.getValue(this.handle + ARRAY_OFFSET_DTYPE_CODE, "i8");
var bits = Module.getValue(this.handle + ARRAY_OFFSET_DTYPE_BITS, "i8");
var lanes = Module.getValue(this.handle + ARRAY_OFFSET_DTYPE_LANES, "i16");
var dtype = new TVMType(code, bits, lanes);
this.dtype = dtype;
this.BYTES_PER_ELEMENT = (dtype.bits * dtype.lanes / 8);
// ctx
var device_type = Module.getValue(this.handle + ARRAY_OFFSET_DEV_TYPE, "i32");
var device_id = Module.getValue(this.handle + ARRAY_OFFSET_DEV_ID, "i32");
this.context = new TVMContext(device_type, device_id);
// byte_offset
this.byteOffset = Module.getValue(this.handle + ARRAY_OFFSET_BYTE_OFFSET, "i64");
}
function TVMFunction(handle) {
this.handle = handle;
}
/**
* Module container of TVM generated functions.
*
* @class
* @memberof tvm
*/
function TVMModule(handle) {
this.handle = handle;
}
/**
* A typed scalar constant.
* This can be used to pass number as integer types to tvm function.
* Use {@link tvm.TVMRuntime}.constant to create an instance.
* @class
* @memberof tvm
*/
function TVMConstant(value, dtype) {
this.value = value;
this.dtype = dtype;
}
//-----------------------------------------
// Private Functions
// ----------------------------------------
function getTVMType(dtype) {
if (dtype instanceof TVMType) return dtype;
if (typeof dtype == "string") {
var pattern = dtype;
var code, bits = 32, lanes = 1;
if (pattern.substring(0, 5) == "float") {
pattern = pattern.substring(5, pattern.length);
code = kFloat;
} else if (pattern.substring(0, 3) == "int") {
pattern = pattern.substring(3, pattern.length);
code = kInt;
} else if (pattern.substring(0, 4) == "uint") {
pattern = pattern.substring(4, pattern.length);
code = kUInt;
} else if (pattern.substring(0, 6) == "handle") {
pattern = pattern.substring(5, pattern.length);
code = kHandle;
bits = 64;
} else {
throw throwError("Unknown dtype " + dtype);
}
var arr = pattern.split("x");
if (arr.length >= 1) {
var parsed = parseInt(arr[0]);
if (parsed == arr[0]) {
bits = parsed;
}
}
if (arr.length >= 2) {
lanes = parseInt(arr[1]);
}
return new TVMType(code, bits, lanes);
} else {
throw throwError("Unknown dtype " + dtype);
}
}
function TVMRetValueToJS(vptr, tcode) {
switch (tcode) {
case kInt:
case kUInt: return Module.getValue(vptr, "i64");
case kFloat: return Module.getValue(vptr, "double");
case kFuncHandle: return makeTVMFunction(Module.getValue(vptr, "*"));
case kModuleHandle: return new TVMModule(Module.getValue(vptr, "*"));
case kNull: return null;
case kStr: return CStringToJS(Module.getValue(vptr, "*"));
case kBytes: return CBytesToJS(Module.getValue(vptr, "*"));
default: throwError("Unsupported return type code=" + tcode);
}
}
function makeTVMFunction(handle) {
var func = new TVMFunction(handle);
var ret = function () {
// alloc
var args = new TVMArgs(arguments.length);
var rvalue = new RefTVMValue();
var rtcode = new RefTVMValue();
args.setArguments(arguments);
TVM_CALL(TVMFuncCall(handle, args.value, args.tcode,
args.nargs, rvalue.data, rtcode.data));
var rv = TVMRetValueToJS(rvalue.data, rtcode.asInt());
// release
args.release();
rvalue.release();
rtcode.release();
return rv;
};
var release = function() {
func.release();
};
ret._tvm_function = func;
ret.release = release;
return ret;
}
//-----------------------------------------
// Javascript PackedCallback System
// ----------------------------------------
var funcTable = [0];
var freeFuncId = [];
function invokeCallback(arg_value, arg_tcode, nargs, ret, handle) {
var args = [];
for (var i = 0; i < nargs; ++i) {
var vptr = arg_value + i * SIZEOF_TVMVALUE;
var tcode = Module.getValue(arg_tcode + i * SIZEOF_INT, "i32");
if (tcode == kNodeHandle ||
tcode == kFuncHandle ||
tcode == kModuleHandle) {
TVM_CALL(TVMCbArgToReturn(vptr, tcode));
}
args.push(TVMRetValueToJS(vptr, tcode));
}
var rv = funcTable[handle].apply(null, args);
if (typeof rv !== "undefined") {
// alloc
var rarg = new TVMArgs(1);
rarg.setArguments([rv]);
TVM_CALL(TVMCFuncSetReturn(ret, rarg.value, rarg.tcode, 1));
// release
rarg.release();
}
return 0;
}
function freeCallback(handle) {
funcTable[handle] = 0;
freeFuncId.push(handle);
}
var fptrInvokeCallback = null;
var fptrFreeCallback = null;
if (typeof Runtime !== "undefined") {
fptrInvokeCallback = Runtime.addFunction(invokeCallback);
fptrFreeCallback = Runtime.addFunction(freeCallback);
}
/**
* Check if a function is TVM PackedFunc
* @param {Function} f function to be checked.
* @return {boolean} Whether f is PackedFunc
*/
this.isPackedFunc = function(f) {
return (typeof f._tvm_function !== "undefined");
};
var isPackedFunc = this.isPackedFunc;
/**
* Convert a javascript function to TVM function.
* @param {Function} f javascript function.
* @return {Function} The created TVMFunction.
*/
this.convertFunc = function(f) {
if (isPackedFunc(f)) return f;
CHECK(fptrInvokeCallback !== null, "Emscripten Runtime is not available");
var fid;
if (freeFuncId.length != 0) {
fid = freeFuncId.pop();
} else {
fid = funcTable.length;
funcTable.push(0);
}
funcTable[fid] = f;
// alloc
var out = new RefTVMValue();
TVM_CALL(TVMFuncCreateFromCFunc(
fptrInvokeCallback, fid, fptrFreeCallback, out.data));
var out_handle = out.asHandle();
// release
out.release();
return makeTVMFunction(out_handle);
};
var convertFunc = this.convertFunc;
//-----------------------------------------
// Private Class declarations
// ----------------------------------------
CBuffer.prototype = {
/**
* Finalizer: resources from the object.
*/
release : function() {
if (this.data != 0) {
Module._free(this.data);
this.data = 0;
}
},
};
// RefTVMValue
RefTVMValue.prototype = {
/**
* Finalizer: resources from the object.
*/
release : function() {
if (this.data != 0) {
Module._free(this.data);
this.data = 0;
}
},
asInt : function() {
return Module.getValue(this.data, "i32");
},
asInt64 : function() {
return Module.getValue(this.data, "i64");
},
asDouble : function() {
return Module.getValue(this.data, "double");
},
asHandle : function() {
return Module.getValue(this.data, "*");
}
};
// TVMArgs
TVMArgs.prototype = {
release : function() {
if (this.value != 0) {
Module._free(this.value);
Module._free(this.tcode);
this.value = 0;
for (var i = 0; i< this.temp.length; ++i) {
if (this.temp[i].release instanceof Function) {
this.temp[i].release();
}
}
}
},
setInt : function(index, value) {
Module.setValue(this.tcode + index * SIZEOF_INT, kInt, "i32");
Module.setValue(this.value + index * SIZEOF_TVMVALUE, value, "i64");
},
setDouble : function(index, value) {
Module.setValue(this.tcode + index * SIZEOF_INT, kFloat, "i32");
Module.setValue(this.value + index * SIZEOF_TVMVALUE, value, "double");
},
setHandle : function(index, value, tcode) {
Module.setValue(this.tcode + index * SIZEOF_INT, tcode, "i32");
Module.setValue(this.value + index * SIZEOF_TVMVALUE, value, "*");
},
setString : function(index, value) {
var sdata = new CBuffer(value.length);
Module.HEAPU8.set(StringToUint8Array(value), sdata.data);
this.temp.push(sdata);
Module.setValue(this.tcode + index * SIZEOF_INT, kStr, "i32");
Module.setValue(this.value + index * SIZEOF_TVMVALUE, sdata.data, "*");
},
setBytes : function(index, value) {
CHECK(value instanceof Uint8Array);
var sdata = new CBuffer(value.length);
var sheader = new CBuffer(SIZEOF_POINTER + SIZEOF_SIZE_T);
Module.HEAPU8.set(new Uint8Array(value), sdata.data);
Module.setValue(sheader.data, sdata.data, "*");
Module.setValue(sheader.data + SIZEOF_POINTER, value.length, "i32");
this.temp.push(sdata);
this.temp.push(sheader);
Module.setValue(this.tcode + index * SIZEOF_INT, kBytes, "i32");
Module.setValue(this.value + index * SIZEOF_TVMVALUE, sheader.data, "*");
},
setArguments : function(args) {
for (var i = 0; i < args.length; ++i) {
var v = args[i];
var tp = typeof v;
if (v instanceof NDArray) {
this.setHandle(i, v.handle, kArrayHandle);
} else if (v instanceof TVMConstant) {
var code = getTVMType(v.dtype).code;
if (code == kInt || code == kUInt) {
this.setInt(i, v.value);
} else if (code == kFloat) {
this.setDouble(i, v.value);
} else {
CHECK(code == kHandle);
this.setHandle(i, v.value, kHandle);
}
} else if (tp == "number") {
this.setDouble(i, v);
} else if (typeof v._tvm_function !== "undefined") {
this.setString(i, v._tvm_function.handle, kFuncHandle);
} else if (v === null) {
this.setHandle(i, 0, kNull);
} else if (tp == "string") {
this.setString(i, v);
} else if (v instanceof Uint8Array) {
this.setBytes(i, v);
} else if (v instanceof Function) {
v = convertFunc(v);
this.temp.push(v);
this.setHandle(i, v._tvm_function.handle, kFuncHandle);
} else {
throwError("Unsupported argument type " + tp);
}
}
}
};
// TVMType
var TYPE_CODE2STR = {
0 : "int",
1 : "uint",
2 : "float",
4 : "handle"
};
TVMType.prototype = {
toString : function() {
var ret = TYPE_CODE2STR[this.code] + this.bits.toString();
if (this.lanes != 1) {
return ret + "x" + this.lanes.toString();
} else {
return ret;
}
}
};
// TVMFunction
TVMFunction.prototype = {
release : function() {
if (this.handle != 0) {
TVM_CALL(TVMFuncFree(this.handle));
this.handle = 0;
}
}
};
// TVMContext
var CTX_MASK2STR = {
1 : "cpu",
2 : "gpu",
4 : "opencl",
8 : "metal",
9 : "vpi"
};
var CTX_STR2MASK = {
"cpu": 1,
"gpu": 2,
"cuda": 2,
"cl": 4,
"opencl": 4,
"metal": 8,
"vpi": 9
};
TVMContext.prototype = {
toString : function() {
return CTX_MASK2STR[this.device_type] + "(" + this.device_id.toString() + ")";
}
};
//-----------------------------------------
// Public Functions
// ----------------------------------------
/**
* Construct a TVMContext given device type and id.
*
* @param {number} device_type, string or int, The device type.
* @param {number} device_id, the device id.
* @return {tvm.TVMContext} The created TVMContext
*/
this.context = function(device_type, device_id) {
if (typeof device_type == "string") {
device_type = CTX_STR2MASK[device_type];
}
return new TVMContext(device_type, device_id);
};
var context = this.context;
/**
* Create empty ndarray with given shape.
*
* @param {Array.<number>} shape The shape of the array.
* @param {string} dtype The data type of the array, optional, default="float32"
* @param {tvm.TVMContext} ctx The context of the array, optional, default=cpu(0).
* @return {tvm.NDArray} The created ndarray.
*/
this.empty = function(shape, dtype, ctx) {
dtype = (typeof dtype !== "undefined") ? dtype: "float32";
ctx = (typeof ctx !== "undefined") ? ctx : context("cpu", 0);
shape = (typeof shape == "number") ? [shape] : shape;
// alloc
var cshape = Module._malloc(SIZEOF_INT64 * shape.length);
var out = new RefTVMValue();
for (var i = 0; i < shape.length; ++i) {
Module.setValue(cshape + i * SIZEOF_INT64, shape[i], "i64");
}
dtype = getTVMType(dtype);
TVM_CALL(TVMArrayAlloc(cshape, shape.length,
dtype.code, dtype.bits, dtype.lanes,
ctx.device_type, ctx.device_id,
out.data));
var out_handle = out.asHandle();
// release
Module._free(cshape);
out.release();
return new NDArray(out_handle);
};
/**
* List all global function names in the TVM runtime.
* @return {Array.<string>} List of global function names.
*/
this.listGlobalFuncNames = function() {
// alloc
var out_size = new RefTVMValue();
var out_array = new RefTVMValue();
TVM_CALL(TVMFuncListGlobalNames(out_size.data, out_array.data));
var length = out_size.asInt();
var base = out_array.asHandle();
var names = [];
for (var i = 0 ; i < length; ++i) {
names.push(
CStringToJS(Module.getValue(base + i * SIZEOF_POINTER, "*")));
}
// release
out_size.release();
out_array.release();
return names;
};
/**
* Get a global function from TVM runtime.
*
* @param {string} The name of the function.
* @return {Function} The corresponding function.
*/
this.getGlobalFunc = function (name) {
// alloc
var out = new RefTVMValue();
TVM_CALL(TVMFuncGetGlobal(name, out.data));
var out_handle = out.asHandle();
// release
out.release();
return makeTVMFunction(out_handle);
};
var getGlobalFunc = this.getGlobalFunc;
/**
* Register function to be global function in tvm runtime.
* @param {string} name The name of the function.
* @param {Function} f function to be registered.
* @param {boolean} override Whether overwrite function in existing registry.
*/
this.registerFunc = function(name, f, override) {
f = convertFunc(f);
override = (typeof override !== "undefined") ? override: false;
var ioverride = override ? 1 : 0;
TVM_CALL(TVMFuncRegisterGlobal(name, f._tvm_function.handle, ioverride));
};
/**
* Create a typed scalar constant.
* This can be used to pass number as integer types to tvm function.
*
* @param {number} value The value of the data.
* @param {string} dtype The data type.
* @param {tvm.TVMConstant} The created typed scalar.
*/
this.constant = function(value, dtype) {
return new TVMConstant(value, dtype);
};
//-----------------------------------------
// Wrap of TVM Functions.
// ----------------------------------------
var fGetSystemLib = getGlobalFunc("module._GetSystemLib");
/**
* Get system-wide library module singleton.
* System lib is a global module that contains self register functions in startup.
* @return {tvm.TVMModule} The system module singleton.
*/
this.systemLib = function() {
return fGetSystemLib();
};
//-----------------------------------------
// Class defintions
// ----------------------------------------
// NDArray.
NDArray.prototype = {
/**
* Finalizer: resources from the object.
*/
release : function() {
if (this.handle != 0) {
TVM_CALL(TVMArrayFree(this.handle));
this.handle = 0;
}
},
/**
* Copy data from another NDArray or javascript array.
* The number of elements must match.
*
* @param {Array} data The source data array.
*/
copyFrom : function(data) {
if (data instanceof NDArray) {
TVM_CALL(TVMArrayCopyFromTo(data.handle, this.handle));
} else {
var size = this.shape.reduce(function(a, b) { return a * b; }, 1);
if (data.length != size) {
throwError("data size and shape mismatch data.length" + data.length + " vs " + size);
}
if (this.dtype == "float32") {
data = Float32Array.from(data);
} else if (this.dtype == "float64") {
data = Float64Array.from(data);
} else if (this.dtype == "int32") {
data = Int32Array.from(data);
} else if (this.dtype == "int8") {
data = Int8Array.from(data);
} else if (this.dtype == "uint8") {
data = Uint8Array.from(data);
} else {
throwError("Unsupported data type " + this.dtype);
}
return this.copyFromRawBytes(new Uint8Array(data.buffer));
}
},
/**
* Copy data from raw bytes.
* @param {Uint8Array} data Uint8Array of bytes.
*/
copyFromRawBytes : function(data) {
var size = this.shape.reduce(function(a, b) { return a * b; }, 1);
var dtype = getTVMType(this.dtype);
var nbytes = this.BYTES_PER_ELEMENT * size;
CHECK(data instanceof Uint8Array);
CHECK(data.length == nbytes,
"Data length and bytes do not match " + data.length +
" vs " + nbytes);
var temp = Module._malloc(nbytes);
Module.HEAPU8.set(data, temp);
TVM_CALL(TVMArrayCopyFromBytes(this.handle, temp, nbytes));
Module._free(temp);
return this;
},
/**
* Return a copied Uint8Array of the raw bytes in the NDArray.
* @return {Uint8Array} The created array.
*/
asRawBytes : function() {
var size = this.shape.reduce(function(a, b) { return a * b; }, 1);
var nbytes = this.BYTES_PER_ELEMENT * size;
var temp = Module._malloc(nbytes);
TVM_CALL(TVMArrayCopyToBytes(this.handle, temp, nbytes));
var ret = new Uint8Array(new ArrayBuffer(nbytes));
ret.set(new Uint8Array(Module.HEAPU8.buffer, temp, nbytes));
Module._free(temp);
return ret;
},
/**
* Return Array data content as javascript typed array.
* @return {TypedArray} The created array.
*/
asArray : function() {
if (this.dtype == "float32") {
return new Float32Array(this.asRawBytes().buffer);
} else if (this.dtype == "float64") {
return new Float64Array(this.asRawBytes().buffer);
} else if (this.dtype == "int32") {
return new Int32Array(this.asRawBytes().buffer);
} else if (this.dtype == "int8") {
return new Int8Array(this.asRawBytes().buffer);
} else if (this.dtype == "uint8") {
return new Uint8Array(this.asRawBytes().buffer);
} else {
throwError("Unsupported data type " + this.dtype);
}
}
};
TVMModule.prototype = {
/**
* Finalizer: resources from the object.
*/
release : function() {
if (this.handle != 0) {
TVM_CALL(TVMModFree(this.handle));
this.handle = 0;
}
},
/**
* Get function from the module.
* @param {string} name The name of the function.
* @return {Function} The correspondin function.
*/
getFunction : function(name) {
// alloc
var out = new RefTVMValue();
TVM_CALL(TVMModGetFunction(this.handle, name, 0, out.data));
var out_handle = out.asHandle();
// release
out.release();
if (out_handle == 0) {
throwError("Module has no function " + name);
}
return makeTVMFunction(out_handle);
},
/**
* Add module to the import list of current one.
* @param {tvm.TVMModule} mod The other module to be imported.
*/
import_module : function(mod) {
CHECK(mod instanceof TVMModule, "mod must be instance of TVMModule");
TVM_CALL(TVMModImport(this.handle, mod.handle));
}
};
//-----------------------------------------
// Static variables.
// ----------------------------------------
/** Float32 type */
this.float32 = "float32";
/** Int32 type */
this.int32 = "int32";
}
/**
* Create a TVM runtime given emscripten module.
* @property {string} create
* @memberof tvm_runtime
* @param Module The emscripten module.
* @param Runtime The emscripten runtime, optional
* @return {tvm.TVMRuntime} The created TVM runtime.
*/
this.create = function(Module, Runtime) {
var tvm = {};
tvm.Module = Module;
if (typeof Runtime == "undefined") {
Runtime = Module.Runtime;
}
tvm.Runtime = Runtime;
TVMRuntime.apply(tvm);
return tvm;
};
}).apply(tvm_runtime);
// export things in node
if (typeof module !== "undefined" && module.exports) {
module.exports = tvm_runtime;
}
/*!
* Copyright (c) 2017 by Contributors
* \file web_runtime.cc
*/
#include "../src/runtime/c_runtime_api.cc"
#include "../src/runtime/cpu_device_api.cc"
#include "../src/runtime/workspace_pool.cc"
#include "../src/runtime/module_util.cc"
#include "../src/runtime/system_lib_module.cc"
#include "../src/runtime/module.cc"
#include "../src/runtime/registry.cc"
#include "../src/runtime/file_util.cc"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment