Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 36a06c5

Browse files
Implement JSString to reduce bridging overhead (#63)
* Implement JSString to reduce briding overhead * Bump runtime library version
1 parent 702dd89 commit 36a06c5

File tree

11 files changed

+194
-41
lines changed

11 files changed

+194
-41
lines changed

‎IntegrationTests/TestSuites/Sources/PrimaryTests/UnitTestUtils.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func expectNumber(_ value: JSValue, file: StaticString = #file, line: UInt = #li
7979

8080
func expectString(_ value: JSValue, file: StaticString = #file, line: UInt = #line, column: UInt = #column) throws -> String {
8181
switch value {
82-
case let .string(string): return string
82+
case let .string(string): return String(string)
8383
default:
8484
throw MessageError("Type of \(value) should be \"string\"", file: file, line: line, column: column)
8585
}

‎IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import JavaScriptKit
22

3+
34
try test("Literal Conversion") {
45
let global = JSObject.global
56
let inputs: [JSValue] = [
@@ -16,7 +17,7 @@ try test("Literal Conversion") {
1617
.undefined,
1718
]
1819
for (index, input) in inputs.enumerated() {
19-
let prop = "prop_\(index)"
20+
let prop = JSString("prop_\(index)")
2021
setJSValue(this: global, name: prop, value: input)
2122
let got = getJSValue(this: global, name: prop)
2223
switch (got, input) {

‎Runtime/src/index.ts‎

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class SwiftRuntimeHeap {
117117
export class SwiftRuntime {
118118
private instance: WebAssembly.Instance | null;
119119
private heap: SwiftRuntimeHeap
120-
private version: number = 610
120+
private version: number = 611
121121

122122
constructor() {
123123
this.instance = null;
@@ -163,9 +163,8 @@ export class SwiftRuntime {
163163
const textDecoder = new TextDecoder('utf-8');
164164
const textEncoder = new TextEncoder(); // Only support utf-8
165165

166-
const readString = (ptr: pointer, len: number) => {
167-
const uint8Memory = new Uint8Array(memory().buffer);
168-
return textDecoder.decode(uint8Memory.subarray(ptr, ptr + len));
166+
const readString = (ref: ref) => {
167+
return this.heap.referenceHeap(ref);
169168
}
170169

171170
const writeString = (ptr: pointer, bytes: Uint8Array) => {
@@ -217,7 +216,7 @@ export class SwiftRuntime {
217216
return payload3;
218217
}
219218
case JavaScriptValueKind.String: {
220-
return readString(payload1,payload2)
219+
return readString(payload1);
221220
}
222221
case JavaScriptValueKind.Object: {
223222
return this.heap.referenceHeap(payload1)
@@ -261,10 +260,9 @@ export class SwiftRuntime {
261260
break;
262261
}
263262
case "string": {
264-
const bytes = textEncoder.encode(value);
265263
writeUint32(kind_ptr, JavaScriptValueKind.String);
266-
writeUint32(payload1_ptr, this.heap.retain(bytes));
267-
writeUint32(payload2_ptr, bytes.length);
264+
writeUint32(payload1_ptr, this.heap.retain(value));
265+
writeUint32(payload2_ptr, 0);
268266
break;
269267
}
270268
case "undefined": {
@@ -308,20 +306,20 @@ export class SwiftRuntime {
308306

309307
return {
310308
swjs_set_prop: (
311-
ref: ref, name: pointer,length: number,
309+
ref: ref, name: ref,
312310
kind: JavaScriptValueKind,
313311
payload1: number, payload2: number, payload3: number
314312
) => {
315313
const obj = this.heap.referenceHeap(ref);
316-
Reflect.set(obj, readString(name,length), decodeValue(kind, payload1, payload2, payload3))
314+
Reflect.set(obj, readString(name), decodeValue(kind, payload1, payload2, payload3))
317315
},
318316
swjs_get_prop: (
319-
ref: ref, name: pointer,length: number,
317+
ref: ref, name: ref,
320318
kind_ptr: pointer,
321319
payload1_ptr: pointer, payload2_ptr: pointer, payload3_ptr: number
322320
) => {
323321
const obj = this.heap.referenceHeap(ref);
324-
const result = Reflect.get(obj, readString(name,length));
322+
const result = Reflect.get(obj, readString(name));
325323
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, payload3_ptr);
326324
},
327325
swjs_set_subscript: (
@@ -341,6 +339,18 @@ export class SwiftRuntime {
341339
const result = Reflect.get(obj, index);
342340
writeValue(result, kind_ptr, payload1_ptr, payload2_ptr, payload3_ptr);
343341
},
342+
swjs_encode_string: (ref: ref, bytes_ptr_result: pointer) => {
343+
const bytes = textEncoder.encode(this.heap.referenceHeap(ref));
344+
const bytes_ptr = this.heap.retain(bytes);
345+
writeUint32(bytes_ptr_result, bytes_ptr);
346+
return bytes.length;
347+
},
348+
swjs_decode_string: (bytes_ptr: pointer, length: number) => {
349+
const uint8Memory = new Uint8Array(memory().buffer);
350+
const bytes = uint8Memory.subarray(bytes_ptr, bytes_ptr + length);
351+
const string = textDecoder.decode(bytes);
352+
return this.heap.retain(string);
353+
},
344354
swjs_load_string: (ref: ref, buffer: pointer) => {
345355
const bytes = this.heap.referenceHeap(ref);
346356
writeString(buffer, bytes);

‎Sources/JavaScriptKit/Compatibility.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
/// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`.
44
@_cdecl("swjs_library_version")
55
func _library_version() -> Double {
6-
return 610
6+
return 611
77
}

‎Sources/JavaScriptKit/FundamentalObjects/JSObject.swift‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ public class JSObject: Equatable {
5757
/// - Parameter name: The name of this object's member to access.
5858
/// - Returns: The value of the `name` member of this object.
5959
public subscript(_ name: String) -> JSValue {
60+
get { getJSValue(this: self, name: JSString(name)) }
61+
set { setJSValue(this: self, name: JSString(name), value: newValue) }
62+
}
63+
64+
/// Access the `name` member dynamically through JavaScript and Swift runtime bridge library.
65+
/// - Parameter name: The name of this object's member to access.
66+
/// - Returns: The value of the `name` member of this object.
67+
public subscript(_ name: JSString) -> JSValue {
6068
get { getJSValue(this: self, name: name) }
6169
set { setJSValue(this: self, name: name, value: newValue) }
6270
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import _CJavaScriptKit
2+
3+
/// `JSString` represents a string in JavaScript and supports bridging string between JavaScript and Swift.
4+
///
5+
/// Conversion between `Swift.String` and `JSString` can be:
6+
///
7+
/// ```swift
8+
/// // Convert `Swift.String` to `JSString`
9+
/// let jsString: JSString = ...
10+
/// let swiftString: String = String(jsString)
11+
///
12+
/// // Convert `JSString` to `Swift.String`
13+
/// let swiftString: String = ...
14+
/// let jsString: JSString = JSString(swiftString)
15+
/// ```
16+
///
17+
public struct JSString: LosslessStringConvertible, Equatable {
18+
/// The internal representation of JS compatible string
19+
/// The initializers of this type must initialize `jsRef` or `buffer`.
20+
/// And the uninitialized one will be lazily initialized
21+
class Guts {
22+
var shouldDealocateRef: Bool = false
23+
lazy var jsRef: JavaScriptObjectRef = {
24+
self.shouldDealocateRef = true
25+
return buffer.withUTF8 { bufferPtr in
26+
return _decode_string(bufferPtr.baseAddress!, Int32(bufferPtr.count))
27+
}
28+
}()
29+
30+
lazy var buffer: String = {
31+
var bytesRef: JavaScriptObjectRef = 0
32+
let bytesLength = Int(_encode_string(jsRef, &bytesRef))
33+
// +1 for null terminator
34+
let buffer = malloc(Int(bytesLength + 1))!.assumingMemoryBound(to: UInt8.self)
35+
defer {
36+
free(buffer)
37+
_release(bytesRef)
38+
}
39+
_load_string(bytesRef, buffer)
40+
buffer[bytesLength] = 0
41+
return String(decodingCString: UnsafePointer(buffer), as: UTF8.self)
42+
}()
43+
44+
init(from stringValue: String) {
45+
self.buffer = stringValue
46+
}
47+
48+
init(from jsRef: JavaScriptObjectRef) {
49+
self.jsRef = jsRef
50+
self.shouldDealocateRef = true
51+
}
52+
53+
deinit {
54+
guard shouldDealocateRef else { return }
55+
_release(jsRef)
56+
}
57+
}
58+
59+
let guts: Guts
60+
61+
internal init(jsRef: JavaScriptObjectRef) {
62+
self.guts = Guts(from: jsRef)
63+
}
64+
65+
/// Instantiate a new `JSString` with given Swift.String.
66+
public init(_ stringValue: String) {
67+
self.guts = Guts(from: stringValue)
68+
}
69+
70+
/// A Swift representation of this `JSString`.
71+
/// Note that this accessor may copy the JS string value into Swift side memory.
72+
public var description: String { guts.buffer }
73+
74+
/// Returns a Boolean value indicating whether two strings are equal values.
75+
///
76+
/// - Parameters:
77+
/// - lhs: A string to compare.
78+
/// - rhs: Another string to compare.
79+
public static func == (lhs: JSString, rhs: JSString) -> Bool {
80+
return lhs.guts.buffer == rhs.guts.buffer
81+
}
82+
}
83+
84+
extension JSString: ExpressibleByStringLiteral {
85+
public init(stringLiteral value: String) {
86+
self.init(value)
87+
}
88+
}
89+
90+
91+
// MARK: - Internal Helpers
92+
extension JSString {
93+
94+
func asInternalJSRef() -> JavaScriptObjectRef {
95+
guts.jsRef
96+
}
97+
98+
func withRawJSValue<T>(_ body: (RawJSValue) -> T) -> T {
99+
let rawValue = RawJSValue(
100+
kind: .string, payload1: guts.jsRef, payload2: 0, payload3: 0
101+
)
102+
return body(rawValue)
103+
}
104+
}

‎Sources/JavaScriptKit/JSValue.swift‎

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import _CJavaScriptKit
33
/// `JSValue` represents a value in JavaScript.
44
public enum JSValue: Equatable {
55
case boolean(Bool)
6-
case string(String)
6+
case string(JSString)
77
case number(Double)
88
case object(JSObject)
99
case null
@@ -21,7 +21,18 @@ public enum JSValue: Equatable {
2121

2222
/// Returns the `String` value of this JS value if the type is string.
2323
/// If not, returns `nil`.
24+
///
25+
/// Note that this accessor may copy the JS string value into Swift side memory.
26+
///
27+
/// To avoid the copying, please consider the `jsString` instead.
2428
public var string: String? {
29+
jsString.map(String.init)
30+
}
31+
32+
/// Returns the `JSString` value of this JS value if the type is string.
33+
/// If not, returns `nil`.
34+
///
35+
public var jsString: JSString? {
2536
switch self {
2637
case let .string(string): return string
2738
default: return nil
@@ -76,6 +87,10 @@ extension JSValue {
7687

7788
extension JSValue {
7889

90+
public static func string(_ value: String) -> JSValue {
91+
.string(JSString(value))
92+
}
93+
7994
/// Deprecated: Please create `JSClosure` directly and manage its lifetime manually.
8095
///
8196
/// Migrate this usage
@@ -108,7 +123,7 @@ extension JSValue {
108123

109124
extension JSValue: ExpressibleByStringLiteral {
110125
public init(stringLiteral value: String) {
111-
self = .string(value)
126+
self = .string(JSString(value))
112127
}
113128
}
114129

@@ -130,17 +145,17 @@ extension JSValue: ExpressibleByNilLiteral {
130145
}
131146
}
132147

133-
public func getJSValue(this: JSObject, name: String) -> JSValue {
148+
public func getJSValue(this: JSObject, name: JSString) -> JSValue {
134149
var rawValue = RawJSValue()
135-
_get_prop(this.id, name,Int32(name.count),
150+
_get_prop(this.id, name.asInternalJSRef(),
136151
&rawValue.kind,
137152
&rawValue.payload1, &rawValue.payload2, &rawValue.payload3)
138153
return rawValue.jsValue()
139154
}
140155

141-
public func setJSValue(this: JSObject, name: String, value: JSValue) {
156+
public func setJSValue(this: JSObject, name: JSString, value: JSValue) {
142157
value.withRawJSValue { rawValue in
143-
_set_prop(this.id, name,Int32(name.count), rawValue.kind, rawValue.payload1, rawValue.payload2, rawValue.payload3)
158+
_set_prop(this.id, name.asInternalJSRef(), rawValue.kind, rawValue.payload1, rawValue.payload2, rawValue.payload3)
144159
}
145160
}
146161

@@ -183,7 +198,7 @@ extension JSValue: CustomStringConvertible {
183198
case let .boolean(boolean):
184199
return boolean.description
185200
case .string(let string):
186-
return string
201+
return string.description
187202
case .number(let number):
188203
return number.description
189204
case .object(let object), .function(let object as JSObject):

‎Sources/JavaScriptKit/JSValueConstructible.swift‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,3 +91,9 @@ extension UInt64: JSValueConstructible {
9191
value.number.map(Self.init)
9292
}
9393
}
94+
95+
extension JSString: JSValueConstructible {
96+
public static func construct(from value: JSValue) -> JSString? {
97+
value.jsString
98+
}
99+
}

‎Sources/JavaScriptKit/JSValueConvertible.swift‎

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ extension Double: JSValueConvertible {
3737
}
3838

3939
extension String: JSValueConvertible {
40-
public func jsValue() -> JSValue { .string(self) }
40+
public func jsValue() -> JSValue { .string(JSString(self)) }
4141
}
4242

4343
extension UInt8: JSValueConvertible {
@@ -72,6 +72,10 @@ extension Int64: JSValueConvertible {
7272
public func jsValue() -> JSValue { .number(Double(self)) }
7373
}
7474

75+
extension JSString: JSValueConvertible {
76+
public func jsValue() -> JSValue { .string(self) }
77+
}
78+
7579
extension JSObject: JSValueCodable {
7680
// `JSObject.jsValue` is defined in JSObject.swift to be able to overridden
7781
// from `JSFunction`
@@ -181,13 +185,7 @@ extension RawJSValue: JSValueConvertible {
181185
case .number:
182186
return .number(payload3)
183187
case .string:
184-
// +1 for null terminator
185-
let buffer = malloc(Int(payload2 + 1))!.assumingMemoryBound(to: UInt8.self)
186-
defer { free(buffer) }
187-
_load_string(JavaScriptObjectRef(payload1), buffer)
188-
buffer[Int(payload2)] = 0
189-
let string = String(decodingCString: UnsafePointer(buffer), as: UTF8.self)
190-
return .string(string)
188+
return .string(JSString(jsRef: payload1))
191189
case .object:
192190
return .object(JSObject(id: UInt32(payload1)))
193191
case .null:
@@ -218,13 +216,8 @@ extension JSValue {
218216
payload1 = 0
219217
payload2 = 0
220218
payload3 = numberValue
221-
case var .string(stringValue):
222-
kind = .string
223-
return stringValue.withUTF8 { bufferPtr in
224-
let ptrValue = UInt32(UInt(bitPattern: bufferPtr.baseAddress!))
225-
let rawValue = RawJSValue(kind: kind, payload1: JavaScriptPayload1(ptrValue), payload2: JavaScriptPayload2(bufferPtr.count), payload3: 0)
226-
return body(rawValue)
227-
}
219+
case let .string(string):
220+
return string.withRawJSValue(body)
228221
case let .object(ref):
229222
kind = .object
230223
payload1 = JavaScriptPayload1(ref.id)

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /