From fe68a31fc5143647de4e73171283b6b01f2ca094 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Mon, 3 Aug 2020 19:44:08 -0400 Subject: [PATCH 01/22] Add a helper method to copy an array of numbers to a JS TypedArray --- .../Sources/PrimaryTests/main.swift | 7 +++ Runtime/src/index.ts | 36 +++++++++++ Sources/JavaScriptKit/JSObject.swift | 63 +++++++++++++++++++ Sources/JavaScriptKit/XcodeSupport.swift | 7 +++ .../_CJavaScriptKit/include/_CJavaScriptKit.h | 22 +++++++ 5 files changed, 135 insertions(+) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 60806e671..dbbbcb410 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -334,3 +334,10 @@ ObjectRef_Lifetime: do { } catch { print(error) } + +TypedArray: do { + let numbers = [UInt8](0 ... 255) + let typedArray = JSObjectRef.createTypedArray(numbers) + try expectEqual(typedArray[12], .number(12)) + try expectEqual(typedArray.toString!(), .string(numbers.map(String.init).joined(separator: ","))) +} diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 94e436aed..404625d0d 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -31,6 +31,32 @@ enum JavaScriptValueKind { Function = 6, } +enum JavaScriptTypedArrayKind { + Int8 = 0, + Uint8 = 1, + Int16 = 2, + Uint16 = 3, + Int32 = 4, + Uint32 = 5, + BigInt64 = 6, + BigUint64 = 7, + Float32 = 8, + Float64 = 9, +} + +type TypedArray = + | Int8ArrayConstructor + | Uint8ArrayConstructor + | Int16ArrayConstructor + | Uint16ArrayConstructor + | Int32ArrayConstructor + | Uint32ArrayConstructor + // | BigInt64ArrayConstructor + // | BigUint64ArrayConstructor + | Float32ArrayConstructor + | Float64ArrayConstructor + + type SwiftRuntimeHeapEntry = { id: number, rc: number, @@ -370,6 +396,16 @@ export class SwiftRuntime { const constructor = this.heap.referenceHeap(constructor_ref) return obj instanceof constructor }, + swjs_copy_typed_array_content: ( + kind: JavaScriptTypedArrayKind, + elementsPtr: pointer, length: number, + result_obj: pointer + ) => { + const ArrayType: TypedArray = this.heap.referenceHeap(0)[JavaScriptTypedArrayKind[kind] + 'Array'] + const array = new ArrayType(memory().buffer, elementsPtr, length); + // Call `.slice()` to copy the memory + writeUint32(result_obj, this.heap.allocHeap(array.slice())); + }, swjs_destroy_ref: (ref: ref) => { this.heap.freeHeap(ref) } diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/JSObject.swift index 47ae2b4a6..7f8d1c1a6 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/JSObject.swift @@ -57,4 +57,67 @@ public class JSObjectRef: Equatable { public func jsValue() -> JSValue { .object(self) } + + public class func createTypedArray(_ array: [Type]) -> JSObjectRef where Type: TypedArrayElement { + let type: JavaScriptTypedArrayKind + switch Type.self { + case is Int8.Type: + type = .int8 + case is UInt8.Type: + type = .uint8 + case is Int16.Type: + type = .int16 + case is UInt16.Type: + type = .uint16 + case is Int32.Type: + type = .int32 + case is UInt32.Type: + type = .uint32 + case is Int64.Type: + type = .bigInt64 + case is UInt64.Type: + type = .bigUint64 + case is Float32.Type: + type = .float32 + case is Float64.Type: + type = .float64 + default: + if Type.self is UInt.Type || Type.self is Int.Type { + if UInt.bitWidth == 32 { + if Type.self is UInt.Type { + type = .uint32 + } else { + type = .int32 + } + } else if UInt.bitWidth == 64 { + if Type.self is UInt.Type { + type = .bigUint64 + } else { + type = .bigInt64 + } + } else { + fatalError("Unsupported bit width type for UInt: \(UInt.bitWidth) (hint: stick to fixed-size ints to avoid this issue)") + } + } else { + fatalError("Unsupported Swift type for TypedArray: \(Type.self)") + } + } + var resultObj = JavaScriptObjectRef() + array.withUnsafeBufferPointer { ptr in + _copy_typed_array_content(type, ptr.baseAddress!, Int32(array.count), &resultObj) + } + return JSObjectRef(id: resultObj) + } } + +public protocol TypedArrayElement {} +extension Int8: TypedArrayElement {} +extension UInt8: TypedArrayElement {} +extension Int16: TypedArrayElement {} +extension UInt16: TypedArrayElement {} +extension Int32: TypedArrayElement {} +extension UInt32: TypedArrayElement {} +extension Int64: TypedArrayElement {} +extension UInt64: TypedArrayElement {} +extension Float32: TypedArrayElement {} +extension Float64: TypedArrayElement {} diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 180072ebf..7a3d1114a 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -73,4 +73,11 @@ import _CJavaScriptKit _: UnsafePointer! ) { fatalError() } func _destroy_ref(_: JavaScriptObjectRef) { fatalError() } + func _copy_typed_array_content( + _: JavaScriptTypedArrayKind, + _: UnsafePointer, + _: Int32, + _: UnsafeMutablePointer! + ) { fatalError() } + #endif diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 61789e16c..85b7a5780 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -18,6 +18,20 @@ typedef enum __attribute__((enum_extensibility(closed))) { JavaScriptValueKindFunction = 6, } JavaScriptValueKind; +typedef enum __attribute__((enum_extensibility(closed))) { + JavaScriptTypedArrayKindInt8 = 0, + JavaScriptTypedArrayKindUint8 = 1, + JavaScriptTypedArrayKindInt16 = 2, + JavaScriptTypedArrayKindUint16 = 3, + JavaScriptTypedArrayKindInt32 = 4, + JavaScriptTypedArrayKindUint32 = 5, + JavaScriptTypedArrayKindBigInt64 = 6, + JavaScriptTypedArrayKindBigUint64 = 7, + JavaScriptTypedArrayKindFloat32 = 8, + JavaScriptTypedArrayKindFloat64 = 9, +} JavaScriptTypedArrayKind; + + typedef unsigned JavaScriptPayload1; typedef unsigned JavaScriptPayload2; typedef double JavaScriptPayload3; @@ -97,6 +111,14 @@ __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_destroy_ref"))) extern void _destroy_ref(const JavaScriptObjectRef ref); +__attribute__(( + __import_module__("javascript_kit"), + __import_name__("swjs_copy_typed_array_content") + )) +extern void _copy_typed_array_content(const JavaScriptTypedArrayKind kind, + const void *elementsPtr, const int length, + JavaScriptObjectRef *result_obj); + #endif #endif /* _CJavaScriptKit_h */ From 0cb3f650b4d7ec51cb13e266d24d0f5028d0b410 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Tue, 4 Aug 2020 14:20:20 -0400 Subject: [PATCH 02/22] =?UTF-8?q?=5Fcopy=5Ftyped=5Farray=5Fcontent=20?= =?UTF-8?q?=E2=86=92=20=5Fcreate=5Ftyped=5Farray?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Runtime/src/index.ts | 2 +- Sources/JavaScriptKit/JSObject.swift | 2 +- Sources/JavaScriptKit/XcodeSupport.swift | 2 +- Sources/_CJavaScriptKit/include/_CJavaScriptKit.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 404625d0d..a658d20b9 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -396,7 +396,7 @@ export class SwiftRuntime { const constructor = this.heap.referenceHeap(constructor_ref) return obj instanceof constructor }, - swjs_copy_typed_array_content: ( + swjs_create_typed_array: ( kind: JavaScriptTypedArrayKind, elementsPtr: pointer, length: number, result_obj: pointer diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/JSObject.swift index 7f8d1c1a6..5252a2e57 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/JSObject.swift @@ -104,7 +104,7 @@ public class JSObjectRef: Equatable { } var resultObj = JavaScriptObjectRef() array.withUnsafeBufferPointer { ptr in - _copy_typed_array_content(type, ptr.baseAddress!, Int32(array.count), &resultObj) + _create_typed_array(type, ptr.baseAddress!, Int32(array.count), &resultObj) } return JSObjectRef(id: resultObj) } diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 7a3d1114a..099a5d0e1 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -73,7 +73,7 @@ import _CJavaScriptKit _: UnsafePointer! ) { fatalError() } func _destroy_ref(_: JavaScriptObjectRef) { fatalError() } - func _copy_typed_array_content( + func _create_typed_array( _: JavaScriptTypedArrayKind, _: UnsafePointer, _: Int32, diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 85b7a5780..5b9470615 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -113,9 +113,9 @@ _destroy_ref(const JavaScriptObjectRef ref); __attribute__(( __import_module__("javascript_kit"), - __import_name__("swjs_copy_typed_array_content") + __import_name__("swjs_create_typed_array") )) -extern void _copy_typed_array_content(const JavaScriptTypedArrayKind kind, +extern void _create_typed_array(const JavaScriptTypedArrayKind kind, const void *elementsPtr, const int length, JavaScriptObjectRef *result_obj); From bff8568e9939fc49cde48a27d935e9cfe7fec69f Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Wed, 12 Aug 2020 17:17:43 -0400 Subject: [PATCH 03/22] Add globalVariable --- Runtime/src/index.ts | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index a658d20b9..8d0cb2e3d 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -8,6 +8,17 @@ type pointer = number; interface GlobalVariable { } declare const window: GlobalVariable; declare const global: GlobalVariable; +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 +} + interface SwiftRuntimeExportedFunctions { swjs_library_version(): number; @@ -67,17 +78,11 @@ class SwiftRuntimeHeap { private _heapNextKey: number; constructor() { - let _global: any; - if (typeof window !== "undefined") { - _global = window - } else if (typeof global !== "undefined") { - _global = global - } this._heapValueById = new Map(); - this._heapValueById.set(0, _global); + this._heapValueById.set(0, globalVariable); this._heapEntryByValue = new Map(); - this._heapEntryByValue.set(_global, { id: 0, rc: 1 }); + this._heapEntryByValue.set(globalVariable, { id: 0, rc: 1 }); // Note: 0 is preserved for global this._heapNextKey = 1; @@ -401,7 +406,7 @@ export class SwiftRuntime { elementsPtr: pointer, length: number, result_obj: pointer ) => { - const ArrayType: TypedArray = this.heap.referenceHeap(0)[JavaScriptTypedArrayKind[kind] + 'Array'] + const ArrayType: TypedArray = globalVariable[JavaScriptTypedArrayKind[kind] + 'Array'] const array = new ArrayType(memory().buffer, elementsPtr, length); // Call `.slice()` to copy the memory writeUint32(result_obj, this.heap.allocHeap(array.slice())); From 136315fd101597ad3d3a49c7d9269a6824370a58 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 11:07:30 -0400 Subject: [PATCH 04/22] Remove broken test target --- Package.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 2f16796a3..7dd05b33a 100644 --- a/Package.swift +++ b/Package.swift @@ -38,10 +38,6 @@ let package = Package( .when(platforms: [.wasi]) ), ] - ), - .testTarget( - name: "JavaScriptKitTests", - dependencies: ["JavaScriptKit"] - ), + ) ] ) From 5b875b24ee1da912592ade5ee4c6360187a44052 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 11:27:40 -0400 Subject: [PATCH 05/22] Create JSTypedArray --- Sources/JavaScriptKit/JSObject.swift | 62 ------ Sources/JavaScriptKit/JSTypedArray.swift | 184 ++++++++++++++++++ .../JavaScriptKit/JSValueConvertible.swift | 4 + 3 files changed, 188 insertions(+), 62 deletions(-) create mode 100644 Sources/JavaScriptKit/JSTypedArray.swift diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/JSObject.swift index 92c0f6390..56940c1a7 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/JSObject.swift @@ -46,67 +46,5 @@ public class JSObjectRef: Equatable { public func jsValue() -> JSValue { .object(self) } - - public class func createTypedArray(_ array: [Type]) -> JSObjectRef where Type: TypedArrayElement { - let type: JavaScriptTypedArrayKind - switch Type.self { - case is Int8.Type: - type = .int8 - case is UInt8.Type: - type = .uint8 - case is Int16.Type: - type = .int16 - case is UInt16.Type: - type = .uint16 - case is Int32.Type: - type = .int32 - case is UInt32.Type: - type = .uint32 - case is Int64.Type: - type = .bigInt64 - case is UInt64.Type: - type = .bigUint64 - case is Float32.Type: - type = .float32 - case is Float64.Type: - type = .float64 - default: - if Type.self is UInt.Type || Type.self is Int.Type { - if UInt.bitWidth == 32 { - if Type.self is UInt.Type { - type = .uint32 - } else { - type = .int32 - } - } else if UInt.bitWidth == 64 { - if Type.self is UInt.Type { - type = .bigUint64 - } else { - type = .bigInt64 - } - } else { - fatalError("Unsupported bit width type for UInt: \(UInt.bitWidth) (hint: stick to fixed-size ints to avoid this issue)") - } - } else { - fatalError("Unsupported Swift type for TypedArray: \(Type.self)") - } - } - var resultObj = JavaScriptObjectRef() - array.withUnsafeBufferPointer { ptr in - _create_typed_array(type, ptr.baseAddress!, Int32(array.count), &resultObj) - } - return JSObjectRef(id: resultObj) - } } -public protocol TypedArrayElement {} -extension Int8: TypedArrayElement {} -extension UInt8: TypedArrayElement {} -extension Int16: TypedArrayElement {} -extension UInt16: TypedArrayElement {} -extension Int32: TypedArrayElement {} -extension UInt32: TypedArrayElement {} -extension Int64: TypedArrayElement {} -extension UInt64: TypedArrayElement {} -extension Float32: TypedArrayElement {} -extension Float64: TypedArrayElement {} diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/JSTypedArray.swift new file mode 100644 index 000000000..df58d696f --- /dev/null +++ b/Sources/JavaScriptKit/JSTypedArray.swift @@ -0,0 +1,184 @@ +// +// Created by Manuel Burghard. Licensed unter MIT. +// + +import _CJavaScriptKit + +public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { + associatedtype ArrayType: JSTypedArray + static func createTypedArray(_ array: [Self]) -> ArrayType + static var typedArrayKind: JavaScriptTypedArrayKind { get } +} + +func createTypedArray(_ array: [Element]) -> Element.ArrayType where Element: TypedArrayElement { + return Element.createTypedArray(array) +} + +public protocol JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where ArrayLiteralElement == Element { + associatedtype Element: TypedArrayElement + static var classRef: JSFunctionRef { get } +} + +extension JSTypedArray { + public static var classRef: JSFunctionRef { + let name = String(describing: Self.self) + let jsName = String(name[name.index(name.startIndex, offsetBy: 2)...name.endIndex]) + return JSObjectRef.global[jsName].function! + } + + public subscript(_ index: Int) -> Element { + get { + return Element.construct(from: getJSValue(this: self, index: Int32(index)))! + } + set { + setJSValue(this: self, index: Int32(index), value: newValue.jsValue()) + } + } + + public init(length: Int) { + self.init(id: Self.classRef.new(length).id) + } + + public init(arrayLiteral elements: Element...) { + + self.init(elements) + } + + public init(_ array: [Element]) { + var resultObj = JavaScriptObjectRef() + array.withUnsafeBufferPointer { ptr in + _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) + } + self.init(id: resultObj) + } + + public init(_ stride: StrideTo) where Element == Type { + let array = stride.map { $0 } + self.init(array) + } +} + +// MARK: - Int and UInt support + +// FIXME: Should be updated to support wasm64 when that becomes available. +func valueForBitWidth(typeName: String, bitWidth: Int, when32: T) -> T { + if bitWidth == 32 { + return when32 + } else if bitWidth == 64 { + fatalError("64-bit \(typeName)s are not yet supported in JSTypedArray") + } else { + fatalError("Unsupported bit width for type \(typeName): \(bitWidth) (hint: stick to fixed-size \(typeName)s to avoid this issue)") + } +} + +extension Int: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> some JSTypedArray { + valueForBitWidth( + typeName: "Int", + bitWidth: Int.bitWidth, + when32: { JSInt32Array(array.map(Int32.init(_:))) } + )() + } + public static var typedArrayKind: JavaScriptTypedArrayKind { + valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: .int32) + } +} +extension UInt: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> some JSTypedArray { + valueForBitWidth( + typeName: "Int", + bitWidth: Int.bitWidth, + when32: { JSUint32Array(array.map(UInt32.init(_:))) } + )() + } + public static var typedArrayKind: JavaScriptTypedArrayKind { + valueForBitWidth(typeName: "UInt", bitWidth: UInt.bitWidth, when32: .uint32) + } +} + +// MARK: - Concrete TypedArray classes + +extension Int8: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSInt8Array { JSInt8Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .int8 } +} +public class JSInt8Array: JSObjectRef, JSTypedArray { + public typealias Element = Int8 +} + +extension UInt8: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSUint8Array { JSUint8Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .uint8 } +} +public class JSUint8Array: JSObjectRef, JSTypedArray { + public typealias Element = UInt8 +} +// TODO: Implement? +//public class JSUint8ClampedArray: JSObjectRef, JSTypedArray { +// public typealias Element = UInt8 +//} + +extension Int16: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSInt16Array { JSInt16Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .int16 } +} +public class JSInt16Array: JSObjectRef, JSTypedArray { + public typealias Element = Int16 +} + +extension UInt16: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSUint16Array { JSUint16Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .uint16 } +} +public class JSUint16Array: JSObjectRef, JSTypedArray { + public typealias Element = UInt16 +} + +extension Int32: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSInt32Array { JSInt32Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .int32 } +} +public class JSInt32Array: JSObjectRef, JSTypedArray { + public typealias Element = Int32 +} + +extension UInt32: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSUint32Array { JSUint32Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .uint32 } +} +public class JSUint32Array: JSObjectRef, JSTypedArray { + public typealias Element = UInt32 +} + +// FIXME: Support passing BigInts across the bridge +//extension Int64: TypedArrayElement { +// public static func createTypedArray(_ array: [Self]) -> JSBigInt64Array { JSBigInt64Array(array) } +// public static var type: JavaScriptTypedArrayKind { .bigInt64 } +//} +//public class JSBigInt64Array: JSObjectRef, JSTypedArray { +// public typealias Element = Int64 +//} +// +//extension UInt64: TypedArrayElement { +// public static func createTypedArray(_ array: [Self]) -> BigUint64Array { BigUint64Array(array) } +// public static var type: JavaScriptTypedArrayKind { .bigUint64 } +//} +//public class BigUint64Array: JSObjectRef, JSTypedArray { +// public typealias Element = UInt64 +//} + +extension Float32: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSFloat32Array { JSFloat32Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .float32 } +} +public class JSFloat32Array: JSObjectRef, JSTypedArray { + public typealias Element = Float +} + +extension Float64: TypedArrayElement { + public static func createTypedArray(_ array: [Self]) -> JSFloat64Array { JSFloat64Array(array) } + public static var typedArrayKind: JavaScriptTypedArrayKind { .float64 } +} +public class JSFloat64Array: JSObjectRef, JSTypedArray { + public typealias Element = Double +} diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 08897aa92..eaf7680a5 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -40,6 +40,10 @@ extension UInt16: JSValueConvertible { public func jsValue() -> JSValue { .number(Double(self)) } } +extension UInt32: JSValueConvertible { + public func jsValue() -> JSValue { .number(Double(self)) } +} + extension Float: JSValueConvertible { public func jsValue() -> JSValue { .number(Double(self)) } } From af57583cb634196528a1bcd37fb1fe5f55996f41 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 11:40:21 -0400 Subject: [PATCH 06/22] Reduce to just a single class --- Sources/JavaScriptKit/JSTypedArray.swift | 110 +++++------------------ 1 file changed, 23 insertions(+), 87 deletions(-) diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/JSTypedArray.swift index df58d696f..c4843d72d 100644 --- a/Sources/JavaScriptKit/JSTypedArray.swift +++ b/Sources/JavaScriptKit/JSTypedArray.swift @@ -5,27 +5,11 @@ import _CJavaScriptKit public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { - associatedtype ArrayType: JSTypedArray - static func createTypedArray(_ array: [Self]) -> ArrayType static var typedArrayKind: JavaScriptTypedArrayKind { get } + static var typedArrayClass: JSFunctionRef { get } } -func createTypedArray(_ array: [Element]) -> Element.ArrayType where Element: TypedArrayElement { - return Element.createTypedArray(array) -} - -public protocol JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where ArrayLiteralElement == Element { - associatedtype Element: TypedArrayElement - static var classRef: JSFunctionRef { get } -} - -extension JSTypedArray { - public static var classRef: JSFunctionRef { - let name = String(describing: Self.self) - let jsName = String(name[name.index(name.startIndex, offsetBy: 2)...name.endIndex]) - return JSObjectRef.global[jsName].function! - } - +public class JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where Element: TypedArrayElement { public subscript(_ index: Int) -> Element { get { return Element.construct(from: getJSValue(this: self, index: Int32(index)))! @@ -36,11 +20,10 @@ extension JSTypedArray { } public init(length: Int) { - self.init(id: Self.classRef.new(length).id) + super.init(id: Element.typedArrayClass.new(length).id) } - public init(arrayLiteral elements: Element...) { - + required public convenience init(arrayLiteral elements: Element...) { self.init(elements) } @@ -49,12 +32,11 @@ extension JSTypedArray { array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) } - self.init(id: resultObj) + super.init(id: UInt32(resultObj)) } - public init(_ stride: StrideTo) where Element == Type { - let array = stride.map { $0 } - self.init(array) + public convenience init(_ stride: StrideTo) where Element: Strideable { + self.init(stride.map({ $0 })) } } @@ -72,24 +54,16 @@ func valueForBitWidth(typeName: String, bitWidth: Int, when32: T) -> T { } extension Int: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> some JSTypedArray { - valueForBitWidth( - typeName: "Int", - bitWidth: Int.bitWidth, - when32: { JSInt32Array(array.map(Int32.init(_:))) } - )() + public static var typedArrayClass: JSFunctionRef { + valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: JSObjectRef.global.Int32Array).function! } public static var typedArrayKind: JavaScriptTypedArrayKind { valueForBitWidth(typeName: "Int", bitWidth: Int.bitWidth, when32: .int32) } } extension UInt: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> some JSTypedArray { - valueForBitWidth( - typeName: "Int", - bitWidth: Int.bitWidth, - when32: { JSUint32Array(array.map(UInt32.init(_:))) } - )() + public static var typedArrayClass: JSFunctionRef { + valueForBitWidth(typeName: "UInt", bitWidth: Int.bitWidth, when32: JSObjectRef.global.Uint32Array).function! } public static var typedArrayKind: JavaScriptTypedArrayKind { valueForBitWidth(typeName: "UInt", bitWidth: UInt.bitWidth, when32: .uint32) @@ -99,86 +73,48 @@ extension UInt: TypedArrayElement { // MARK: - Concrete TypedArray classes extension Int8: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSInt8Array { JSInt8Array(array) } + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Int8Array.function! } public static var typedArrayKind: JavaScriptTypedArrayKind { .int8 } } -public class JSInt8Array: JSObjectRef, JSTypedArray { - public typealias Element = Int8 -} - extension UInt8: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSUint8Array { JSUint8Array(array) } + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Uint8Array.function! } public static var typedArrayKind: JavaScriptTypedArrayKind { .uint8 } } -public class JSUint8Array: JSObjectRef, JSTypedArray { - public typealias Element = UInt8 -} -// TODO: Implement? -//public class JSUint8ClampedArray: JSObjectRef, JSTypedArray { -// public typealias Element = UInt8 -//} +// TODO: Support Uint8ClampedArray? extension Int16: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSInt16Array { JSInt16Array(array) } - public static var typedArrayKind: JavaScriptTypedArrayKind { .int16 } -} -public class JSInt16Array: JSObjectRef, JSTypedArray { - public typealias Element = Int16 + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Int16Array.function! } + public static var typedArrayKind: JavaScriptTypedArrayKind { .int16 } } - extension UInt16: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSUint16Array { JSUint16Array(array) } + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Uint16Array.function! } public static var typedArrayKind: JavaScriptTypedArrayKind { .uint16 } } -public class JSUint16Array: JSObjectRef, JSTypedArray { - public typealias Element = UInt16 -} extension Int32: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSInt32Array { JSInt32Array(array) } + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Int32Array.function! } public static var typedArrayKind: JavaScriptTypedArrayKind { .int32 } } -public class JSInt32Array: JSObjectRef, JSTypedArray { - public typealias Element = Int32 -} - extension UInt32: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSUint32Array { JSUint32Array(array) } + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Uint32Array.function! } public static var typedArrayKind: JavaScriptTypedArrayKind { .uint32 } } -public class JSUint32Array: JSObjectRef, JSTypedArray { - public typealias Element = UInt32 -} // FIXME: Support passing BigInts across the bridge //extension Int64: TypedArrayElement { -// public static func createTypedArray(_ array: [Self]) -> JSBigInt64Array { JSBigInt64Array(array) } +// public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.BigInt64Array.function! } // public static var type: JavaScriptTypedArrayKind { .bigInt64 } //} -//public class JSBigInt64Array: JSObjectRef, JSTypedArray { -// public typealias Element = Int64 -//} -// //extension UInt64: TypedArrayElement { -// public static func createTypedArray(_ array: [Self]) -> BigUint64Array { BigUint64Array(array) } +// public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.BigUint64Array.function! } // public static var type: JavaScriptTypedArrayKind { .bigUint64 } //} -//public class BigUint64Array: JSObjectRef, JSTypedArray { -// public typealias Element = UInt64 -//} extension Float32: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSFloat32Array { JSFloat32Array(array) } + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Float32Array.function! } public static var typedArrayKind: JavaScriptTypedArrayKind { .float32 } } -public class JSFloat32Array: JSObjectRef, JSTypedArray { - public typealias Element = Float -} - extension Float64: TypedArrayElement { - public static func createTypedArray(_ array: [Self]) -> JSFloat64Array { JSFloat64Array(array) } + public static var typedArrayClass: JSFunctionRef { JSObjectRef.global.Float64Array.function! } public static var typedArrayKind: JavaScriptTypedArrayKind { .float64 } } -public class JSFloat64Array: JSObjectRef, JSTypedArray { - public typealias Element = Double -} From e71fc561a23087937328cc0f824d2b0badf0381d Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 11:43:16 -0400 Subject: [PATCH 07/22] Clean up types --- Sources/JavaScriptKit/JSObject.swift | 2 +- Sources/JavaScriptKit/JSTypedArray.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/JSObject.swift index 56940c1a7..7729bf756 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/JSObject.swift @@ -34,7 +34,7 @@ public class JSObjectRef: Equatable { _instanceof(id, constructor.id) } - static let _JS_Predef_Value_Global: UInt32 = 0 + static let _JS_Predef_Value_Global: JavaScriptObjectRef = 0 public static let global = JSObjectRef(id: _JS_Predef_Value_Global) deinit { _destroy_ref(id) } diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/JSTypedArray.swift index c4843d72d..cc5123e0a 100644 --- a/Sources/JavaScriptKit/JSTypedArray.swift +++ b/Sources/JavaScriptKit/JSTypedArray.swift @@ -32,7 +32,7 @@ public class JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) } - super.init(id: UInt32(resultObj)) + super.init(id: resultObj) } public convenience init(_ stride: StrideTo) where Element: Strideable { From 17d83f549ba69d4ad46595b0cea3dacd16f1c2e7 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 15:33:35 -0400 Subject: [PATCH 08/22] Fix tests --- IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 25157a734..6e85d9b4b 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -353,7 +353,7 @@ Closure_Identifiers: do { TypedArray: do { let numbers = [UInt8](0 ... 255) - let typedArray = JSObjectRef.createTypedArray(numbers) + let typedArray = JSTypedArray(numbers) try expectEqual(typedArray[12], .number(12)) try expectEqual(typedArray.toString!(), .string(numbers.map(String.init).joined(separator: ","))) } From 64342d22428f9f6dbab7f9f08b30f92b53dd6b7f Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 15:42:51 -0400 Subject: [PATCH 09/22] Formatting --- Sources/_CJavaScriptKit/include/_CJavaScriptKit.h | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 5b9470615..6c0837b6d 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -31,7 +31,6 @@ typedef enum __attribute__((enum_extensibility(closed))) { JavaScriptTypedArrayKindFloat64 = 9, } JavaScriptTypedArrayKind; - typedef unsigned JavaScriptPayload1; typedef unsigned JavaScriptPayload2; typedef double JavaScriptPayload3; From ab974af0fa04bf0fac13f2b141eeeca6ca38ae45 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 16:02:38 -0400 Subject: [PATCH 10/22] Test all the array types --- .../Sources/PrimaryTests/main.swift | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 6e85d9b4b..b3d94d57f 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -351,9 +351,51 @@ Closure_Identifiers: do { print(error) } +func checkArray(_ array: [T]) throws where T: TypedArrayElement { + try expectEqual(JSTypedArray(array).toString!(), .string(jsStringify(array))) +} + +func jsStringify(_ array: [Any]) -> String { + array.map({ String(describing: $0) }).joined(separator: ",") +} + TypedArray: do { let numbers = [UInt8](0 ... 255) let typedArray = JSTypedArray(numbers) try expectEqual(typedArray[12], .number(12)) - try expectEqual(typedArray.toString!(), .string(numbers.map(String.init).joined(separator: ","))) + + try checkArray([0, .max, 127, 1] as [UInt8]) + try checkArray([0, 1, .max, .min, -1] as [Int8]) + + try checkArray([0, .max, 255, 1] as [UInt16]) + try checkArray([0, 1, .max, .min, -1] as [Int16]) + + try checkArray([0, .max, 255, 1] as [UInt32]) + try checkArray([0, 1, .max, .min, -1] as [Int32]) + + try checkArray([0, .max, 255, 1] as [UInt]) + try checkArray([0, 1, .max, .min, -1] as [Int]) + + let float32Array: [Float32] = [0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude, .leastNormalMagnitude, 42] + let jsFloat32Array = JSTypedArray(float32Array) + for (i, num) in float32Array.enumerated() { + try expectEqual(num, jsFloat32Array[i]) + } + + let float64Array: [Float64] = [0, 1, .pi, .greatestFiniteMagnitude, .infinity, .leastNonzeroMagnitude, .leastNormalMagnitude, 42] + let jsFloat64Array = JSTypedArray(float64Array) + for (i, num) in float64Array.enumerated() { + try expectEqual(num, jsFloat64Array[i]) + } +} + +TypedArray_Mutation: do { + let array = JSTypedArray(length: 100) + for i in 0..<100 { + array[i] = i + } + for i in 0..<100 { + try expectEqual(i, array[i]) + } + try expectEqual(array.toString!(), jsStringify(0..<100)) } From 0928da80cf70646f4b0ed59161fa5278560461f9 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 19:29:56 -0400 Subject: [PATCH 11/22] Fix test error --- IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index b3d94d57f..ee752525a 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -397,5 +397,5 @@ TypedArray_Mutation: do { for i in 0..<100 { try expectEqual(i, array[i]) } - try expectEqual(array.toString!(), jsStringify(0..<100)) + try expectEqual(array.toString!(), .string(jsStringify(Array(0..<100)))) } From a1f5b035f693bb040e295aca411585a0b9df0276 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 19:31:02 -0400 Subject: [PATCH 12/22] Add a test("name") { ... } helper that makes it easy to find out which test errors on the JS side happen in --- .../Sources/PrimaryTests/UnitTestUtils.swift | 12 +++++ .../Sources/PrimaryTests/main.swift | 53 ++++++------------- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift index c9b4d00b8..f05708cae 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift @@ -1,5 +1,17 @@ import JavaScriptKit +let printTestNames = false + +func test(_ name: String, testBlock: () throws -> Void) { + if printTestNames { print(name) } + do { + try testBlock() + } catch { + print("Error in \(name)") + print(error) + } +} + struct MessageError: Error { let message: String let file: StaticString diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index ee752525a..9b0ce9d23 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -1,6 +1,6 @@ import JavaScriptKit -Literal_Conversion: do { +test("Literal Conversion") { let global = JSObjectRef.global let inputs: [JSValue] = [ .boolean(true), @@ -27,11 +27,9 @@ Literal_Conversion: do { try expectEqual(got, input) } } -} catch { - print(error) } -Object_Conversion: do { +test("Object Conversion") { // Notes: globalObject1 is defined in JavaScript environment // // ```js @@ -70,12 +68,9 @@ Object_Conversion: do { } try expectEqual(getJSValue(this: globalObject1Ref, name: "undefined_prop"), .undefined) - -} catch { - print(error) } -Value_Construction: do { +test("Value Construction") { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try expectObject(globalObject1) let prop_2 = getJSValue(this: globalObject1Ref, name: "prop_2") @@ -85,11 +80,9 @@ Value_Construction: do { let prop_7 = getJSValue(this: globalObject1Ref, name: "prop_7") try expectEqual(Double.construct(from: prop_7), 3.14) try expectEqual(Float.construct(from: prop_7), 3.14) -} catch { - print(error) } -Array_Iterator: do { +test("Array Iterator") { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try expectObject(globalObject1) let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4") @@ -100,7 +93,7 @@ Array_Iterator: do { try expectEqual(Array(array), expectedProp_4) } -Array_RandomAccessCollection: do { +test("Array RandomAccessCollection") { let globalObject1 = getJSValue(this: .global, name: "globalObject1") let globalObject1Ref = try expectObject(globalObject1) let prop_4 = getJSValue(this: globalObject1Ref, name: "prop_4") @@ -111,7 +104,7 @@ Array_RandomAccessCollection: do { try expectEqual([array[0], array[1], array[2], array[3]], expectedProp_4) } -Value_Decoder: do { +test("Value Decoder") { struct GlobalObject1: Codable { struct Prop1: Codable { let nested_prop: Int @@ -131,7 +124,7 @@ Value_Decoder: do { try expectEqual(globalObject1.prop_7, 3.14) } -Function_Call: do { +test("Function Call") { // Notes: globalObject1 is defined in JavaScript environment // // ```js @@ -173,12 +166,9 @@ Function_Call: do { try expectEqual(func6(true, 1, 2), .number(1)) try expectEqual(func6(false, 1, 2), .number(2)) try expectEqual(func6(true, "OK", 2), .string("OK")) - -} catch { - print(error) } -Host_Function_Registration: do { +test("Host Function Registration") { // ```js // global.globalObject1 = { // ... @@ -221,11 +211,9 @@ Host_Function_Registration: do { try expectEqual(hostFunc2(3), .number(6)) _ = try expectString(hostFunc2(true)) hostFunc2.release() -} catch { - print(error) } -New_Object_Construction: do { +test("New Object Construction") { // ```js // global.Animal = function(name, age, isCat) { // this.name = name @@ -247,11 +235,9 @@ New_Object_Construction: do { let dog1 = objectConstructor.new("Pochi", 3, false) let dog1Bark = try expectFunction(getJSValue(this: dog1, name: "bark")) try expectEqual(dog1Bark(), .string("wan")) -} catch { - print(error) } -Call_Function_With_This: do { +test("Call Function With This") { // ```js // global.Animal = function(name, age, isCat) { // this.name = name @@ -275,12 +261,9 @@ Call_Function_With_This: do { // Call with this let gotIsCat = getIsCat(this: cat1) try expectEqual(gotIsCat, .boolean(true)) - -} catch { - print(error) } -Object_Conversion: do { +test("Object Conversion") { let array1 = [1, 2, 3] let jsArray1 = array1.jsValue().object! try expectEqual(jsArray1.length, .number(3)) @@ -307,11 +290,9 @@ Object_Conversion: do { let jsDict1 = dict1.jsValue().object! try expectEqual(jsDict1.prop1, .number(1)) try expectEqual(jsDict1.prop2, .string("foo")) -} catch { - print(error) } -ObjectRef_Lifetime: do { +test("ObjectRef Lifetime") { // ```js // global.globalObject1 = { // "prop_1": { @@ -332,8 +313,6 @@ ObjectRef_Lifetime: do { try expectEqual(ref1.prop_2, .number(2)) try expectEqual(ref2.prop_2, .number(2)) identity.release() -} catch { - print(error) } func closureScope() -> ObjectIdentifier { @@ -343,12 +322,10 @@ func closureScope() -> ObjectIdentifier { return result } -Closure_Identifiers: do { +test("Closure Identifiers") { let oid1 = closureScope() let oid2 = closureScope() try expectEqual(oid1, oid2) -} catch { - print(error) } func checkArray(_ array: [T]) throws where T: TypedArrayElement { @@ -359,7 +336,7 @@ func jsStringify(_ array: [Any]) -> String { array.map({ String(describing: $0) }).joined(separator: ",") } -TypedArray: do { +test("TypedArray") { let numbers = [UInt8](0 ... 255) let typedArray = JSTypedArray(numbers) try expectEqual(typedArray[12], .number(12)) @@ -389,7 +366,7 @@ TypedArray: do { } } -TypedArray_Mutation: do { +test("TypedArray_Mutation") { let array = JSTypedArray(length: 100) for i in 0..<100 { array[i] = i From 7971185fe842a1147ba022581b6ce3640924384a Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 19:40:52 -0400 Subject: [PATCH 13/22] Rename allocHeap and freeHeap to retain/release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This matches the Objective-C names and better corresponds with their tasks (freeHeap just decreases ref count so it doesn’t always free). I’m not as sure about allocHeap → retain because it adds to the heap in addition to increasing the refcount. --- Runtime/src/index.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 8d0cb2e3d..9d56f41dc 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -88,7 +88,7 @@ class SwiftRuntimeHeap { this._heapNextKey = 1; } - allocHeap(value: any) { + retain(value: any) { const isObject = typeof value == "object"; const entry = this._heapEntryByValue.get(value); if (isObject && entry) { @@ -103,7 +103,7 @@ class SwiftRuntimeHeap { return id } - freeHeap(ref: ref) { + release(ref: ref) { const value = this._heapValueById.get(ref); const isObject = typeof value == "object" if (isObject) { @@ -161,7 +161,7 @@ export class SwiftRuntime { writeValue(argument, base, base + 4, base + 8, base + 16) } let output: any; - const callback_func_ref = this.heap.allocHeap(function (result: any) { + const callback_func_ref = this.heap.retain(function (result: any) { output = result }) exports.swjs_call_host_function(host_func_id, argv, argc, callback_func_ref) @@ -272,7 +272,7 @@ export class SwiftRuntime { case "string": { const bytes = textEncoder.encode(value); writeUint32(kind_ptr, JavaScriptValueKind.String); - writeUint32(payload1_ptr, this.heap.allocHeap(bytes)); + writeUint32(payload1_ptr, this.heap.retain(bytes)); writeUint32(payload2_ptr, bytes.length); break; } @@ -284,13 +284,13 @@ export class SwiftRuntime { } case "object": { writeUint32(kind_ptr, JavaScriptValueKind.Object); - writeUint32(payload1_ptr, this.heap.allocHeap(value)); + writeUint32(payload1_ptr, this.heap.retain(value)); writeUint32(payload2_ptr, 0); break; } case "function": { writeUint32(kind_ptr, JavaScriptValueKind.Function); - writeUint32(payload1_ptr, this.heap.allocHeap(value)); + writeUint32(payload1_ptr, this.heap.retain(value)); writeUint32(payload2_ptr, 0); break; } @@ -378,7 +378,7 @@ export class SwiftRuntime { host_func_id: number, func_ref_ptr: pointer, ) => { - const func_ref = this.heap.allocHeap(function () { + const func_ref = this.heap.retain(function () { return callHostFunction(host_func_id, Array.prototype.slice.call(arguments)) }) writeUint32(func_ref_ptr, func_ref) @@ -391,7 +391,7 @@ export class SwiftRuntime { 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.allocHeap(result)); + writeUint32(result_obj, this.heap.retain(result)); }, swjs_instanceof: ( obj_ref: ref, constructor_ref: ref, @@ -409,10 +409,10 @@ export class SwiftRuntime { const ArrayType: TypedArray = globalVariable[JavaScriptTypedArrayKind[kind] + 'Array'] const array = new ArrayType(memory().buffer, elementsPtr, length); // Call `.slice()` to copy the memory - writeUint32(result_obj, this.heap.allocHeap(array.slice())); + writeUint32(result_obj, this.heap.retain(array.slice())); }, swjs_destroy_ref: (ref: ref) => { - this.heap.freeHeap(ref) + this.heap.release(ref) } } } From dde8cf2632791ad9535b38295fccc848bd66d63a Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 19:41:35 -0400 Subject: [PATCH 14/22] Propagate names through to the Swift side --- Runtime/src/index.ts | 2 +- Sources/JavaScriptKit/JSObject.swift | 2 +- Sources/JavaScriptKit/XcodeSupport.swift | 2 +- Sources/_CJavaScriptKit/include/_CJavaScriptKit.h | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index 9d56f41dc..be7e3063d 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -411,7 +411,7 @@ export class SwiftRuntime { // Call `.slice()` to copy the memory writeUint32(result_obj, this.heap.retain(array.slice())); }, - swjs_destroy_ref: (ref: ref) => { + swjs_release: (ref: ref) => { this.heap.release(ref) } } diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/JSObject.swift index 7729bf756..cac51b1a1 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/JSObject.swift @@ -37,7 +37,7 @@ public class JSObjectRef: Equatable { static let _JS_Predef_Value_Global: JavaScriptObjectRef = 0 public static let global = JSObjectRef(id: _JS_Predef_Value_Global) - deinit { _destroy_ref(id) } + deinit { _release(id) } public static func == (lhs: JSObjectRef, rhs: JSObjectRef) -> Bool { return lhs.id == rhs.id diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 917484ce6..ed42417c8 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -72,7 +72,7 @@ import _CJavaScriptKit _: JavaScriptHostFuncRef, _: UnsafePointer! ) { fatalError() } - func _destroy_ref(_: JavaScriptObjectRef) { fatalError() } + func _release(_: JavaScriptObjectRef) { fatalError() } func _create_typed_array( _: JavaScriptTypedArrayKind, _: UnsafePointer, diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 6c0837b6d..83f454e44 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -107,8 +107,8 @@ _create_function(const JavaScriptHostFuncRef host_func_id, const JavaScriptObjectRef *func_ref_ptr); __attribute__((__import_module__("javascript_kit"), - __import_name__("swjs_destroy_ref"))) extern void -_destroy_ref(const JavaScriptObjectRef ref); + __import_name__("swjs_release"))) extern void +_release(const JavaScriptObjectRef ref); __attribute__(( __import_module__("javascript_kit"), From 74610c21369b7dd573be36bf70f2a0a9c8eed9ad Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 19:41:54 -0400 Subject: [PATCH 15/22] Add an explicit retain() function and fix a ref counting bug --- Runtime/src/index.ts | 3 +++ Sources/JavaScriptKit/JSTypedArray.swift | 1 + Sources/JavaScriptKit/XcodeSupport.swift | 1 + Sources/_CJavaScriptKit/include/_CJavaScriptKit.h | 4 ++++ 4 files changed, 9 insertions(+) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index be7e3063d..f51ec89e4 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -411,6 +411,9 @@ export class SwiftRuntime { // Call `.slice()` to copy the memory writeUint32(result_obj, this.heap.retain(array.slice())); }, + swjs_retain: (ref: ref) => { + this.heap.retain(this.heap.referenceHeap(ref)) + }, swjs_release: (ref: ref) => { this.heap.release(ref) } diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/JSTypedArray.swift index cc5123e0a..21c5cea69 100644 --- a/Sources/JavaScriptKit/JSTypedArray.swift +++ b/Sources/JavaScriptKit/JSTypedArray.swift @@ -21,6 +21,7 @@ public class JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where public init(length: Int) { super.init(id: Element.typedArrayClass.new(length).id) + _retain(id) } required public convenience init(arrayLiteral elements: Element...) { diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index ed42417c8..371c1f74c 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -72,6 +72,7 @@ import _CJavaScriptKit _: JavaScriptHostFuncRef, _: UnsafePointer! ) { fatalError() } + func _retain(_: JavaScriptObjectRef) { fatalError() } func _release(_: JavaScriptObjectRef) { fatalError() } func _create_typed_array( _: JavaScriptTypedArrayKind, diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 83f454e44..81b0230f3 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -106,6 +106,10 @@ __attribute__((__import_module__("javascript_kit"), _create_function(const JavaScriptHostFuncRef host_func_id, const JavaScriptObjectRef *func_ref_ptr); +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_retain"))) extern void +_retain(const JavaScriptObjectRef ref); + __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_release"))) extern void _release(const JavaScriptObjectRef ref); From 14ab088c99c480d1599d9da29384f818c57fce46 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 19:47:49 -0400 Subject: [PATCH 16/22] Add error when reading invalid reference MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously it would just return undefined which would throw an error when attempting to use the object later on. Now you’re able to demangle the stack trace to find exactly where you’re misusing a ref. --- Runtime/src/index.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index f51ec89e4..d75035cbf 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -119,7 +119,11 @@ class SwiftRuntimeHeap { } referenceHeap(ref: ref) { - return this._heapValueById.get(ref) + const value = this._heapValueById.get(ref) + if (value === undefined) { + throw new ReferenceError("Attempted to read invalid reference " + ref) + } + return value } } From b6602c8b284f721cbe858332587d6ebc9cf7d961 Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 19:48:33 -0400 Subject: [PATCH 17/22] Actually fix the tests --- Sources/JavaScriptKit/JSTypedArray.swift | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/JSTypedArray.swift index 21c5cea69..e05f28216 100644 --- a/Sources/JavaScriptKit/JSTypedArray.swift +++ b/Sources/JavaScriptKit/JSTypedArray.swift @@ -20,8 +20,9 @@ public class JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where } public init(length: Int) { - super.init(id: Element.typedArrayClass.new(length).id) - _retain(id) + let jsObject = Element.typedArrayClass.new(length) + _retain(jsObject.id) + super.init(id: jsObject.id) } required public convenience init(arrayLiteral elements: Element...) { From 561b8a6d26b141807796bb7945f5a42eca16290f Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 23:22:25 -0400 Subject: [PATCH 18/22] Explain why _retain is necessary --- Sources/JavaScriptKit/JSTypedArray.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/JSTypedArray.swift index e05f28216..b6488e0dc 100644 --- a/Sources/JavaScriptKit/JSTypedArray.swift +++ b/Sources/JavaScriptKit/JSTypedArray.swift @@ -21,6 +21,11 @@ public class JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where public init(length: Int) { let jsObject = Element.typedArrayClass.new(length) + // _retain is necessary here because the JSObjectRef we used to create the array + // goes out of scope and is deinitialized when this init() returns, causing + // the JS side to decrement the object's reference count. JSTypedArray will also + // call _release() when deinitialized because it inherits from JSObjectRef, so this + // will not leak memory. _retain(jsObject.id) super.init(id: jsObject.id) } From b0ff949b9d86771b3b32ee336dbf003c97a930ca Mon Sep 17 00:00:00 2001 From: Jed Fox Date: Thu, 13 Aug 2020 23:28:37 -0400 Subject: [PATCH 19/22] Update _CJavaScriptKit.h --- Sources/_CJavaScriptKit/include/_CJavaScriptKit.h | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 81b0230f3..7e12d0e50 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -114,13 +114,11 @@ __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_release"))) extern void _release(const JavaScriptObjectRef ref); -__attribute__(( - __import_module__("javascript_kit"), - __import_name__("swjs_create_typed_array") - )) -extern void _create_typed_array(const JavaScriptTypedArrayKind kind, - const void *elementsPtr, const int length, - JavaScriptObjectRef *result_obj); +__attribute__((__import_module__("javascript_kit"), + __import_name__("swjs_create_typed_array"))) extern void +_create_typed_array(const JavaScriptTypedArrayKind kind, + const void *elementsPtr, const int length, + JavaScriptObjectRef *result_obj); #endif From d64def7bf31921ad8338d95ff602c2616425bf32 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 9 Sep 2020 11:35:14 +0900 Subject: [PATCH 20/22] Remove manual reference counting --- .../{ => BasicObjects}/JSTypedArray.swift | 29 ++++++++++--------- Sources/JavaScriptKit/XcodeSupport.swift | 1 - .../_CJavaScriptKit/include/_CJavaScriptKit.h | 4 --- 3 files changed, 16 insertions(+), 18 deletions(-) rename Sources/JavaScriptKit/{ => BasicObjects}/JSTypedArray.swift (84%) diff --git a/Sources/JavaScriptKit/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift similarity index 84% rename from Sources/JavaScriptKit/JSTypedArray.swift rename to Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index b6488e0dc..3806cfc28 100644 --- a/Sources/JavaScriptKit/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -9,37 +9,40 @@ public protocol TypedArrayElement: JSValueConvertible, JSValueConstructible { static var typedArrayClass: JSFunctionRef { get } } -public class JSTypedArray: JSObjectRef, ExpressibleByArrayLiteral where Element: TypedArrayElement { +public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLiteral where Element: TypedArrayElement { + let ref: JSObject + public func jsValue() -> JSValue { + .object(ref) + } + public subscript(_ index: Int) -> Element { get { - return Element.construct(from: getJSValue(this: self, index: Int32(index)))! + return Element.construct(from: getJSValue(this: ref, index: Int32(index)))! } set { - setJSValue(this: self, index: Int32(index), value: newValue.jsValue()) + setJSValue(this: ref, index: Int32(index), value: newValue.jsValue()) } } + + public init(_ object: JSObject) { + self.ref = object + } - public init(length: Int) { + public convenience init(length: Int) { let jsObject = Element.typedArrayClass.new(length) - // _retain is necessary here because the JSObjectRef we used to create the array - // goes out of scope and is deinitialized when this init() returns, causing - // the JS side to decrement the object's reference count. JSTypedArray will also - // call _release() when deinitialized because it inherits from JSObjectRef, so this - // will not leak memory. - _retain(jsObject.id) - super.init(id: jsObject.id) + self.init(jsObject) } required public convenience init(arrayLiteral elements: Element...) { self.init(elements) } - public init(_ array: [Element]) { + public convenience init(_ array: [Element]) { var resultObj = JavaScriptObjectRef() array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) } - super.init(id: resultObj) + self.init(JSObject(id: resultObj)) } public convenience init(_ stride: StrideTo) where Element: Strideable { diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 371c1f74c..ed42417c8 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -72,7 +72,6 @@ import _CJavaScriptKit _: JavaScriptHostFuncRef, _: UnsafePointer! ) { fatalError() } - func _retain(_: JavaScriptObjectRef) { fatalError() } func _release(_: JavaScriptObjectRef) { fatalError() } func _create_typed_array( _: JavaScriptTypedArrayKind, diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index 7e12d0e50..9bdfc9b63 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -106,10 +106,6 @@ __attribute__((__import_module__("javascript_kit"), _create_function(const JavaScriptHostFuncRef host_func_id, const JavaScriptObjectRef *func_ref_ptr); -__attribute__((__import_module__("javascript_kit"), - __import_name__("swjs_retain"))) extern void -_retain(const JavaScriptObjectRef ref); - __attribute__((__import_module__("javascript_kit"), __import_name__("swjs_release"))) extern void _release(const JavaScriptObjectRef ref); From fab45e149b185ed59b32a2c7d38b801f4999c77e Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Wed, 9 Sep 2020 11:37:11 +0900 Subject: [PATCH 21/22] Fix test cases --- .../TestSuites/Sources/PrimaryTests/main.swift | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift index 3be0a6f90..292e6f3a5 100644 --- a/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift +++ b/IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift @@ -343,14 +343,18 @@ try test("Closure Identifiers") { } func checkArray(_ array: [T]) throws where T: TypedArrayElement { - try expectEqual(JSTypedArray(array).toString!(), .string(jsStringify(array))) + try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array)) +} + +func toString(_ object: T) -> String { + return object.toString!().string! } func jsStringify(_ array: [Any]) -> String { array.map({ String(describing: $0) }).joined(separator: ",") } -test("TypedArray") { +try test("TypedArray") { let numbers = [UInt8](0 ... 255) let typedArray = JSTypedArray(numbers) try expectEqual(typedArray[12], 12) @@ -380,7 +384,7 @@ test("TypedArray") { } } -test("TypedArray_Mutation") { +try test("TypedArray_Mutation") { let array = JSTypedArray(length: 100) for i in 0..<100 { array[i] = i @@ -388,5 +392,5 @@ test("TypedArray_Mutation") { for i in 0..<100 { try expectEqual(i, array[i]) } - try expectEqual(array.toString!(), .string(jsStringify(Array(0..<100)))) + try expectEqual(toString(array.jsValue().object!), jsStringify(Array(0..<100))) } From f83a84c36bd9f006615a39a8e1de41be42130216 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 10 Sep 2020 15:58:09 +0900 Subject: [PATCH 22/22] Expose failable initializer --- .../JavaScriptKit/BasicObjects/JSTypedArray.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift index 3806cfc28..beb430a5f 100644 --- a/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift +++ b/Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift @@ -24,13 +24,19 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera } } - public init(_ object: JSObject) { + // This private initializer assumes that the passed object is TypedArray + private init(unsafe object: JSObject) { + self.ref = object + } + + public init?(_ object: JSObject) { + guard object.isInstanceOf(Element.typedArrayClass) else { return nil } self.ref = object } public convenience init(length: Int) { let jsObject = Element.typedArrayClass.new(length) - self.init(jsObject) + self.init(unsafe: jsObject) } required public convenience init(arrayLiteral elements: Element...) { @@ -42,7 +48,7 @@ public class JSTypedArray: JSValueConvertible, ExpressibleByArrayLitera array.withUnsafeBufferPointer { ptr in _create_typed_array(Element.typedArrayKind, ptr.baseAddress!, Int32(array.count), &resultObj) } - self.init(JSObject(id: resultObj)) + self.init(unsafe: JSObject(id: resultObj)) } public convenience init(_ stride: StrideTo) where Element: Strideable {