From 8f0a15464238781a90141b93558d79c14d101bd5 Mon Sep 17 00:00:00 2001 From: Manuel Burghard Date: Sun, 7 Jun 2020 16:07:55 +0200 Subject: [PATCH 1/2] - Defined _JS_Predef_Value_Global as an external symbol. - Switched to passing a RawJSValue pointers. - Added _instance_of function. - Added _copy_typed_array_content function. - Switched to using DataView to write memory. - Switched to using Double instead of Int32 in JSValue. --- Runtime/src/index.ts | 237 +++++++++--------- Sources/JavaScriptKit/JSFunction.swift | 12 +- Sources/JavaScriptKit/JSObject.swift | 49 +++- Sources/JavaScriptKit/JSValue.swift | 21 +- .../JavaScriptKit/JSValueConvertible.swift | 20 +- Sources/JavaScriptKit/XcodeSupport.swift | 21 +- Sources/_CJavaScriptKit/dummy.c | 1 + .../_CJavaScriptKit/include/_CJavaScriptKit.h | 46 ++-- 8 files changed, 211 insertions(+), 196 deletions(-) diff --git a/Runtime/src/index.ts b/Runtime/src/index.ts index d7642f323..1466abe3d 100644 --- a/Runtime/src/index.ts +++ b/Runtime/src/index.ts @@ -100,13 +100,12 @@ export class SwiftRuntime { const exports = this.instance.exports as any as SwiftRuntimeExportedFunctions; const argc = args.length const argv = exports.swjs_prepare_host_function_call(argc) + const uint32Memory = new Uint32Array(memory().buffer, argv, args.length * 3); for (let index = 0; index < args.length; index++) { - const argument = args[index] - const value = encodeValue(argument) - const base = argv + 12 * index - writeUint32(base, value.kind) - writeUint32(base + 4, value.payload1) - writeUint32(base + 8, value.payload2) + const argument = args[index]; + const offset = 12 * index; + const dataView = new DataView(memory().buffer, argv + offset, 12); + encodeValue(argument, dataView); } let output: any; const callback_func_ref = this.heap.allocHeap(function (result: any) { @@ -121,120 +120,107 @@ export class SwiftRuntime { const textEncoder = new TextEncoder(); // Only support utf-8 const readString = (ptr: pointer, len: number) => { - const uint8Memory = new Uint8Array(memory().buffer); - return textDecoder.decode(uint8Memory.subarray(ptr, ptr + len)); + const uint8Memory = new Uint8Array(memory().buffer, ptr, len); + return textDecoder.decode(uint8Memory); } const writeString = (ptr: pointer, value: string) => { const bytes = textEncoder.encode(value); - const uint8Memory = new Uint8Array(memory().buffer); + const uint8Memory = new Uint8Array(memory().buffer, ptr); for (const [index, byte] of bytes.entries()) { - uint8Memory[ptr + index] = byte + uint8Memory[index] = byte } - uint8Memory[ptr] } const readUInt32 = (ptr: pointer) => { - const uint8Memory = new Uint8Array(memory().buffer); - return uint8Memory[ptr + 0] - + (uint8Memory[ptr + 1] << 8) - + (uint8Memory[ptr + 2] << 16) - + (uint8Memory[ptr + 3] << 24) + const uint32Memory = new Uint32Array(memory().buffer, ptr); + return uint32Memory[0]; } const writeUint32 = (ptr: pointer, value: number) => { - 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 uint32Memory = new Uint32Array(memory().buffer, ptr); + uint32Memory[0] = (value & 0xffffffff); } const decodeValue = ( - kind: JavaScriptValueKind, - payload1: number, payload2: number - ) => { - switch (kind) { - case JavaScriptValueKind.Boolean: { - switch (payload1) { - case 0: return false - case 1: return true + dataView: DataView + ) => { + const kind = dataView.getUint32(0, true) + switch (kind) { + case JavaScriptValueKind.Boolean: { + switch (dataView.getUint32(Uint32Array.BYTES_PER_ELEMENT, true)) { + case 0: return false + case 1: return true + } + } + case JavaScriptValueKind.Number: { + return dataView.getFloat64(Uint32Array.BYTES_PER_ELEMENT, true); + } + case JavaScriptValueKind.String: { + return readString(dataView.getUint32(Uint32Array.BYTES_PER_ELEMENT, true), + dataView.getUint32(2 * Uint32Array.BYTES_PER_ELEMENT, true)) + } + case JavaScriptValueKind.Object: { + return this.heap.referenceHeap(dataView.getUint32(Uint32Array.BYTES_PER_ELEMENT, true)) + } + case JavaScriptValueKind.Null: { + return null + } + case JavaScriptValueKind.Undefined: { + return undefined + } + case JavaScriptValueKind.Function: { + return this.heap.referenceHeap(dataView.getUint32(Uint32Array.BYTES_PER_ELEMENT, true)) + } + default: + throw new Error(`Type kind "${kind}" is not supported`) } } - case JavaScriptValueKind.Number: { - return payload1 - } - case JavaScriptValueKind.String: { - return readString(payload1, payload2) - } - 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 encodeValue = (value: any) => { + const encodeValue = (value: any, dataView: DataView) => { if (value === null) { - return { - kind: JavaScriptValueKind.Null, - payload1: 0, - payload2: 0, - } + dataView.setUint32(0, JavaScriptValueKind.Null, true); + dataView.setUint32(Uint32Array.BYTES_PER_ELEMENT, 0, true); + dataView.setUint32(2 * Uint32Array.BYTES_PER_ELEMENT, 0, true); + return; } switch (typeof value) { case "boolean": { - return { - kind: JavaScriptValueKind.Boolean, - payload1: value ? 1 : 0, - payload2: 0, - } + dataView.setUint32(0, JavaScriptValueKind.Boolean, true); + dataView.setUint32(Uint32Array.BYTES_PER_ELEMENT, value ? 1 : 0, true); + dataView.setUint32(2 * Uint32Array.BYTES_PER_ELEMENT, 0, true); + break; } case "number": { - return { - kind: JavaScriptValueKind.Number, - payload1: value, - payload2: 0, - } + dataView.setUint32(0, JavaScriptValueKind.Number, true); + dataView.setFloat64(Uint32Array.BYTES_PER_ELEMENT, value, true); + break; } case "string": { const bytes = textEncoder.encode(value); - return { - kind: JavaScriptValueKind.String, - payload1: this.heap.allocHeap(value), - payload2: bytes.length, - } + dataView.setUint32(0, JavaScriptValueKind.String, true); + dataView.setUint32(Uint32Array.BYTES_PER_ELEMENT, this.heap.allocHeap(value), true); + dataView.setUint32(2 * Uint32Array.BYTES_PER_ELEMENT, bytes.length, true); + break; } case "undefined": { - return { - kind: JavaScriptValueKind.Undefined, - payload1: 0, - payload2: 0, - } + dataView.setUint32(0, JavaScriptValueKind.Undefined, true); + dataView.setUint32(Uint32Array.BYTES_PER_ELEMENT, 0, true); + dataView.setUint32(2 * Uint32Array.BYTES_PER_ELEMENT, 0, true); + break; } case "object": { - return { - kind: JavaScriptValueKind.Object, - payload1: this.heap.allocHeap(value), - payload2: 0, - } + dataView.setUint32(0, JavaScriptValueKind.Object, true); + dataView.setUint32(Uint32Array.BYTES_PER_ELEMENT, this.heap.allocHeap(value), true); + dataView.setUint32(2 * Uint32Array.BYTES_PER_ELEMENT, 0, true); + break; } case "function": { - return { - kind: JavaScriptValueKind.Function, - payload1: this.heap.allocHeap(value), - payload2: 0, - } + dataView.setUint32(0, JavaScriptValueKind.Function, true); + dataView.setUint32(Uint32Array.BYTES_PER_ELEMENT, this.heap.allocHeap(value), true); + dataView.setUint32(2 * Uint32Array.BYTES_PER_ELEMENT, 0, true); + + break; } default: throw new Error(`Type "${typeof value}" is not supported yet`) @@ -247,11 +233,9 @@ export class SwiftRuntime { const decodeValues = (ptr: pointer, length: number) => { let result = [] for (let index = 0; index < length; index++) { - const base = ptr + 12 * index - const kind = readUInt32(base) - const payload1 = readUInt32(base + 4) - const payload2 = readUInt32(base + 8) - result.push(decodeValue(kind, payload1, payload2)) + const offset = 12 * index; + const dataView = new DataView(memory().buffer, ptr + offset, 12); + result.push(decodeValue(dataView)) } return result } @@ -259,43 +243,37 @@ export class SwiftRuntime { return { swjs_set_prop: ( ref: ref, name: pointer, length: number, - kind: JavaScriptValueKind, - payload1: number, payload2: number + rawJSValuePtr: pointer ) => { const obj = this.heap.referenceHeap(ref); - Reflect.set(obj, readString(name, length), decodeValue(kind, payload1, payload2)) + const dataView = new DataView(memory().buffer, rawJSValuePtr, 12); + Reflect.set(obj, readString(name, length), decodeValue(dataView)) }, swjs_get_prop: ( ref: ref, name: pointer, length: number, - kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + rawJSValuePtr: pointer ) => { const obj = this.heap.referenceHeap(ref); const result = Reflect.get(obj, readString(name, length)); - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + const dataView = new DataView(memory().buffer, rawJSValuePtr, 12); + encodeValue(result, dataView); }, swjs_set_subscript: ( ref: ref, index: number, - kind: JavaScriptValueKind, - payload1: number, payload2: number + rawJSValuePtr: pointer ) => { const obj = this.heap.referenceHeap(ref); - Reflect.set(obj, index, decodeValue(kind, payload1, payload2)) + const dataView = new DataView(memory().buffer, rawJSValuePtr, 12); + Reflect.set(obj, index, decodeValue(dataView)) }, swjs_get_subscript: ( ref: ref, index: number, - kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + rawJSValuePtr: pointer ) => { const obj = this.heap.referenceHeap(ref); const result = Reflect.get(obj, index); - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + const dataView = new DataView(memory().buffer, rawJSValuePtr, 12); + encodeValue(result, dataView); }, swjs_load_string: (ref: ref, buffer: pointer) => { const string = this.heap.referenceHeap(ref); @@ -303,29 +281,23 @@ export class SwiftRuntime { }, swjs_call_function: ( ref: ref, argv: pointer, argc: number, - kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + rawJSValuePtr: pointer ) => { const func = this.heap.referenceHeap(ref) const result = Reflect.apply(func, undefined, decodeValues(argv, argc)) - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + const dataView = new DataView(memory().buffer, rawJSValuePtr, 12); + encodeValue(result, dataView); }, swjs_call_function_with_this: ( obj_ref: ref, func_ref: ref, argv: pointer, argc: number, - kind_ptr: pointer, - payload1_ptr: pointer, payload2_ptr: pointer + rawJSValuePtr: pointer ) => { const obj = this.heap.referenceHeap(obj_ref) const func = this.heap.referenceHeap(func_ref) const result = Reflect.apply(func, obj, decodeValues(argv, argc)) - const { kind, payload1, payload2 } = encodeValue(result); - writeUint32(kind_ptr, kind); - writeUint32(payload1_ptr, payload1); - writeUint32(payload2_ptr, payload2); + const dataView = new DataView(memory().buffer, rawJSValuePtr, 12); + encodeValue(result, dataView); }, swjs_create_function: ( host_func_id: number, @@ -348,7 +320,26 @@ export class SwiftRuntime { }, swjs_destroy_ref: (ref: ref) => { this.heap.freeHeap(ref) + }, + swjs_instance_of: ( + ref: ref, name: pointer, length: number, + rawJSValuePtr: pointer) => { + + const obj = this.heap.referenceHeap(ref) + const cName = readString(name, length) + const result = (cName in window) && (obj instanceof (window as any)[cName]) + const dataView = new DataView(memory().buffer, rawJSValuePtr, 12); + encodeValue(result, dataView); + }, + swjs_copy_typed_array_content: ( + ref: ref, elementsPtr: pointer, length: number) => { + + const obj = this.heap.referenceHeap(ref) + const view = new obj.constructor(memory().buffer, elementsPtr, length); + view.forEach(function(value: number, index: number, array: any){ + obj[index] = value; + }); } } } -} \ No newline at end of file +} diff --git a/Sources/JavaScriptKit/JSFunction.swift b/Sources/JavaScriptKit/JSFunction.swift index 211988d90..ca0297f9e 100644 --- a/Sources/JavaScriptKit/JSFunction.swift +++ b/Sources/JavaScriptKit/JSFunction.swift @@ -11,8 +11,8 @@ public class JSFunctionRef: JSObjectRef { let argc = bufferPointer.count var result = RawJSValue() _call_function( - self.id, argv, Int32(argc), - &result.kind, &result.payload1, &result.payload2 + self._id, argv, Int32(argc), + &result ) return result } @@ -29,9 +29,9 @@ public class JSFunctionRef: JSObjectRef { let argv = bufferPointer.baseAddress let argc = bufferPointer.count var result = RawJSValue() - _call_function_with_this(this.id, - self.id, argv, Int32(argc), - &result.kind, &result.payload1, &result.payload2 + _call_function_with_this(this._id, + self._id, argv, Int32(argc), + &result ) return result } @@ -46,7 +46,7 @@ public class JSFunctionRef: JSObjectRef { let argc = bufferPointer.count var resultObj = JavaScriptPayload() _call_new( - self.id, argv, Int32(argc), + self._id, argv, Int32(argc), &resultObj ) return JSObjectRef(id: resultObj) diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/JSObject.swift index cfdae5c47..e55e34b7b 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/JSObject.swift @@ -2,9 +2,9 @@ import _CJavaScriptKit @dynamicMemberLookup public class JSObjectRef: Equatable { - let id: UInt32 - init(id: UInt32) { - self.id = id + public let _id: UInt32 + public init(id: UInt32) { + self._id = id } public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? { @@ -17,40 +17,63 @@ public class JSObjectRef: Equatable { } public subscript(dynamicMember name: String) -> JSValue { - get { get(name) } - set { set(name, newValue) } + get { js_get(name) } + set { js_set(name, newValue) } } - public func get(_ name: String) -> JSValue { + public subscript(dynamicMember name: String) -> Type { + get { js_get(name).fromJSValue() } + set { js_set(name, newValue.jsValue()) } + } + + func js_get(_ name: String) -> JSValue { getJSValue(this: self, name: name) } - public func set(_ name: String, _ value: JSValue) { + func js_set(_ name: String, _ value: JSValue) { setJSValue(this: self, name: name, value: value) } - public func get(_ index: Int) -> JSValue { + func js_get(_ index: Int) -> JSValue { getJSValue(this: self, index: Int32(index)) } public subscript(_ index: Int) -> JSValue { - get { get(index) } - set { set(index, newValue) } + get { js_get(index) } + set { js_set(index, newValue) } } - public func set(_ index: Int, _ value: JSValue) { + func js_set(_ index: Int, _ value: JSValue) { setJSValue(this: self, index: Int32(index), value: value) } + public func instanceOf(_ constructor: String) -> Bool { + var result = RawJSValue() + _instance_of(_id, constructor, Int32(constructor.count), &result) + + return result.jsValue().fromJSValue() + } + public static let global = JSObjectRef(id: _JS_Predef_Value_Global) - deinit { _destroy_ref(id) } + deinit { _destroy_ref(_id) } public static func == (lhs: JSObjectRef, rhs: JSObjectRef) -> Bool { - return lhs.id == rhs.id + return lhs._id == rhs._id + } } public func jsValue() -> JSValue { .object(self) } } + +extension JSObjectRef { + + public func copyTypedArrayContent(_ array: [Type]) { + + array.withUnsafeBufferPointer { (ptr) in + _copy_typed_array_content(_id, ptr.baseAddress, Int32(array.count)) + } + } +} diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index 265aaa26d..d414758f0 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -3,7 +3,7 @@ import _CJavaScriptKit public enum JSValue: Equatable { case boolean(Bool) case string(String) - case number(Int32) + case number(Double) case object(JSObjectRef) case null case undefined @@ -22,7 +22,7 @@ public enum JSValue: Equatable { default: return nil } } - public var number: Int32? { + public var number: Double? { switch self { case let .number(number): return number default: return nil @@ -58,33 +58,28 @@ extension JSValue: ExpressibleByIntegerLiteral { public func getJSValue(this: JSObjectRef, name: String) -> JSValue { var rawValue = RawJSValue() - _get_prop(this.id, name, Int32(name.count), - &rawValue.kind, - &rawValue.payload1, &rawValue.payload2) + _get_prop(this._id, name, Int32(name.count), &rawValue) return rawValue.jsValue() } public func setJSValue(this: JSObjectRef, name: String, value: JSValue) { value.withRawJSValue { rawValue in - _set_prop(this.id, name, Int32(name.count), rawValue.kind, rawValue.payload1, rawValue.payload2) + _set_prop(this._id, name, Int32(name.count), &rawValue) } } - public func getJSValue(this: JSObjectRef, index: Int32) -> JSValue { var rawValue = RawJSValue() - _get_subscript(this.id, index, - &rawValue.kind, - &rawValue.payload1, &rawValue.payload2) + _get_subscript(this._id, index, &rawValue) return rawValue.jsValue() } public func setJSValue(this: JSObjectRef, index: Int32, value: JSValue) { value.withRawJSValue { rawValue in - _set_subscript(this.id, index, - rawValue.kind, - rawValue.payload1, rawValue.payload2) + _set_subscript(this._id, index, &rawValue) + } +} } } diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index ae6ea22ae..60d437bcb 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -69,7 +69,7 @@ extension RawJSValue: JSValueConvertible { case JavaScriptValueKind_Boolean: return .boolean(payload1 != 0) case JavaScriptValueKind_Number: - return .number(Int32(bitPattern: payload1)) + return .number(Double(bitPattern: UInt64(payload1) | (UInt64(payload2) << 32))) case JavaScriptValueKind_String: // +1 for null terminator let buffer = malloc(Int(payload2 + 1))!.assumingMemoryBound(to: UInt8.self) @@ -93,7 +93,7 @@ extension RawJSValue: JSValueConvertible { } extension JSValue { - func withRawJSValue(_ body: (RawJSValue) -> T) -> T { + func withRawJSValue(_ body: (inout RawJSValue) -> T) -> T { let kind: JavaScriptValueKind let payload1: JavaScriptPayload let payload2: JavaScriptPayload @@ -104,18 +104,18 @@ extension JSValue { payload2 = 0 case let .number(numberValue): kind = JavaScriptValueKind_Number - payload1 = JavaScriptPayload(bitPattern: numberValue) - payload2 = 0 + payload1 = UInt32(numberValue.bitPattern & 0x00000000ffffffff) + payload2 = UInt32((numberValue.bitPattern & 0xffffffff00000000) >> 32) case var .string(stringValue): kind = JavaScriptValueKind_String return stringValue.withUTF8 { bufferPtr in let ptrValue = UInt32(UInt(bitPattern: bufferPtr.baseAddress!)) - let rawValue = RawJSValue(kind: kind, payload1: ptrValue, payload2: JavaScriptPayload(bufferPtr.count)) - return body(rawValue) + var rawValue = RawJSValue(kind: kind, payload1: ptrValue, payload2: JavaScriptPayload(bufferPtr.count)) + return body(&rawValue) } case let .object(ref): kind = JavaScriptValueKind_Object - payload1 = ref.id + payload1 = ref._id payload2 = 0 case .null: kind = JavaScriptValueKind_Null @@ -127,11 +127,11 @@ extension JSValue { payload2 = 0 case let .function(functionRef): kind = JavaScriptValueKind_Function - payload1 = functionRef.id + payload1 = functionRef._id payload2 = 0 } - let rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2) - return body(rawValue) + var rawValue = RawJSValue(kind: kind, payload1: payload1, payload2: payload2) + return body(&rawValue) } } diff --git a/Sources/JavaScriptKit/XcodeSupport.swift b/Sources/JavaScriptKit/XcodeSupport.swift index 96abee01f..7904cb5ef 100644 --- a/Sources/JavaScriptKit/XcodeSupport.swift +++ b/Sources/JavaScriptKit/XcodeSupport.swift @@ -9,27 +9,19 @@ import _CJavaScriptKit func _set_prop( _ _this: JavaScriptObjectRef, _ prop: UnsafePointer!, _ length: Int32, - _ kind: JavaScriptValueKind, - _ payload1: JavaScriptPayload, - _ payload2: JavaScriptPayload) { fatalError() } + _ rawJSValue: UnsafePointer!) { fatalError() } func _get_prop( _ _this: JavaScriptObjectRef, _ prop: UnsafePointer!, _ length: Int32, - _ kind: UnsafeMutablePointer!, - _ payload1: UnsafeMutablePointer!, - _ payload2: UnsafeMutablePointer!) { fatalError() } + _ rawJSValue: UnsafeMutablePointer!) { fatalError() } func _set_subscript( _ _this: JavaScriptObjectRef, _ index: Int32, - _ kind: JavaScriptValueKind, - _ payload1: JavaScriptPayload, - _ payload2: JavaScriptPayload) { fatalError() } + _ rawJSValue: UnsafePointer!) { fatalError() } func _get_subscript( _ _this: JavaScriptObjectRef, _ index: Int32, - _ kind: UnsafeMutablePointer!, - _ payload1: UnsafeMutablePointer!, - _ payload2: UnsafeMutablePointer!) { fatalError() } + _ rawJSValue: UnsafeMutablePointer!) { fatalError() } func _load_string( _ ref: JavaScriptObjectRef, _ buffer: UnsafeMutablePointer!) { fatalError() } @@ -54,4 +46,9 @@ func _create_function( _ host_func_id: JavaScriptHostFuncRef, _ func_ref_ptr: UnsafePointer!) { fatalError() } func _destroy_ref(_ ref: JavaScriptObjectRef) { fatalError() } +func _instance_of( + _ ref: JavaScriptObjectRef, + _ constructorName: UnsafePointer!, + _ constructorLength: Int32, + _ rawJSValue: UnsafeMutablePointer!) { fatalError() } #endif diff --git a/Sources/_CJavaScriptKit/dummy.c b/Sources/_CJavaScriptKit/dummy.c index 8b1378917..ff2c17895 100644 --- a/Sources/_CJavaScriptKit/dummy.c +++ b/Sources/_CJavaScriptKit/dummy.c @@ -1 +1,2 @@ +const unsigned int _JS_Predef_Value_Global = 0; diff --git a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h index fb81f928f..5202bc434 100644 --- a/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h +++ b/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h @@ -26,7 +26,7 @@ typedef struct { } RawJSValue; -const unsigned int _JS_Predef_Value_Global = 0; +extern const unsigned int _JS_Predef_Value_Global; __attribute__(( __import_module__("javascript_kit"), @@ -35,9 +35,7 @@ __attribute__(( extern void _set_prop( const JavaScriptObjectRef _this, const char *prop, const int length, - const JavaScriptValueKind kind, - const JavaScriptPayload payload1, - const JavaScriptPayload payload2 + const RawJSValue *rawJSValue ); __attribute__(( @@ -47,9 +45,7 @@ __attribute__(( extern void _get_prop( const JavaScriptObjectRef _this, const char *prop, const int length, - JavaScriptValueKind *kind, - JavaScriptPayload *payload1, - JavaScriptPayload *payload2 + RawJSValue *rawJSValue ); __attribute__(( @@ -59,9 +55,7 @@ __attribute__(( extern void _set_subscript( const JavaScriptObjectRef _this, const int length, - const JavaScriptValueKind kind, - const JavaScriptPayload payload1, - const JavaScriptPayload payload2 + const RawJSValue *rawJSValue ); __attribute__(( @@ -71,9 +65,7 @@ __attribute__(( extern void _get_subscript( const JavaScriptObjectRef _this, const int length, - JavaScriptValueKind *kind, - JavaScriptPayload *payload1, - JavaScriptPayload *payload2 + RawJSValue *rawJSValue ); __attribute__(( @@ -92,9 +84,7 @@ __attribute__(( extern void _call_function( const JavaScriptObjectRef ref, const RawJSValue *argv, const int argc, - JavaScriptValueKind *result_kind, - JavaScriptPayload *result_payload1, - JavaScriptPayload *result_payload2 + RawJSValue *rawJSValue ); __attribute__(( @@ -105,9 +95,7 @@ extern void _call_function_with_this( const JavaScriptObjectRef _this, const JavaScriptObjectRef func_ref, const RawJSValue *argv, const int argc, - JavaScriptValueKind *result_kind, - JavaScriptPayload *result_payload1, - JavaScriptPayload *result_payload2 + RawJSValue *rawJSValue ); __attribute__(( @@ -137,4 +125,24 @@ extern void _destroy_ref( const JavaScriptObjectRef ref ); +__attribute__(( + __import_module__("javascript_kit"), + __import_name__("swjs_instance_of") +)) +extern void _instance_of( + const JavaScriptObjectRef _this, + const char *constructor, const int length, + RawJSValue *rawJSValue + +); + +__attribute__(( + __import_module__("javascript_kit"), + __import_name__("swjs_copy_typed_array_content") +)) +extern void _copy_typed_array_content( + const JavaScriptObjectRef _this, + const void *elementsPtr, const int length +); + #endif /* _CJavaScriptKit_h */ From 260a13ad347a4b5352481582ad73423237185d00 Mon Sep 17 00:00:00 2001 From: Manuel Burghard Date: Sun, 7 Jun 2020 16:09:27 +0200 Subject: [PATCH 2/2] - Changes related to WebIDL support. --- Sources/JavaScriptKit/JSFunction.swift | 72 ++++- Sources/JavaScriptKit/JSObject.swift | 28 +- Sources/JavaScriptKit/JSValue.swift | 70 ++++- .../JavaScriptKit/JSValueConvertible.swift | 209 +++++++++++++-- Sources/JavaScriptKit/Support.swift | 246 ++++++++++++++++++ 5 files changed, 589 insertions(+), 36 deletions(-) create mode 100644 Sources/JavaScriptKit/Support.swift diff --git a/Sources/JavaScriptKit/JSFunction.swift b/Sources/JavaScriptKit/JSFunction.swift index ca0297f9e..05e9c2832 100644 --- a/Sources/JavaScriptKit/JSFunction.swift +++ b/Sources/JavaScriptKit/JSFunction.swift @@ -3,8 +3,12 @@ import _CJavaScriptKit @dynamicCallable public class JSFunctionRef: JSObjectRef { + public override class func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isFunction + } + @discardableResult - public func dynamicallyCall(withArguments arguments: [JSValueConvertible]) -> JSValue { + public func dynamicallyCall(withArguments arguments: [JSValueEncodable]) -> JSValue { let result = arguments.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer -> RawJSValue in let argv = bufferPointer.baseAddress @@ -20,10 +24,10 @@ public class JSFunctionRef: JSObjectRef { return result.jsValue() } - public func apply(this: JSObjectRef, arguments: JSValueConvertible...) -> JSValue { + public func apply(this: JSObjectRef, arguments: JSValueEncodable...) -> JSValue { apply(this: this, argumentList: arguments) } - public func apply(this: JSObjectRef, argumentList: [JSValueConvertible]) -> JSValue { + public func apply(this: JSObjectRef, argumentList: [JSValueEncodable]) -> JSValue { let result = argumentList.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer -> RawJSValue in let argv = bufferPointer.baseAddress @@ -39,7 +43,7 @@ public class JSFunctionRef: JSObjectRef { return result.jsValue() } - public func new(_ arguments: JSValueConvertible...) -> JSObjectRef { + public func new(_ arguments: JSValueEncodable...) -> JSObjectRef { return arguments.withRawJSValues { rawValues in rawValues.withUnsafeBufferPointer { bufferPointer in let argv = bufferPointer.baseAddress @@ -54,7 +58,7 @@ public class JSFunctionRef: JSObjectRef { } } - static var sharedFunctions: [([JSValue]) -> JSValue] = [] + public static var sharedFunctions: [([JSValue]) -> JSValue] = [] public static func from(_ body: @escaping ([JSValue]) -> JSValue) -> JSFunctionRef { let id = JavaScriptHostFuncRef(sharedFunctions.count) sharedFunctions.append(body) @@ -64,6 +68,15 @@ public class JSFunctionRef: JSObjectRef { return JSFunctionRef(id: funcRef) } + public convenience required init(jsValue: JSValue) { + switch jsValue { + case .function(let value): + self.init(id: value._id) + default: + fatalError() + } + } + public override func jsValue() -> JSValue { .function(self) } @@ -94,3 +107,52 @@ public func _call_host_function( let callbackFuncRef = JSFunctionRef(id: callbackFuncRef) _ = callbackFuncRef(result) } + + +extension JSFunctionRef { + + public static func from(_ body: @escaping (A0) -> RT) -> JSFunctionRef { + + return from({ arguments in body(arguments[0].fromJSValue()).jsValue() }) + } + + public static func from(_ body: @escaping (A0, A1) -> RT) -> JSFunctionRef { + + return from({ arguments in body(arguments[0].fromJSValue(), arguments[1].fromJSValue()).jsValue() }) + } + + public static func from(_ body: @escaping (A0, A1, A2) -> RT) -> JSFunctionRef { + + return from({ arguments in body(arguments[0].fromJSValue(), arguments[1].fromJSValue(), arguments[2].fromJSValue()).jsValue() }) + } + + public static func from(_ body: @escaping (A0, A1, A2, A3) -> RT) -> JSFunctionRef { + + return from({ arguments in body(arguments[0].fromJSValue(), arguments[1].fromJSValue(), arguments[2].fromJSValue(), arguments[3].fromJSValue()).jsValue() }) + } + + public static func from(_ body: @escaping (A0, A1, A2, A3, A4) -> RT) -> JSFunctionRef { + + return from({ arguments in body(arguments[0].fromJSValue(), arguments[1].fromJSValue(), arguments[2].fromJSValue(), arguments[3].fromJSValue(), arguments[4].fromJSValue()).jsValue() }) + } + + public func wrappedClosure() -> (A0) -> RT { + return { (arg0) in self.dynamicallyCall(withArguments: [arg0]).fromJSValue() } + } + + public func wrappedClosure() -> (A0, A1) -> RT { + return { (arg0, arg1) in self.dynamicallyCall(withArguments: [arg0, arg1]).fromJSValue() } + } + + public func wrappedClosure() -> (A0, A1, A2) -> RT { + return { (arg0, arg1, arg2) in self.dynamicallyCall(withArguments: [arg0, arg1, arg2]).fromJSValue() } + } + + public func wrappedClosure() -> (A0, A1, A2, A3) -> RT { + return { (arg0, arg1, arg2, arg3) in self.dynamicallyCall(withArguments: [arg0, arg1, arg2, arg3]).fromJSValue() } + } + + public func wrappedClosure() -> (A0, A1, A2, A3, A4) -> RT { + return { (arg0, arg1, arg2, arg3, arg4) in self.dynamicallyCall(withArguments: [arg0, arg1, arg2, arg3, arg4]).fromJSValue() } + } +} diff --git a/Sources/JavaScriptKit/JSObject.swift b/Sources/JavaScriptKit/JSObject.swift index e55e34b7b..4e692a3fc 100644 --- a/Sources/JavaScriptKit/JSObject.swift +++ b/Sources/JavaScriptKit/JSObject.swift @@ -2,15 +2,29 @@ import _CJavaScriptKit @dynamicMemberLookup public class JSObjectRef: Equatable { + + public class func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isObject + } + + private var functionCache = [String : JSFunctionRef]() public let _id: UInt32 public init(id: UInt32) { self._id = id } - public subscript(dynamicMember name: String) -> ((JSValueConvertible...) -> JSValue)? { + public subscript(dynamicMember name: String) -> ((JSValueEncodable...) -> JSValue)? { get { - guard let function = self[dynamicMember: name].function else { return nil } - return { (arguments: JSValueConvertible...) in + let function: JSFunctionRef + if let f = functionCache[name] { + function = f + } else if let f = self[dynamicMember: name].function { + functionCache[name] = f + function = f + } else { + return nil + } + return { (arguments: JSValueEncodable...) in function.apply(this: self, argumentList: arguments) } } @@ -61,6 +75,14 @@ public class JSObjectRef: Equatable { public static func == (lhs: JSObjectRef, rhs: JSObjectRef) -> Bool { return lhs._id == rhs._id } + + public convenience required init(jsValue: JSValue) { + switch jsValue { + case .object(let value): + self.init(id: value._id) + default: + fatalError() + } } public func jsValue() -> JSValue { diff --git a/Sources/JavaScriptKit/JSValue.swift b/Sources/JavaScriptKit/JSValue.swift index d414758f0..c25a5ccc9 100644 --- a/Sources/JavaScriptKit/JSValue.swift +++ b/Sources/JavaScriptKit/JSValue.swift @@ -28,20 +28,59 @@ public enum JSValue: Equatable { default: return nil } } + public var object: JSObjectRef? { switch self { case let .object(object): return object default: return nil } } - public var isNull: Bool { return self == .null } - public var isUndefined: Bool { return self == .undefined } + public var function: JSFunctionRef? { switch self { case let .function(function): return function default: return nil } } + + public var isBoolean: Bool { + guard case .boolean = self else { return false } + return true + } + + public var isString: Bool { + guard case .string = self else { return false } + return true + } + + public var isNumber: Bool { + guard case .number = self else { return false } + return true + } + + public var isObject: Bool { + guard case .object = self else { return false } + return true + } + + public var isNull: Bool { + return self == .null + } + + public var isUndefined: Bool { + return self == .undefined + } + + public var isFunction: Bool { + guard case .function = self else { return false } + return true + } +} + +extension JSValue { + public func fromJSValue() -> Type where Type: JSValueDecodable { + return Type(jsValue: self) + } } extension JSValue: ExpressibleByStringLiteral { @@ -52,6 +91,12 @@ extension JSValue: ExpressibleByStringLiteral { extension JSValue: ExpressibleByIntegerLiteral { public init(integerLiteral value: Int32) { + self = .number(Double(value)) + } +} + +extension JSValue: ExpressibleByFloatLiteral { + public init(floatLiteral value: Double) { self = .number(value) } } @@ -80,6 +125,27 @@ public func setJSValue(this: JSObjectRef, index: Int32, value: JSValue) { _set_subscript(this._id, index, &rawValue) } } + +extension JSValue { + + public func instanceOf(_ constructor: String) -> Bool { + + switch self { + case .boolean: + return constructor == "Bool" + case .string: + return constructor == "String" + case .number: + fatalError() + case .object(let ref): + return ref.instanceOf(constructor) + case .null: + fatalError() + case .undefined: + fatalError() + case .function(_): + fatalError() + } } } diff --git a/Sources/JavaScriptKit/JSValueConvertible.swift b/Sources/JavaScriptKit/JSValueConvertible.swift index 60d437bcb..8fa2fb8b3 100644 --- a/Sources/JavaScriptKit/JSValueConvertible.swift +++ b/Sources/JavaScriptKit/JSValueConvertible.swift @@ -1,59 +1,200 @@ import _CJavaScriptKit -public protocol JSValueConvertible { + +public protocol JSBridgedType: JSValueCodable, CustomStringConvertible { + var objectRef: JSObjectRef { get } + init(objectRef: JSObjectRef) +} + +public protocol JSValueEncodable { func jsValue() -> JSValue } -extension JSValue: JSValueConvertible { +public protocol JSValueDecodable { + init(jsValue: JSValue) + + static func canDecode(from jsValue: JSValue) -> Bool +} + +extension JSBridgedType { + + public var description: String { + return objectRef.toString!().fromJSValue() + } +} + +public typealias JSValueCodable = JSValueEncodable & JSValueDecodable + +extension JSBridgedType { + + public static func canDecode(from jsValue: JSValue) -> Bool { + jsValue.isObject && jsValue.instanceOf(String(describing: Self.self)) + } + + public init(jsValue: JSValue) { + + self.init(objectRef: jsValue.object!) + } + + public func jsValue() -> JSValue { + return JSValue.object(objectRef) + } +} + +extension JSValue: JSValueCodable { + + public static func canDecode(from: JSValue) -> Bool { + return true + } + + public init(jsValue: JSValue) { + self = jsValue + } + public func jsValue() -> JSValue { self } } -extension Bool: JSValueConvertible { +extension Bool: JSValueCodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isBoolean + } + + public init(jsValue: JSValue) { + switch jsValue { + case .boolean(let value): + self = value + default: + fatalError("JSValue \(jsValue) is not decodable to Bool") + } + } + public func jsValue() -> JSValue { .boolean(self) } } -extension Int: JSValueConvertible { - public func jsValue() -> JSValue { .number(Int32(self)) } +extension Int: JSValueCodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = Int(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { .number(Double(self)) } } -extension String: JSValueConvertible { +extension Double: JSValueCodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = value + default: + fatalError() + } + } + + public func jsValue() -> JSValue { .number(self) } +} + +extension String: JSValueCodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isString + } + + public init(jsValue: JSValue) { + switch jsValue { + case .string(let value): + self = value + default: + fatalError() + } + } + public func jsValue() -> JSValue { .string(self) } } -extension JSObjectRef: JSValueConvertible { +extension JSObjectRef: JSValueCodable { + // `JSObjectRef.jsValue` is defined in JSObjectRef.swift to be able to overridden // from `JSFunctionRef` } private let Object = JSObjectRef.global.Object.function! -extension Dictionary where Value: JSValueConvertible, Key == String { - public func jsValue() -> JSValue { - Swift.Dictionary.jsValue(self)() - } -} +extension Dictionary: JSValueEncodable where Value: JSValueEncodable, Key == String { -extension Dictionary: JSValueConvertible where Value == JSValueConvertible, Key == String { public func jsValue() -> JSValue { let object = Object.new() for (key, value) in self { - object.set(key, value.jsValue()) + object.js_set(key, value.jsValue()) } return .object(object) } } -private let Array = JSObjectRef.global.Array.function! +extension Dictionary: JSValueDecodable where Value: JSValueDecodable, Key == String { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isObject + } + + public init(jsValue: JSValue) { + + let objectRef: JSObjectRef = jsValue.object! + + let keys: [String] = Object.keys!(objectRef.jsValue()).fromJSValue() + self = Dictionary(uniqueKeysWithValues: keys.map({ + return ($0, objectRef[dynamicMember: $0].fromJSValue()) + })) + } +} + +extension Optional: JSValueDecodable where Wrapped: JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNull || Wrapped.canDecode(from: jsValue) + } + + public init(jsValue: JSValue) { + switch jsValue { + case .null: + self = .none + default: + self = Wrapped(jsValue: jsValue) + } + } +} + +extension Optional: JSValueEncodable where Wrapped: JSValueEncodable { -extension Array where Element: JSValueConvertible { public func jsValue() -> JSValue { - Swift.Array.jsValue(self)() + switch self { + case .none: return .null + case .some(let wrapped): return wrapped.jsValue() + } } } -extension Array: JSValueConvertible where Element == JSValueConvertible { +private let JSArray = JSObjectRef.global.Array.function! + +extension Array: JSValueEncodable where Element: JSValueEncodable { + + public func jsValue() -> JSValue { - let array = Array.new(count) + let array = JSArray.new(count) for (index, element) in self.enumerated() { array[index] = element.jsValue() } @@ -61,7 +202,25 @@ extension Array: JSValueConvertible where Element == JSValueConvertible { } } -extension RawJSValue: JSValueConvertible { +extension Array: JSValueDecodable where Element: JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isObject + } + + public init(jsValue: JSValue) { + + let objectRef: JSObjectRef = jsValue.object! + let count: Int = objectRef.length.fromJSValue() + + self = (0 ..< count).map { + return objectRef[$0].fromJSValue() + } + } +} + +extension RawJSValue: JSValueEncodable { + public func jsValue() -> JSValue { switch kind { case JavaScriptValueKind_Invalid: @@ -135,12 +294,10 @@ extension JSValue { } } - - -extension Array where Element == JSValueConvertible { +extension Array where Element == JSValueEncodable { func withRawJSValues(_ body: ([RawJSValue]) -> T) -> T { func _withRawJSValues( - _ values: [JSValueConvertible], _ index: Int, + _ values: [JSValueEncodable], _ index: Int, _ results: inout [RawJSValue], _ body: ([RawJSValue]) -> T) -> T { if index == values.count { return body(results) } return values[index].jsValue().withRawJSValue { (rawValue) -> T in @@ -153,8 +310,8 @@ extension Array where Element == JSValueConvertible { } } -extension Array where Element: JSValueConvertible { +extension Array where Element: JSValueEncodable { func withRawJSValues(_ body: ([RawJSValue]) -> T) -> T { - Swift.Array.withRawJSValues(self)(body) + Swift.Array.withRawJSValues(self)(body) } } diff --git a/Sources/JavaScriptKit/Support.swift b/Sources/JavaScriptKit/Support.swift new file mode 100644 index 000000000..8a2735b28 --- /dev/null +++ b/Sources/JavaScriptKit/Support.swift @@ -0,0 +1,246 @@ + +extension UInt8: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = UInt8(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension UInt16: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = UInt16(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension UInt32: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = UInt32(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension UInt64: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = UInt64(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension Int8: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = Int8(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension Int16: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = Int16(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension Int32: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = Int32(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension Int64: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = Int64(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +extension Float: JSValueEncodable, JSValueDecodable { + + public static func canDecode(from jsValue: JSValue) -> Bool { + return jsValue.isNumber + } + + public init(jsValue: JSValue) { + switch jsValue { + case .number(let value): + self = Float(value) + default: + fatalError() + } + } + + public func jsValue() -> JSValue { + return .number(Double(self)) + } +} + +protocol _AnyJSValueCodable { + + func jsValue() -> JSValue +} + +public struct AnyJSValueCodable: JSValueCodable, ExpressibleByNilLiteral { + + public static func canDecode(from jsValue: JSValue) -> Bool { + true + } + + public static let void = AnyJSValueCodable(jsValue: .undefined) + + private struct Box: _AnyJSValueCodable { + let value: T + + func jsValue() -> JSValue { + return value.jsValue() + } + } + + private struct ConcreteBox: _AnyJSValueCodable { + let value: JSValue + + func jsValue() -> JSValue { + return value + } + } + + private let value: _AnyJSValueCodable + + public init(_ value: T) where T: JSValueEncodable { + self.value = Box(value: value) + } + + public init(jsValue: JSValue) { + self.value = ConcreteBox(value: jsValue) + } + + public init(nilLiteral: ()) { + self.value = ConcreteBox(value: .null) + } + + public func jsValue() -> JSValue { + return value.jsValue() + } + + public func fromJSValue() -> Type { + return jsValue().fromJSValue() + } +} + + +public func staticCast(_ ref: JSBridgedType) -> Type { + + return Type(objectRef: ref.objectRef) +} + +public func dynamicCast(_ ref: JSBridgedType) -> Type? { + + guard ref.objectRef.instanceOf(String(describing: Type.self)) else { + return nil + } + return staticCast(ref) +}