Skip to content

Commit d58b94f

Browse files
Support Host function invocation
1 parent 0a2eae2 commit d58b94f

File tree

9 files changed

+160
-18
lines changed

9 files changed

+160
-18
lines changed

src/swift/Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ build: .wasi-sdk/dummy
77
-Xswiftc -Xclang-linker \
88
-Xswiftc --sysroot=$(WASI_SDK_DIR)/share/wasi-sysroot \
99
-Xcc --sysroot=$(WASI_SDK_DIR)/share/wasi-sysroot \
10-
-Xlinker --allow-undefined
10+
-Xlinker --allow-undefined \
11+
-Xlinker --export=swjs_call_host_function \
12+
-Xlinker --export=swjs_prepare_host_function_call
1113
.wasi-sdk/dummy:
1214
./script/install-wasi-sdk.sh $(WASI_SDK_DIR)
1315
touch .wasi-sdk/dummy

src/swift/Sources/JavaScriptKit/JSFunction.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,35 @@ public class JSFunctionRef: Equatable {
2727
}
2828
return result.jsValue()
2929
}
30+
31+
static var sharedFunctions: [([JSValue]) -> JSValue] = []
32+
public static func from(_ body: @escaping ([JSValue]) -> JSValue) -> JSFunctionRef {
33+
let id = JavaScriptHostFuncRef(sharedFunctions.count)
34+
sharedFunctions.append(body)
35+
var funcRef: JavaScriptObjectRef = 0
36+
_create_function(id, &funcRef)
37+
38+
return JSFunctionRef(id: funcRef)
39+
}
40+
}
41+
42+
43+
@_cdecl("swjs_prepare_host_function_call")
44+
public func _prepare_host_function_call(_ argc: Int32) -> UnsafeMutableRawPointer {
45+
let argumentSize = MemoryLayout<RawJSValue>.size * Int(argc)
46+
return malloc(Int(argumentSize))!
47+
}
48+
49+
@_cdecl("swjs_call_host_function")
50+
public func _call_host_function(
51+
_ hostFuncRef: JavaScriptHostFuncRef,
52+
_ argv: UnsafePointer<RawJSValue>, _ argc: Int32,
53+
_ callbackFuncRef: JavaScriptPayload) {
54+
let hostFunc = JSFunctionRef.sharedFunctions[Int(hostFuncRef)]
55+
let args = UnsafeBufferPointer(start: argv, count: Int(argc)).map {
56+
$0.jsValue()
57+
}
58+
let result = hostFunc(args)
59+
let callbackFuncRef = JSFunctionRef(id: callbackFuncRef)
60+
_ = callbackFuncRef(result)
3061
}

src/swift/Sources/JavaScriptKit/JSValueConvertible.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension RawJSValue: JSValueConvertible {
2323
// +1 for null terminator
2424
let buffer = malloc(Int(payload2 + 1))!.assumingMemoryBound(to: UInt8.self)
2525
defer { free(buffer) }
26-
_load_string(payload1 as JavaScriptValueId, buffer)
26+
_load_string(payload1 as JavaScriptObjectRef, buffer)
2727
buffer[Int(payload2)] = 0
2828
let string = String(decodingCString: UnsafePointer(buffer), as: UTF8.self)
2929
return .string(string)

src/swift/Sources/JavaScriptKit/XcodeSupport.swift

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,37 @@ import _CJavaScriptKit
77
/// When running with JavaScript runtime library, they are ignored completely.
88
#if Xcode
99
func _set_prop(
10-
_ _this: JavaScriptValueId,
10+
_ _this: JavaScriptObjectRef,
1111
_ prop: UnsafePointer<Int8>!, _ length: Int32,
1212
_ kind: JavaScriptValueKind,
1313
_ payload1: JavaScriptPayload,
1414
_ payload2: JavaScriptPayload) { fatalError() }
1515
func _get_prop(
16-
_ _this: JavaScriptValueId,
16+
_ _this: JavaScriptObjectRef,
1717
_ prop: UnsafePointer<Int8>!, _ length: Int32,
1818
_ kind: UnsafeMutablePointer<JavaScriptValueKind>!,
1919
_ payload1: UnsafeMutablePointer<JavaScriptPayload>!,
2020
_ payload2: UnsafeMutablePointer<JavaScriptPayload>!) { fatalError() }
2121
func _set_subscript(
22-
_ _this: JavaScriptValueId,
22+
_ _this: JavaScriptObjectRef,
2323
_ index: Int32,
2424
_ kind: JavaScriptValueKind,
2525
_ payload1: JavaScriptPayload,
2626
_ payload2: JavaScriptPayload) { fatalError() }
2727
func _get_subscript(
28-
_ _this: JavaScriptValueId,
28+
_ _this: JavaScriptObjectRef,
2929
_ index: Int32,
3030
_ kind: UnsafeMutablePointer<JavaScriptValueKind>!,
3131
_ payload1: UnsafeMutablePointer<JavaScriptPayload>!,
3232
_ payload2: UnsafeMutablePointer<JavaScriptPayload>!) { fatalError() }
3333
func _load_string(
34-
_ ref: JavaScriptValueId,
34+
_ ref: JavaScriptObjectRef,
3535
_ buffer: UnsafeMutablePointer<UInt8>!) { fatalError() }
3636
func _call_function(
37-
_ ref: JavaScriptValueId,
38-
_ args: UnsafePointer<RawJSValue>!, _ length: Int32,
37+
_ ref: JavaScriptObjectRef,
38+
_ argv: UnsafePointer<RawJSValue>!, _ argc: Int32,
3939
_ result_kind: UnsafeMutablePointer<JavaScriptValueKind>!,
4040
_ result_payload1: UnsafeMutablePointer<JavaScriptPayload>!,
4141
_ result_payload2: UnsafeMutablePointer<JavaScriptPayload>!) { fatalError() }
42+
func _create_function(_ host_func_id: JavaScriptHostFuncRef, _ func_ref_ptr: UnsafePointer<JavaScriptObjectRef>!) {}
4243
#endif

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

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33

44
#include <stdlib.h>
55

6-
typedef unsigned int JavaScriptValueId;
6+
typedef unsigned int JavaScriptObjectRef;
7+
typedef unsigned int JavaScriptHostFuncRef;
78

89
typedef enum {
910
JavaScriptValueKind_Invalid = -1,
@@ -32,7 +33,7 @@ __attribute__((
3233
__import_name__("swjs_set_prop")
3334
))
3435
extern void _set_prop(
35-
const JavaScriptValueId _this,
36+
const JavaScriptObjectRef _this,
3637
const char *prop,
3738
const int length,
3839
const JavaScriptValueKind kind,
@@ -45,7 +46,7 @@ __attribute__((
4546
__import_name__("swjs_get_prop")
4647
))
4748
extern void _get_prop(
48-
const JavaScriptValueId _this,
49+
const JavaScriptObjectRef _this,
4950
const char *prop,
5051
const int length,
5152
JavaScriptValueKind *kind,
@@ -58,7 +59,7 @@ __attribute__((
5859
__import_name__("swjs_set_subscript")
5960
))
6061
extern void _set_subscript(
61-
const JavaScriptValueId _this,
62+
const JavaScriptObjectRef _this,
6263
const int length,
6364
const JavaScriptValueKind kind,
6465
const JavaScriptPayload payload1,
@@ -70,7 +71,7 @@ __attribute__((
7071
__import_name__("swjs_get_subscript")
7172
))
7273
extern void _get_subscript(
73-
const JavaScriptValueId _this,
74+
const JavaScriptObjectRef _this,
7475
const int length,
7576
JavaScriptValueKind *kind,
7677
JavaScriptPayload *payload1,
@@ -82,7 +83,7 @@ __attribute__((
8283
__import_name__("swjs_load_string")
8384
))
8485
extern void _load_string(
85-
const JavaScriptValueId ref,
86+
const JavaScriptObjectRef ref,
8687
unsigned char *buffer
8788
);
8889

@@ -91,11 +92,20 @@ __attribute__((
9192
__import_name__("swjs_call_function")
9293
))
9394
extern void _call_function(
94-
const JavaScriptValueId ref,
95-
const RawJSValue *args, const int length,
95+
const JavaScriptObjectRef ref,
96+
const RawJSValue *argv, const int argc,
9697
JavaScriptValueKind *result_kind,
9798
JavaScriptPayload *result_payload1,
9899
JavaScriptPayload *result_payload2
99100
);
100101

102+
__attribute__((
103+
__import_module__("javascript_kit"),
104+
__import_name__("swjs_create_function")
105+
))
106+
extern void _create_function(
107+
const JavaScriptHostFuncRef host_func_id,
108+
const JavaScriptObjectRef *func_ref_ptr
109+
);
110+
101111
#endif /* _CJavaScriptKit_h */

src/web/src/index.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ interface GlobalVariable { }
99
declare const window: GlobalVariable;
1010
declare const global: GlobalVariable;
1111

12+
interface SwiftRuntimeExportedFunctions {
13+
swjs_prepare_host_function_call(size: number): pointer;
14+
swjs_call_host_function(
15+
host_func_id: number,
16+
argv: pointer, argc: number,
17+
callback_func_ref: ref
18+
): void;
19+
}
1220

1321
enum JavaScriptValueKind {
1422
Invalid = -1,
@@ -56,6 +64,28 @@ export class SwiftRuntime {
5664
return id
5765
}
5866

67+
const callHostFunction = (host_func_id: number, args: any[]) => {
68+
if (!this.instance)
69+
throw new Error("WebAssembly instance is not set yet");
70+
const exports = this.instance.exports as any as SwiftRuntimeExportedFunctions;
71+
const argc = args.length
72+
const argv = exports.swjs_prepare_host_function_call(argc)
73+
for (let index = 0; index < args.length; index++) {
74+
const argument = args[index]
75+
const value = encodeValue(argument)
76+
const base = argv + 12 * index
77+
writeUint32(base, value.kind)
78+
writeUint32(base + 4, value.payload1)
79+
writeUint32(base + 8, value.payload2)
80+
}
81+
let output: any;
82+
const callback_func_ref = allocValue(function(result: any) {
83+
output = result
84+
})
85+
exports.swjs_call_host_function(host_func_id, argv, argc, callback_func_ref)
86+
return output
87+
}
88+
5989
const textDecoder = new TextDecoder('utf-8');
6090
const textEncoder = new TextEncoder(); // Only support utf-8
6191

@@ -250,7 +280,16 @@ export class SwiftRuntime {
250280
writeUint32(kind_ptr, kind);
251281
writeUint32(payload1_ptr, payload1);
252282
writeUint32(payload2_ptr, payload2);
253-
}
283+
},
284+
swjs_create_function: (
285+
host_func_id: number,
286+
func_ref_ptr: pointer,
287+
) => {
288+
const func_ref = allocValue(function() {
289+
return callHostFunction(host_func_id, Array.prototype.slice.call(arguments))
290+
})
291+
writeUint32(func_ref_ptr, func_ref)
292+
},
254293
}
255294
}
256295
}

test/JavaScriptKitExec/Sources/JavaScriptKitExec/UnitTestUtils.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,11 @@ func expectNumber(_ value: JSValue) throws -> Int32 {
4444
throw MessageError("Type of \(value) should be \"number\"")
4545
}
4646
}
47+
48+
func expectString(_ value: JSValue) throws -> String {
49+
switch value {
50+
case .string(let string): return string
51+
default:
52+
throw MessageError("Type of \(value) should be \"string\"")
53+
}
54+
}

test/JavaScriptKitExec/Sources/JavaScriptKitExec/main.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ Function_Call: do {
8080
// if (c) { return a } else { return b }
8181
// },
8282
// }
83+
// ...
8384
// }
8485
// ```
8586
//
@@ -109,3 +110,48 @@ Function_Call: do {
109110
} catch {
110111
print(error)
111112
}
113+
114+
Host_Function_Registration: do {
115+
116+
// ```js
117+
// global.globalObject1 = {
118+
// ...
119+
// "prop_6": {
120+
// "call_host_1": function() {
121+
// return global.globalObject1.prop_6.host_func_1()
122+
// }
123+
// }
124+
// }
125+
// ```
126+
let globalObject1 = getJSValue(this: .global(), name: "globalObject1")
127+
let globalObject1Ref = try expectObject(globalObject1)
128+
let prop_6 = getJSValue(this: globalObject1Ref, name: "prop_6")
129+
let prop_6Ref = try expectObject(prop_6)
130+
131+
var isHostFunc1Called = false
132+
let hostFunc1 = JSFunctionRef.from { (arguments) -> JSValue in
133+
isHostFunc1Called = true
134+
return .number(1)
135+
}
136+
137+
setJSValue(this: prop_6Ref, name: "host_func_1", value: .function(hostFunc1))
138+
139+
let call_host_1 = getJSValue(this: prop_6Ref, name: "call_host_1")
140+
let call_host_1Func = try expectFunction(call_host_1)
141+
try expectEqual(call_host_1Func(), .number(1))
142+
try expectEqual(isHostFunc1Called, true)
143+
144+
let hostFunc2 = JSFunctionRef.from { (arguments) -> JSValue in
145+
do {
146+
let input = try expectNumber(arguments[0])
147+
return .number(input * 2)
148+
} catch {
149+
return .string(String(describing: error))
150+
}
151+
}
152+
153+
try expectEqual(hostFunc2(.number(3)), .number(6))
154+
_ = try expectString(hostFunc2(.boolean(true)))
155+
} catch {
156+
print(error)
157+
}

test/index.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ global.globalObject1 = {
3636
"func6": function (c, a, b) {
3737
if (c) { return a } else { return b }
3838
},
39+
},
40+
"prop_6": {
41+
"call_host_1": () => {
42+
return global.globalObject1.prop_6.host_func_1()
43+
}
3944
}
4045
}
4146

0 commit comments

Comments
 (0)