From d3b0a4c51df1d3e0c2e3fcfc575189c02679ad4d Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Wed, 4 Nov 2020 18:07:15 +0800 Subject: [PATCH 1/9] Implement sleep --- Runtime/src/index.ts | 80 ++++++++++++++++++- Sources/JavaScriptKit/PauseExecution.swift | 9 +++ Sources/JavaScriptKit/XcodeSupport.swift | 1 + .../_CJavaScriptKit/include/_CJavaScriptKit.h | 10 +++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 Sources/JavaScriptKit/PauseExecution.swift diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 144fed737..ed8877f20 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -31,6 +31,29 @@ interface SwiftRuntimeExportedFunctions { ): void; } +/** + * Optional methods exposed by Wasm modules after running an `asyncify` pass, + * e.g. `wasm-opt -asyncify`. + * More details at [Pause and Resume WebAssembly with Binaryen's Asyncify](https://kripken.github.io/blog/wasm/2019/07/16/asyncify.html). +*/ +interface AsyncifyExportedFunctions { + asyncify_start_rewind(stack: pointer): void; + asyncify_stop_rewind(): void; + asyncify_start_unwind(stack: pointer): void; + asyncify_stop_unwind(): void; +} + +/** + * Runtime check if Wasm module exposes asyncify methods +*/ +function isAsyncified(exports: any): exports is AsyncifyExportedFunctions { + const asyncifiedExports = exports as AsyncifyExportedFunctions; + return asyncifiedExports.asyncify_start_rewind !== undefined && + asyncifiedExports.asyncify_stop_rewind !== undefined && + asyncifiedExports.asyncify_start_unwind !== undefined && + asyncifiedExports.asyncify_stop_unwind !== undefined; +} + enum JavaScriptValueKind { Invalid = -1, Boolean = 0, @@ -119,19 +142,48 @@ export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; private version: number = 701; + private isSleeping: boolean; + private instanceIsAsyncified: boolean; + private resumeCallback: () => void; constructor() { this.instance = null; this.heap = new SwiftRuntimeHeap(); + this.isSleeping = false; + this.instanceIsAsyncified = false; + this.resumeCallback = () => { }; } - setInstance(instance: WebAssembly.Instance) { + /** + * Set the Wasm instance + * @param instance The instantiate Wasm instance + * @param resumeCallback Optional callback for resuming instance after + * unwinding and rewinding stack (for asyncified modules). + */ + setInstance(instance: WebAssembly.Instance, resumeCallback?: () => void) { this.instance = instance; + if (resumeCallback) { + this.resumeCallback = resumeCallback; + } const exports = (this.instance .exports as any) as SwiftRuntimeExportedFunctions; if (exports.swjs_library_version() != this.version) { throw new Error("The versions of JavaScriptKit are incompatible."); } + this.instanceIsAsyncified = isAsyncified(exports); + console.log(`instanceIsAsyncified :: ${this.instanceIsAsyncified}`, 'background: #222; color: #bada55'); + } + + /** + * Report that the module has been started. + * Required for asyncified Wasm modules, so runtime has a chance to call required methods. + **/ + didStart() { + if (this.instance && this.instanceIsAsyncified) { + const asyncifyExports = (this.instance + .exports as any) as AsyncifyExportedFunctions; + asyncifyExports.asyncify_stop_unwind(); + } } importObjects() { @@ -520,6 +572,32 @@ export class SwiftRuntime { swjs_release: (ref: ref) => { this.heap.release(ref); }, + swjs_sleep: (ms: number) => { + if (!this.instance || !this.instanceIsAsyncified) { + throw new Error("'sleep' requires asyncified WebAssembly"); + } + const int32Memory = new Int32Array(memory().buffer); + const exports = (this.instance + .exports as any) as AsyncifyExportedFunctions; + const ASYNCIFY_STACK_POINTER = 16; // Where the unwind/rewind data structure will live. + if (!this.isSleeping) { + // Fill in the data structure. The first value has the stack location, + // which for simplicity we can start right after the data structure itself. + int32Memory[ASYNCIFY_STACK_POINTER >> 2] = ASYNCIFY_STACK_POINTER + 8; + // Stack size + int32Memory[ASYNCIFY_STACK_POINTER + 4 >> 2] = 4096; + exports.asyncify_start_unwind(ASYNCIFY_STACK_POINTER); + this.isSleeping = true; + setTimeout(() => { + exports.asyncify_start_rewind(ASYNCIFY_STACK_POINTER); + this.resumeCallback(); + }, ms); + } else { + // We are called as part of a resume/rewind. Stop sleeping. + exports.asyncify_stop_rewind(); + this.isSleeping = false; + } + }, }; } } diff --git a/Sources/JavaScriptKit/PauseExecution.swift b/Sources/JavaScriptKit/PauseExecution.swift new file mode 100644 index 000000000..baeaac3ab --- /dev/null +++ b/Sources/JavaScriptKit/PauseExecution.swift @@ -0,0 +1,9 @@ +import _CJavaScriptKit + +/// Unwind Wasm module execution stack and rewind it after specified milliseconds, +/// allowing JavaScript events to continue to be processed. +/// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), +/// otherwise JavaScriptKit's runtime will throw an exception. +public func pauseExecution(milliseconds: Int32) { + _sleep(milliseconds) +} diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 0777e911a..6ce22ee13 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -89,5 +89,6 @@ import _CJavaScriptKit _: Int32, _: UnsafeMutablePointer! ) { fatalError() } + func _sleep(_ ms: Int32) { fatalError() } #endif diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 6d383b3ea..01de7447d 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -258,6 +258,16 @@ extern void _create_typed_array(const JavaScriptObjectRef constructor, const void *elements_ptr, const int length, JavaScriptObjectRef *result_obj); +/// Unwind Wasm module execution stack and rewind it after specified milliseconds, +/// allowing JavaScript events to continue to be processed. +/// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), +/// otherwise JavaScriptKit's runtime will throw an exception. +/// +/// @param ms Length of time in milliseconds to pause execution for. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_sleep"))) +extern void _sleep(const int ms); + #endif #endif /* _CJavaScriptKit_h */ From 62953b7582e6e6aabdc853571278720a84312c98 Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Thu, 5 Nov 2020 18:49:53 +0800 Subject: [PATCH 2/9] Add Promise sync handlers --- Runtime/src/index.ts | 102 +++++++++++++----- Sources/JavaScriptKit/PauseExecution.swift | 31 ++++++ Sources/JavaScriptKit/XcodeSupport.swift | 15 ++- .../_CJavaScriptKit/include/_CJavaScriptKit.h | 33 ++++++ 4 files changed, 156 insertions(+), 25 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index ed8877f20..d79c18cc6 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -138,6 +138,19 @@ class SwiftRuntimeHeap { } } +// Helper methods for asyncify +const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +const promiseWithTimout = (promise: Promise, timeout: number) => { + let timeoutPromise = new Promise((resolve, reject) => { + let timeoutID = setTimeout(() => { + clearTimeout(timeoutID); + reject(Error(`Promise timed out in ${timeout} ms`)); + }, timeout); + }); + return Promise.race([promise, timeoutPromise]); +}; + export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; @@ -380,6 +393,51 @@ export class SwiftRuntime { return result; }; + const syncAwait = ( + promise: Promise, + kind_ptr?: pointer, + payload1_ptr?: pointer, + payload2_ptr?: pointer + ) => { + if (!this.instance || !this.instanceIsAsyncified) { + throw new Error("Calling async methods requires preprocessing Wasm module with `--asyncify`"); + } + const exports = (this.instance + .exports as any) as AsyncifyExportedFunctions; + if (!this.isSleeping) { + // Fill in the data structure. The first value has the stack location, + // which for simplicity we can start right after the data structure itself. + const int32Memory = new Int32Array(memory().buffer); + const ASYNCIFY_STACK_POINTER = 16; // Where the unwind/rewind data structure will live. + int32Memory[ASYNCIFY_STACK_POINTER >> 2] = ASYNCIFY_STACK_POINTER + 8; + // Stack size + int32Memory[ASYNCIFY_STACK_POINTER + 4 >> 2] = 4096; + exports.asyncify_start_unwind(ASYNCIFY_STACK_POINTER); + this.isSleeping = true; + const resume = () => { + exports.asyncify_start_rewind(ASYNCIFY_STACK_POINTER); + this.resumeCallback(); + }; + promise + .then(result => { + if (kind_ptr && payload1_ptr && payload2_ptr) { + writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false); + } + resume(); + }) + .catch(error => { + if (kind_ptr && payload1_ptr && payload2_ptr) { + writeValue(error, kind_ptr, payload1_ptr, payload2_ptr, true); + } + resume(); + }); + } else { + // We are called as part of a resume/rewind. Stop sleeping. + exports.asyncify_stop_rewind(); + this.isSleeping = false; + } + }; + return { swjs_set_prop: ( ref: ref, @@ -573,30 +631,26 @@ export class SwiftRuntime { this.heap.release(ref); }, swjs_sleep: (ms: number) => { - if (!this.instance || !this.instanceIsAsyncified) { - throw new Error("'sleep' requires asyncified WebAssembly"); - } - const int32Memory = new Int32Array(memory().buffer); - const exports = (this.instance - .exports as any) as AsyncifyExportedFunctions; - const ASYNCIFY_STACK_POINTER = 16; // Where the unwind/rewind data structure will live. - if (!this.isSleeping) { - // Fill in the data structure. The first value has the stack location, - // which for simplicity we can start right after the data structure itself. - int32Memory[ASYNCIFY_STACK_POINTER >> 2] = ASYNCIFY_STACK_POINTER + 8; - // Stack size - int32Memory[ASYNCIFY_STACK_POINTER + 4 >> 2] = 4096; - exports.asyncify_start_unwind(ASYNCIFY_STACK_POINTER); - this.isSleeping = true; - setTimeout(() => { - exports.asyncify_start_rewind(ASYNCIFY_STACK_POINTER); - this.resumeCallback(); - }, ms); - } else { - // We are called as part of a resume/rewind. Stop sleeping. - exports.asyncify_stop_rewind(); - this.isSleeping = false; - } + syncAwait(delay(ms)); + }, + swjs_sync_await: ( + promiseRef: ref, + kind_ptr: pointer, + payload1_ptr: pointer, + payload2_ptr: pointer + ) => { + const promise: Promise = this.heap.referenceHeap(promiseRef); + syncAwait(promise, kind_ptr, payload1_ptr, payload2_ptr); + }, + swjs_sync_await_with_timeout: ( + promiseRef: ref, + timeout: number, + kind_ptr: pointer, + payload1_ptr: pointer, + payload2_ptr: pointer + ) => { + const promise: Promise = this.heap.referenceHeap(promiseRef); + syncAwait(promiseWithTimout(promise, timeout), kind_ptr, payload1_ptr, payload2_ptr); }, }; } diff --git a/Sources/JavaScriptKit/PauseExecution.swift b/Sources/JavaScriptKit/PauseExecution.swift index baeaac3ab..2f2519439 100644 --- a/Sources/JavaScriptKit/PauseExecution.swift +++ b/Sources/JavaScriptKit/PauseExecution.swift @@ -7,3 +7,34 @@ import _CJavaScriptKit public func pauseExecution(milliseconds: Int32) { _sleep(milliseconds) } + + /// Unwind Wasm module execution stack and rewind it after promise resolves, + /// allowing JavaScript events to continue to be processed in the meantime. + /// - Parameters: + /// - timeout: If provided, method will return a failure if promise cannot resolve + /// before timeout is reached. + /// + /// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), + /// otherwise JavaScriptKit's runtime will throw an exception. + public func syncAwait(timeout: Int32? = nil) -> Result { + var kindAndFlags = JavaScriptValueKindAndFlags() + var payload1 = JavaScriptPayload1() + var payload2 = JavaScriptPayload2() + + if let timout = timeout { + _syncAwaitWithTimout(jsObject.id, timout, &kindAndFlags, &payload1, &payload2) + } else { + _syncAwait(jsObject.id, &kindAndFlags, &payload1, &payload2) + } + let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2).jsValue() + if kindAndFlags.isException { + if let error = JSError(from: result) { + return .failure(error) + } else { + return .failure(JSError(message: "Could not build proper JSError from result \(result)")) + } + } else { + return .success(result) + } + } +} diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 6ce22ee13..729ff3bf6 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -89,6 +89,19 @@ import _CJavaScriptKit _: Int32, _: UnsafeMutablePointer! ) { fatalError() } - func _sleep(_ ms: Int32) { fatalError() } + func _sleep(_: Int32) { fatalError() } + func _syncAwait( + _: JavaScriptObjectRef, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer! + ) { fatalError() } + func _syncAwaitWithTimout( + _: JavaScriptObjectRef, + _: Int32, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer!, + _: UnsafeMutablePointer! + ) { fatalError() } #endif diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 01de7447d..6405326dd 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -268,6 +268,39 @@ __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_sleep"))) extern void _sleep(const int ms); +/// Unwind Wasm module execution stack and rewind it after promise is fulfilled. +/// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), +/// otherwise JavaScriptKit's runtime will throw an exception. +/// +/// @param promise target JavaScript promise. +/// @param result_kind A result pointer of JavaScript value kind of returned result or thrown exception. +/// @param result_payload1 A result pointer of first payload of JavaScript value of returned result or thrown exception. +/// @param result_payload2 A result pointer of second payload of JavaScript value of returned result or thrown exception. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_sync_await"))) +extern void _syncAwait(const JavaScriptObjectRef promise, + JavaScriptValueKindAndFlags *result_kind, + JavaScriptPayload1 *result_payload1, + JavaScriptPayload2 *result_payload2); + +/// Unwind Wasm module execution stack and rewind it after promise is fulfilled or timeout is reached. +/// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), +/// otherwise JavaScriptKit's runtime will throw an exception. +/// +/// @param promise target JavaScript promise. +/// @param ms Length of timeout in milliseconds. +/// @param result_kind A result pointer of JavaScript value kind of returned result or thrown exception. +/// @param result_payload1 A result pointer of first payload of JavaScript value of returned result or thrown exception. +/// @param result_payload2 A result pointer of second payload of JavaScript value of returned result or thrown exception. +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_sync_await_with_timeout"))) +extern void _syncAwaitWithTimout(const JavaScriptObjectRef promise, + const int ms, + JavaScriptValueKindAndFlags *result_kind, + JavaScriptPayload1 *result_payload1, + JavaScriptPayload2 *result_payload2); + + #endif #endif /* _CJavaScriptKit_h */ From 074467d97cdfe7031572904b2e55fea83d14576a Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Thu, 5 Nov 2020 18:51:47 +0800 Subject: [PATCH 3/9] Add Promise sync handlers --- Sources/JavaScriptKit/PauseExecution.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/JavaScriptKit/PauseExecution.swift b/Sources/JavaScriptKit/PauseExecution.swift index 2f2519439..09c035785 100644 --- a/Sources/JavaScriptKit/PauseExecution.swift +++ b/Sources/JavaScriptKit/PauseExecution.swift @@ -8,6 +8,8 @@ public func pauseExecution(milliseconds: Int32) { _sleep(milliseconds) } + +extension JSPromise where Success == JSValue, Failure == JSError { /// Unwind Wasm module execution stack and rewind it after promise resolves, /// allowing JavaScript events to continue to be processed in the meantime. /// - Parameters: From 3ae49b6e6c1e13ee0fcb09b6d9edb7b8f8a61e01 Mon Sep 17 00:00:00 2001 From: yonihemi Date: Thu, 5 Nov 2020 21:48:02 +0800 Subject: [PATCH 4/9] Update index.ts --- Runtime/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index d79c18cc6..1d7255ce3 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -184,7 +184,6 @@ export class SwiftRuntime { throw new Error("The versions of JavaScriptKit are incompatible."); } this.instanceIsAsyncified = isAsyncified(exports); - console.log(`instanceIsAsyncified :: ${this.instanceIsAsyncified}`, 'background: #222; color: #bada55'); } /** From ccb7297684714c77126b9c41108d939dbfc2a2de Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Tue, 24 Nov 2020 18:23:52 +0800 Subject: [PATCH 5/9] Correctly allocate buffer for Asyncify stack --- Runtime/src/index.ts | 61 ++++++++++++----------- Sources/_CJavaScriptKit/_CJavaScriptKit.c | 15 ++++++ 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 1d7255ce3..d43da3981 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -22,6 +22,7 @@ if (typeof globalThis !== "undefined") { interface SwiftRuntimeExportedFunctions { swjs_library_version(): number; swjs_prepare_host_function_call(size: number): pointer; + swjs_allocate_asyncify_buffer(size: number): pointer; swjs_cleanup_host_function_call(argv: pointer): void; swjs_call_host_function( host_func_id: number, @@ -158,6 +159,7 @@ export class SwiftRuntime { private isSleeping: boolean; private instanceIsAsyncified: boolean; private resumeCallback: () => void; + private asyncifyBufferPointer: pointer | null; constructor() { this.instance = null; @@ -165,6 +167,7 @@ export class SwiftRuntime { this.isSleeping = false; this.instanceIsAsyncified = false; this.resumeCallback = () => { }; + this.asyncifyBufferPointer = null; } /** @@ -401,40 +404,38 @@ export class SwiftRuntime { if (!this.instance || !this.instanceIsAsyncified) { throw new Error("Calling async methods requires preprocessing Wasm module with `--asyncify`"); } - const exports = (this.instance - .exports as any) as AsyncifyExportedFunctions; - if (!this.isSleeping) { - // Fill in the data structure. The first value has the stack location, - // which for simplicity we can start right after the data structure itself. - const int32Memory = new Int32Array(memory().buffer); - const ASYNCIFY_STACK_POINTER = 16; // Where the unwind/rewind data structure will live. - int32Memory[ASYNCIFY_STACK_POINTER >> 2] = ASYNCIFY_STACK_POINTER + 8; - // Stack size - int32Memory[ASYNCIFY_STACK_POINTER + 4 >> 2] = 4096; - exports.asyncify_start_unwind(ASYNCIFY_STACK_POINTER); - this.isSleeping = true; - const resume = () => { - exports.asyncify_start_rewind(ASYNCIFY_STACK_POINTER); - this.resumeCallback(); - }; - promise - .then(result => { - if (kind_ptr && payload1_ptr && payload2_ptr) { - writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false); - } - resume(); - }) - .catch(error => { - if (kind_ptr && payload1_ptr && payload2_ptr) { - writeValue(error, kind_ptr, payload1_ptr, payload2_ptr, true); - } - resume(); - }); - } else { + const exports = (this.instance.exports as any) as AsyncifyExportedFunctions; + if (this.isSleeping) { // We are called as part of a resume/rewind. Stop sleeping. exports.asyncify_stop_rewind(); this.isSleeping = false; + return; + } + + if (this.asyncifyBufferPointer == null) { + const runtimeExports = (this.instance + .exports as any) as SwiftRuntimeExportedFunctions; + this.asyncifyBufferPointer = runtimeExports.swjs_allocate_asyncify_buffer(4096); } + exports.asyncify_start_unwind(this.asyncifyBufferPointer!); + this.isSleeping = true; + const resume = () => { + exports.asyncify_start_rewind(this.asyncifyBufferPointer!); + this.resumeCallback(); + }; + promise + .then(result => { + if (kind_ptr && payload1_ptr && payload2_ptr) { + writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, false); + } + resume(); + }) + .catch(error => { + if (kind_ptr && payload1_ptr && payload2_ptr) { + writeValue(error, kind_ptr, payload1_ptr, payload2_ptr, true); + } + resume(); + }); }; return { diff --git a/Sources/_CJavaScriptKit/_CJavaScriptKit.c b/Sources/_CJavaScriptKit/_CJavaScriptKit.c index dd0f40959..93ab1d7e1 100644 --- a/Sources/_CJavaScriptKit/_CJavaScriptKit.c +++ b/Sources/_CJavaScriptKit/_CJavaScriptKit.c @@ -32,4 +32,19 @@ int _library_version() { return 701; } +/// The structure pointing to the Asyncify stack buffer +typedef struct __attribute__((packed)) { + void *start; + void *end; +} _asyncify_data_pointer; + +__attribute__((export_name("swjs_allocate_asyncify_buffer"))) +void *_allocate_asyncify_buffer(const int size) { + void *buffer = malloc(size); + _asyncify_data_pointer *pointer = malloc(sizeof(_asyncify_data_pointer)); + pointer->start = buffer; + pointer->end = (void *)((int)buffer + size); + return pointer; +} + #endif From d1fff95d0498c599bfff0f5af91da9e1317f4fd7 Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Fri, 8 Jan 2021 16:12:02 +0800 Subject: [PATCH 6/9] For incoming function calls while instance is sleeping, process after instance is resumed --- Runtime/src/index.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index d43da3981..2a80f5e69 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -4,6 +4,8 @@ interface ExportedMemory { type ref = number; type pointer = number; +// Function invocation call, using a host function ID and array of parameters +type function_call = [number, any[]]; interface GlobalVariable {} declare const window: GlobalVariable; @@ -156,10 +158,14 @@ export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; private version: number = 701; + + // Support Asyncified modules private isSleeping: boolean; private instanceIsAsyncified: boolean; private resumeCallback: () => void; private asyncifyBufferPointer: pointer | null; + // Keeps track of function calls requested while instance is sleeping + private pendingHostFunctionCalls: function_call[]; constructor() { this.instance = null; @@ -168,6 +174,7 @@ export class SwiftRuntime { this.instanceIsAsyncified = false; this.resumeCallback = () => { }; this.asyncifyBufferPointer = null; + this.pendingHostFunctionCalls = []; } /** @@ -211,6 +218,10 @@ export class SwiftRuntime { const callHostFunction = (host_func_id: number, args: any[]) => { if (!this.instance) throw new Error("WebAssembly instance is not set yet"); + if (this.isSleeping) { + this.pendingHostFunctionCalls.push([host_func_id, args]); + return; + } const exports = (this.instance .exports as any) as SwiftRuntimeExportedFunctions; const argc = args.length; @@ -409,6 +420,11 @@ export class SwiftRuntime { // We are called as part of a resume/rewind. Stop sleeping. exports.asyncify_stop_rewind(); this.isSleeping = false; + const pendingCalls = this.pendingHostFunctionCalls; + this.pendingHostFunctionCalls = []; + pendingCalls.forEach(call => { + callHostFunction(call[0], call[1]); + }); return; } From 2def067a5d0c9a54c093995f4dea1fc5e6d43d6d Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Fri, 8 Jan 2021 17:02:42 +0800 Subject: [PATCH 7/9] Remove await with timeout to simplify runtime interface --- Runtime/src/index.ts | 20 ------------------- Sources/JavaScriptKit/PauseExecution.swift | 8 ++------ .../_CJavaScriptKit/include/_CJavaScriptKit.h | 18 ----------------- 3 files changed, 2 insertions(+), 44 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 2a80f5e69..3b4d32896 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -144,16 +144,6 @@ class SwiftRuntimeHeap { // Helper methods for asyncify const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); -const promiseWithTimout = (promise: Promise, timeout: number) => { - let timeoutPromise = new Promise((resolve, reject) => { - let timeoutID = setTimeout(() => { - clearTimeout(timeoutID); - reject(Error(`Promise timed out in ${timeout} ms`)); - }, timeout); - }); - return Promise.race([promise, timeoutPromise]); -}; - export class SwiftRuntime { private instance: WebAssembly.Instance | null; private heap: SwiftRuntimeHeap; @@ -658,16 +648,6 @@ export class SwiftRuntime { const promise: Promise = this.heap.referenceHeap(promiseRef); syncAwait(promise, kind_ptr, payload1_ptr, payload2_ptr); }, - swjs_sync_await_with_timeout: ( - promiseRef: ref, - timeout: number, - kind_ptr: pointer, - payload1_ptr: pointer, - payload2_ptr: pointer - ) => { - const promise: Promise = this.heap.referenceHeap(promiseRef); - syncAwait(promiseWithTimout(promise, timeout), kind_ptr, payload1_ptr, payload2_ptr); - }, }; } } diff --git a/Sources/JavaScriptKit/PauseExecution.swift b/Sources/JavaScriptKit/PauseExecution.swift index 09c035785..8bb7443c4 100644 --- a/Sources/JavaScriptKit/PauseExecution.swift +++ b/Sources/JavaScriptKit/PauseExecution.swift @@ -18,16 +18,12 @@ extension JSPromise where Success == JSValue, Failure == JSError { /// /// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), /// otherwise JavaScriptKit's runtime will throw an exception. - public func syncAwait(timeout: Int32? = nil) -> Result { + public func syncAwait() -> Result { var kindAndFlags = JavaScriptValueKindAndFlags() var payload1 = JavaScriptPayload1() var payload2 = JavaScriptPayload2() - if let timout = timeout { - _syncAwaitWithTimout(jsObject.id, timout, &kindAndFlags, &payload1, &payload2) - } else { - _syncAwait(jsObject.id, &kindAndFlags, &payload1, &payload2) - } + _syncAwait(jsObject.id, &kindAndFlags, &payload1, &payload2) let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2).jsValue() if kindAndFlags.isException { if let error = JSError(from: result) { diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 6405326dd..12c51dbf9 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -283,24 +283,6 @@ extern void _syncAwait(const JavaScriptObjectRef promise, JavaScriptPayload1 *result_payload1, JavaScriptPayload2 *result_payload2); -/// Unwind Wasm module execution stack and rewind it after promise is fulfilled or timeout is reached. -/// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), -/// otherwise JavaScriptKit's runtime will throw an exception. -/// -/// @param promise target JavaScript promise. -/// @param ms Length of timeout in milliseconds. -/// @param result_kind A result pointer of JavaScript value kind of returned result or thrown exception. -/// @param result_payload1 A result pointer of first payload of JavaScript value of returned result or thrown exception. -/// @param result_payload2 A result pointer of second payload of JavaScript value of returned result or thrown exception. -__attribute__((__import_module__("javascript_kit"), - __import_name__("swjs_sync_await_with_timeout"))) -extern void _syncAwaitWithTimout(const JavaScriptObjectRef promise, - const int ms, - JavaScriptValueKindAndFlags *result_kind, - JavaScriptPayload1 *result_payload1, - JavaScriptPayload2 *result_payload2); - - #endif #endif /* _CJavaScriptKit_h */ From bcfa3cb919811430347eba4d3a52be2c8a5b3fe3 Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Sat, 30 Jan 2021 22:39:32 +0800 Subject: [PATCH 8/9] Use new simplified JSPromise API --- Sources/JavaScriptKit/PauseExecution.swift | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Sources/JavaScriptKit/PauseExecution.swift b/Sources/JavaScriptKit/PauseExecution.swift index 8bb7443c4..c443e7e1c 100644 --- a/Sources/JavaScriptKit/PauseExecution.swift +++ b/Sources/JavaScriptKit/PauseExecution.swift @@ -9,7 +9,7 @@ public func pauseExecution(milliseconds: Int32) { } -extension JSPromise where Success == JSValue, Failure == JSError { +extension JSPromise { /// Unwind Wasm module execution stack and rewind it after promise resolves, /// allowing JavaScript events to continue to be processed in the meantime. /// - Parameters: @@ -18,7 +18,7 @@ extension JSPromise where Success == JSValue, Failure == JSError { /// /// **Important**: Wasm module must be [asyncified](https://emscripten.org/docs/porting/asyncify.html), /// otherwise JavaScriptKit's runtime will throw an exception. - public func syncAwait() -> Result { + public func syncAwait() -> Result { var kindAndFlags = JavaScriptValueKindAndFlags() var payload1 = JavaScriptPayload1() var payload2 = JavaScriptPayload2() @@ -26,11 +26,7 @@ extension JSPromise where Success == JSValue, Failure == JSError { _syncAwait(jsObject.id, &kindAndFlags, &payload1, &payload2) let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2).jsValue() if kindAndFlags.isException { - if let error = JSError(from: result) { - return .failure(error) - } else { - return .failure(JSError(message: "Could not build proper JSError from result \(result)")) - } + return .failure(result) } else { return .success(result) } From cd302ce061c8d749696faadc8352a1fdf557414c Mon Sep 17 00:00:00 2001 From: Jonathan Hemi Date: Sun, 31 Jan 2021 11:37:18 +0800 Subject: [PATCH 9/9] Defer resuming after caught rejected Promise --- Runtime/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 3b4d32896..59b09ecf3 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -440,7 +440,7 @@ export class SwiftRuntime { if (kind_ptr && payload1_ptr && payload2_ptr) { writeValue(error, kind_ptr, payload1_ptr, payload2_ptr, true); } - resume(); + queueMicrotask(resume); }); }; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy