Skip to content
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for BigInts and BigInt-based TypedArrays #184

Merged
merged 35 commits into from
Apr 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
fa93b91
Initial BigInt support
j-f1 Apr 9, 2022
3ca1539
Switch to const enums
j-f1 Apr 9, 2022
bf3bac3
Add JSBigInt/clamped method, fix overload
j-f1 Apr 9, 2022
8c02c79
Convert BigInts into Swift ints automatically
j-f1 Apr 9, 2022
814774b
Add support for JAVASCRIPTKIT_WITHOUT_BIGINTS
j-f1 Apr 9, 2022
0bcb359
Allow Int64/UInt64-based arrays
j-f1 Apr 9, 2022
d1543fc
Don’t use BigInt literal for backwards compat
j-f1 Apr 9, 2022
f8838eb
bump lib version
j-f1 Apr 9, 2022
26b51c7
long long?
j-f1 Apr 9, 2022
4dd4aae
document JAVASCRIPTKIT_WITHOUT_BIGINTS flag
j-f1 Apr 9, 2022
cd3c076
check compatibility workflow builds all variants
j-f1 Apr 9, 2022
76303b9
Increase error stack trace limit
j-f1 Apr 9, 2022
c356aba
Use correct type for setTimeout in JavaScriptEventLoop
j-f1 Apr 9, 2022
6f150ad
Add symbol support to runtime’s JSValue.decode
j-f1 Apr 9, 2022
6257d35
remove JavaScriptValueKindInvalid since neither side produces it
j-f1 Apr 9, 2022
203844f
use assertNever to ensure runtime switch statements are kept up to date
j-f1 Apr 9, 2022
9d066f9
BigInt tests
j-f1 Apr 9, 2022
1fdcd26
Revert changes to README
j-f1 Apr 10, 2022
14541b8
consistent whitespace (by semantic grouping) in index.ts
j-f1 Apr 10, 2022
dcd38c7
Rename: type→kind
j-f1 Apr 10, 2022
63f33d0
drop implicit return
j-f1 Apr 10, 2022
30a3e66
assert Int/UInt types are 32-bit for now
j-f1 Apr 10, 2022
68cb689
Require BigInts for ConvertibleToJSValue conformance on Int64/UInt64
j-f1 Apr 10, 2022
01ae32d
run Prettier on primary-tests.js
j-f1 Apr 10, 2022
a44de1a
move stackTraceLimit change to non-benchmark tests only
j-f1 Apr 10, 2022
6517b7e
remove JAVASCRIPTKIT_WITHOUT_BIGINTS
j-f1 Apr 16, 2022
c54bd10
SPI for JSObject_id
j-f1 Apr 16, 2022
7af5917
Move i64 stuff to a separate module
j-f1 Apr 16, 2022
b7a6a7d
fix Signed/UnsignedInteger ConstructibleFromJSValue
j-f1 Apr 16, 2022
b83611f
fix tests?
j-f1 Apr 16, 2022
4ca7705
Address review comments
j-f1 Apr 16, 2022
35dd9f7
Simplify JSValue: CustomStringConvertible conformance
j-f1 Apr 16, 2022
0ec8fc3
rename to JavaScriptBigIntSupport
j-f1 Apr 22, 2022
7960198
Formatting tweak
j-f1 Apr 22, 2022
5cff142
fix typo
j-f1 Apr 22, 2022
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
1 change: 1 addition & 0 deletions .github/workflows/compatibility.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ jobs:
make bootstrap
cd Example/JavaScriptKitExample
swift build --triple wasm32-unknown-wasi
swift build --triple wasm32-unknown-wasi -Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS
3 changes: 2 additions & 1 deletion IntegrationTests/TestSuites/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ let package = Package(
// This package doesn't work on macOS host, but should be able to be built for it
// for developing on Xcode. This minimum version requirement is to prevent availability
// errors for Concurrency API, whose runtime support is shipped from macOS 12.0
.macOS("12.0")
.macOS("12.0"),
],
products: [
.executable(
Expand All @@ -25,6 +25,7 @@ let package = Package(
targets: [
.target(name: "CHelpers"),
.target(name: "PrimaryTests", dependencies: [
.product(name: "JavaScriptBigIntSupport", package: "JavaScriptKit"),
"JavaScriptKit",
"CHelpers",
]),
Expand Down
35 changes: 35 additions & 0 deletions IntegrationTests/TestSuites/Sources/PrimaryTests/I64.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import JavaScriptBigIntSupport
import JavaScriptKit

func testI64() throws {
try test("BigInt") {
func expectPassesThrough(signed value: Int64) throws {
let bigInt = JSBigInt(value)
try expectEqual(bigInt.description, value.description)
}

func expectPassesThrough(unsigned value: UInt64) throws {
let bigInt = JSBigInt(unsigned: value)
try expectEqual(bigInt.description, value.description)
}

try expectPassesThrough(signed: 0)
try expectPassesThrough(signed: 1 << 62)
try expectPassesThrough(signed: -2305)
for _ in 0 ..< 100 {
try expectPassesThrough(signed: .random(in: .min ... .max))
}
try expectPassesThrough(signed: .min)
try expectPassesThrough(signed: .max)

try expectPassesThrough(unsigned: 0)
try expectPassesThrough(unsigned: 1 << 62)
try expectPassesThrough(unsigned: 1 << 63)
try expectPassesThrough(unsigned: .min)
try expectPassesThrough(unsigned: .max)
try expectPassesThrough(unsigned: ~0)
for _ in 0 ..< 100 {
try expectPassesThrough(unsigned: .random(in: .min ... .max))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -852,5 +852,5 @@ try test("JSValueDecoder") {
try expectEqual(decodedTama.isCat, true)
}


try testI64()
Expectation.wait(expectations)
2 changes: 2 additions & 0 deletions IntegrationTests/bin/concurrency-tests.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const { startWasiTask } = require("../lib");

Error.stackTraceLimit = Infinity;

startWasiTask("./dist/ConcurrencyTests.wasm").catch((err) => {
console.log(err);
process.exit(1);
Expand Down
35 changes: 20 additions & 15 deletions IntegrationTests/bin/primary-tests.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Error.stackTraceLimit = Infinity;

global.globalObject1 = {
prop_1: {
nested_prop: 1,
Expand Down Expand Up @@ -44,23 +46,26 @@ global.globalObject1 = {
throw "String Error";
},
func3: function () {
throw 3.0
throw 3.0;
},
},
eval_closure: function (fn) {
return fn(arguments[1])
return fn(arguments[1]);
},
observable_obj: {
set_called: false,
target: new Proxy({
nested: {}
}, {
set(target, key, value) {
global.globalObject1.observable_obj.set_called = true;
target[key] = value;
return true;
target: new Proxy(
{
nested: {},
},
{
set(target, key, value) {
global.globalObject1.observable_obj.set_called = true;
target[key] = value;
return true;
},
}
})
),
},
};

Expand All @@ -79,16 +84,16 @@ global.Animal = function (name, age, isCat) {
};
this.setName = function (name) {
this.name = name;
}
};
};

global.callThrowingClosure = (c) => {
global.callThrowingClosure = (c) => {
try {
c()
c();
} catch (error) {
return error
return error;
}
}
};

const { startWasiTask } = require("../lib");

Expand Down
6 changes: 6 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ let package = Package(
products: [
.library(name: "JavaScriptKit", targets: ["JavaScriptKit"]),
.library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]),
.library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]),
],
targets: [
.target(
name: "JavaScriptKit",
dependencies: ["_CJavaScriptKit"]
),
.target(name: "_CJavaScriptKit"),
.target(
name: "JavaScriptBigIntSupport",
dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"]
),
.target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]),
.target(
name: "JavaScriptEventLoop",
dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ JavaScript features should work, which currently includes:
- Mobile Safari 14.8+

If you need to support older browser versions, you'll have to build with
`JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags
the `JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags
when compiling. This should lower browser requirements to these versions:

- Edge 16+
Expand Down
29 changes: 22 additions & 7 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class SwiftRuntime {
private _instance: WebAssembly.Instance | null;
private _memory: Memory | null;
private _closureDeallocator: SwiftClosureDeallocator | null;
private version: number = 706;
private version: number = 707;

private textDecoder = new TextDecoder("utf-8");
private textEncoder = new TextEncoder(); // Only support utf-8
Expand Down Expand Up @@ -114,7 +114,6 @@ export class SwiftRuntime {
const value = JSValue.decode(kind, payload1, payload2, this.memory);
obj[key] = value;
},

swjs_get_prop: (
ref: ref,
name: ref,
Expand Down Expand Up @@ -146,7 +145,6 @@ export class SwiftRuntime {
const value = JSValue.decode(kind, payload1, payload2, this.memory);
obj[index] = value;
},

swjs_get_subscript: (
ref: ref,
index: number,
Expand All @@ -172,15 +170,13 @@ export class SwiftRuntime {
this.memory.writeUint32(bytes_ptr_result, bytes_ptr);
return bytes.length;
},

swjs_decode_string: (bytes_ptr: pointer, length: number) => {
const bytes = this.memory
.bytes()
.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.memory.writeBytes(buffer, bytes);
Expand Down Expand Up @@ -290,7 +286,6 @@ export class SwiftRuntime {
this.memory
);
},

swjs_call_function_with_this_no_catch: (
obj_ref: ref,
func_ref: ref,
Expand Down Expand Up @@ -328,13 +323,13 @@ export class SwiftRuntime {
}
}
},

swjs_call_new: (ref: ref, argv: pointer, argc: number) => {
const constructor = this.memory.getObject(ref);
const args = JSValue.decodeArray(argv, argc, this.memory);
const instance = new constructor(...args);
return this.memory.retain(instance);
},

swjs_call_throwing_new: (
ref: ref,
argv: pointer,
Expand Down Expand Up @@ -409,5 +404,25 @@ export class SwiftRuntime {
swjs_release: (ref: ref) => {
this.memory.release(ref);
},

swjs_i64_to_bigint: (value: bigint, signed: number) => {
return this.memory.retain(
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}`);
}
if (signed) {
return object;
} else {
if (object < BigInt(0)) {
return BigInt(0);
}
return BigInt.asIntN(64, object);
}
},
};
}
37 changes: 23 additions & 14 deletions Runtime/src/js-value.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { Memory } from "./memory";
import { pointer } from "./types";
import { assertNever, pointer } from "./types";

export enum Kind {
Invalid = -1,
export const enum Kind {
Boolean = 0,
String = 1,
Number = 2,
Expand All @@ -11,6 +10,7 @@ export enum Kind {
Undefined = 5,
Function = 6,
Symbol = 7,
BigInt = 8,
}

export const decode = (
Expand All @@ -33,6 +33,8 @@ export const decode = (
case Kind.String:
case Kind.Object:
case Kind.Function:
case Kind.Symbol:
case Kind.BigInt:
return memory.getObject(payload1);

case Kind.Null:
Expand All @@ -42,7 +44,7 @@ export const decode = (
return undefined;

default:
throw new Error(`JSValue Type kind "${kind}" is not supported`);
assertNever(kind, `JSValue Type kind "${kind}" is not supported`);
}
};

Expand Down Expand Up @@ -73,7 +75,14 @@ export const write = (
memory.writeUint32(kind_ptr, exceptionBit | Kind.Null);
return;
}
switch (typeof value) {

const writeRef = (kind: Kind) => {
memory.writeUint32(kind_ptr, exceptionBit | kind);
memory.writeUint32(payload1_ptr, memory.retain(value));
};

const type = typeof value;
switch (type) {
case "boolean": {
memory.writeUint32(kind_ptr, exceptionBit | Kind.Boolean);
memory.writeUint32(payload1_ptr, value ? 1 : 0);
Expand All @@ -85,30 +94,30 @@ export const write = (
break;
}
case "string": {
memory.writeUint32(kind_ptr, exceptionBit | Kind.String);
memory.writeUint32(payload1_ptr, memory.retain(value));
writeRef(Kind.String);
break;
}
case "undefined": {
memory.writeUint32(kind_ptr, exceptionBit | Kind.Undefined);
break;
}
case "object": {
memory.writeUint32(kind_ptr, exceptionBit | Kind.Object);
memory.writeUint32(payload1_ptr, memory.retain(value));
writeRef(Kind.Object);
break;
}
case "function": {
memory.writeUint32(kind_ptr, exceptionBit | Kind.Function);
memory.writeUint32(payload1_ptr, memory.retain(value));
writeRef(Kind.Function);
break;
}
case "symbol": {
memory.writeUint32(kind_ptr, exceptionBit | Kind.Symbol);
memory.writeUint32(payload1_ptr, memory.retain(value));
writeRef(Kind.Symbol);
break;
}
case "bigint": {
writeRef(Kind.BigInt);
break;
}
default:
throw new Error(`Type "${typeof value}" is not supported yet`);
assertNever(type, `Type "${type}" is not supported yet`);
}
};
9 changes: 8 additions & 1 deletion Runtime/src/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@ export class Memory {
bytes = () => new Uint8Array(this.rawMemory.buffer);
dataView = () => new DataView(this.rawMemory.buffer);

writeBytes = (ptr: pointer, bytes: Uint8Array) => this.bytes().set(bytes, ptr);
writeBytes = (ptr: pointer, bytes: Uint8Array) =>
this.bytes().set(bytes, ptr);

readUint32 = (ptr: pointer) => this.dataView().getUint32(ptr, true);
readUint64 = (ptr: pointer) => this.dataView().getBigUint64(ptr, true);
readInt64 = (ptr: pointer) => this.dataView().getBigInt64(ptr, true);
readFloat64 = (ptr: pointer) => this.dataView().getFloat64(ptr, true);

writeUint32 = (ptr: pointer, value: number) =>
this.dataView().setUint32(ptr, value, true);
writeUint64 = (ptr: pointer, value: bigint) =>
this.dataView().setBigUint64(ptr, value, true);
writeInt64 = (ptr: pointer, value: bigint) =>
this.dataView().setBigInt64(ptr, value, true);
writeFloat64 = (ptr: pointer, value: number) =>
this.dataView().setFloat64(ptr, value, true);
}
Loading