diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1e9060f19..69f63a27c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -119,6 +119,16 @@ jobs: env: DEVELOPER_DIR: /Applications/${{ matrix.xcode }}.app/Contents/Developer/ + prettier: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: '20' + - run: npm install + - run: npx prettier --check Runtime/src + format: runs-on: ubuntu-latest container: diff --git a/Plugins/PackageToJS/Templates/runtime.mjs b/Plugins/PackageToJS/Templates/runtime.mjs index a40fc3df6..e0bcf3e05 100644 --- a/Plugins/PackageToJS/Templates/runtime.mjs +++ b/Plugins/PackageToJS/Templates/runtime.mjs @@ -1,6 +1,6 @@ /// Memory lifetime of closures in Swift are managed by Swift side class SwiftClosureDeallocator { - constructor(exports) { + constructor(exports$1) { if (typeof FinalizationRegistry === "undefined") { throw new Error("The Swift part of JavaScriptKit was configured to require " + "the availability of JavaScript WeakRefs. Please build " + @@ -8,7 +8,7 @@ class SwiftClosureDeallocator { "disable features that use WeakRefs."); } this.functionRegistry = new FinalizationRegistry((id) => { - exports.swjs_free_host_function(id); + exports$1.swjs_free_host_function(id); }); } track(func, func_ref) { @@ -126,12 +126,12 @@ class ITCInterface { } send(sendingObject, transferringObjects, sendingContext) { const object = this.memory.getObject(sendingObject); - const transfer = transferringObjects.map(ref => this.memory.getObject(ref)); + const transfer = transferringObjects.map((ref) => this.memory.getObject(ref)); return { object, sendingContext, transfer }; } sendObjects(sendingObjects, transferringObjects, sendingContext) { - const objects = sendingObjects.map(ref => this.memory.getObject(ref)); - const transfer = transferringObjects.map(ref => this.memory.getObject(ref)); + const objects = sendingObjects.map((ref) => this.memory.getObject(ref)); + const transfer = transferringObjects.map((ref) => this.memory.getObject(ref)); return { object: objects, sendingContext, transfer }; } release(objectRef) { @@ -168,7 +168,9 @@ class MessageBroker { this.handlers.onResponse(message); return; } - const transfer = message.data.response.ok ? message.data.response.value.transfer : []; + const transfer = message.data.response.ok + ? message.data.response.value.transfer + : []; if ("postMessageToWorkerThread" in this.threadChannel) { // The response is for another worker thread sent from the main thread this.threadChannel.postMessageToWorkerThread(message.data.sourceTid, message, transfer); @@ -186,7 +188,7 @@ class MessageBroker { this.handlers.onRequest(message); } else if ("postMessageToWorkerThread" in this.threadChannel) { - // Receive a request from a worker thread to other worker on main thread. + // Receive a request from a worker thread to other worker on main thread. // Proxy the request to the target worker thread. this.threadChannel.postMessageToWorkerThread(message.data.targetTid, message, []); } @@ -202,7 +204,9 @@ class MessageBroker { else if ("postMessageToWorkerThread" in this.threadChannel) { // Receive a response from a worker thread to other worker on main thread. // Proxy the response to the target worker thread. - const transfer = message.data.response.ok ? message.data.response.value.transfer : []; + const transfer = message.data.response.ok + ? message.data.response.value.transfer + : []; this.threadChannel.postMessageToWorkerThread(message.data.sourceTid, message, transfer); } else if ("postMessageToMainThread" in this.threadChannel) { @@ -213,7 +217,14 @@ class MessageBroker { } function serializeError(error) { if (error instanceof Error) { - return { isError: true, value: { message: error.message, name: error.name, stack: error.stack } }; + return { + isError: true, + value: { + message: error.message, + name: error.name, + stack: error.stack, + }, + }; } return { isError: false, value: error }; } @@ -224,19 +235,7 @@ function deserializeError(error) { return error.value; } -let globalVariable; -if (typeof globalThis !== "undefined") { - globalVariable = globalThis; -} -else if (typeof window !== "undefined") { - globalVariable = window; -} -else if (typeof global !== "undefined") { - globalVariable = global; -} -else if (typeof self !== "undefined") { - globalVariable = self; -} +const globalVariable = globalThis; class JSObjectSpace { constructor() { @@ -312,7 +311,8 @@ class SwiftRuntime { // 1. It may not be available in the global scope if the context is not cross-origin isolated. // 2. The underlying buffer may be still backed by SAB even if the context is not cross-origin // isolated (e.g. localhost on Chrome on Android). - if (Object.getPrototypeOf(wasmMemory.buffer).constructor.name === "SharedArrayBuffer") { + if (Object.getPrototypeOf(wasmMemory.buffer).constructor.name === + "SharedArrayBuffer") { // When the wasm memory is backed by a SharedArrayBuffer, growing the memory // doesn't invalidate the data view by setting the byte length to 0. Instead, // the data view points to an old buffer after growing the memory. So we have @@ -463,7 +463,10 @@ class SwiftRuntime { returnValue = { ok: true, value: result }; } catch (error) { - returnValue = { ok: false, error: serializeError(error) }; + returnValue = { + ok: false, + error: serializeError(error), + }; } const responseMessage = { type: "response", @@ -479,7 +482,7 @@ class SwiftRuntime { catch (error) { responseMessage.data.response = { ok: false, - error: serializeError(new TypeError(`Failed to serialize message: ${error}`)) + error: serializeError(new TypeError(`Failed to serialize message: ${error}`)), }; newBroker.reply(responseMessage); } @@ -494,7 +497,7 @@ class SwiftRuntime { const errorObject = this.memory.retain(error); this.exports.swjs_receive_error(errorObject, message.data.context); } - } + }, }); broker = newBroker; return newBroker; @@ -532,21 +535,19 @@ class SwiftRuntime { this.getDataView().setUint32(bytes_ptr_result, bytes_ptr, true); return bytes.length; }, - swjs_decode_string: ( + swjs_decode_string: // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer this.options.sharedMemory == true - ? ((bytes_ptr, length) => { - const bytes = this.getUint8Array() - .slice(bytes_ptr, bytes_ptr + length); + ? (bytes_ptr, length) => { + const bytes = this.getUint8Array().slice(bytes_ptr, bytes_ptr + length); const string = this.textDecoder.decode(bytes); return this.memory.retain(string); - }) - : ((bytes_ptr, length) => { - const bytes = this.getUint8Array() - .subarray(bytes_ptr, bytes_ptr + length); + } + : (bytes_ptr, length) => { + const bytes = this.getUint8Array().subarray(bytes_ptr, bytes_ptr + length); const string = this.textDecoder.decode(bytes); return this.memory.retain(string); - })), + }, swjs_load_string: (ref, buffer) => { const bytes = this.memory.getObject(ref); this.getUint8Array().set(bytes, buffer); @@ -658,7 +659,9 @@ class SwiftRuntime { // Call `.slice()` to copy the memory return this.memory.retain(array.slice()); }, - swjs_create_object: () => { return this.memory.retain({}); }, + swjs_create_object: () => { + return this.memory.retain({}); + }, swjs_load_typed_array: (ref, buffer) => { const memory = this.memory; const typedArray = memory.getObject(ref); @@ -683,8 +686,8 @@ class SwiftRuntime { request: { method: "release", parameters: [ref], - } - } + }, + }, }); }, swjs_i64_to_bigint: (value, signed) => { @@ -708,17 +711,23 @@ class SwiftRuntime { swjs_i64_to_bigint_slow: (lower, upper, signed) => { const value = BigInt.asUintN(32, BigInt(lower)) + (BigInt.asUintN(32, BigInt(upper)) << BigInt(32)); - return this.memory.retain(signed ? BigInt.asIntN(64, value) : BigInt.asUintN(64, value)); + return this.memory.retain(signed + ? BigInt.asIntN(64, value) + : BigInt.asUintN(64, value)); }, swjs_unsafe_event_loop_yield: () => { throw new UnsafeEventLoopYield(); }, swjs_send_job_to_main_thread: (unowned_job) => { - this.postMessageToMainThread({ type: "job", data: unowned_job }); + this.postMessageToMainThread({ + type: "job", + data: unowned_job, + }); }, swjs_listen_message_from_main_thread: () => { const threadChannel = this.options.threadChannel; - if (!(threadChannel && "listenMessageFromMainThread" in threadChannel)) { + if (!(threadChannel && + "listenMessageFromMainThread" in threadChannel)) { throw new Error("listenMessageFromMainThread is not set in options given to SwiftRuntime. Please set it to listen to wake events from the main thread."); } const broker = getMessageBroker(threadChannel); @@ -746,7 +755,8 @@ class SwiftRuntime { }, swjs_listen_message_from_worker_thread: (tid) => { const threadChannel = this.options.threadChannel; - if (!(threadChannel && "listenMessageFromWorkerThread" in threadChannel)) { + if (!(threadChannel && + "listenMessageFromWorkerThread" in threadChannel)) { throw new Error("listenMessageFromWorkerThread is not set in options given to SwiftRuntime. Please set it to listen to jobs from worker threads."); } const broker = getMessageBroker(threadChannel); @@ -795,9 +805,13 @@ class SwiftRuntime { context: sending_context, request: { method: "send", - parameters: [sending_object, transferringObjects, sending_context], - } - } + parameters: [ + sending_object, + transferringObjects, + sending_context, + ], + }, + }, }); }, swjs_request_sending_objects: (sending_objects, sending_objects_count, transferring_objects, transferring_objects_count, object_source_tid, sending_context) => { @@ -817,9 +831,13 @@ class SwiftRuntime { context: sending_context, request: { method: "sendObjects", - parameters: [sendingObjects, transferringObjects, sending_context], - } - } + parameters: [ + sendingObjects, + transferringObjects, + sending_context, + ], + }, + }, }); }, }; diff --git a/Runtime/src/closure-heap.ts b/Runtime/src/closure-heap.ts index 269934390..0ec0ba9ac 100644 --- a/Runtime/src/closure-heap.ts +++ b/Runtime/src/closure-heap.ts @@ -10,7 +10,7 @@ export class SwiftClosureDeallocator { "The Swift part of JavaScriptKit was configured to require " + "the availability of JavaScript WeakRefs. Please build " + "with `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` to " + - "disable features that use WeakRefs." + "disable features that use WeakRefs.", ); } diff --git a/Runtime/src/find-global.ts b/Runtime/src/find-global.ts index f20afbf34..1530f446e 100644 --- a/Runtime/src/find-global.ts +++ b/Runtime/src/find-global.ts @@ -1,13 +1 @@ -interface GlobalVariable {} -declare const global: GlobalVariable; - -export let globalVariable: any; -if (typeof globalThis !== "undefined") { - globalVariable = globalThis; -} else if (typeof window !== "undefined") { - globalVariable = window; -} else if (typeof global !== "undefined") { - globalVariable = global; -} else if (typeof self !== "undefined") { - globalVariable = self; -} +export const globalVariable: any = globalThis; diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 27b52c7da..1d36c95d1 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -7,7 +7,16 @@ import { MAIN_THREAD_TID, } from "./types.js"; import * as JSValue from "./js-value.js"; -import { deserializeError, MainToWorkerMessage, MessageBroker, ResponseMessage, ITCInterface, serializeError, SwiftRuntimeThreadChannel, WorkerToMainMessage } from "./itc.js"; +import { + deserializeError, + MainToWorkerMessage, + MessageBroker, + ResponseMessage, + ITCInterface, + serializeError, + SwiftRuntimeThreadChannel, + WorkerToMainMessage, +} from "./itc.js"; import { decodeObjectRefs } from "./js-value.js"; import { JSObjectSpace } from "./object-heap.js"; export { SwiftRuntimeThreadChannel }; @@ -36,8 +45,8 @@ export class SwiftRuntime { private textEncoder = new TextEncoder(); // Only support utf-8 /** The thread ID of the current thread. */ private tid: number | null; - private getDataView: (() => DataView); - private getUint8Array: (() => Uint8Array); + private getDataView: () => DataView; + private getUint8Array: () => Uint8Array; private wasmMemory: WebAssembly.Memory | null; UnsafeEventLoopYield = UnsafeEventLoopYield; @@ -49,10 +58,14 @@ export class SwiftRuntime { this.tid = null; this.options = options || {}; this.getDataView = () => { - throw new Error("Please call setInstance() before using any JavaScriptKit APIs from Swift."); + throw new Error( + "Please call setInstance() before using any JavaScriptKit APIs from Swift.", + ); }; this.getUint8Array = () => { - throw new Error("Please call setInstance() before using any JavaScriptKit APIs from Swift."); + throw new Error( + "Please call setInstance() before using any JavaScriptKit APIs from Swift.", + ); }; this.wasmMemory = null; } @@ -70,7 +83,10 @@ export class SwiftRuntime { // 1. It may not be available in the global scope if the context is not cross-origin isolated. // 2. The underlying buffer may be still backed by SAB even if the context is not cross-origin // isolated (e.g. localhost on Chrome on Android). - if (Object.getPrototypeOf(wasmMemory.buffer).constructor.name === "SharedArrayBuffer") { + if ( + Object.getPrototypeOf(wasmMemory.buffer).constructor.name === + "SharedArrayBuffer" + ) { // When the wasm memory is backed by a SharedArrayBuffer, growing the memory // doesn't invalidate the data view by setting the byte length to 0. Instead, // the data view points to an old buffer after growing the memory. So we have @@ -105,14 +121,16 @@ export class SwiftRuntime { } this.wasmMemory = wasmMemory; } else { - throw new Error("instance.exports.memory is not a WebAssembly.Memory!?"); + throw new Error( + "instance.exports.memory is not a WebAssembly.Memory!?", + ); } if (typeof (this.exports as any)._start === "function") { throw new Error( `JavaScriptKit supports only WASI reactor ABI. Please make sure you are building with: -Xswiftc -Xclang-linker -Xswiftc -mexec-model=reactor - ` + `, ); } if (this.exports.swjs_library_version() != this.version) { @@ -120,7 +138,7 @@ export class SwiftRuntime { `The versions of JavaScriptKit are incompatible. WebAssembly runtime ${this.exports.swjs_library_version()} != JS runtime ${ this.version - }` + }`, ); } } @@ -159,7 +177,7 @@ export class SwiftRuntime { instance.exports.wasi_thread_start(tid, startArg); } else { throw new Error( - `The WebAssembly module is not built for wasm32-unknown-wasip1-threads target.` + `The WebAssembly module is not built for wasm32-unknown-wasip1-threads target.`, ); } } catch (error) { @@ -190,7 +208,7 @@ export class SwiftRuntime { (features & LibraryFeatures.WeakRefs) != 0; if (librarySupportsWeakRef) { this._closureDeallocator = new SwiftClosureDeallocator( - this.exports + this.exports, ); } return this._closureDeallocator; @@ -200,7 +218,7 @@ export class SwiftRuntime { host_func_id: number, line: number, file: string, - args: any[] + args: any[], ) { const argc = args.length; const argv = this.exports.swjs_prepare_host_function_call(argc); @@ -209,7 +227,15 @@ export class SwiftRuntime { for (let index = 0; index < args.length; index++) { const argument = args[index]; const base = argv + 16 * index; - JSValue.write(argument, base, base + 4, base + 8, false, dataView, memory); + JSValue.write( + argument, + base, + base + 4, + base + 8, + false, + dataView, + memory, + ); } let output: any; // This ref is released by the swjs_call_host_function implementation @@ -220,11 +246,11 @@ export class SwiftRuntime { host_func_id, argv, argc, - callback_func_ref + callback_func_ref, ); if (alreadyReleased) { throw new Error( - `The JSClosure has been already released by Swift side. The closure is created at ${file}:${line} @${host_func_id}` + `The JSClosure has been already released by Swift side. The closure is created at ${file}:${line} @${host_func_id}`, ); } this.exports.swjs_cleanup_host_function_call(argv); @@ -244,10 +270,15 @@ export class SwiftRuntime { let returnValue: ResponseMessage["data"]["response"]; try { // @ts-ignore - const result = itcInterface[message.data.request.method](...message.data.request.parameters); + const result = itcInterface[ + message.data.request.method + ](...message.data.request.parameters); returnValue = { ok: true, value: result }; } catch (error) { - returnValue = { ok: false, error: serializeError(error) }; + returnValue = { + ok: false, + error: serializeError(error), + }; } const responseMessage: ResponseMessage = { type: "response", @@ -256,38 +287,52 @@ export class SwiftRuntime { context: message.data.context, response: returnValue, }, - } + }; try { newBroker.reply(responseMessage); } catch (error) { responseMessage.data.response = { ok: false, - error: serializeError(new TypeError(`Failed to serialize message: ${error}`)) + error: serializeError( + new TypeError( + `Failed to serialize message: ${error}`, + ), + ), }; newBroker.reply(responseMessage); } }, onResponse: (message) => { if (message.data.response.ok) { - const object = this.memory.retain(message.data.response.value.object); - this.exports.swjs_receive_response(object, message.data.context); + const object = this.memory.retain( + message.data.response.value.object, + ); + this.exports.swjs_receive_response( + object, + message.data.context, + ); } else { - const error = deserializeError(message.data.response.error); + const error = deserializeError( + message.data.response.error, + ); const errorObject = this.memory.retain(error); - this.exports.swjs_receive_error(errorObject, message.data.context); + this.exports.swjs_receive_error( + errorObject, + message.data.context, + ); } - } - }) + }, + }); broker = newBroker; return newBroker; - } + }; return { swjs_set_prop: ( ref: ref, name: ref, kind: JSValue.Kind, payload1: number, - payload2: number + payload2: number, ) => { const memory = this.memory; const obj = memory.getObject(ref); @@ -299,7 +344,7 @@ export class SwiftRuntime { ref: ref, name: ref, payload1_ptr: pointer, - payload2_ptr: pointer + payload2_ptr: pointer, ) => { const memory = this.memory; const obj = memory.getObject(ref); @@ -311,7 +356,7 @@ export class SwiftRuntime { payload2_ptr, false, this.getDataView(), - this.memory + this.memory, ); }, @@ -320,7 +365,7 @@ export class SwiftRuntime { index: number, kind: JSValue.Kind, payload1: number, - payload2: number + payload2: number, ) => { const memory = this.memory; const obj = memory.getObject(ref); @@ -331,7 +376,7 @@ export class SwiftRuntime { ref: ref, index: number, payload1_ptr: pointer, - payload2_ptr: pointer + payload2_ptr: pointer, ) => { const obj = this.memory.getObject(ref); const result = obj[index]; @@ -341,7 +386,7 @@ export class SwiftRuntime { payload2_ptr, false, this.getDataView(), - this.memory + this.memory, ); }, @@ -352,22 +397,25 @@ export class SwiftRuntime { this.getDataView().setUint32(bytes_ptr_result, bytes_ptr, true); return bytes.length; }, - swjs_decode_string: ( + swjs_decode_string: // NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer this.options.sharedMemory == true - ? ((bytes_ptr: pointer, length: number) => { - const bytes = this.getUint8Array() - .slice(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); - return this.memory.retain(string); - }) - : ((bytes_ptr: pointer, length: number) => { - const bytes = this.getUint8Array() - .subarray(bytes_ptr, bytes_ptr + length); - const string = this.textDecoder.decode(bytes); - return this.memory.retain(string); - }) - ), + ? (bytes_ptr: pointer, length: number) => { + const bytes = this.getUint8Array().slice( + bytes_ptr, + bytes_ptr + length, + ); + const string = this.textDecoder.decode(bytes); + return this.memory.retain(string); + } + : (bytes_ptr: pointer, length: number) => { + const bytes = this.getUint8Array().subarray( + bytes_ptr, + bytes_ptr + length, + ); + const string = this.textDecoder.decode(bytes); + return this.memory.retain(string); + }, swjs_load_string: (ref: ref, buffer: pointer) => { const bytes = this.memory.getObject(ref); this.getUint8Array().set(bytes, buffer); @@ -378,13 +426,18 @@ export class SwiftRuntime { argv: pointer, argc: number, payload1_ptr: pointer, - payload2_ptr: pointer + payload2_ptr: pointer, ) => { const memory = this.memory; const func = memory.getObject(ref); let result = undefined; try { - const args = JSValue.decodeArray(argv, argc, this.getDataView(), memory); + const args = JSValue.decodeArray( + argv, + argc, + this.getDataView(), + memory, + ); result = func(...args); } catch (error) { return JSValue.writeAndReturnKindBits( @@ -393,7 +446,7 @@ export class SwiftRuntime { payload2_ptr, true, this.getDataView(), - this.memory + this.memory, ); } return JSValue.writeAndReturnKindBits( @@ -402,7 +455,7 @@ export class SwiftRuntime { payload2_ptr, false, this.getDataView(), - this.memory + this.memory, ); }, swjs_call_function_no_catch: ( @@ -410,11 +463,16 @@ export class SwiftRuntime { argv: pointer, argc: number, payload1_ptr: pointer, - payload2_ptr: pointer + payload2_ptr: pointer, ) => { const memory = this.memory; const func = memory.getObject(ref); - const args = JSValue.decodeArray(argv, argc, this.getDataView(), memory); + const args = JSValue.decodeArray( + argv, + argc, + this.getDataView(), + memory, + ); const result = func(...args); return JSValue.writeAndReturnKindBits( result, @@ -422,7 +480,7 @@ export class SwiftRuntime { payload2_ptr, false, this.getDataView(), - this.memory + this.memory, ); }, @@ -432,14 +490,19 @@ export class SwiftRuntime { argv: pointer, argc: number, payload1_ptr: pointer, - payload2_ptr: pointer + payload2_ptr: pointer, ) => { const memory = this.memory; const obj = memory.getObject(obj_ref); const func = memory.getObject(func_ref); let result: any; try { - const args = JSValue.decodeArray(argv, argc, this.getDataView(), memory); + const args = JSValue.decodeArray( + argv, + argc, + this.getDataView(), + memory, + ); result = func.apply(obj, args); } catch (error) { return JSValue.writeAndReturnKindBits( @@ -448,7 +511,7 @@ export class SwiftRuntime { payload2_ptr, true, this.getDataView(), - this.memory + this.memory, ); } return JSValue.writeAndReturnKindBits( @@ -457,7 +520,7 @@ export class SwiftRuntime { payload2_ptr, false, this.getDataView(), - this.memory + this.memory, ); }, swjs_call_function_with_this_no_catch: ( @@ -466,13 +529,18 @@ export class SwiftRuntime { argv: pointer, argc: number, payload1_ptr: pointer, - payload2_ptr: pointer + payload2_ptr: pointer, ) => { const memory = this.memory; const obj = memory.getObject(obj_ref); const func = memory.getObject(func_ref); let result = undefined; - const args = JSValue.decodeArray(argv, argc, this.getDataView(), memory); + const args = JSValue.decodeArray( + argv, + argc, + this.getDataView(), + memory, + ); result = func.apply(obj, args); return JSValue.writeAndReturnKindBits( result, @@ -480,14 +548,19 @@ export class SwiftRuntime { payload2_ptr, false, this.getDataView(), - this.memory + this.memory, ); }, swjs_call_new: (ref: ref, argv: pointer, argc: number) => { const memory = this.memory; const constructor = memory.getObject(ref); - const args = JSValue.decodeArray(argv, argc, this.getDataView(), memory); + const args = JSValue.decodeArray( + argv, + argc, + this.getDataView(), + memory, + ); const instance = new constructor(...args); return this.memory.retain(instance); }, @@ -497,13 +570,18 @@ export class SwiftRuntime { argc: number, exception_kind_ptr: pointer, exception_payload1_ptr: pointer, - exception_payload2_ptr: pointer + exception_payload2_ptr: pointer, ) => { let memory = this.memory; const constructor = memory.getObject(ref); let result: any; try { - const args = JSValue.decodeArray(argv, argc, this.getDataView(), memory); + const args = JSValue.decodeArray( + argv, + argc, + this.getDataView(), + memory, + ); result = new constructor(...args); } catch (error) { JSValue.write( @@ -513,7 +591,7 @@ export class SwiftRuntime { exception_payload2_ptr, true, this.getDataView(), - this.memory + this.memory, ); return -1; } @@ -525,7 +603,7 @@ export class SwiftRuntime { exception_payload2_ptr, false, this.getDataView(), - memory + memory, ); return memory.retain(result); }, @@ -547,7 +625,7 @@ export class SwiftRuntime { swjs_create_function: ( host_func_id: number, line: number, - file: ref + file: ref, ) => { const fileString = this.memory.getObject(file) as string; const func = (...args: any[]) => @@ -560,7 +638,7 @@ export class SwiftRuntime { swjs_create_oneshot_function: ( host_func_id: number, line: number, - file: ref + file: ref, ) => { const fileString = this.memory.getObject(file) as string; const func = (...args: any[]) => @@ -569,13 +647,30 @@ export class SwiftRuntime { return func_ref; }, - swjs_create_typed_array: ( + swjs_create_typed_array: < + T extends + | Int8Array + | Uint8Array + | Int16Array + | Uint16Array + | Int32Array + | Uint32Array + | BigInt64Array + | BigUint64Array + | Float32Array + | Float64Array + | Uint8ClampedArray, + >( constructor_ref: ref, elementsPtr: pointer, - length: number + length: number, ) => { type TypedArrayConstructor = { - new (buffer: ArrayBuffer, byteOffset: number, length: number): T; + new ( + buffer: ArrayBuffer, + byteOffset: number, + length: number, + ): T; new (): T; }; const ArrayType: TypedArrayConstructor = @@ -592,13 +687,15 @@ export class SwiftRuntime { const array = new ArrayType( this.wasmMemory!.buffer, elementsPtr, - length + length, ); // Call `.slice()` to copy the memory return this.memory.retain(array.slice()); }, - swjs_create_object: () => { return this.memory.retain({}); }, + swjs_create_object: () => { + return this.memory.retain({}); + }, swjs_load_typed_array: (ref: ref, buffer: pointer) => { const memory = this.memory; @@ -613,7 +710,9 @@ export class SwiftRuntime { swjs_release_remote: (tid: number, ref: ref) => { if (!this.options.threadChannel) { - throw new Error("threadChannel is not set in options given to SwiftRuntime. Please set it to release objects on remote threads."); + throw new Error( + "threadChannel is not set in options given to SwiftRuntime. Please set it to release objects on remote threads.", + ); } const broker = getMessageBroker(this.options.threadChannel); broker.request({ @@ -625,20 +724,22 @@ export class SwiftRuntime { request: { method: "release", parameters: [ref], - } - } - }) + }, + }, + }); }, swjs_i64_to_bigint: (value: bigint, signed: number) => { return this.memory.retain( - signed ? value : BigInt.asUintN(64, value) + signed ? value : BigInt.asUintN(64, value), ); }, swjs_bigint_to_i64: (ref: ref, signed: number) => { const object = this.memory.getObject(ref); if (typeof object !== "bigint") { - throw new Error(`Expected a BigInt, but got ${typeof object}`); + throw new Error( + `Expected a BigInt, but got ${typeof object}`, + ); } if (signed) { return object; @@ -649,44 +750,60 @@ export class SwiftRuntime { return BigInt.asIntN(64, object); } }, - swjs_i64_to_bigint_slow: (lower: number, upper: number, signed: number) => { + swjs_i64_to_bigint_slow: ( + lower: number, + upper: number, + signed: number, + ) => { const value = BigInt.asUintN(32, BigInt(lower)) + (BigInt.asUintN(32, BigInt(upper)) << BigInt(32)); return this.memory.retain( - signed ? BigInt.asIntN(64, value) : BigInt.asUintN(64, value) + signed + ? BigInt.asIntN(64, value) + : BigInt.asUintN(64, value), ); }, swjs_unsafe_event_loop_yield: () => { throw new UnsafeEventLoopYield(); }, swjs_send_job_to_main_thread: (unowned_job: number) => { - this.postMessageToMainThread({ type: "job", data: unowned_job }); + this.postMessageToMainThread({ + type: "job", + data: unowned_job, + }); }, swjs_listen_message_from_main_thread: () => { const threadChannel = this.options.threadChannel; - if (!(threadChannel && "listenMessageFromMainThread" in threadChannel)) { + if ( + !( + threadChannel && + "listenMessageFromMainThread" in threadChannel + ) + ) { throw new Error( - "listenMessageFromMainThread is not set in options given to SwiftRuntime. Please set it to listen to wake events from the main thread." + "listenMessageFromMainThread is not set in options given to SwiftRuntime. Please set it to listen to wake events from the main thread.", ); } const broker = getMessageBroker(threadChannel); threadChannel.listenMessageFromMainThread((message) => { switch (message.type) { - case "wake": - this.exports.swjs_wake_worker_thread(); - break; - case "request": { - broker.onReceivingRequest(message); - break; - } - case "response": { - broker.onReceivingResponse(message); - break; - } - default: - const unknownMessage: never = message; - throw new Error(`Unknown message type: ${unknownMessage}`); + case "wake": + this.exports.swjs_wake_worker_thread(); + break; + case "request": { + broker.onReceivingRequest(message); + break; + } + case "response": { + broker.onReceivingResponse(message); + break; + } + default: + const unknownMessage: never = message; + throw new Error( + `Unknown message type: ${unknownMessage}`, + ); } }); }, @@ -695,17 +812,23 @@ export class SwiftRuntime { }, swjs_listen_message_from_worker_thread: (tid: number) => { const threadChannel = this.options.threadChannel; - if (!(threadChannel && "listenMessageFromWorkerThread" in threadChannel)) { + if ( + !( + threadChannel && + "listenMessageFromWorkerThread" in threadChannel + ) + ) { throw new Error( - "listenMessageFromWorkerThread is not set in options given to SwiftRuntime. Please set it to listen to jobs from worker threads." + "listenMessageFromWorkerThread is not set in options given to SwiftRuntime. Please set it to listen to jobs from worker threads.", ); } const broker = getMessageBroker(threadChannel); - threadChannel.listenMessageFromWorkerThread( - tid, (message) => { - switch (message.type) { + threadChannel.listenMessageFromWorkerThread(tid, (message) => { + switch (message.type) { case "job": - this.exports.swjs_enqueue_main_job_from_worker(message.data); + this.exports.swjs_enqueue_main_job_from_worker( + message.data, + ); break; case "request": { broker.onReceivingRequest(message); @@ -717,10 +840,11 @@ export class SwiftRuntime { } default: const unknownMessage: never = message; - throw new Error(`Unknown message type: ${unknownMessage}`); - } - }, - ); + throw new Error( + `Unknown message type: ${unknownMessage}`, + ); + } + }); }, swjs_terminate_worker_thread: (tid: number) => { const threadChannel = this.options.threadChannel; @@ -740,10 +864,16 @@ export class SwiftRuntime { sending_context: pointer, ) => { if (!this.options.threadChannel) { - throw new Error("threadChannel is not set in options given to SwiftRuntime. Please set it to request transferring objects."); + throw new Error( + "threadChannel is not set in options given to SwiftRuntime. Please set it to request transferring objects.", + ); } const broker = getMessageBroker(this.options.threadChannel); - const transferringObjects = decodeObjectRefs(transferring_objects, transferring_objects_count, this.getDataView()); + const transferringObjects = decodeObjectRefs( + transferring_objects, + transferring_objects_count, + this.getDataView(), + ); broker.request({ type: "request", data: { @@ -752,10 +882,14 @@ export class SwiftRuntime { context: sending_context, request: { method: "send", - parameters: [sending_object, transferringObjects, sending_context], - } - } - }) + parameters: [ + sending_object, + transferringObjects, + sending_context, + ], + }, + }, + }); }, swjs_request_sending_objects: ( sending_objects: pointer, @@ -766,12 +900,22 @@ export class SwiftRuntime { sending_context: pointer, ) => { if (!this.options.threadChannel) { - throw new Error("threadChannel is not set in options given to SwiftRuntime. Please set it to request transferring objects."); + throw new Error( + "threadChannel is not set in options given to SwiftRuntime. Please set it to request transferring objects.", + ); } const broker = getMessageBroker(this.options.threadChannel); const dataView = this.getDataView(); - const sendingObjects = decodeObjectRefs(sending_objects, sending_objects_count, dataView); - const transferringObjects = decodeObjectRefs(transferring_objects, transferring_objects_count, dataView); + const sendingObjects = decodeObjectRefs( + sending_objects, + sending_objects_count, + dataView, + ); + const transferringObjects = decodeObjectRefs( + transferring_objects, + transferring_objects_count, + dataView, + ); broker.request({ type: "request", data: { @@ -780,29 +924,40 @@ export class SwiftRuntime { context: sending_context, request: { method: "sendObjects", - parameters: [sendingObjects, transferringObjects, sending_context], - } - } - }) + parameters: [ + sendingObjects, + transferringObjects, + sending_context, + ], + }, + }, + }); }, }; } - private postMessageToMainThread(message: WorkerToMainMessage, transfer: any[] = []) { + private postMessageToMainThread( + message: WorkerToMainMessage, + transfer: any[] = [], + ) { const threadChannel = this.options.threadChannel; if (!(threadChannel && "postMessageToMainThread" in threadChannel)) { throw new Error( - "postMessageToMainThread is not set in options given to SwiftRuntime. Please set it to send messages to the main thread." + "postMessageToMainThread is not set in options given to SwiftRuntime. Please set it to send messages to the main thread.", ); } threadChannel.postMessageToMainThread(message, transfer); } - private postMessageToWorkerThread(tid: number, message: MainToWorkerMessage, transfer: any[] = []) { + private postMessageToWorkerThread( + tid: number, + message: MainToWorkerMessage, + transfer: any[] = [], + ) { const threadChannel = this.options.threadChannel; if (!(threadChannel && "postMessageToWorkerThread" in threadChannel)) { throw new Error( - "postMessageToWorkerThread is not set in options given to SwiftRuntime. Please set it to send messages to worker threads." + "postMessageToWorkerThread is not set in options given to SwiftRuntime. Please set it to send messages to worker threads.", ); } threadChannel.postMessageToWorkerThread(tid, message, transfer); diff --git a/Runtime/src/itc.ts b/Runtime/src/itc.ts index 08b420640..9fadff54a 100644 --- a/Runtime/src/itc.ts +++ b/Runtime/src/itc.ts @@ -39,19 +39,24 @@ import { JSObjectSpace as JSObjectSpace } from "./object-heap.js"; */ export type SwiftRuntimeThreadChannel = | { - /** - * This function is used to send messages from the worker thread to the main thread. - * The message submitted by this function is expected to be listened by `listenMessageFromWorkerThread`. - * @param message The message to be sent to the main thread. - * @param transfer The array of objects to be transferred to the main thread. - */ - postMessageToMainThread: (message: WorkerToMainMessage, transfer: any[]) => void; + /** + * This function is used to send messages from the worker thread to the main thread. + * The message submitted by this function is expected to be listened by `listenMessageFromWorkerThread`. + * @param message The message to be sent to the main thread. + * @param transfer The array of objects to be transferred to the main thread. + */ + postMessageToMainThread: ( + message: WorkerToMainMessage, + transfer: any[], + ) => void; /** * This function is expected to be set in the worker thread and should listen * to messages from the main thread sent by `postMessageToWorkerThread`. * @param listener The listener function to be called when a message is received from the main thread. */ - listenMessageFromMainThread: (listener: (message: MainToWorkerMessage) => void) => void; + listenMessageFromMainThread: ( + listener: (message: MainToWorkerMessage) => void, + ) => void; } | { /** @@ -61,7 +66,11 @@ export type SwiftRuntimeThreadChannel = * @param message The message to be sent to the worker thread. * @param transfer The array of objects to be transferred to the worker thread. */ - postMessageToWorkerThread: (tid: number, message: MainToWorkerMessage, transfer: any[]) => void; + postMessageToWorkerThread: ( + tid: number, + message: MainToWorkerMessage, + transfer: any[], + ) => void; /** * This function is expected to be set in the main thread and should listen * to messages sent by `postMessageToMainThread` from the worker thread. @@ -70,7 +79,7 @@ export type SwiftRuntimeThreadChannel = */ listenMessageFromWorkerThread: ( tid: number, - listener: (message: WorkerToMainMessage) => void + listener: (message: WorkerToMainMessage) => void, ) => void; /** @@ -81,23 +90,34 @@ export type SwiftRuntimeThreadChannel = terminateWorkerThread?: (tid: number) => void; }; - export class ITCInterface { constructor(private memory: JSObjectSpace) {} - send(sendingObject: ref, transferringObjects: ref[], sendingContext: pointer): { object: any, sendingContext: pointer, transfer: Transferable[] } { + send( + sendingObject: ref, + transferringObjects: ref[], + sendingContext: pointer, + ): { object: any; sendingContext: pointer; transfer: Transferable[] } { const object = this.memory.getObject(sendingObject); - const transfer = transferringObjects.map(ref => this.memory.getObject(ref)); + const transfer = transferringObjects.map((ref) => + this.memory.getObject(ref), + ); return { object, sendingContext, transfer }; } - sendObjects(sendingObjects: ref[], transferringObjects: ref[], sendingContext: pointer): { object: any[], sendingContext: pointer, transfer: Transferable[] } { - const objects = sendingObjects.map(ref => this.memory.getObject(ref)); - const transfer = transferringObjects.map(ref => this.memory.getObject(ref)); + sendObjects( + sendingObjects: ref[], + transferringObjects: ref[], + sendingContext: pointer, + ): { object: any[]; sendingContext: pointer; transfer: Transferable[] } { + const objects = sendingObjects.map((ref) => this.memory.getObject(ref)); + const transfer = transferringObjects.map((ref) => + this.memory.getObject(ref), + ); return { object: objects, sendingContext, transfer }; } - release(objectRef: ref): { object: undefined, transfer: Transferable[] } { + release(objectRef: ref): { object: undefined; transfer: Transferable[] } { this.memory.release(objectRef); return { object: undefined, transfer: [] }; } @@ -105,16 +125,18 @@ export class ITCInterface { type AllRequests> = { [K in keyof Interface]: { - method: K, - parameters: Parameters, - } -} + method: K; + parameters: Parameters; + }; +}; -type ITCRequest> = AllRequests[keyof AllRequests]; +type ITCRequest> = + AllRequests[keyof AllRequests]; type AllResponses> = { - [K in keyof Interface]: ReturnType -} -type ITCResponse> = AllResponses[keyof AllResponses]; + [K in keyof Interface]: ReturnType; +}; +type ITCResponse> = + AllResponses[keyof AllResponses]; export type RequestMessage = { type: "request"; @@ -127,10 +149,12 @@ export type RequestMessage = { context: pointer; /** The request content */ request: ITCRequest; - } -} + }; +}; -type SerializedError = { isError: true; value: Error } | { isError: false; value: unknown } +type SerializedError = + | { isError: true; value: Error } + | { isError: false; value: unknown }; export type ResponseMessage = { type: "response"; @@ -140,36 +164,42 @@ export type ResponseMessage = { /** The context pointer of the request */ context: pointer; /** The response content */ - response: { - ok: true, - value: ITCResponse; - } | { - ok: false, - error: SerializedError; - }; - } -} + response: + | { + ok: true; + value: ITCResponse; + } + | { + ok: false; + error: SerializedError; + }; + }; +}; -export type MainToWorkerMessage = { - type: "wake"; -} | RequestMessage | ResponseMessage; - -export type WorkerToMainMessage = { - type: "job"; - data: number; -} | RequestMessage | ResponseMessage; +export type MainToWorkerMessage = + | { + type: "wake"; + } + | RequestMessage + | ResponseMessage; +export type WorkerToMainMessage = + | { + type: "job"; + data: number; + } + | RequestMessage + | ResponseMessage; export class MessageBroker { constructor( private selfTid: number, private threadChannel: SwiftRuntimeThreadChannel, private handlers: { - onRequest: (message: RequestMessage) => void, - onResponse: (message: ResponseMessage) => void, - } - ) { - } + onRequest: (message: RequestMessage) => void; + onResponse: (message: ResponseMessage) => void; + }, + ) {} request(message: RequestMessage) { if (message.data.targetTid == this.selfTid) { @@ -177,7 +207,11 @@ export class MessageBroker { this.handlers.onRequest(message); } else if ("postMessageToWorkerThread" in this.threadChannel) { // The request is for another worker thread sent from the main thread - this.threadChannel.postMessageToWorkerThread(message.data.targetTid, message, []); + this.threadChannel.postMessageToWorkerThread( + message.data.targetTid, + message, + [], + ); } else if ("postMessageToMainThread" in this.threadChannel) { // The request is for other worker threads or the main thread sent from a worker thread this.threadChannel.postMessageToMainThread(message, []); @@ -192,10 +226,16 @@ export class MessageBroker { this.handlers.onResponse(message); return; } - const transfer = message.data.response.ok ? message.data.response.value.transfer : []; + const transfer = message.data.response.ok + ? message.data.response.value.transfer + : []; if ("postMessageToWorkerThread" in this.threadChannel) { // The response is for another worker thread sent from the main thread - this.threadChannel.postMessageToWorkerThread(message.data.sourceTid, message, transfer); + this.threadChannel.postMessageToWorkerThread( + message.data.sourceTid, + message, + transfer, + ); } else if ("postMessageToMainThread" in this.threadChannel) { // The response is for other worker threads or the main thread sent from a worker thread this.threadChannel.postMessageToMainThread(message, transfer); @@ -208,9 +248,13 @@ export class MessageBroker { if (message.data.targetTid == this.selfTid) { this.handlers.onRequest(message); } else if ("postMessageToWorkerThread" in this.threadChannel) { - // Receive a request from a worker thread to other worker on main thread. + // Receive a request from a worker thread to other worker on main thread. // Proxy the request to the target worker thread. - this.threadChannel.postMessageToWorkerThread(message.data.targetTid, message, []); + this.threadChannel.postMessageToWorkerThread( + message.data.targetTid, + message, + [], + ); } else if ("postMessageToMainThread" in this.threadChannel) { // A worker thread won't receive a request for other worker threads throw new Error("unreachable"); @@ -223,8 +267,14 @@ export class MessageBroker { } else if ("postMessageToWorkerThread" in this.threadChannel) { // Receive a response from a worker thread to other worker on main thread. // Proxy the response to the target worker thread. - const transfer = message.data.response.ok ? message.data.response.value.transfer : []; - this.threadChannel.postMessageToWorkerThread(message.data.sourceTid, message, transfer); + const transfer = message.data.response.ok + ? message.data.response.value.transfer + : []; + this.threadChannel.postMessageToWorkerThread( + message.data.sourceTid, + message, + transfer, + ); } else if ("postMessageToMainThread" in this.threadChannel) { // A worker thread won't receive a response for other worker threads throw new Error("unreachable"); @@ -234,7 +284,14 @@ export class MessageBroker { export function serializeError(error: unknown): SerializedError { if (error instanceof Error) { - return { isError: true, value: { message: error.message, name: error.name, stack: error.stack } }; + return { + isError: true, + value: { + message: error.message, + name: error.name, + stack: error.stack, + }, + }; } return { isError: false, value: error }; } diff --git a/Runtime/src/js-value.ts b/Runtime/src/js-value.ts index a188808bb..26574b532 100644 --- a/Runtime/src/js-value.ts +++ b/Runtime/src/js-value.ts @@ -1,5 +1,10 @@ import { JSObjectSpace } from "./object-heap.js"; -import { assertNever, JavaScriptValueKindAndFlags, pointer, ref } from "./types.js"; +import { + assertNever, + JavaScriptValueKindAndFlags, + pointer, + ref, +} from "./types.js"; export const enum Kind { Boolean = 0, @@ -16,7 +21,7 @@ export const decode = ( kind: Kind, payload1: number, payload2: number, - objectSpace: JSObjectSpace + objectSpace: JSObjectSpace, ) => { switch (kind) { case Kind.Boolean: @@ -48,7 +53,12 @@ export const decode = ( // Note: // `decodeValues` assumes that the size of RawJSValue is 16. -export const decodeArray = (ptr: pointer, length: number, memory: DataView, objectSpace: JSObjectSpace) => { +export const decodeArray = ( + ptr: pointer, + length: number, + memory: DataView, + objectSpace: JSObjectSpace, +) => { // fast path for empty array if (length === 0) { return []; @@ -76,7 +86,7 @@ export const write = ( payload2_ptr: pointer, is_exception: boolean, memory: DataView, - objectSpace: JSObjectSpace + objectSpace: JSObjectSpace, ) => { const kind = writeAndReturnKindBits( value, @@ -84,7 +94,7 @@ export const write = ( payload2_ptr, is_exception, memory, - objectSpace + objectSpace, ); memory.setUint32(kind_ptr, kind, true); }; @@ -95,7 +105,7 @@ export const writeAndReturnKindBits = ( payload2_ptr: pointer, is_exception: boolean, memory: DataView, - objectSpace: JSObjectSpace + objectSpace: JSObjectSpace, ): JavaScriptValueKindAndFlags => { const exceptionBit = (is_exception ? 1 : 0) << 31; if (value === null) { @@ -141,7 +151,11 @@ export const writeAndReturnKindBits = ( throw new Error("Unreachable"); }; -export function decodeObjectRefs(ptr: pointer, length: number, memory: DataView): ref[] { +export function decodeObjectRefs( + ptr: pointer, + length: number, + memory: DataView, +): ref[] { const result: ref[] = new Array(length); for (let i = 0; i < length; i++) { result[i] = memory.getUint32(ptr + 4 * i, true); diff --git a/Runtime/src/object-heap.ts b/Runtime/src/object-heap.ts index b90fc4eff..ba9cf8021 100644 --- a/Runtime/src/object-heap.ts +++ b/Runtime/src/object-heap.ts @@ -51,7 +51,7 @@ export class JSObjectSpace { const value = this._heapValueById.get(ref); if (value === undefined) { throw new ReferenceError( - "Attempted to read invalid reference " + ref + "Attempted to read invalid reference " + ref, ); } return value; diff --git a/Runtime/src/types.ts b/Runtime/src/types.ts index b8345cdfa..b39e949b2 100644 --- a/Runtime/src/types.ts +++ b/Runtime/src/types.ts @@ -13,7 +13,7 @@ export interface ExportedFunctions { host_func_id: number, argv: pointer, argc: number, - callback_func_ref: ref + callback_func_ref: ref, ): bool; swjs_free_host_function(host_func_id: number): void;