Commit 589a2651 by Zhixun Tan Committed by Tianqi Chen

Add test case: Create a static WebGL library and run it in the browser. (#932)

* Add test case: Create a static WebGL library and run it in the browser.

* Add documentation for loadModuleFromFile

* Modify emscripten.createjs
parent 7859058e
...@@ -60,3 +60,5 @@ def create_js(output, ...@@ -60,3 +60,5 @@ def create_js(output,
msg = "Compilation error:\n" msg = "Compilation error:\n"
msg += out msg += out
raise RuntimeError(msg) raise RuntimeError(msg)
create_js.object_format = "bc"
...@@ -84,6 +84,8 @@ class Module(ModuleBase): ...@@ -84,6 +84,8 @@ class Module(ModuleBase):
fcompile : function(target, file_list, kwargs), optional fcompile : function(target, file_list, kwargs), optional
Compilation function to use create dynamic library. Compilation function to use create dynamic library.
If fcompile has attribute object_format, will compile host library
to that format. Otherwise, will use default format "o".
kwargs : dict, optiona; kwargs : dict, optiona;
Additional arguments passed to fcompile Additional arguments passed to fcompile
...@@ -95,7 +97,11 @@ class Module(ModuleBase): ...@@ -95,7 +97,11 @@ class Module(ModuleBase):
if self.type_key != "llvm": if self.type_key != "llvm":
raise ValueError("Module[%s]: Only llvm support export shared" % self.type_key) raise ValueError("Module[%s]: Only llvm support export shared" % self.type_key)
temp = _util.tempdir() temp = _util.tempdir()
path_obj = temp.relpath("lib.o") if fcompile is not None and hasattr(fcompile, "object_format"):
object_format = fcompile.object_format
else:
object_format = "o"
path_obj = temp.relpath("lib." + object_format)
self.save(path_obj) self.save(path_obj)
files = [path_obj] files = [path_obj]
is_system_lib = self.get_function("__tvm_is_system_module")() is_system_lib = self.get_function("__tvm_is_system_module")()
......
...@@ -13,8 +13,8 @@ namespace runtime { ...@@ -13,8 +13,8 @@ namespace runtime {
class SystemLibModuleNode : public ModuleNode { class SystemLibModuleNode : public ModuleNode {
public: public:
SystemLibModuleNode() { SystemLibModuleNode() = default;
}
const char* type_key() const final { const char* type_key() const final {
return "system_lib"; return "system_lib";
} }
...@@ -23,6 +23,13 @@ class SystemLibModuleNode : public ModuleNode { ...@@ -23,6 +23,13 @@ class SystemLibModuleNode : public ModuleNode {
const std::string& name, const std::string& name,
const std::shared_ptr<ModuleNode>& sptr_to_self) final { const std::shared_ptr<ModuleNode>& sptr_to_self) final {
std::lock_guard<std::mutex> lock(mutex_); std::lock_guard<std::mutex> lock(mutex_);
if (module_blob_ != nullptr) {
// If we previously recorded submodules, load them now.
ImportModuleBlob(reinterpret_cast<const char*>(module_blob_), &imports_);
module_blob_ = nullptr;
}
auto it = tbl_.find(name); auto it = tbl_.find(name);
if (it != tbl_.end()) { if (it != tbl_.end()) {
return WrapPackedFunc( return WrapPackedFunc(
...@@ -38,7 +45,14 @@ class SystemLibModuleNode : public ModuleNode { ...@@ -38,7 +45,14 @@ class SystemLibModuleNode : public ModuleNode {
void** ctx_addr = reinterpret_cast<void**>(ptr); void** ctx_addr = reinterpret_cast<void**>(ptr);
*ctx_addr = this; *ctx_addr = this;
} else if (name == symbol::tvm_dev_mblob) { } else if (name == symbol::tvm_dev_mblob) {
ImportModuleBlob(reinterpret_cast<const char*>(ptr), &imports_); // Record pointer to content of submodules to be loaded.
// We defer loading submodules to the first call to GetFunction().
// The reason is that RegisterSymbol() gets called when initializing the
// syslib (i.e. library loading time), and the registeries aren't ready
// yet. Therefore, we might not have the functionality to load submodules
// now.
CHECK(module_blob_ == nullptr) << "Resetting mobule blob?";
module_blob_ = ptr;
} else { } else {
auto it = tbl_.find(name); auto it = tbl_.find(name);
if (it != tbl_.end()) { if (it != tbl_.end()) {
...@@ -65,6 +79,8 @@ class SystemLibModuleNode : public ModuleNode { ...@@ -65,6 +79,8 @@ class SystemLibModuleNode : public ModuleNode {
std::mutex mutex_; std::mutex mutex_;
// Internal symbol table // Internal symbol table
std::unordered_map<std::string, void*> tbl_; std::unordered_map<std::string, void*> tbl_;
// Module blob to be imported
void* module_blob_{nullptr};
}; };
TVM_REGISTER_GLOBAL("module._GetSystemLib") TVM_REGISTER_GLOBAL("module._GetSystemLib")
......
<html>
<head>
<meta charset="UTF-8">
<title>TVM RPC Test Page</title>
</head>
<body>
<h1>TVM Test Page</h1>
<div id="log"></div>
<canvas id="canvas"></canvas>
<script>
var Module = {};
Module["canvas"] = document.getElementById("canvas");
</script>
<script src="identity_static.js"></script>
<script src="tvm_runtime.js"></script>
<script>
var tvm = tvm_runtime.create(Module);
tvm.logger = function (message) {
console.log(message);
var d = document.createElement("div");
d.innerHTML = message;
document.getElementById("log").appendChild(d);
};
function randomArray(length, max) {
return Array.apply(null, Array(length)).map(function () {
return Math.random() * max;
});
}
setTimeout(function () {
this.syslib = tvm.systemLib();
this.identity = this.syslib.getFunction("identity");
this.n = 16;
this.a = randomArray(this.n, 1);
this.ctx = tvm.context("opengl", 0);
this.A = tvm.empty(this.n, "float32", ctx).copyFrom(this.a);
this.B = tvm.empty(this.n, "float32", ctx);
identity(this.A, this.B);
this.a = this.A.asArray();
this.b = this.B.asArray();
for (var i = 0; i < n; ++i) {
tvm.assert(this.a[i] == this.b[i]);
}
this.identity.release();
}, 1000);
</script>
</body>
</html>
\ No newline at end of file
"""Create a static WebGL library and run it in the browser."""
from __future__ import absolute_import, print_function
import os, shutil, SimpleHTTPServer, SocketServer
import tvm
from tvm.contrib import emscripten, util
import numpy as np
def try_static_webgl_library():
curr_path = os.path.dirname(os.path.abspath(os.path.expanduser(__file__)))
# Change to lib/ which contains "libtvm_runtime.bc".
os.chdir(os.path.join(curr_path, "../../lib"))
# Create OpenGL module.
n = tvm.var("n")
A = tvm.placeholder((n,), name='A', dtype="float")
B = tvm.compute((n,), lambda *i: A[i], name="B")
s = tvm.create_schedule(B.op)
s[B].opengl()
target_host = "llvm -target=asmjs-unknown-emscripten -system-lib"
f = tvm.build(s, [A, B], name="identity", target="opengl",
target_host=target_host)
# Create a JS library that contains both the module and the tvm runtime.
path_dso = "identity_static.js"
f.export_library(path_dso, emscripten.create_js, options=[
"-s", "USE_GLFW=3",
"-s", "USE_WEBGL2=1",
"-lglfw",
])
# Create "tvm_runtime.js" and "identity_static.html" in lib/
shutil.copyfile(os.path.join(curr_path, "../../web/tvm_runtime.js"),
"tvm_runtime.js")
shutil.copyfile(os.path.join(curr_path, "test_static_webgl_library.html"),
"identity_static.html")
port = 8080
handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", port), handler)
print("Please open http://localhost:" + str(port) + "/identity_static.html")
httpd.serve_forever()
if __name__ == "__main__":
try_static_webgl_library()
...@@ -229,6 +229,14 @@ var tvm_runtime = tvm_runtime || {}; ...@@ -229,6 +229,14 @@ var tvm_runtime = tvm_runtime || {};
"number" // size_t nbytes "number" // size_t nbytes
]); ]);
var TVMModLoadFromFile = Module.cwrap
("TVMModLoadFromFile",
"number",
["string", // const char* file_name
"string", // const char* format
"number" // TVMModuleHandle* out
])
//----------------------------------------- //-----------------------------------------
// Static utility functions // Static utility functions
// ---------------------------------------- // ----------------------------------------
...@@ -940,6 +948,35 @@ var tvm_runtime = tvm_runtime || {}; ...@@ -940,6 +948,35 @@ var tvm_runtime = tvm_runtime || {};
} }
return new RPCServer(counter); return new RPCServer(counter);
}; };
/**
* Load a TVM module from a library file.
* The file must be present in the Emscripten virtual file system.
* For example, you can pass "--preload-file file" or "--preload-file dir/"
* to "emcc" when compiling the TVM library, in order to populate files into
* the file system.
* For more detail, see:
* https://kripken.github.io/emscripten-site/docs/porting/files/packaging_files
* @param {string} file_name Path of the file to be loaded. The path refers
* to the Emscripten virtual file system.
* @param {string} format The format of the file.
* @return {tvm.TVMModule} The loaded module.
*/
this.loadModuleFromFile = function (file_name, format) {
// alloc
var out = new RefTVMValue();
TVM_CALL(TVMModLoadFromFile(file_name, format, out.data));
var out_handle = out.asHandle();
// release
out.release();
if (out_handle != 0) {
return new TVMModule(out_handle);
} else {
return null;
}
};
var loadModuleFromFile = this.loadModuleFromFile;
//----------------------------------------- //-----------------------------------------
// Class defintions // Class defintions
// ---------------------------------------- // ----------------------------------------
......
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