Roll forward stateful loop_emscripten changes. (#11801)
This is a roll forward of https://github.com/iree-org/iree/pull/11507
Successful CI run:
https://github.com/iree-org/iree/actions/runs/3897710708/jobs/6655737094
---
I had been testing locally with
[`experimental/web/testing/build_tests.sh`](https://github.com/iree-org/iree/blob/main/experimental/web/testing/build_tests.sh),
which sets `-DCMAKE_BUILD_TYPE=RelWithDebInfo`. The CI uses
[`build_tools/cmake/build_runtime_emscripten.sh`](https://github.com/iree-org/iree/blob/main/build_tools/cmake/build_runtime_emscripten.sh),
which leaves the build type unset and defaults to `Release`.
Release builds were failing with `SyntaxError: Unexpected token
(1:72626)` in `/emsdk/upstream/emscripten/tools/acorn-optimizer.js:1852`
with this code:

While `for (const [key, value] of Object.entries(object1))` is the
recommended way to iterate through an object's enumerable string-keyed
property key-value pairs
([source](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries)),
Emscripten does not appear to support that newer syntax out of the box.
diff --git a/build_tools/cmake/iree_copts.cmake b/build_tools/cmake/iree_copts.cmake
index 5f3740d..f0612cb 100644
--- a/build_tools/cmake/iree_copts.cmake
+++ b/build_tools/cmake/iree_copts.cmake
@@ -358,6 +358,16 @@
"-natvis:${IREE_ROOT_DIR}/runtime/iree.natvis"
)
+# Our Emscripten library code uses dynCall, which needs these link flags.
+# TODO(scotttodd): Find a way to refactor this, this is nasty to always set :(
+if(EMSCRIPTEN)
+ iree_select_compiler_opts(IREE_DEFAULT_LINKOPTS
+ ALL
+ "-sDYNCALLS=1"
+ "-sEXPORTED_RUNTIME_METHODS=['dynCall']"
+ )
+endif()
+
#-------------------------------------------------------------------------------
# Size-optimized build flags
#-------------------------------------------------------------------------------
diff --git a/experimental/web/testing/build_tests.sh b/experimental/web/testing/build_tests.sh
index 2cf46ba..b83d740 100644
--- a/experimental/web/testing/build_tests.sh
+++ b/experimental/web/testing/build_tests.sh
@@ -59,6 +59,7 @@
-DIREE_HAL_EXECUTABLE_LOADER_VMVX_MODULE=ON \
-DIREE_BUILD_SAMPLES=OFF \
-DIREE_ENABLE_CPUINFO=OFF \
+ -DIREE_ENABLE_ASAN=OFF \
-DIREE_BUILD_TESTS=ON
echo "=== Building default targets ==="
diff --git a/runtime/src/iree/base/loop_emscripten.c b/runtime/src/iree/base/loop_emscripten.c
index 8b4bd50..983e04a 100644
--- a/runtime/src/iree/base/loop_emscripten.c
+++ b/runtime/src/iree/base/loop_emscripten.c
@@ -16,8 +16,14 @@
// externs from loop_emscripten.js
//===----------------------------------------------------------------------===//
-extern iree_status_t loop_command_call(iree_loop_callback_fn_t callback,
- void* user_data, iree_loop_t loop);
+typedef uint32_t iree_loop_emscripten_scope_t; // Opaque handle.
+
+extern iree_loop_emscripten_scope_t iree_loop_allocate_scope();
+extern void iree_loop_free_scope(iree_loop_emscripten_scope_t scope);
+
+extern iree_status_t iree_loop_command_call(iree_loop_emscripten_scope_t scope,
+ iree_loop_callback_fn_t callback,
+ void* user_data, iree_loop_t loop);
//===----------------------------------------------------------------------===//
// iree_loop_emscripten_t
@@ -25,9 +31,7 @@
typedef struct iree_loop_emscripten_t {
iree_allocator_t allocator;
-
- // TODO(scotttodd): handle to a "scope"/object (managed in JS), so multiple
- // loops can exist at once
+ iree_loop_emscripten_scope_t scope;
} iree_loop_emscripten_t;
IREE_API_EXPORT iree_status_t iree_loop_emscripten_allocate(
@@ -37,6 +41,7 @@
IREE_RETURN_IF_ERROR(
iree_allocator_malloc(allocator, sizeof(*loop), (void**)&loop));
loop->allocator = allocator;
+ loop->scope = iree_loop_allocate_scope();
*out_loop = loop;
return iree_ok_status();
}
@@ -45,9 +50,7 @@
IREE_ASSERT_ARGUMENT(loop);
iree_allocator_t allocator = loop->allocator;
- // TODO(scotttodd): cleanup:
- // abort pending operations (neuter callbacks/Promises)
- // assert if any work is still outstanding
+ iree_loop_free_scope(loop->scope);
// After all operations are cleared we can release the data structures.
iree_allocator_free(allocator, loop);
@@ -56,8 +59,8 @@
static iree_status_t iree_loop_emscripten_run_call(
iree_loop_emscripten_t* loop_emscripten, iree_loop_call_params_t* params) {
iree_loop_t loop = iree_loop_emscripten(loop_emscripten);
- return loop_command_call(params->callback.fn, params->callback.user_data,
- loop);
+ return iree_loop_command_call(loop_emscripten->scope, params->callback.fn,
+ params->callback.user_data, loop);
}
// Control function for the Emscripten loop.
diff --git a/runtime/src/iree/base/loop_emscripten.h b/runtime/src/iree/base/loop_emscripten.h
index 4f8c6b7..4156b77 100644
--- a/runtime/src/iree/base/loop_emscripten.h
+++ b/runtime/src/iree/base/loop_emscripten.h
@@ -35,7 +35,6 @@
const void* params, void** inout_ptr);
// Returns a loop that uses |data|.
-// TODO(scotttodd): rework structs with "scope" so 2+ loops can exist at once
static inline iree_loop_t iree_loop_emscripten(iree_loop_emscripten_t* data) {
iree_loop_t loop = {
data,
diff --git a/runtime/src/iree/base/loop_emscripten.js b/runtime/src/iree/base/loop_emscripten.js
index 06190d3..2baf8f1 100644
--- a/runtime/src/iree/base/loop_emscripten.js
+++ b/runtime/src/iree/base/loop_emscripten.js
@@ -11,33 +11,113 @@
// * https://github.com/evanw/emscripten-library-generator
// * https://github.com/emscripten-core/emscripten/tree/main/src
-const LibraryLoopEmscripten = {
- $loop_emscripten_support__postset: 'loop_emscripten_support();',
- $loop_emscripten_support: function() {
- class LoopEmscripten {
- constructor() {
- // TODO(scotttodd): store state here
+const IreeLibraryLoopEmscripten = {
+ $iree_loop_emscripten_support__postset: 'iree_loop_emscripten_support();',
+ $iree_loop_emscripten_support: function() {
+ const IREE_STATUS_OK = 0;
+ const IREE_STATUS_CODE_MASK = 0x1F;
+ const IREE_STATUS_ABORTED = 10 & IREE_STATUS_CODE_MASK;
+ const IREE_STATUS_OUT_OF_RANGE = 11 & IREE_STATUS_CODE_MASK;
+
+ class LoopCommand {
+ abort() {}
+ }
+
+ // IREE_LOOP_COMMAND_CALL
+ class LoopCommandCall extends LoopCommand {
+ constructor(scope, operationId, callback, user_data, loop) {
+ super();
+
+ this.callback = callback;
+ this.user_data = user_data;
+ this.loop = loop;
+
+ this.timeoutId = setTimeout(() => {
+ Module['dynCall'](
+ 'iiii', this.callback, this.user_data, this.loop, IREE_STATUS_OK);
+ // TODO(scotttodd): handle the returned status (sticky failure state?)
+ // at least free the status so it doesn't leak
+ delete scope.pendingOperations[operationId];
+ }, 0);
}
- loop_command_call(callback, user_data, loop) {
- const IREE_STATUS_OK = 0;
+ abort() {
+ clearTimeout(this.timeoutId);
+ Module['dynCall'](
+ 'iiii', this.callback, this.user_data, this.loop,
+ IREE_STATUS_ABORTED);
+ }
+ }
- setTimeout(() => {
- const ret =
- Module['dynCall_iiii'](callback, user_data, loop, IREE_STATUS_OK);
- // TODO(scotttodd): handle the returned status (sticky failure state?)
- }, 0);
+ class LoopEmscriptenScope {
+ constructor() {
+ this.nextOperationId = 0;
+ // Dictionary of operationIds -> LoopCommands.
+ this.pendingOperations = {};
+ }
+
+ destroy() {
+ for (const id in this.pendingOperations) {
+ const operation = this.pendingOperations[id];
+ operation.abort();
+ delete this.pendingOperations[id];
+ }
+ }
+
+ command_call(callback, user_data, loop) {
+ // TODO(scotttodd): assert not destroyed to avoid reentrant queueing?
+ const operationId = this.nextOperationId++;
+ this.pendingOperations[operationId] =
+ new LoopCommandCall(this, operationId, callback, user_data, loop);
return IREE_STATUS_OK;
}
}
- const instance = new LoopEmscripten();
- _loop_command_call = instance.loop_command_call.bind(instance);
- },
+ class LoopEmscripten {
+ constructor() {
+ this.nextScopeHandle = 0;
- loop_command_call: function() {},
- loop_command_call__deps: ['$loop_emscripten_support'],
+ // Dictionary of scopeHandles -> LoopEmscriptenScopes.
+ this.scopes = {};
+ }
+
+ iree_loop_allocate_scope() {
+ const scopeHandle = this.nextScopeHandle++;
+ this.scopes[scopeHandle] = new LoopEmscriptenScope();
+ return scopeHandle;
+ }
+
+ iree_loop_free_scope(scope_handle) {
+ if (!(scope_handle in this.scopes)) return;
+
+ const scope = this.scopes[scope_handle];
+ scope.destroy();
+ delete this.scopes[scope_handle];
+ }
+
+ iree_loop_command_call(scope_handle, callback, user_data, loop) {
+ if (!(scope_handle in this.scopes)) return IREE_STATUS_OUT_OF_RANGE;
+
+ const scope = this.scopes[scope_handle];
+ return scope.command_call(callback, user_data, loop);
+ }
+ }
+
+ const instance = new LoopEmscripten();
+ _iree_loop_allocate_scope =
+ instance.iree_loop_allocate_scope.bind(instance);
+ _iree_loop_free_scope = instance.iree_loop_free_scope.bind(instance);
+ _iree_loop_command_call = instance.iree_loop_command_call.bind(instance);
+ },
+ $iree_loop_emscripten_support__deps: ['$dynCall'],
+
+ iree_loop_allocate_scope: function() {},
+ iree_loop_allocate_scope__deps: ['$iree_loop_emscripten_support'],
+ iree_loop_free_scope: function() {},
+ iree_loop_free_scope__deps: ['$iree_loop_emscripten_support'],
+ iree_loop_command_call: function() {},
+ iree_loop_command_call__deps: ['$iree_loop_emscripten_support'],
}
-mergeInto(LibraryManager.library, LibraryLoopEmscripten);
+mergeInto(LibraryManager.library, IreeLibraryLoopEmscripten);