Skip to content

Commit 0a2eae2

Browse files
Support JavaScript function invocation
1 parent 1719b8d commit 0a2eae2

File tree

8 files changed

+160
-20
lines changed

8 files changed

+160
-20
lines changed
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import _CJavaScriptKit
2+
3+
@dynamicCallable
4+
public class JSFunctionRef: Equatable {
5+
let id: UInt32
6+
7+
init(id: UInt32) {
8+
self.id = id
9+
}
10+
11+
public static func == (lhs: JSFunctionRef, rhs: JSFunctionRef) -> Bool {
12+
return lhs.id == rhs.id
13+
}
14+
15+
public func dynamicallyCall(withArguments arguments: [JSValue]) -> JSValue {
16+
let result = arguments.withRawJSValues { rawValues in
17+
rawValues.withUnsafeBufferPointer { bufferPointer -> RawJSValue in
18+
let argv = bufferPointer.baseAddress
19+
let argc = bufferPointer.count
20+
var result = RawJSValue()
21+
_call_function(
22+
self.id, argv, Int32(argc),
23+
&result.kind, &result.payload1, &result.payload2
24+
)
25+
return result
26+
}
27+
}
28+
return result.jsValue()
29+
}
30+
}

src/swift/Sources/JavaScriptKit/JSValue.swift

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,6 @@ public class JSObjectRef: Equatable {
1818
}
1919
}
2020

21-
public class JSFunctionRef: Equatable {
22-
let id: UInt32
23-
24-
init(id: UInt32) {
25-
self.id = id
26-
}
27-
28-
public static func == (lhs: JSFunctionRef, rhs: JSFunctionRef) -> Bool {
29-
return lhs.id == rhs.id
30-
}
31-
}
3221

3322
public enum JSValue: Equatable {
3423
case boolean(Bool)

src/swift/Sources/JavaScriptKit/JSValueConvertible.swift

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,6 @@ extension Bool: JSValueConvertible {
1010
}
1111
}
1212

13-
struct RawJSValue {
14-
var kind: JavaScriptValueKind = JavaScriptValueKind_Invalid
15-
var payload1: JavaScriptPayload = 0
16-
var payload2: JavaScriptPayload = 0
17-
}
18-
1913
extension RawJSValue: JSValueConvertible {
2014
func jsValue() -> JSValue {
2115
switch kind {
@@ -89,3 +83,19 @@ extension JSValue {
8983
return body(rawValue)
9084
}
9185
}
86+
87+
extension Array where Element == JSValue {
88+
func withRawJSValues<T>(_ body: ([RawJSValue]) -> T) -> T {
89+
func _withCollectedRawJSValue<T>(
90+
_ values: [JSValue], _ index: Int,
91+
_ results: inout [RawJSValue], _ body: ([RawJSValue]) -> T) -> T {
92+
if index == values.count { return body(results) }
93+
return values[index].withRawJSValue { (rawValue) -> T in
94+
results.append(rawValue)
95+
return _withCollectedRawJSValue(values, index + 1, &results, body)
96+
}
97+
}
98+
var _results = [RawJSValue]()
99+
return _withCollectedRawJSValue(self, 0, &_results, body)
100+
}
101+
}

src/swift/Sources/JavaScriptKit/XcodeSupport.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,10 @@ func _get_subscript(
3333
func _load_string(
3434
_ ref: JavaScriptValueId,
3535
_ buffer: UnsafeMutablePointer<UInt8>!) { fatalError() }
36+
func _call_function(
37+
_ ref: JavaScriptValueId,
38+
_ args: UnsafePointer<RawJSValue>!, _ length: Int32,
39+
_ result_kind: UnsafeMutablePointer<JavaScriptValueKind>!,
40+
_ result_payload1: UnsafeMutablePointer<JavaScriptPayload>!,
41+
_ result_payload2: UnsafeMutablePointer<JavaScriptPayload>!) { fatalError() }
3642
#endif

src/swift/Sources/_CJavaScriptKit/include/_CJavaScriptKit.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ typedef enum {
1818

1919
typedef unsigned JavaScriptPayload;
2020

21+
typedef struct {
22+
JavaScriptValueKind kind;
23+
JavaScriptPayload payload1;
24+
JavaScriptPayload payload2;
25+
} RawJSValue;
26+
2127

2228
const unsigned int _JS_Predef_Value_Global = 0;
2329

@@ -80,4 +86,16 @@ extern void _load_string(
8086
unsigned char *buffer
8187
);
8288

89+
__attribute__((
90+
__import_module__("javascript_kit"),
91+
__import_name__("swjs_call_function")
92+
))
93+
extern void _call_function(
94+
const JavaScriptValueId ref,
95+
const RawJSValue *args, const int length,
96+
JavaScriptValueKind *result_kind,
97+
JavaScriptPayload *result_payload1,
98+
JavaScriptPayload *result_payload2
99+
);
100+
83101
#endif /* _CJavaScriptKit_h */

src/web/src/index.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ export class SwiftRuntime {
7373
uint8Memory[ptr]
7474
}
7575

76+
const readUInt32 = (ptr: pointer) => {
77+
const uint8Memory = new Uint8Array(memory().buffer);
78+
return uint8Memory[ptr + 0]
79+
+ (uint8Memory[ptr + 1] << 8)
80+
+ (uint8Memory[ptr + 2] << 16)
81+
+ (uint8Memory[ptr + 3] << 24)
82+
}
83+
7684
const writeUint32 = (ptr: pointer, value: number) => {
7785
const uint8Memory = new Uint8Array(memory().buffer);
7886
uint8Memory[ptr + 0] = (value & 0x000000ff) >> 0
@@ -171,6 +179,21 @@ export class SwiftRuntime {
171179
}
172180
}
173181

182+
// Note:
183+
// `decodeValues` assumes that the size of RawJSValue is 12
184+
// and the alignment of it is 4
185+
const decodeValues = (ptr: pointer, length: number) => {
186+
let result = []
187+
for (let index = 0; index < length; index++) {
188+
const base = ptr + 12 * index
189+
const kind = readUInt32(base)
190+
const payload1 = readUInt32(base + 4)
191+
const payload2 = readUInt32(base + 8)
192+
result.push(decodeValue(kind, payload1, payload2))
193+
}
194+
return result
195+
}
196+
174197
return {
175198
swjs_set_prop: (
176199
ref: ref, name: pointer, length: number,
@@ -215,6 +238,18 @@ export class SwiftRuntime {
215238
swjs_load_string: (ref: ref, buffer: pointer) => {
216239
const string = this._heapValues[ref];
217240
writeString(buffer, string);
241+
},
242+
swjs_call_function: (
243+
ref: ref, argv: pointer, argc: number,
244+
kind_ptr: pointer,
245+
payload1_ptr: pointer, payload2_ptr: pointer
246+
) => {
247+
const func = this._heapValues[ref]
248+
const result = Reflect.apply(func, undefined, decodeValues(argv, argc))
249+
const { kind, payload1, payload2 } = encodeValue(result);
250+
writeUint32(kind_ptr, kind);
251+
writeUint32(payload1_ptr, payload1);
252+
writeUint32(payload2_ptr, payload2);
218253
}
219254
}
220255
}

test/JavaScriptKitExec/Sources/JavaScriptKitExec/main.swift

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Object_Conversion: do {
3535
// "prop_4": [
3636
// 3, 4, "str_elm_1", 5,
3737
// ],
38-
// "prop_5": function () {},
38+
// ...
3939
// }
4040
// ```
4141
//
@@ -60,8 +60,51 @@ Object_Conversion: do {
6060
try expectEqual(actualElement, expectedElement)
6161
}
6262

63+
} catch {
64+
print(error)
65+
}
66+
67+
Function_Call: do {
68+
// Notes: globalObject1 is defined in JavaScript environment
69+
//
70+
// ```js
71+
// global.globalObject1 = {
72+
// ...
73+
// "prop_5": {
74+
// "func1": function () { return },
75+
// "func2": function () { return 1 },
76+
// "func3": function (n) { return n * 2 },
77+
// "func4": function (a, b, c) { return a + b + c },
78+
// "func5": function (x) { return "Hello, " + x },
79+
// "func6": function (c, a, b) {
80+
// if (c) { return a } else { return b }
81+
// },
82+
// }
83+
// }
84+
// ```
85+
//
86+
87+
// Notes: If the size of `RawJSValue` is updated, these test suites will fail.
88+
let globalObject1 = getJSValue(this: .global(), name: "globalObject1")
89+
let globalObject1Ref = try expectObject(globalObject1)
6390
let prop_5 = getJSValue(this: globalObject1Ref, name: "prop_5")
64-
_ = try expectFunction(prop_5)
91+
let prop_5Ref = try expectObject(prop_5)
92+
93+
let func1 = try expectFunction(getJSValue(this: prop_5Ref, name: "func1"))
94+
try expectEqual(func1(), .undefined)
95+
let func2 = try expectFunction(getJSValue(this: prop_5Ref, name: "func2"))
96+
try expectEqual(func2(), .number(1))
97+
let func3 = try expectFunction(getJSValue(this: prop_5Ref, name: "func3"))
98+
try expectEqual(func3(.number(2)), .number(4))
99+
let func4 = try expectFunction(getJSValue(this: prop_5Ref, name: "func4"))
100+
try expectEqual(func4(.number(2), .number(3), .number(4)), .number(9))
101+
try expectEqual(func4(.number(2), .number(3), .number(4), .number(5)), .number(9))
102+
let func5 = try expectFunction(getJSValue(this: prop_5Ref, name: "func5"))
103+
try expectEqual(func5(.string("World!")), .string("Hello, World!"))
104+
let func6 = try expectFunction(getJSValue(this: prop_5Ref, name: "func6"))
105+
try expectEqual(func6(.boolean(true), .number(1), .number(2)), .number(1))
106+
try expectEqual(func6(.boolean(false), .number(1), .number(2)), .number(2))
107+
try expectEqual(func6(.boolean(true), .string("OK"), .number(2)), .string("OK"))
65108

66109
} catch {
67110
print(error)

test/index.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,16 @@ global.globalObject1 = {
2727
"prop_4": [
2828
3, 4, "str_elm_1", 5,
2929
],
30-
"prop_5": function () {},
30+
"prop_5": {
31+
"func1": function () { return },
32+
"func2": function () { return 1 },
33+
"func3": function (n) { return n * 2},
34+
"func4": function (a, b, c) { return a + b + c },
35+
"func5": function (x) { return "Hello, " + x },
36+
"func6": function (c, a, b) {
37+
if (c) { return a } else { return b }
38+
},
39+
}
3140
}
3241

3342
const startWasiTask = async () => {

0 commit comments

Comments
 (0)