Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Add Asyncify support#107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Open
yonihemi wants to merge12 commits intoswiftwasm:main
base:main
Choose a base branch
Loading
fromyonihemi:asyncify
Open
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
12 commits
Select commitHold shift + click to select a range
d3b0a4c
Implement sleep
yonihemiNov 4, 2020
62953b7
Add Promise sync handlers
yonihemiNov 5, 2020
074467d
Add Promise sync handlers
yonihemiNov 5, 2020
3ae49b6
Update index.ts
yonihemiNov 5, 2020
ccb7297
Correctly allocate buffer for Asyncify stack
yonihemiNov 24, 2020
a3dcfa7
Merge remote-tracking branch 'upstream/main' into asyncify
yonihemiNov 30, 2020
8704225
Merge remote-tracking branch 'upstream/main' into asyncify
yonihemiJan 8, 2021
d1fff95
For incoming function calls while instance is sleeping, process after…
yonihemiJan 8, 2021
2def067
Remove await with timeout to simplify runtime interface
yonihemiJan 8, 2021
a733efb
Merge remote-tracking branch 'upstream/main' into asyncify
yonihemiJan 30, 2021
bcfa3cb
Use new simplified JSPromise API
yonihemiJan 30, 2021
cd302ce
Defer resuming after caught rejected Promise
yonihemiJan 31, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 129 additions & 1 deletionRuntime/src/index.ts
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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;
Expand All@@ -22,6 +24,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,
Expand All@@ -31,6 +34,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,
Expand DownExpand Up@@ -115,23 +141,61 @@ class SwiftRuntimeHeap {
}
}

// Helper methods for asyncify
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

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;
this.heap = new SwiftRuntimeHeap();
this.isSleeping = false;
this.instanceIsAsyncified = false;
this.resumeCallback = () => { };
this.asyncifyBufferPointer = null;
this.pendingHostFunctionCalls = [];
}

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);
}

/**
* 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() {
Expand All@@ -144,6 +208,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;
Expand DownExpand Up@@ -328,6 +396,54 @@ export class SwiftRuntime {
return result;
};

const syncAwait = (
promise: Promise<any>,
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) {
// 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;
}

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);
}
queueMicrotask(resume);
});
};

return {
swjs_set_prop: (
ref: ref,
Expand DownExpand Up@@ -520,6 +636,18 @@ export class SwiftRuntime {
swjs_release: (ref: ref) => {
this.heap.release(ref);
},
swjs_sleep: (ms: number) => {
syncAwait(delay(ms));
},
swjs_sync_await: (
promiseRef: ref,
kind_ptr: pointer,
payload1_ptr: pointer,
payload2_ptr: pointer
) => {
const promise: Promise<any> = this.heap.referenceHeap(promiseRef);
syncAwait(promise, kind_ptr, payload1_ptr, payload2_ptr);
},
};
}
}
34 changes: 34 additions & 0 deletionsSources/JavaScriptKit/PauseExecution.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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)
}


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:
/// - 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() -> Result<JSValue, JSValue> {
var kindAndFlags = JavaScriptValueKindAndFlags()
var payload1 = JavaScriptPayload1()
var payload2 = JavaScriptPayload2()

_syncAwait(jsObject.id, &kindAndFlags, &payload1, &payload2)
let result = RawJSValue(kind: kindAndFlags.kind, payload1: payload1, payload2: payload2).jsValue()
if kindAndFlags.isException {
return .failure(result)
} else {
return .success(result)
}
}
}
14 changes: 14 additions & 0 deletionsSources/JavaScriptKit/XcodeSupport.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -89,5 +89,19 @@ import _CJavaScriptKit
_: Int32,
_: UnsafeMutablePointer<JavaScriptObjectRef>!
) { fatalError() }
func _sleep(_: Int32) { fatalError() }
func _syncAwait(
_: JavaScriptObjectRef,
_: UnsafeMutablePointer<JavaScriptValueKindAndFlags>!,
_: UnsafeMutablePointer<JavaScriptPayload1>!,
_: UnsafeMutablePointer<JavaScriptPayload2>!
) { fatalError() }
func _syncAwaitWithTimout(
_: JavaScriptObjectRef,
_: Int32,
_: UnsafeMutablePointer<JavaScriptValueKindAndFlags>!,
_: UnsafeMutablePointer<JavaScriptPayload1>!,
_: UnsafeMutablePointer<JavaScriptPayload2>!
) { fatalError() }

#endif
15 changes: 15 additions & 0 deletionsSources/_CJavaScriptKit/_CJavaScriptKit.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -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
25 changes: 25 additions & 0 deletionsSources/_CJavaScriptKit/include/_CJavaScriptKit.h
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -258,6 +258,31 @@ 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);

/// 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);

#endif

#endif /* _CJavaScriptKit_h */

[8]ページ先頭

©2009-2025 Movatter.jp