GeezerVison is showing github.com — Add support for BigInts and BigInt-based TypedArrays (#184) · t089/JavaScriptKit@34bf9e1
File treeExpand file treeCollapse file tree Open diff view settings _CJavaScriptBigIntSupport
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: .github/workflows/compatibility.yml +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 22 | 22 | make bootstrap | 23 | 23 | cd Example/JavaScriptKitExample | 24 | 24 | swift build --triple wasm32-unknown-wasi | | 25 | + swift build --triple wasm32-unknown-wasi -Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS |
Collapse file Copy file name to clipboardExpand all lines: IntegrationTests/TestSuites/Package.swift +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -8,7 +8,7 @@ let package = Package( | 8 | 8 | // This package doesn't work on macOS host, but should be able to be built for it | 9 | 9 | // for developing on Xcode. This minimum version requirement is to prevent availability | 10 | 10 | // errors for Concurrency API, whose runtime support is shipped from macOS 12.0 | 11 | | - .macOS("12.0") | | 11 | + .macOS("12.0"), | 12 | 12 | ], | 13 | 13 | products: [ | 14 | 14 | .executable( | @@ -25,6 +25,7 @@ let package = Package( | 25 | 25 | targets: [ | 26 | 26 | .target(name: "CHelpers"), | 27 | 27 | .target(name: "PrimaryTests", dependencies: [ | | 28 | + .product(name: "JavaScriptBigIntSupport", package: "JavaScriptKit"), | 28 | 29 | "JavaScriptKit", | 29 | 30 | "CHelpers", | 30 | 31 | ]), | |
Collapse file Copy file name to clipboard +35Lines changed: 35 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +import JavaScriptBigIntSupport | | 2 | +import JavaScriptKit | | 3 | + | | 4 | +func testI64() throws { | | 5 | + try test("BigInt") { | | 6 | + func expectPassesThrough(signed value: Int64) throws { | | 7 | + let bigInt = JSBigInt(value) | | 8 | + try expectEqual(bigInt.description, value.description) | | 9 | + } | | 10 | + | | 11 | + func expectPassesThrough(unsigned value: UInt64) throws { | | 12 | + let bigInt = JSBigInt(unsigned: value) | | 13 | + try expectEqual(bigInt.description, value.description) | | 14 | + } | | 15 | + | | 16 | + try expectPassesThrough(signed: 0) | | 17 | + try expectPassesThrough(signed: 1 << 62) | | 18 | + try expectPassesThrough(signed: -2305) | | 19 | + for _ in 0 ..< 100 { | | 20 | + try expectPassesThrough(signed: .random(in: .min ... .max)) | | 21 | + } | | 22 | + try expectPassesThrough(signed: .min) | | 23 | + try expectPassesThrough(signed: .max) | | 24 | + | | 25 | + try expectPassesThrough(unsigned: 0) | | 26 | + try expectPassesThrough(unsigned: 1 << 62) | | 27 | + try expectPassesThrough(unsigned: 1 << 63) | | 28 | + try expectPassesThrough(unsigned: .min) | | 29 | + try expectPassesThrough(unsigned: .max) | | 30 | + try expectPassesThrough(unsigned: ~0) | | 31 | + for _ in 0 ..< 100 { | | 32 | + try expectPassesThrough(unsigned: .random(in: .min ... .max)) | | 33 | + } | | 34 | + } | | 35 | +} |
Collapse file Copy file name to clipboardExpand all lines: IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -852,5 +852,5 @@ try test("JSValueDecoder") { | 852 | 852 | try expectEqual(decodedTama.isCat, true) | 853 | 853 | } | 854 | 854 |
| 855 | | - | | 855 | +try testI64() | 856 | 856 | Expectation.wait(expectations) |
Collapse file Copy file name to clipboardExpand all lines: IntegrationTests/bin/concurrency-tests.js +2Lines changed: 2 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | const { startWasiTask } = require("../lib"); | 2 | 2 |
| | 3 | +Error.stackTraceLimit = Infinity; | | 4 | + | 3 | 5 | startWasiTask("./dist/ConcurrencyTests.wasm").catch((err) => { | 4 | 6 | console.log(err); | 5 | 7 | process.exit(1); | |
Collapse file Copy file name to clipboardExpand all lines: IntegrationTests/bin/primary-tests.js +20-15Lines changed: 20 additions & 15 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +Error.stackTraceLimit = Infinity; | | 2 | + | 1 | 3 | global.globalObject1 = { | 2 | 4 | prop_1: { | 3 | 5 | nested_prop: 1, | @@ -44,23 +46,26 @@ global.globalObject1 = { | 44 | 46 | throw "String Error"; | 45 | 47 | }, | 46 | 48 | func3: function () { | 47 | | - throw 3.0 | | 49 | + throw 3.0; | 48 | 50 | }, | 49 | 51 | }, | 50 | 52 | eval_closure: function (fn) { | 51 | | - return fn(arguments[1]) | | 53 | + return fn(arguments[1]); | 52 | 54 | }, | 53 | 55 | observable_obj: { | 54 | 56 | set_called: false, | 55 | | - target: new Proxy({ | 56 | | - nested: {} | 57 | | - }, { | 58 | | - set(target, key, value) { | 59 | | - global.globalObject1.observable_obj.set_called = true; | 60 | | - target[key] = value; | 61 | | - return true; | | 57 | + target: new Proxy( | | 58 | + { | | 59 | + nested: {}, | | 60 | + }, | | 61 | + { | | 62 | + set(target, key, value) { | | 63 | + global.globalObject1.observable_obj.set_called = true; | | 64 | + target[key] = value; | | 65 | + return true; | | 66 | + }, | 62 | 67 | } | 63 | | - }) | | 68 | + ), | 64 | 69 | }, | 65 | 70 | }; | 66 | 71 |
| @@ -79,16 +84,16 @@ global.Animal = function (name, age, isCat) { | 79 | 84 | }; | 80 | 85 | this.setName = function (name) { | 81 | 86 | this.name = name; | 82 | | - } | | 87 | + }; | 83 | 88 | }; | 84 | 89 |
| 85 | | -global.callThrowingClosure = (c) => { | | 90 | +global.callThrowingClosure = (c) => { | 86 | 91 | try { | 87 | | - c() | | 92 | + c(); | 88 | 93 | } catch (error) { | 89 | | - return error | | 94 | + return error; | 90 | 95 | } | 91 | | -} | | 96 | +}; | 92 | 97 |
| 93 | 98 | const { startWasiTask } = require("../lib"); | 94 | 99 |
| |
Collapse file Copy file name to clipboardExpand all lines: Package.swift +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -7,13 +7,19 @@ let package = Package( | 7 | 7 | products: [ | 8 | 8 | .library(name: "JavaScriptKit", targets: ["JavaScriptKit"]), | 9 | 9 | .library(name: "JavaScriptEventLoop", targets: ["JavaScriptEventLoop"]), | | 10 | + .library(name: "JavaScriptBigIntSupport", targets: ["JavaScriptBigIntSupport"]), | 10 | 11 | ], | 11 | 12 | targets: [ | 12 | 13 | .target( | 13 | 14 | name: "JavaScriptKit", | 14 | 15 | dependencies: ["_CJavaScriptKit"] | 15 | 16 | ), | 16 | 17 | .target(name: "_CJavaScriptKit"), | | 18 | + .target( | | 19 | + name: "JavaScriptBigIntSupport", | | 20 | + dependencies: ["_CJavaScriptBigIntSupport", "JavaScriptKit"] | | 21 | + ), | | 22 | + .target(name: "_CJavaScriptBigIntSupport", dependencies: ["_CJavaScriptKit"]), | 17 | 23 | .target( | 18 | 24 | name: "JavaScriptEventLoop", | 19 | 25 | dependencies: ["JavaScriptKit", "_CJavaScriptEventLoop"] | |
Collapse file Copy file name to clipboardExpand all lines: README.md +1-1Lines changed: 1 addition & 1 deletion - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -149,7 +149,7 @@ JavaScript features should work, which currently includes: | 149 | 149 | - Mobile Safari 14.8+ | 150 | 150 |
| 151 | 151 | If you need to support older browser versions, you'll have to build with | 152 | | -`JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags | | 152 | +the `JAVASCRIPTKIT_WITHOUT_WEAKREFS` flag, passing `-Xswiftc -DJAVASCRIPTKIT_WITHOUT_WEAKREFS` flags | 153 | 153 | when compiling. This should lower browser requirements to these versions: | 154 | 154 |
| 155 | 155 | - Edge 16+ | |
Collapse file Copy file name to clipboardExpand all lines: Runtime/src/index.ts +22-7Lines changed: 22 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -14,7 +14,7 @@ export class SwiftRuntime { | 14 | 14 | private _instance: WebAssembly.Instance | null; | 15 | 15 | private _memory: Memory | null; | 16 | 16 | private _closureDeallocator: SwiftClosureDeallocator | null; | 17 | | - private version: number = 706; | | 17 | + private version: number = 707; | 18 | 18 |
| 19 | 19 | private textDecoder = new TextDecoder("utf-8"); | 20 | 20 | private textEncoder = new TextEncoder(); // Only support utf-8 | @@ -114,7 +114,6 @@ export class SwiftRuntime { | 114 | 114 | const value = JSValue.decode(kind, payload1, payload2, this.memory); | 115 | 115 | obj[key] = value; | 116 | 116 | }, | 117 | | - | 118 | 117 | swjs_get_prop: ( | 119 | 118 | ref: ref, | 120 | 119 | name: ref, | @@ -146,7 +145,6 @@ export class SwiftRuntime { | 146 | 145 | const value = JSValue.decode(kind, payload1, payload2, this.memory); | 147 | 146 | obj[index] = value; | 148 | 147 | }, | 149 | | - | 150 | 148 | swjs_get_subscript: ( | 151 | 149 | ref: ref, | 152 | 150 | index: number, | @@ -172,15 +170,13 @@ export class SwiftRuntime { | 172 | 170 | this.memory.writeUint32(bytes_ptr_result, bytes_ptr); | 173 | 171 | return bytes.length; | 174 | 172 | }, | 175 | | - | 176 | 173 | swjs_decode_string: (bytes_ptr: pointer, length: number) => { | 177 | 174 | const bytes = this.memory | 178 | 175 | .bytes() | 179 | 176 | .subarray(bytes_ptr, bytes_ptr + length); | 180 | 177 | const string = this.textDecoder.decode(bytes); | 181 | 178 | return this.memory.retain(string); | 182 | 179 | }, | 183 | | - | 184 | 180 | swjs_load_string: (ref: ref, buffer: pointer) => { | 185 | 181 | const bytes = this.memory.getObject(ref); | 186 | 182 | this.memory.writeBytes(buffer, bytes); | @@ -290,7 +286,6 @@ export class SwiftRuntime { | 290 | 286 | this.memory | 291 | 287 | ); | 292 | 288 | }, | 293 | | - | 294 | 289 | swjs_call_function_with_this_no_catch: ( | 295 | 290 | obj_ref: ref, | 296 | 291 | func_ref: ref, | @@ -328,13 +323,13 @@ export class SwiftRuntime { | 328 | 323 | } | 329 | 324 | } | 330 | 325 | }, | | 326 | + | 331 | 327 | swjs_call_new: (ref: ref, argv: pointer, argc: number) => { | 332 | 328 | const constructor = this.memory.getObject(ref); | 333 | 329 | const args = JSValue.decodeArray(argv, argc, this.memory); | 334 | 330 | const instance = new constructor(...args); | 335 | 331 | return this.memory.retain(instance); | 336 | 332 | }, | 337 | | - | 338 | 333 | swjs_call_throwing_new: ( | 339 | 334 | ref: ref, | 340 | 335 | argv: pointer, | @@ -409,5 +404,25 @@ export class SwiftRuntime { | 409 | 404 | swjs_release: (ref: ref) => { | 410 | 405 | this.memory.release(ref); | 411 | 406 | }, | | 407 | + | | 408 | + swjs_i64_to_bigint: (value: bigint, signed: number) => { | | 409 | + return this.memory.retain( | | 410 | + signed ? value : BigInt.asUintN(64, value) | | 411 | + ); | | 412 | + }, | | 413 | + swjs_bigint_to_i64: (ref: ref, signed: number) => { | | 414 | + const object = this.memory.getObject(ref); | | 415 | + if (typeof object !== "bigint") { | | 416 | + throw new Error(`Expected a BigInt, but got ${typeof object}`); | | 417 | + } | | 418 | + if (signed) { | | 419 | + return object; | | 420 | + } else { | | 421 | + if (object < BigInt(0)) { | | 422 | + return BigInt(0); | | 423 | + } | | 424 | + return BigInt.asIntN(64, object); | | 425 | + } | | 426 | + }, | 412 | 427 | }; | 413 | 428 | } |
Collapse file Copy file name to clipboardExpand all lines: Runtime/src/js-value.ts +23-14Lines changed: 23 additions & 14 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | import { Memory } from "./memory"; | 2 | | -import { pointer } from "./types"; | | 2 | +import { assertNever, pointer } from "./types"; | 3 | 3 |
| 4 | | -export enum Kind { | 5 | | - Invalid = -1, | | 4 | +export const enum Kind { | 6 | 5 | Boolean = 0, | 7 | 6 | String = 1, | 8 | 7 | Number = 2, | @@ -11,6 +10,7 @@ export enum Kind { | 11 | 10 | Undefined = 5, | 12 | 11 | Function = 6, | 13 | 12 | Symbol = 7, | | 13 | + BigInt = 8, | 14 | 14 | } | 15 | 15 |
| 16 | 16 | export const decode = ( | @@ -33,6 +33,8 @@ export const decode = ( | 33 | 33 | case Kind.String: | 34 | 34 | case Kind.Object: | 35 | 35 | case Kind.Function: | | 36 | + case Kind.Symbol: | | 37 | + case Kind.BigInt: | 36 | 38 | return memory.getObject(payload1); | 37 | 39 |
| 38 | 40 | case Kind.Null: | @@ -42,7 +44,7 @@ export const decode = ( | 42 | 44 | return undefined; | 43 | 45 |
| 44 | 46 | default: | 45 | | - throw new Error(`JSValue Type kind "${kind}" is not supported`); | | 47 | + assertNever(kind, `JSValue Type kind "${kind}" is not supported`); | 46 | 48 | } | 47 | 49 | }; | 48 | 50 |
| @@ -73,7 +75,14 @@ export const write = ( | 73 | 75 | memory.writeUint32(kind_ptr, exceptionBit | Kind.Null); | 74 | 76 | return; | 75 | 77 | } | 76 | | - switch (typeof value) { | | 78 | + | | 79 | + const writeRef = (kind: Kind) => { | | 80 | + memory.writeUint32(kind_ptr, exceptionBit | kind); | | 81 | + memory.writeUint32(payload1_ptr, memory.retain(value)); | | 82 | + }; | | 83 | + | | 84 | + const type = typeof value; | | 85 | + switch (type) { | 77 | 86 | case "boolean": { | 78 | 87 | memory.writeUint32(kind_ptr, exceptionBit | Kind.Boolean); | 79 | 88 | memory.writeUint32(payload1_ptr, value ? 1 : 0); | @@ -85,30 +94,30 @@ export const write = ( | 85 | 94 | break; | 86 | 95 | } | 87 | 96 | case "string": { | 88 | | - memory.writeUint32(kind_ptr, exceptionBit | Kind.String); | 89 | | - memory.writeUint32(payload1_ptr, memory.retain(value)); | | 97 | + writeRef(Kind.String); | 90 | 98 | break; | 91 | 99 | } | 92 | 100 | case "undefined": { | 93 | 101 | memory.writeUint32(kind_ptr, exceptionBit | Kind.Undefined); | 94 | 102 | break; | 95 | 103 | } | 96 | 104 | case "object": { | 97 | | - memory.writeUint32(kind_ptr, exceptionBit | Kind.Object); | 98 | | - memory.writeUint32(payload1_ptr, memory.retain(value)); | | 105 | + writeRef(Kind.Object); | 99 | 106 | break; | 100 | 107 | } | 101 | 108 | case "function": { | 102 | | - memory.writeUint32(kind_ptr, exceptionBit | Kind.Function); | 103 | | - memory.writeUint32(payload1_ptr, memory.retain(value)); | | 109 | + writeRef(Kind.Function); | 104 | 110 | break; | 105 | 111 | } | 106 | 112 | case "symbol": { | 107 | | - memory.writeUint32(kind_ptr, exceptionBit | Kind.Symbol); | 108 | | - memory.writeUint32(payload1_ptr, memory.retain(value)); | | 113 | + writeRef(Kind.Symbol); | | 114 | + break; | | 115 | + } | | 116 | + case "bigint": { | | 117 | + writeRef(Kind.BigInt); | 109 | 118 | break; | 110 | 119 | } | 111 | 120 | default: | 112 | | - throw new Error(`Type "${typeof value}" is not supported yet`); | | 121 | + assertNever(type, `Type "${type}" is not supported yet`); | 113 | 122 | } | 114 | 123 | }; |
|