javascript-kit-swift
Version:
A runtime library of JavaScriptKit which is Swift framework to interact with JavaScript through WebAssembly.
309 lines (308 loc) • 13.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SwiftRuntime = void 0;
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;
}
var JavaScriptValueKind;
(function (JavaScriptValueKind) {
JavaScriptValueKind[JavaScriptValueKind["Invalid"] = -1] = "Invalid";
JavaScriptValueKind[JavaScriptValueKind["Boolean"] = 0] = "Boolean";
JavaScriptValueKind[JavaScriptValueKind["String"] = 1] = "String";
JavaScriptValueKind[JavaScriptValueKind["Number"] = 2] = "Number";
JavaScriptValueKind[JavaScriptValueKind["Object"] = 3] = "Object";
JavaScriptValueKind[JavaScriptValueKind["Null"] = 4] = "Null";
JavaScriptValueKind[JavaScriptValueKind["Undefined"] = 5] = "Undefined";
JavaScriptValueKind[JavaScriptValueKind["Function"] = 6] = "Function";
})(JavaScriptValueKind || (JavaScriptValueKind = {}));
class SwiftRuntimeHeap {
constructor() {
this._heapValueById = new Map();
this._heapValueById.set(0, globalVariable);
this._heapEntryByValue = new Map();
this._heapEntryByValue.set(globalVariable, { id: 0, rc: 1 });
// Note: 0 is preserved for global
this._heapNextKey = 1;
}
retain(value) {
const isObject = typeof value == "object";
const entry = this._heapEntryByValue.get(value);
if (isObject && entry) {
entry.rc++;
return entry.id;
}
const id = this._heapNextKey++;
this._heapValueById.set(id, value);
if (isObject) {
this._heapEntryByValue.set(value, { id: id, rc: 1 });
}
return id;
}
release(ref) {
const value = this._heapValueById.get(ref);
const isObject = typeof value == "object";
if (isObject) {
const entry = this._heapEntryByValue.get(value);
entry.rc--;
if (entry.rc != 0)
return;
this._heapEntryByValue.delete(value);
this._heapValueById.delete(ref);
}
else {
this._heapValueById.delete(ref);
}
}
referenceHeap(ref) {
const value = this._heapValueById.get(ref);
if (value === undefined) {
throw new ReferenceError("Attempted to read invalid reference " + ref);
}
return value;
}
}
class SwiftRuntime {
constructor() {
this.version = 700;
this.instance = null;
this.heap = new SwiftRuntimeHeap();
}
setInstance(instance) {
this.instance = instance;
const exports = this.instance
.exports;
if (exports.swjs_library_version() != this.version) {
throw new Error("The versions of JavaScriptKit are incompatible.");
}
}
importObjects() {
const memory = () => {
if (this.instance)
return this.instance.exports.memory;
throw new Error("WebAssembly instance is not set yet");
};
const callHostFunction = (host_func_id, args) => {
if (!this.instance)
throw new Error("WebAssembly instance is not set yet");
const exports = this.instance
.exports;
const argc = args.length;
const argv = exports.swjs_prepare_host_function_call(argc);
for (let index = 0; index < args.length; index++) {
const argument = args[index];
const base = argv + 16 * index;
writeValue(argument, base, base + 4, base + 8);
}
let output;
const callback_func_ref = this.heap.retain(function (result) {
output = result;
});
exports.swjs_call_host_function(host_func_id, argv, argc, callback_func_ref);
exports.swjs_cleanup_host_function_call(argv);
return output;
};
const textDecoder = new TextDecoder("utf-8");
const textEncoder = new TextEncoder(); // Only support utf-8
const readString = (ref) => {
return this.heap.referenceHeap(ref);
};
const writeString = (ptr, bytes) => {
const uint8Memory = new Uint8Array(memory().buffer);
for (const [index, byte] of bytes.entries()) {
uint8Memory[ptr + index] = byte;
}
uint8Memory[ptr];
};
const readUInt32 = (ptr) => {
const uint8Memory = new Uint8Array(memory().buffer);
return (uint8Memory[ptr + 0] +
(uint8Memory[ptr + 1] << 8) +
(uint8Memory[ptr + 2] << 16) +
(uint8Memory[ptr + 3] << 24));
};
const readFloat64 = (ptr) => {
const dataView = new DataView(memory().buffer);
return dataView.getFloat64(ptr, true);
};
const writeUint32 = (ptr, value) => {
const uint8Memory = new Uint8Array(memory().buffer);
uint8Memory[ptr + 0] = (value & 0x000000ff) >> 0;
uint8Memory[ptr + 1] = (value & 0x0000ff00) >> 8;
uint8Memory[ptr + 2] = (value & 0x00ff0000) >> 16;
uint8Memory[ptr + 3] = (value & 0xff000000) >> 24;
};
const writeFloat64 = (ptr, value) => {
const dataView = new DataView(memory().buffer);
dataView.setFloat64(ptr, value, true);
};
const decodeValue = (kind, payload1, payload2) => {
switch (kind) {
case JavaScriptValueKind.Boolean: {
switch (payload1) {
case 0:
return false;
case 1:
return true;
}
}
case JavaScriptValueKind.Number: {
return payload2;
}
case JavaScriptValueKind.String: {
return readString(payload1);
}
case JavaScriptValueKind.Object: {
return this.heap.referenceHeap(payload1);
}
case JavaScriptValueKind.Null: {
return null;
}
case JavaScriptValueKind.Undefined: {
return undefined;
}
case JavaScriptValueKind.Function: {
return this.heap.referenceHeap(payload1);
}
default:
throw new Error(`Type kind "${kind}" is not supported`);
}
};
const writeValue = (value, kind_ptr, payload1_ptr, payload2_ptr) => {
if (value === null) {
writeUint32(kind_ptr, JavaScriptValueKind.Null);
return;
}
switch (typeof value) {
case "boolean": {
writeUint32(kind_ptr, JavaScriptValueKind.Boolean);
writeUint32(payload1_ptr, value ? 1 : 0);
break;
}
case "number": {
writeUint32(kind_ptr, JavaScriptValueKind.Number);
writeFloat64(payload2_ptr, value);
break;
}
case "string": {
writeUint32(kind_ptr, JavaScriptValueKind.String);
writeUint32(payload1_ptr, this.heap.retain(value));
break;
}
case "undefined": {
writeUint32(kind_ptr, JavaScriptValueKind.Undefined);
break;
}
case "object": {
writeUint32(kind_ptr, JavaScriptValueKind.Object);
writeUint32(payload1_ptr, this.heap.retain(value));
break;
}
case "function": {
writeUint32(kind_ptr, JavaScriptValueKind.Function);
writeUint32(payload1_ptr, this.heap.retain(value));
break;
}
default:
throw new Error(`Type "${typeof value}" is not supported yet`);
}
};
// Note:
// `decodeValues` assumes that the size of RawJSValue is 16.
const decodeValues = (ptr, length) => {
let result = [];
for (let index = 0; index < length; index++) {
const base = ptr + 16 * index;
const kind = readUInt32(base);
const payload1 = readUInt32(base + 4);
const payload2 = readFloat64(base + 8);
result.push(decodeValue(kind, payload1, payload2));
}
return result;
};
return {
swjs_set_prop: (ref, name, kind, payload1, payload2) => {
const obj = this.heap.referenceHeap(ref);
Reflect.set(obj, readString(name), decodeValue(kind, payload1, payload2));
},
swjs_get_prop: (ref, name, kind_ptr, payload1_ptr, payload2_ptr) => {
const obj = this.heap.referenceHeap(ref);
const result = Reflect.get(obj, readString(name));
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
},
swjs_set_subscript: (ref, index, kind, payload1, payload2) => {
const obj = this.heap.referenceHeap(ref);
Reflect.set(obj, index, decodeValue(kind, payload1, payload2));
},
swjs_get_subscript: (ref, index, kind_ptr, payload1_ptr, payload2_ptr) => {
const obj = this.heap.referenceHeap(ref);
const result = Reflect.get(obj, index);
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
},
swjs_encode_string: (ref, bytes_ptr_result) => {
const bytes = textEncoder.encode(this.heap.referenceHeap(ref));
const bytes_ptr = this.heap.retain(bytes);
writeUint32(bytes_ptr_result, bytes_ptr);
return bytes.length;
},
swjs_decode_string: (bytes_ptr, length) => {
const uint8Memory = new Uint8Array(memory().buffer);
const bytes = uint8Memory.subarray(bytes_ptr, bytes_ptr + length);
const string = textDecoder.decode(bytes);
return this.heap.retain(string);
},
swjs_load_string: (ref, buffer) => {
const bytes = this.heap.referenceHeap(ref);
writeString(buffer, bytes);
},
swjs_call_function: (ref, argv, argc, kind_ptr, payload1_ptr, payload2_ptr) => {
const func = this.heap.referenceHeap(ref);
const result = Reflect.apply(func, undefined, decodeValues(argv, argc));
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
},
swjs_call_function_with_this: (obj_ref, func_ref, argv, argc, kind_ptr, payload1_ptr, payload2_ptr) => {
const obj = this.heap.referenceHeap(obj_ref);
const func = this.heap.referenceHeap(func_ref);
const result = Reflect.apply(func, obj, decodeValues(argv, argc));
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr);
},
swjs_create_function: (host_func_id, func_ref_ptr) => {
const func_ref = this.heap.retain(function () {
return callHostFunction(host_func_id, Array.prototype.slice.call(arguments));
});
writeUint32(func_ref_ptr, func_ref);
},
swjs_call_new: (ref, argv, argc, result_obj) => {
const obj = this.heap.referenceHeap(ref);
const result = Reflect.construct(obj, decodeValues(argv, argc));
if (typeof result != "object")
throw Error(`Invalid result type of object constructor of "${obj}": "${result}"`);
writeUint32(result_obj, this.heap.retain(result));
},
swjs_instanceof: (obj_ref, constructor_ref) => {
const obj = this.heap.referenceHeap(obj_ref);
const constructor = this.heap.referenceHeap(constructor_ref);
return obj instanceof constructor;
},
swjs_create_typed_array: (constructor_ref, elementsPtr, length, result_obj) => {
const ArrayType = this.heap.referenceHeap(constructor_ref);
const array = new ArrayType(memory().buffer, elementsPtr, length);
// Call `.slice()` to copy the memory
writeUint32(result_obj, this.heap.retain(array.slice()));
},
swjs_release: (ref) => {
this.heap.release(ref);
},
};
}
}
exports.SwiftRuntime = SwiftRuntime;