Skip to content

Commit 0a511ce

Browse files
Optimize object property access for StaticString keys
1 parent 2bb0694 commit 0a511ce

File tree

6 files changed

+167
-25
lines changed

6 files changed

+167
-25
lines changed

Diff for: Runtime/src/index.ts

+39-20
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export class SwiftRuntime {
141141
importObjects = () => this.wasmImports;
142142

143143
get wasmImports(): ImportedFunctions {
144+
const decodeString = JSValue.makeStringDecoder(this.options.sharedMemory == true, this.textDecoder);
144145
return {
145146
swjs_set_prop: (
146147
ref: ref,
@@ -174,6 +175,39 @@ export class SwiftRuntime {
174175
);
175176
},
176177

178+
swjs_set_prop_with_string_key: (
179+
ref: ref,
180+
prop_ptr: pointer,
181+
prop_length: number,
182+
kind: JSValue.Kind,
183+
payload1: number,
184+
payload2: number
185+
) => {
186+
const memory = this.memory;
187+
const obj = memory.getObject(ref);
188+
const key = decodeString(prop_ptr, prop_length, memory);
189+
const value = JSValue.decode(kind, payload1, payload2, memory);
190+
obj[key] = value;
191+
},
192+
swjs_get_prop_with_string_key: (
193+
ref: ref,
194+
prop_ptr: pointer,
195+
prop_length: number,
196+
payload1_ptr: pointer,
197+
payload2_ptr: pointer
198+
) => {
199+
const memory = this.memory;
200+
const obj = memory.getObject(ref);
201+
const key = decodeString(prop_ptr, prop_length, memory);
202+
const result = obj[key];
203+
return JSValue.writeAndReturnKindBits(
204+
result,
205+
payload1_ptr,
206+
payload2_ptr,
207+
false,
208+
memory
209+
);
210+
},
177211
swjs_set_subscript: (
178212
ref: ref,
179213
index: number,
@@ -210,26 +244,11 @@ export class SwiftRuntime {
210244
memory.writeUint32(bytes_ptr_result, bytes_ptr);
211245
return bytes.length;
212246
},
213-
swjs_decode_string: (
214-
// NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer
215-
this.options.sharedMemory == true
216-
? ((bytes_ptr: pointer, length: number) => {
217-
const memory = this.memory;
218-
const bytes = memory
219-
.bytes()
220-
.slice(bytes_ptr, bytes_ptr + length);
221-
const string = this.textDecoder.decode(bytes);
222-
return memory.retain(string);
223-
})
224-
: ((bytes_ptr: pointer, length: number) => {
225-
const memory = this.memory;
226-
const bytes = memory
227-
.bytes()
228-
.subarray(bytes_ptr, bytes_ptr + length);
229-
const string = this.textDecoder.decode(bytes);
230-
return memory.retain(string);
231-
})
232-
),
247+
swjs_decode_string: (bytes_ptr: pointer, length: number) => {
248+
const memory = this.memory;
249+
const string = decodeString(bytes_ptr, length, memory);
250+
return memory.retain(string);
251+
},
233252
swjs_load_string: (ref: ref, buffer: pointer) => {
234253
const memory = this.memory;
235254
const bytes = memory.getObject(ref);

Diff for: Runtime/src/js-value.ts

+17
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,23 @@ export const decode = (
4848
}
4949
};
5050

51+
export function makeStringDecoder(sharedMemory: boolean, textDecoder: TextDecoder): (ptr: pointer, length: number, memory: Memory) => string {
52+
// NOTE: TextDecoder can't decode typed arrays backed by SharedArrayBuffer
53+
return sharedMemory == true
54+
? ((bytes_ptr: pointer, length: number, memory: Memory) => {
55+
const bytes = memory
56+
.bytes()
57+
.slice(bytes_ptr, bytes_ptr + length);
58+
return textDecoder.decode(bytes);
59+
})
60+
: ((bytes_ptr: pointer, length: number, memory: Memory) => {
61+
const bytes = memory
62+
.bytes()
63+
.subarray(bytes_ptr, bytes_ptr + length);
64+
return textDecoder.decode(bytes);
65+
})
66+
}
67+
5168
// Note:
5269
// `decodeValues` assumes that the size of RawJSValue is 16.
5370
export const decodeArray = (ptr: pointer, length: number, memory: Memory) => {

Diff for: Runtime/src/types.ts

+15
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,21 @@ export interface ImportedFunctions {
3535
payload1_ptr: pointer,
3636
payload2_ptr: pointer
3737
): JavaScriptValueKind;
38+
swjs_set_prop_with_string_key(
39+
ref: number,
40+
name: number,
41+
prop_ptr: pointer,
42+
prop_length: number,
43+
payload1: number,
44+
payload2: number
45+
): void;
46+
swjs_get_prop_with_string_key(
47+
ref: number,
48+
prop_ptr: pointer,
49+
prop_length: number,
50+
payload1_ptr: pointer,
51+
payload2_ptr: pointer
52+
): JavaScriptValueKind;
3853
swjs_set_subscript(
3954
ref: number,
4055
index: number,

Diff for: Sources/JavaScriptKit/FundamentalObjects/JSObject.swift

+40-3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,24 @@ public class JSObject: Equatable {
4141
}
4242
}
4343

44+
/// Returns the `name` member method binding this object as `this` context.
45+
///
46+
/// e.g.
47+
/// ```swift
48+
/// let document = JSObject.global.document.object!
49+
/// let divElement = document.createElement!("div")
50+
/// ```
51+
///
52+
/// - Parameter name: The name of this object's member to access.
53+
/// - Returns: The `name` member method binding this object as `this` context.
54+
@_disfavoredOverload
55+
public subscript(_ name: StaticString) -> ((ConvertibleToJSValue...) -> JSValue)? {
56+
guard let function = self[name].function else { return nil }
57+
return { (arguments: ConvertibleToJSValue...) in
58+
function(this: self, arguments: arguments)
59+
}
60+
}
61+
4462
/// Returns the `name` member method binding this object as `this` context.
4563
///
4664
/// e.g.
@@ -62,13 +80,13 @@ public class JSObject: Equatable {
6280
/// A convenience method of `subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?`
6381
/// to access the member through Dynamic Member Lookup.
6482
@_disfavoredOverload
65-
public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue)? {
83+
public subscript(dynamicMember name: StaticString) -> ((ConvertibleToJSValue...) -> JSValue)? {
6684
self[name]
6785
}
6886

6987
/// A convenience method of `subscript(_ name: String) -> JSValue`
7088
/// to access the member through Dynamic Member Lookup.
71-
public subscript(dynamicMember name: String) -> JSValue {
89+
public subscript(dynamicMember name: StaticString) -> JSValue {
7290
get { self[name] }
7391
set { self[name] = newValue }
7492
}
@@ -81,6 +99,14 @@ public class JSObject: Equatable {
8199
set { setJSValue(this: self, name: JSString(name), value: newValue) }
82100
}
83101

102+
/// Access the `name` member dynamically through JavaScript and Swift runtime bridge library.
103+
/// - Parameter name: The name of this object's member to access.
104+
/// - Returns: The value of the `name` member of this object.
105+
public subscript(_ name: StaticString) -> JSValue {
106+
get { getJSValue(this: self, name: name) }
107+
set { setJSValue(this: self, name: name, value: newValue) }
108+
}
109+
84110
/// Access the `name` member dynamically through JavaScript and Swift runtime bridge library.
85111
/// - Parameter name: The name of this object's member to access.
86112
/// - Returns: The value of the `name` member of this object.
@@ -197,6 +223,17 @@ public class JSThrowingObject {
197223
self.base = base
198224
}
199225

226+
/// Returns the `name` member method binding this object as `this` context.
227+
/// - Parameter name: The name of this object's member to access.
228+
/// - Returns: The `name` member method binding this object as `this` context.
229+
@_disfavoredOverload
230+
public subscript(_ name: StaticString) -> ((ConvertibleToJSValue...) throws -> JSValue)? {
231+
guard let function = base[name].function?.throws else { return nil }
232+
return { [base] (arguments: ConvertibleToJSValue...) in
233+
try function(this: base, arguments: arguments)
234+
}
235+
}
236+
200237
/// Returns the `name` member method binding this object as `this` context.
201238
/// - Parameter name: The name of this object's member to access.
202239
/// - Returns: The `name` member method binding this object as `this` context.
@@ -211,7 +248,7 @@ public class JSThrowingObject {
211248
/// A convenience method of `subscript(_ name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)?`
212249
/// to access the member through Dynamic Member Lookup.
213250
@_disfavoredOverload
214-
public subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) throws -> JSValue)? {
251+
public subscript(dynamicMember name: StaticString) -> ((ConvertibleToJSValue...) throws -> JSValue)? {
215252
self[name]
216253
}
217254
}

Diff for: Sources/JavaScriptKit/JSValue.swift

+25-2
Original file line numberDiff line numberDiff line change
@@ -103,13 +103,13 @@ public enum JSValue: Equatable {
103103
public extension JSValue {
104104
/// An unsafe convenience method of `JSObject.subscript(_ name: String) -> ((ConvertibleToJSValue...) -> JSValue)?`
105105
/// - Precondition: `self` must be a JavaScript Object and specified member should be a callable object.
106-
subscript(dynamicMember name: String) -> ((ConvertibleToJSValue...) -> JSValue) {
106+
subscript(dynamicMember name: StaticString) -> ((ConvertibleToJSValue...) -> JSValue) {
107107
object![dynamicMember: name]!
108108
}
109109

110110
/// An unsafe convenience method of `JSObject.subscript(_ index: Int) -> JSValue`
111111
/// - Precondition: `self` must be a JavaScript Object.
112-
subscript(dynamicMember name: String) -> JSValue {
112+
subscript(dynamicMember name: StaticString) -> JSValue {
113113
get { self.object![name] }
114114
set { self.object![name] = newValue }
115115
}
@@ -194,6 +194,29 @@ extension JSValue: ExpressibleByNilLiteral {
194194
}
195195
}
196196

197+
internal func getJSValue(this: JSObject, name: StaticString) -> JSValue {
198+
var rawValue = RawJSValue()
199+
let rawBitPattern = name.withUTF8Buffer { buffer in
200+
swjs_get_prop_with_string_key(
201+
this.id, buffer.baseAddress, Int32(buffer.count),
202+
&rawValue.payload1, &rawValue.payload2
203+
)
204+
}
205+
rawValue.kind = unsafeBitCast(rawBitPattern, to: JavaScriptValueKind.self)
206+
return rawValue.jsValue
207+
}
208+
209+
internal func setJSValue(this: JSObject, name: StaticString, value: JSValue) {
210+
value.withRawJSValue { rawValue in
211+
name.withUTF8Buffer { buffer in
212+
swjs_set_prop_with_string_key(
213+
this.id, buffer.baseAddress, Int32(buffer.count),
214+
rawValue.kind, rawValue.payload1, rawValue.payload2
215+
)
216+
}
217+
}
218+
}
219+
197220
public func getJSValue(this: JSObject, name: JSString) -> JSValue {
198221
var rawValue = RawJSValue()
199222
let rawBitPattern = swjs_get_prop(

Diff for: Sources/_CJavaScriptKit/include/_CJavaScriptKit.h

+31
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,37 @@ IMPORT_JS_FUNCTION(swjs_get_prop, uint32_t, (const JavaScriptObjectRef _this,
107107
JavaScriptPayload1 *payload1,
108108
JavaScriptPayload2 *payload2))
109109

110+
/// Sets a value of `_this` JavaScript object with a string key
111+
/// This is a fast path of `swjs_set_prop` for the case where property key is known to be a string in the linear memory
112+
///
113+
/// @param _this The target JavaScript object to set the given value.
114+
/// @param prop_ptr A `uint8_t` byte sequence to decode.
115+
/// @param prop_length The length of `prop_ptr` byte sequence.
116+
/// @param kind A kind of JavaScript value to set the target object.
117+
/// @param payload1 The first payload of JavaScript value to set the target object.
118+
/// @param payload2 The second payload of JavaScript value to set the target object.
119+
IMPORT_JS_FUNCTION(swjs_set_prop_with_string_key, void, (const JavaScriptObjectRef _this,
120+
const unsigned char *prop_ptr,
121+
const int prop_length,
122+
const JavaScriptValueKind kind,
123+
const JavaScriptPayload1 payload1,
124+
const JavaScriptPayload2 payload2))
125+
126+
/// Gets a value of `_this` JavaScript object with a string key
127+
/// This is a fast path of `swjs_get_prop` for the case where property key is known to be a string in the linear memory
128+
///
129+
/// @param _this The target JavaScript object to get its member value.
130+
/// @param prop_ptr A `uint8_t` byte sequence to decode.
131+
/// @param prop_length The length of `prop_ptr` byte sequence.
132+
/// @param payload1 A result pointer of first payload of JavaScript value to set the target object.
133+
/// @param payload2 A result pointer of second payload of JavaScript value to set the target object.
134+
/// @return A `JavaScriptValueKind` bits represented as 32bit integer for the returned value.
135+
IMPORT_JS_FUNCTION(swjs_get_prop_with_string_key, uint32_t, (const JavaScriptObjectRef _this,
136+
const unsigned char *prop_ptr,
137+
const int prop_length,
138+
JavaScriptPayload1 *payload1,
139+
JavaScriptPayload2 *payload2))
140+
110141
/// Sets a value of `_this` JavaScript object.
111142
///
112143
/// @param _this The target JavaScript object to set its member value.

0 commit comments

Comments
 (0)