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,7 +35,10 @@ def find_lib_path(): ...@@ -30,7 +35,10 @@ 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:
lib_dll_path = [os.path.join(p, name) for p in dll_path]
runtime_dll_path = []
else:
if os.name == 'nt': if os.name == 'nt':
lib_dll_path = [os.path.join(p, 'libtvm.dll') 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] runtime_dll_path = [os.path.join(p, 'libtvm_runtime.dll') for p in 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
/*!
* 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