Add dynamic linking web sample using Emscripten. (#8319)
Like the static web sample introduced in https://github.com/google/iree/pull/8171, this uses Emscripten to compile IREE's runtime to WebAssembly. What's new here is the use of Emscripten's [runtime dynamic linking support](https://emscripten.org/docs/compiling/Dynamic-Linking.html#runtime-dynamic-linking-with-dlopen) to be able to separately serve and load compiled programs.
The sample currenly loads [`simple_abs.mlir`](https://github.com/google/iree/blob/main/iree/samples/models/simple_abs.mlir), queries some metadata, then calls the exported function:
https://github.com/google/iree/blob/b959830d762b0614d42a0cf3d2a623410cb3247a/iree/samples/models/simple_abs.mlir#L1-L4
```
iree_worker.js:20 WebAssembly module onRuntimeInitialized()
(index):34 IREE initialized, loading program...
iree_worker.js:75 worker received message: {messageType: 'loadProgram', id: 0, payload: './simple_abs.vmfb'}
iree_worker.js:33 fetching program at './simple_abs.vmfb'
iree_worker.js:38 XMLHttpRequest completed, passing to Wasm module
iree_worker.js:14 (C) load_program() received 5158 bytes of data
iree_worker.js:14 (C) === module properties ===
iree_worker.js:14 (C) module name: 'module'
iree_worker.js:14 (C) module signature:
iree_worker.js:14 (C) 18 imported functions
iree_worker.js:14 (C) 2 exported functions
iree_worker.js:14 (C) 2 internal functions
iree_worker.js:14 (C) exported functions:
iree_worker.js:14 (C) function name: 'abs', calling convention: 0r_r'
iree_worker.js:14 (C) function name: '__init', calling convention: 0v_v'
iree_worker.js:14 (C) abs(-5.500000) -> result: 5.500000
iree_worker.js:49 Result from loadProgramFn(): 0
(index):37 Load program success!
```
Future work may add drag-and-drop -> `iree-run-module` behavior. I also have some ideas for building a little UI that shows you properties of any .vmfb like which executable formats are included in it.
I explored enabling multithreading together with the dynamic linking, but ran into issues with Emscripten's experimental [dynamic linking + pthreads](https://emscripten.org/docs/compiling/Dynamic-Linking.html#pthreads-support) implementation. I left some notes on this at the bottom of the new `README.md`.diff --git a/CMakeLists.txt b/CMakeLists.txt
index 695c3d3..bcfb6b9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -602,7 +602,7 @@
endif()
if(${IREE_BUILD_EXPERIMENTAL_WEB_SAMPLES})
- add_subdirectory(experimental/sample_web_static)
+ add_subdirectory(experimental/web)
endif()
set(IREE_PUBLIC_INCLUDE_DIRS "${IREE_COMMON_INCLUDE_DIRS}"
diff --git a/experimental/web/CMakeLists.txt b/experimental/web/CMakeLists.txt
new file mode 100644
index 0000000..3a57a21
--- /dev/null
+++ b/experimental/web/CMakeLists.txt
@@ -0,0 +1,11 @@
+# Copyright 2022 The IREE Authors
+#
+# Licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+if(NOT EMSCRIPTEN)
+ return()
+endif()
+
+iree_add_all_subdirs()
diff --git a/experimental/web/sample_dynamic/CMakeLists.txt b/experimental/web/sample_dynamic/CMakeLists.txt
new file mode 100644
index 0000000..f1391f5
--- /dev/null
+++ b/experimental/web/sample_dynamic/CMakeLists.txt
@@ -0,0 +1,48 @@
+# Copyright 2022 The IREE Authors
+#
+# Licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+if(NOT EMSCRIPTEN)
+ return()
+endif()
+
+#-------------------------------------------------------------------------------
+# Sync
+#-------------------------------------------------------------------------------
+
+set(_NAME "iree_experimental_web_sample_dynamic_sync")
+add_executable(${_NAME} "")
+target_sources(${_NAME}
+ PRIVATE
+ main.c
+ device_sync.c
+)
+set_target_properties(${_NAME} PROPERTIES OUTPUT_NAME "web-sample-dynamic-sync")
+
+# Note: we have to be very careful about dependencies here.
+#
+# The general purpose libraries link in multiple executable loaders and HAL
+# drivers/devices, which include code not compatible with Emscripten.
+target_link_libraries(${_NAME}
+ iree_runtime_runtime
+ iree_hal_local_loaders_system_library_loader
+ iree_hal_local_sync_driver
+)
+
+target_link_options(${_NAME} PRIVATE
+ # https://emscripten.org/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.html#interacting-with-code-ccall-cwrap
+ "-sEXPORTED_FUNCTIONS=['_load_program']"
+ "-sEXPORTED_RUNTIME_METHODS=['ccall','cwrap']"
+ #
+ "-sASSERTIONS=1"
+ #
+ # https://developer.chrome.com/blog/wasm-debugging-2020/
+ "-g"
+ "-gseparate-dwarf"
+ #
+ # Dynamic linking: https://emscripten.org/docs/compiling/Dynamic-Linking.html
+ "-sMAIN_MODULE"
+ # "-sALLOW_TABLE_GROWTH"
+)
diff --git a/experimental/web/sample_dynamic/README.md b/experimental/web/sample_dynamic/README.md
new file mode 100644
index 0000000..2762e78
--- /dev/null
+++ b/experimental/web/sample_dynamic/README.md
@@ -0,0 +1,52 @@
+# Dynamic Web Sample
+
+This experimental sample demonstrates one way to target the web platform with
+IREE. The output artifact is a web page that loads a separately provided IREE
+`.vmfb` (compiled ML model) and tests calling functions on it.
+
+## Quickstart
+
+1. Install IREE's host tools (e.g. by building the `install` target with CMake)
+2. Install the Emscripten SDK by
+ [following these directions](https://emscripten.org/docs/getting_started/downloads.html)
+3. Initialize your Emscripten environment (e.g. run `emsdk_env.bat`)
+4. From this directory, run `bash ./build_sample.sh`
+ * You may need to set the path to your host tools install
+5. Open the localhost address linked in the script output
+
+To rebuild most parts of the sample (C runtime, sample HTML, CMake config,
+etc.), just `control + C` to stop the local webserver and rerun the script.
+
+## How it works
+
+[Emscripten](https://emscripten.org/) is used (via the `emcmake` CMake wrapper)
+to compile the runtime into WebAssembly and JavaScript files.
+
+Any supported IREE program, such as
+[simple_abs.mlir](../../../iree/samples/models/simple_abs.mlir), is compiled using
+the "system library" linking mode (i.e. `--iree-llvm-link-embedded=false`).
+This creates a shared object (typically .so/.dll, .wasm in this case). When the
+runtime attempts to load this file using `dlopen()` and `dlsym()`, Emscripten
+makes use of its
+[runtime dynamic linking support](https://emscripten.org/docs/compiling/Dynamic-Linking.html#runtime-dynamic-linking-with-dlopen)
+to instantiate a new `WebAssembly.Instance` which shares memory with the main
+runtime then resolve each export provided by the new Wasm module.
+
+### Asynchronous API
+
+* [`iree_api.js`](./iree_api.js) exposes a Promise-based API to the hosting
+ application in [`index.html`](./index.html)
+* [`iree_api.js`](./iree_api.js) creates a worker running iree_worker.js, which
+ includes Emscripten's JS code and instantiates the WebAssembly module
+* messages are passed back and forth between [`iree_api.js`](./iree_api.js) and
+ [`iree_worker.js`](./iree_worker.js) internally
+
+### Multithreading
+
+Multithreading is _not supported yet_. Emscripten only has experimental support
+for dynamic linking + pthreads:
+https://emscripten.org/docs/compiling/Dynamic-Linking.html#pthreads-support.
+Compiled programs produced by IREE link with `wasm-ld`, while Emscripten expects
+programs to be linked using `emcc` with the `-s SIDE_MODULE` option, which
+includes several Emscripten-pthreads-specific module exported functions such as
+`emscripten_tls_init`.
diff --git a/experimental/web/sample_dynamic/build_sample.sh b/experimental/web/sample_dynamic/build_sample.sh
new file mode 100644
index 0000000..afce01e
--- /dev/null
+++ b/experimental/web/sample_dynamic/build_sample.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+# Copyright 2022 The IREE Authors
+#
+# Licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+set -e
+
+###############################################################################
+# Setup and checking for dependencies #
+###############################################################################
+
+if ! command -v emcmake &> /dev/null
+then
+ echo "'emcmake' not found, setup environment according to https://emscripten.org/docs/getting_started/downloads.html"
+ exit
+fi
+
+CMAKE_BIN=${CMAKE_BIN:-$(which cmake)}
+ROOT_DIR=$(git rev-parse --show-toplevel)
+SOURCE_DIR=${ROOT_DIR}/experimental/web/sample_dynamic
+
+BUILD_DIR=${ROOT_DIR?}/build-emscripten
+mkdir -p ${BUILD_DIR}
+
+BINARY_DIR=${BUILD_DIR}/experimental/web/sample_dynamic
+mkdir -p ${BINARY_DIR}
+
+###############################################################################
+# Compile from .mlir input to portable .vmfb file using host tools #
+###############################################################################
+
+# TODO(scotttodd): portable path ... discover from python install if on $PATH?
+INSTALL_ROOT="D:\dev\projects\iree-build\install\bin"
+TRANSLATE_TOOL="${INSTALL_ROOT?}/iree-translate.exe"
+EMBED_DATA_TOOL="${INSTALL_ROOT?}/generate_embed_data.exe"
+INPUT_NAME="simple_abs"
+INPUT_PATH="${ROOT_DIR?}/iree/samples/models/simple_abs.mlir"
+
+echo "=== Translating MLIR to Wasm VM flatbuffer output (.vmfb) ==="
+${TRANSLATE_TOOL?} ${INPUT_PATH} \
+ --iree-mlir-to-vm-bytecode-module \
+ --iree-input-type=mhlo \
+ --iree-hal-target-backends=llvm \
+ --iree-llvm-target-triple=wasm32-unknown-emscripten \
+ --iree-llvm-target-cpu-features=+atomics,+bulk-memory,+simd128 \
+ --iree-llvm-link-embedded=false \
+ --o ${BINARY_DIR}/${INPUT_NAME}.vmfb
+
+###############################################################################
+# Build the web artifacts using Emscripten #
+###############################################################################
+
+echo "=== Building web artifacts using Emscripten ==="
+
+pushd ${BUILD_DIR}
+
+# Configure using Emscripten's CMake wrapper, then build.
+# Note: The sample creates a device directly, so no drivers are required.
+emcmake "${CMAKE_BIN?}" -G Ninja .. \
+ -DCMAKE_BUILD_TYPE=RelWithDebInfo \
+ -DIREE_HOST_BINARY_ROOT=$PWD/../build-host/install \
+ -DIREE_BUILD_EXPERIMENTAL_WEB_SAMPLES=ON \
+ -DIREE_HAL_DRIVER_DEFAULTS=OFF \
+ -DIREE_BUILD_COMPILER=OFF \
+ -DIREE_BUILD_TESTS=OFF
+
+"${CMAKE_BIN?}" --build . --target \
+ iree_experimental_web_sample_dynamic_sync
+
+popd
+
+###############################################################################
+# Serve the sample using a local webserver #
+###############################################################################
+
+echo "=== Copying static files (.html, .js) to the build directory ==="
+
+cp ${SOURCE_DIR?}/index.html ${BINARY_DIR}
+cp ${SOURCE_DIR?}/iree_api.js ${BINARY_DIR}
+cp ${SOURCE_DIR?}/iree_worker.js ${BINARY_DIR}
+
+echo "=== Running local webserver, open at http://localhost:8000/ ==="
+
+python3 ${ROOT_DIR?}/scripts/local_web_server.py --directory ${BINARY_DIR}
diff --git a/experimental/web/sample_dynamic/device_sync.c b/experimental/web/sample_dynamic/device_sync.c
new file mode 100644
index 0000000..684b11c
--- /dev/null
+++ b/experimental/web/sample_dynamic/device_sync.c
@@ -0,0 +1,43 @@
+// Copyright 2022 The IREE Authors
+//
+// Licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include "iree/hal/local/loaders/system_library_loader.h"
+#include "iree/hal/local/sync_device.h"
+
+iree_status_t create_device_with_wasm_loader(iree_allocator_t host_allocator,
+ iree_hal_device_t** out_device) {
+ iree_hal_sync_device_params_t params;
+ iree_hal_sync_device_params_initialize(¶ms);
+
+ iree_status_t status = iree_ok_status();
+
+ iree_hal_executable_loader_t* loaders[1] = {NULL};
+ iree_host_size_t loader_count = 0;
+ if (iree_status_is_ok(status)) {
+ status = iree_hal_system_library_loader_create(
+ iree_hal_executable_import_provider_null(), host_allocator,
+ &loaders[loader_count++]);
+ }
+
+ iree_string_view_t identifier = iree_make_cstring_view("sync");
+ iree_hal_allocator_t* device_allocator = NULL;
+ if (iree_status_is_ok(status)) {
+ status = iree_hal_allocator_create_heap(identifier, host_allocator,
+ host_allocator, &device_allocator);
+ }
+
+ if (iree_status_is_ok(status)) {
+ status = iree_hal_sync_device_create(identifier, ¶ms, loader_count,
+ loaders, device_allocator,
+ host_allocator, out_device);
+ }
+
+ iree_hal_allocator_release(device_allocator);
+ for (iree_host_size_t i = 0; i < loader_count; ++i) {
+ iree_hal_executable_loader_release(loaders[i]);
+ }
+ return status;
+}
diff --git a/experimental/web/sample_dynamic/index.html b/experimental/web/sample_dynamic/index.html
new file mode 100644
index 0000000..9342f94
--- /dev/null
+++ b/experimental/web/sample_dynamic/index.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+
+<!--
+Copyright 2022 The IREE Authors
+
+Licensed under the Apache License v2.0 with LLVM Exceptions.
+See https://llvm.org/LICENSE.txt for license information.
+SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+-->
+
+<head>
+ <meta charset="utf-8" />
+ <title>IREE Dynamic Web Sample (dlopen)</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <script src="./iree_api.js"></script>
+</head>
+
+<body style="background-color: #2b2c30; color: #ABB2BF">
+ <h1>IREE Dynamic Web Sample (dlopen)</h1>
+
+ <!-- TODO(scotttodd): drag and drop UI -->
+ <!-- TODO(scotttodd): <div>s with output (or just console.log?)-->
+
+ <script>
+ let ireeInitialized = false;
+
+ // TODO(scotttodd): rewrite with async + await?
+ // TODO(scotttodd): call ireeLoadProgram after some user interaction?
+ ireeInitializeWorker().then((result) => {
+ ireeInitialized = true;
+
+ console.log("IREE initialized, loading program...");
+
+ ireeLoadProgram("./simple_abs.vmfb").then((result) => {
+ console.log("Load program success!");
+ }).catch((error) => {
+ console.error("Failed to load program, error: '" + error + "'");
+ });
+ }).catch((error) => {
+ console.error("Failed to initialize IREE, error: '" + error + "'");
+ });
+ </script>
+</body>
+
+</html>
diff --git a/experimental/web/sample_dynamic/iree_api.js b/experimental/web/sample_dynamic/iree_api.js
new file mode 100644
index 0000000..5e08e49
--- /dev/null
+++ b/experimental/web/sample_dynamic/iree_api.js
@@ -0,0 +1,71 @@
+// Copyright 2022 The IREE Authors
+//
+// Licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// Promise-based API for interacting with the IREE runtime.
+
+let ireeWorker = null;
+let nextMessageId = 0;
+const pendingPromises = {};
+
+// Communication protocol to and from the worker:
+// {
+// 'messageType': string
+// * the type of message (initialized, loadProgramResult, etc.)
+// 'id': number?
+// * optional id to disambiguate messages of the same type
+// 'payload': Object?
+// * optional message data, format defined by message type
+// 'error': string?
+// * optional error message
+// }
+
+function _handleMessageFromWorker(messageEvent) {
+ const {messageType, id, payload, error} = messageEvent.data;
+
+ if (messageType == 'initialized') {
+ pendingPromises['initialize']['resolve']();
+ delete pendingPromises['initialize'];
+ } else if (messageType == 'loadProgramResult') {
+ if (error !== undefined) {
+ pendingPromises[id]['reject'](error);
+ } else {
+ pendingPromises[id]['resolve'](payload);
+ }
+ delete pendingPromises[id];
+ }
+}
+
+// Initializes IREE's web worker asynchronously.
+// Resolves when the worker is fully initialized.
+function ireeInitializeWorker() {
+ return new Promise((resolve, reject) => {
+ pendingPromises['initialize'] = {
+ 'resolve': resolve,
+ 'reject': reject,
+ };
+
+ ireeWorker = new Worker('iree_worker.js', {name: 'IREE-main'});
+ ireeWorker.onmessage = _handleMessageFromWorker;
+ });
+}
+
+function ireeLoadProgram(vmfbPath) {
+ return new Promise((resolve, reject) => {
+ const messageId = nextMessageId++;
+ const message = {
+ 'messageType': 'loadProgram',
+ 'id': messageId,
+ 'payload': vmfbPath,
+ };
+
+ pendingPromises[messageId] = {
+ 'resolve': resolve,
+ 'reject': reject,
+ };
+
+ ireeWorker.postMessage(message);
+ });
+}
diff --git a/experimental/web/sample_dynamic/iree_worker.js b/experimental/web/sample_dynamic/iree_worker.js
new file mode 100644
index 0000000..de4ecb7
--- /dev/null
+++ b/experimental/web/sample_dynamic/iree_worker.js
@@ -0,0 +1,82 @@
+// Copyright 2022 The IREE Authors
+//
+// Licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+// TODO(scotttodd): configure this through the build system / scripts?
+// const MAIN_SCRIPT_URL = 'web-sample-dynamic-multithreaded.js';
+const MAIN_SCRIPT_URL = 'web-sample-dynamic-sync.js';
+
+let wasmLoadProgramFn;
+var Module = {
+ print: function(text) {
+ console.log('(C)', text);
+ },
+ printErr: function(text) {
+ console.error('(C)', text);
+ },
+ onRuntimeInitialized: function() {
+ console.log('WebAssembly module onRuntimeInitialized()');
+
+ wasmLoadProgramFn =
+ Module.cwrap('load_program', 'number', ['number', 'number']);
+
+ postMessage({
+ 'messageType': 'initialized',
+ });
+ },
+ noInitialRun: true,
+};
+
+function loadProgram(id, vmfbPath) {
+ console.log('fetching program at \'%s\'', vmfbPath);
+
+ const fetchRequest = new XMLHttpRequest();
+
+ fetchRequest.onload = function(progressEvent) {
+ console.log('XMLHttpRequest completed, passing to Wasm module');
+
+ const programDataBuffer = progressEvent.target.response;
+ const programDataView = new Int8Array(programDataBuffer);
+
+ const programDataWasmBuffer = Module._malloc(
+ programDataView.length * programDataView.BYTES_PER_ELEMENT);
+ Module.HEAP8.set(programDataView, programDataWasmBuffer);
+
+ const result =
+ wasmLoadProgramFn(programDataWasmBuffer, programDataBuffer.byteLength);
+ console.log('Result from loadProgramFn():', result);
+ Module._free(programDataWasmBuffer);
+
+ if (result !== 0) {
+ postMessage({
+ 'messageType': 'loadProgramResult',
+ 'id': id,
+ 'error': 'Wasm module error, check console for details',
+ });
+ } else {
+ postMessage({
+ 'messageType': 'loadProgramResult',
+ 'id': id,
+ 'payload': 'success',
+ });
+ }
+ };
+
+ fetchRequest.open('GET', vmfbPath);
+ fetchRequest.responseType = 'arraybuffer';
+ fetchRequest.send();
+}
+
+self.onmessage = function(messageEvent) {
+ const {messageType, id, payload} = messageEvent.data;
+
+ console.log('worker received message:', messageEvent.data);
+
+ if (messageType == 'loadProgram') {
+ loadProgram(id, payload);
+ }
+};
+
+importScripts(MAIN_SCRIPT_URL);
diff --git a/experimental/web/sample_dynamic/main.c b/experimental/web/sample_dynamic/main.c
new file mode 100644
index 0000000..460842d
--- /dev/null
+++ b/experimental/web/sample_dynamic/main.c
@@ -0,0 +1,162 @@
+// Copyright 2022 The IREE Authors
+//
+// Licensed under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+#include <stdint.h>
+#include <stdio.h>
+
+#include "iree/runtime/api.h"
+#include "iree/vm/bytecode_module.h"
+
+//===----------------------------------------------------------------------===//
+// Public API
+//===----------------------------------------------------------------------===//
+
+int load_program(uint8_t* vmfb_data, size_t length);
+
+//===----------------------------------------------------------------------===//
+// Implementation
+//===----------------------------------------------------------------------===//
+
+// We're not really using cpuinfo here, but the linker complains about this
+// function not being defined. It actually is defined though, _if_ you patch
+// cpuinfo's CMake configuration: https://github.com/pytorch/cpuinfo/issues/34.
+void __attribute__((weak)) cpuinfo_emscripten_init(void) {}
+
+extern iree_status_t create_device_with_wasm_loader(
+ iree_allocator_t host_allocator, iree_hal_device_t** out_device);
+
+void inspect_module(iree_vm_module_t* module) {
+ fprintf(stdout, "=== module properties ===\n");
+
+ iree_string_view_t module_name = iree_vm_module_name(module);
+ fprintf(stdout, " module name: '%.*s'\n", (int)module_name.size,
+ module_name.data);
+
+ iree_vm_module_signature_t module_signature =
+ iree_vm_module_signature(module);
+ fprintf(stdout, " module signature:\n");
+ fprintf(stdout, " %" PRIhsz " imported functions\n",
+ module_signature.import_function_count);
+ fprintf(stdout, " %" PRIhsz " exported functions\n",
+ module_signature.export_function_count);
+ fprintf(stdout, " %" PRIhsz " internal functions\n",
+ module_signature.internal_function_count);
+
+ fprintf(stdout, " exported functions:\n");
+ for (iree_host_size_t i = 0; i < module_signature.export_function_count;
+ ++i) {
+ iree_vm_function_t function;
+ iree_status_t status = iree_vm_module_lookup_function_by_ordinal(
+ module, IREE_VM_FUNCTION_LINKAGE_EXPORT, i, &function);
+
+ iree_string_view_t function_name = iree_vm_function_name(&function);
+ iree_vm_function_signature_t function_signature =
+ iree_vm_function_signature(&function);
+ iree_string_view_t calling_convention =
+ function_signature.calling_convention;
+ fprintf(stdout, " function name: '%.*s', calling convention: %.*s'\n",
+ (int)function_name.size, function_name.data,
+ (int)calling_convention.size, calling_convention.data);
+ }
+}
+
+int load_program(uint8_t* vmfb_data, size_t length) {
+ fprintf(stdout, "load_program() received %zu bytes of data\n", length);
+
+ iree_runtime_instance_options_t instance_options;
+ iree_runtime_instance_options_initialize(IREE_API_VERSION_LATEST,
+ &instance_options);
+ // Note: no call to iree_runtime_instance_options_use_all_available_drivers().
+
+ iree_runtime_instance_t* instance = NULL;
+ iree_status_t status = iree_runtime_instance_create(
+ &instance_options, iree_allocator_system(), &instance);
+
+ iree_hal_device_t* device = NULL;
+ if (iree_status_is_ok(status)) {
+ status = create_device_with_wasm_loader(iree_allocator_system(), &device);
+ }
+
+ iree_runtime_session_options_t session_options;
+ iree_runtime_session_options_initialize(&session_options);
+ iree_runtime_session_t* session = NULL;
+ if (iree_status_is_ok(status)) {
+ status = iree_runtime_session_create_with_device(
+ instance, &session_options, device,
+ iree_runtime_instance_host_allocator(instance), &session);
+ }
+
+ iree_vm_module_t* program_module = NULL;
+ if (iree_status_is_ok(status)) {
+ status = iree_vm_bytecode_module_create(
+ iree_make_const_byte_span(vmfb_data, length), iree_allocator_null(),
+ iree_allocator_system(), &program_module);
+ }
+
+ if (iree_status_is_ok(status)) {
+ inspect_module(program_module);
+ status = iree_runtime_session_append_module(session, program_module);
+ }
+
+ // Call the 'abs' function in the module.
+ iree_runtime_call_t call;
+ if (iree_status_is_ok(status)) {
+ status = iree_runtime_call_initialize_by_name(
+ session, iree_make_cstring_view("module.abs"), &call);
+ }
+
+ iree_hal_buffer_view_t* arg0 = NULL;
+ float arg0_data[1] = {-5.5};
+ if (iree_status_is_ok(status)) {
+ status = iree_hal_buffer_view_allocate_buffer(
+ iree_runtime_session_device_allocator(session), /*shape=*/NULL,
+ /*shape_rank=*/0, IREE_HAL_ELEMENT_TYPE_FLOAT_32,
+ IREE_HAL_ENCODING_TYPE_DENSE_ROW_MAJOR,
+ IREE_HAL_MEMORY_TYPE_HOST_LOCAL | IREE_HAL_MEMORY_TYPE_DEVICE_VISIBLE,
+ IREE_HAL_BUFFER_USAGE_DISPATCH | IREE_HAL_BUFFER_USAGE_TRANSFER,
+ iree_make_const_byte_span((void*)arg0_data, sizeof(arg0_data)), &arg0);
+ }
+ if (iree_status_is_ok(status)) {
+ status = iree_runtime_call_inputs_push_back_buffer_view(&call, arg0);
+ }
+ iree_hal_buffer_view_release(arg0);
+
+ if (iree_status_is_ok(status)) {
+ status = iree_runtime_call_invoke(&call, /*flags=*/0);
+ }
+
+ iree_hal_buffer_view_t* ret_buffer_view = NULL;
+ if (iree_status_is_ok(status)) {
+ status = iree_runtime_call_outputs_pop_front_buffer_view(&call,
+ &ret_buffer_view);
+ }
+ float result[1] = {0.0f};
+ if (iree_status_is_ok(status)) {
+ status =
+ iree_hal_buffer_read_data(iree_hal_buffer_view_buffer(ret_buffer_view),
+ 0, result, sizeof(result));
+ }
+ iree_hal_buffer_view_release(ret_buffer_view);
+
+ if (iree_status_is_ok(status)) {
+ fprintf(stdout, "abs(%f) -> result: %f\n", arg0_data[0], result[0]);
+ }
+
+ iree_runtime_call_deinitialize(&call);
+
+ iree_vm_module_release(program_module);
+ iree_hal_device_release(device);
+ iree_runtime_session_release(session);
+ iree_runtime_instance_release(instance);
+
+ if (!iree_status_is_ok(status)) {
+ iree_status_fprint(stderr, status);
+ iree_status_free(status);
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/experimental/sample_web_static/CMakeLists.txt b/experimental/web/sample_static/CMakeLists.txt
similarity index 89%
rename from experimental/sample_web_static/CMakeLists.txt
rename to experimental/web/sample_static/CMakeLists.txt
index d4c7a12..494cb6b 100644
--- a/experimental/sample_web_static/CMakeLists.txt
+++ b/experimental/web/sample_static/CMakeLists.txt
@@ -8,7 +8,12 @@
return()
endif()
-set(_MNIST_OBJECT_NAME "iree_experimental_sample_web_static_mnist")
+if(NOT EXISTS "${CMAKE_CURRENT_BINARY_DIR}/mnist_static.h")
+ message(WARNING "Missing mnist_static.h, run ${CMAKE_CURRENT_SOURCE_DIR}/build_sample.sh to generate it")
+ return()
+endif()
+
+set(_MNIST_OBJECT_NAME "iree_experimental_web_sample_static_mnist")
add_library(${_MNIST_OBJECT_NAME} STATIC ${CMAKE_CURRENT_BINARY_DIR}/mnist_static.o)
SET_TARGET_PROPERTIES(${_MNIST_OBJECT_NAME} PROPERTIES LINKER_LANGUAGE C)
@@ -16,7 +21,7 @@
# Sync
#-------------------------------------------------------------------------------
-set(_NAME "iree_experimental_sample_web_static_sync")
+set(_NAME "iree_experimental_web_sample_static_sync")
add_executable(${_NAME} "")
target_include_directories(${_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
@@ -29,7 +34,7 @@
${CMAKE_CURRENT_BINARY_DIR}/mnist_bytecode.h
${CMAKE_CURRENT_BINARY_DIR}/mnist_bytecode.c
)
-set_target_properties(${_NAME} PROPERTIES OUTPUT_NAME "sample-web-static-sync")
+set_target_properties(${_NAME} PROPERTIES OUTPUT_NAME "web-sample-static-sync")
# Note: we have to be very careful about dependencies here.
#
@@ -58,7 +63,7 @@
# Multithreaded
#-------------------------------------------------------------------------------
-set(_NAME "iree_experimental_sample_web_static_multithreaded")
+set(_NAME "iree_experimental_web_sample_static_multithreaded")
add_executable(${_NAME} "")
target_include_directories(${_NAME} PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>
@@ -71,7 +76,7 @@
${CMAKE_CURRENT_BINARY_DIR}/mnist_bytecode.h
${CMAKE_CURRENT_BINARY_DIR}/mnist_bytecode.c
)
-set_target_properties(${_NAME} PROPERTIES OUTPUT_NAME "sample-web-static-multithreaded")
+set_target_properties(${_NAME} PROPERTIES OUTPUT_NAME "web-sample-static-multithreaded")
# Note: we have to be very careful about dependencies here.
#
diff --git a/experimental/sample_web_static/README.md b/experimental/web/sample_static/README.md
similarity index 78%
rename from experimental/sample_web_static/README.md
rename to experimental/web/sample_static/README.md
index 8fceff1..7360d3b 100644
--- a/experimental/sample_web_static/README.md
+++ b/experimental/web/sample_static/README.md
@@ -4,25 +4,28 @@
IREE. The output artifact is a web page containing an interactive MNIST digits
classifier.
+The MNIST ML model is compiled statically together with the IREE runtime into
+a single .js + .wasm bundle.
+
## Quickstart
1. Install IREE's host tools (e.g. by building the `install` target with CMake)
2. Install the Emscripten SDK by
[following these directions](https://emscripten.org/docs/getting_started/downloads.html)
3. Initialize your Emscripten environment (e.g. run `emsdk_env.bat`)
-4. From this directory, run `bash ./build_static_emscripten_demo.sh`
+4. From this directory, run `bash ./build_sample.sh`
* You may need to set the path to your host tools install
5. Open the localhost address linked in the script output
-To rebuild most parts of the demo (C runtime, sample HTML, CMake config, etc.),
-just `control + C` to stop the local webserver and rerun the script.
+To rebuild most parts of the sample (C runtime, sample HTML, CMake config,
+etc.), just `control + C` to stop the local webserver and rerun the script.
## How it works
-This [MNIST model](../../iree/samples/models/mnist.mlir), also used in the
-[Vision sample](../../iree/samples/vision/), is compiled using the "static
+This [MNIST model](../../../iree/samples/models/mnist.mlir), also used in the
+[Vision sample](../../../iree/samples/vision/), is compiled using the "static
library" output setting of IREE's compiler (see the
-[Static library sample](../../iree/samples/static_library)). The resulting
+[Static library sample](../../../iree/samples/static_library)). The resulting
`.h` and `.o` files are compiled together with `main.c`, while the `.vmfb` is
embedded into a C file that is similarly linked in.
diff --git a/experimental/sample_web_static/build_static_emscripten_demo.sh b/experimental/web/sample_static/build_sample.sh
similarity index 83%
rename from experimental/sample_web_static/build_static_emscripten_demo.sh
rename to experimental/web/sample_static/build_sample.sh
index 3c16a34..51665ae 100644
--- a/experimental/sample_web_static/build_static_emscripten_demo.sh
+++ b/experimental/web/sample_static/build_sample.sh
@@ -19,11 +19,12 @@
CMAKE_BIN=${CMAKE_BIN:-$(which cmake)}
ROOT_DIR=$(git rev-parse --show-toplevel)
+SOURCE_DIR=${ROOT_DIR}/experimental/web/sample_static
BUILD_DIR=${ROOT_DIR?}/build-emscripten
mkdir -p ${BUILD_DIR}
-BINARY_DIR=${BUILD_DIR}/experimental/sample_web_static/
+BINARY_DIR=${BUILD_DIR}/experimental/web/sample_static/
mkdir -p ${BINARY_DIR}
###############################################################################
@@ -62,35 +63,33 @@
echo "=== Building web artifacts using Emscripten ==="
-pushd ${ROOT_DIR?}/build-emscripten
+pushd ${BUILD_DIR}
# Configure using Emscripten's CMake wrapper, then build.
-# Note: The sample creates a task device directly, so no drivers are required,
-# but some targets are gated on specific CMake options.
+# Note: The sample creates a device directly, so no drivers are required.
emcmake "${CMAKE_BIN?}" -G Ninja .. \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DIREE_HOST_BINARY_ROOT=$PWD/../build-host/install \
-DIREE_BUILD_EXPERIMENTAL_WEB_SAMPLES=ON \
-DIREE_HAL_DRIVER_DEFAULTS=OFF \
- -DIREE_HAL_DRIVER_DYLIB=ON \
-DIREE_BUILD_COMPILER=OFF \
-DIREE_BUILD_TESTS=OFF
"${CMAKE_BIN?}" --build . --target \
- iree_experimental_sample_web_static_sync \
- iree_experimental_sample_web_static_multithreaded
+ iree_experimental_web_sample_static_sync \
+ iree_experimental_web_sample_static_multithreaded
popd
###############################################################################
-# Serve the demo using a local webserver #
+# Serve the sample using a local webserver #
###############################################################################
echo "=== Copying static files to the build directory ==="
-cp ${ROOT_DIR?}/experimental/sample_web_static/index.html ${BINARY_DIR}
-cp ${ROOT_DIR?}/experimental/sample_web_static/iree_api.js ${BINARY_DIR}
-cp ${ROOT_DIR?}/experimental/sample_web_static/iree_worker.js ${BINARY_DIR}
+cp ${SOURCE_DIR}/index.html ${BINARY_DIR}
+cp ${SOURCE_DIR}/iree_api.js ${BINARY_DIR}
+cp ${SOURCE_DIR}/iree_worker.js ${BINARY_DIR}
EASELJS_LIBRARY=${BINARY_DIR}/easeljs.min.js
test -f ${EASELJS_LIBRARY} || \
diff --git a/experimental/sample_web_static/device_multithreaded.c b/experimental/web/sample_static/device_multithreaded.c
similarity index 100%
rename from experimental/sample_web_static/device_multithreaded.c
rename to experimental/web/sample_static/device_multithreaded.c
diff --git a/experimental/sample_web_static/device_sync.c b/experimental/web/sample_static/device_sync.c
similarity index 100%
rename from experimental/sample_web_static/device_sync.c
rename to experimental/web/sample_static/device_sync.c
diff --git a/experimental/sample_web_static/index.html b/experimental/web/sample_static/index.html
similarity index 100%
rename from experimental/sample_web_static/index.html
rename to experimental/web/sample_static/index.html
diff --git a/experimental/sample_web_static/iree_api.js b/experimental/web/sample_static/iree_api.js
similarity index 100%
rename from experimental/sample_web_static/iree_api.js
rename to experimental/web/sample_static/iree_api.js
diff --git a/experimental/sample_web_static/iree_worker.js b/experimental/web/sample_static/iree_worker.js
similarity index 96%
rename from experimental/sample_web_static/iree_worker.js
rename to experimental/web/sample_static/iree_worker.js
index 77b27fc..7800669 100644
--- a/experimental/sample_web_static/iree_worker.js
+++ b/experimental/web/sample_static/iree_worker.js
@@ -17,8 +17,8 @@
}
// TODO(scotttodd): configure this through the build system / scripts?
-const MAIN_SCRIPT_URL = 'sample-web-static-multithreaded.js';
-// const MAIN_SCRIPT_URL = 'sample-web-static-sync.js';
+const MAIN_SCRIPT_URL = 'web-sample-static-multithreaded.js';
+// const MAIN_SCRIPT_URL = 'web-sample-static-sync.js';
let wasmSetupSampleFn;
let wasmCleanupSampleFn;
diff --git a/experimental/sample_web_static/main.c b/experimental/web/sample_static/main.c
similarity index 98%
rename from experimental/sample_web_static/main.c
rename to experimental/web/sample_static/main.c
index 23441bf..64af5fc 100644
--- a/experimental/sample_web_static/main.c
+++ b/experimental/web/sample_static/main.c
@@ -16,7 +16,6 @@
//===----------------------------------------------------------------------===//
typedef struct iree_sample_state_t iree_sample_state_t;
-static void iree_sample_state_initialize(iree_sample_state_t* out_state);
// TODO(scotttodd): figure out error handling and state management
// * out_state and return status would make sense, but emscripten...
diff --git a/iree/base/internal/dynamic_library_posix.c b/iree/base/internal/dynamic_library_posix.c
index 73948f6..b72b1f0 100644
--- a/iree/base/internal/dynamic_library_posix.c
+++ b/iree/base/internal/dynamic_library_posix.c
@@ -15,7 +15,7 @@
#include "iree/base/tracing.h"
#if defined(IREE_PLATFORM_ANDROID) || defined(IREE_PLATFORM_APPLE) || \
- defined(IREE_PLATFORM_LINUX)
+ defined(IREE_PLATFORM_LINUX) || defined(IREE_PLATFORM_EMSCRIPTEN)
#include <dlfcn.h>
#include <errno.h>
diff --git a/iree/compiler/Dialect/HAL/Target/LLVM/internal/WasmLinkerTool.cpp b/iree/compiler/Dialect/HAL/Target/LLVM/internal/WasmLinkerTool.cpp
index 81807ba..9a6a32c 100644
--- a/iree/compiler/Dialect/HAL/Target/LLVM/internal/WasmLinkerTool.cpp
+++ b/iree/compiler/Dialect/HAL/Target/LLVM/internal/WasmLinkerTool.cpp
@@ -86,9 +86,17 @@
// Treat warnings as errors.
"--fatal-warnings",
- // Generated a shared object, not an executable.
- // Note: disabled since creating shared libraries is not yet supported.
- // "--shared",
+ // Generated a shared object containing position-independent-code.
+ "--experimental-pic",
+ "--shared",
+
+ // Import [shared] memory from the environment.
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Memory#creating_a_shared_memory
+ // TODO(scotttodd): Add a flag controlling these - some combination is
+ // required when using multithreading + SharedArrayBuffer, but they
+ // must be left off when running single threaded.
+ // "--import-memory",
+ // "--shared-memory",
"-o " + artifacts.libraryFile.path,
};
diff --git a/iree/hal/local/BUILD b/iree/hal/local/BUILD
index 26bcff7..1d1b786 100644
--- a/iree/hal/local/BUILD
+++ b/iree/hal/local/BUILD
@@ -115,7 +115,9 @@
iree_cmake_extra_content(
content = """
# task_driver is used by asynchronuous drivers.
-if(NOT (${IREE_HAL_DRIVER_DYLIB} OR ${IREE_HAL_DRIVER_VMVX}))
+# TODO(scotttodd): refactor this - code depending on threading should be
+# possible to declare in the build system but conditionally link in
+if(NOT EMSCRIPTEN AND NOT (${IREE_HAL_DRIVER_DYLIB} OR ${IREE_HAL_DRIVER_VMVX}))
return()
endif()
""",
diff --git a/iree/hal/local/CMakeLists.txt b/iree/hal/local/CMakeLists.txt
index b28fcd8..0befb9c 100644
--- a/iree/hal/local/CMakeLists.txt
+++ b/iree/hal/local/CMakeLists.txt
@@ -105,7 +105,9 @@
)
# task_driver is used by asynchronuous drivers.
-if(NOT (${IREE_HAL_DRIVER_DYLIB} OR ${IREE_HAL_DRIVER_VMVX}))
+# TODO(scotttodd): refactor this - code depending on threading should be
+# possible to declare in the build system but conditionally link in
+if(NOT EMSCRIPTEN AND NOT (${IREE_HAL_DRIVER_DYLIB} OR ${IREE_HAL_DRIVER_VMVX}))
return()
endif()
diff --git a/iree/hal/local/loaders/system_library_loader.c b/iree/hal/local/loaders/system_library_loader.c
index f4cf909..d327eaa 100644
--- a/iree/hal/local/loaders/system_library_loader.c
+++ b/iree/hal/local/loaders/system_library_loader.c
@@ -413,6 +413,8 @@
#define IREE_PLATFORM_DYLIB_TYPE "dylib"
#elif defined(IREE_PLATFORM_WINDOWS)
#define IREE_PLATFORM_DYLIB_TYPE "dll"
+#elif defined(IREE_PLATFORM_EMSCRIPTEN)
+#define IREE_PLATFORM_DYLIB_TYPE "wasm"
#else
#define IREE_PLATFORM_DYLIB_TYPE "elf"
#endif // IREE_PLATFORM_*