Skip to content

Commit 53a2de6

Browse files
authoredJan 24, 2022
Add a way for Swift code to access raw contents of a Typed Array (#151)
1 parent 616284a commit 53a2de6

File tree

6 files changed

+65
-5
lines changed

6 files changed

+65
-5
lines changed
 

Diff for: ‎IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift

+11-1
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,9 @@ try test("Closure Identifiers") {
424424
}
425425
#endif
426426

427-
func checkArray<T>(_ array: [T]) throws where T: TypedArrayElement {
427+
func checkArray<T>(_ array: [T]) throws where T: TypedArrayElement & Equatable {
428428
try expectEqual(toString(JSTypedArray(array).jsValue().object!), jsStringify(array))
429+
try checkArrayUnsafeBytes(array)
429430
}
430431

431432
func toString<T: JSObject>(_ object: T) -> String {
@@ -436,14 +437,23 @@ func jsStringify(_ array: [Any]) -> String {
436437
array.map({ String(describing: $0) }).joined(separator: ",")
437438
}
438439

440+
func checkArrayUnsafeBytes<T>(_ array: [T]) throws where T: TypedArrayElement & Equatable {
441+
let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in
442+
Array(buffer)
443+
}
444+
try expectEqual(copyOfArray, array)
445+
}
446+
439447
try test("TypedArray") {
440448
let numbers = [UInt8](0 ... 255)
441449
let typedArray = JSTypedArray(numbers)
442450
try expectEqual(typedArray[12], 12)
451+
try expectEqual(numbers.count, typedArray.lengthInBytes)
443452

444453
let numbersSet = Set(0 ... 255)
445454
let typedArrayFromSet = JSTypedArray(numbersSet)
446455
try expectEqual(typedArrayFromSet.jsObject.length, 256)
456+
try expectEqual(typedArrayFromSet.lengthInBytes, 256 * MemoryLayout<Int>.size)
447457

448458
try checkArray([0, .max, 127, 1] as [UInt8])
449459
try checkArray([0, 1, .max, .min, -1] as [Int8])

Diff for: ‎Runtime/src/index.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ export class SwiftRuntime {
141141
private instance: WebAssembly.Instance | null;
142142
private heap: SwiftRuntimeHeap;
143143
private _closureHeap: SwiftClosureHeap | null;
144-
private version: number = 703;
144+
private version: number = 704;
145145

146146
constructor() {
147147
this.instance = null;
@@ -224,7 +224,7 @@ export class SwiftRuntime {
224224
return this.heap.referenceHeap(ref);
225225
};
226226

227-
const writeString = (ptr: pointer, bytes: Uint8Array) => {
227+
const writeBytes = (ptr: pointer, bytes: Uint8Array) => {
228228
const uint8Memory = new Uint8Array(memory().buffer);
229229
uint8Memory.set(bytes, ptr);
230230
};
@@ -445,7 +445,7 @@ export class SwiftRuntime {
445445

446446
swjs_load_string: (ref: ref, buffer: pointer) => {
447447
const bytes = this.heap.referenceHeap(ref);
448-
writeString(buffer, bytes);
448+
writeBytes(buffer, bytes);
449449
},
450450

451451
swjs_call_function: (
@@ -582,6 +582,12 @@ export class SwiftRuntime {
582582
return this.heap.retain(array.slice());
583583
},
584584

585+
swjs_load_typed_array: (ref: ref, buffer: pointer) => {
586+
const typedArray = this.heap.referenceHeap(ref);
587+
const bytes = new Uint8Array(typedArray.buffer);
588+
writeBytes(buffer, bytes);
589+
},
590+
585591
swjs_release: (ref: ref) => {
586592
this.heap.release(ref);
587593
},

Diff for: ‎Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift

+32
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,38 @@ public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral wh
5454
public convenience init<S: Sequence>(_ sequence: S) where S.Element == Element {
5555
self.init(Array(sequence))
5656
}
57+
58+
/// Length (in bytes) of the typed array.
59+
/// The value is established when a TypedArray is constructed and cannot be changed.
60+
/// If the TypedArray is not specifying a `byteOffset` or a `length`, the `length` of the referenced `ArrayBuffer` will be returned.
61+
public var lengthInBytes: Int {
62+
Int(jsObject["byteLength"].number!)
63+
}
64+
65+
/// Calls the given closure with a pointer to a copy of the underlying bytes of the
66+
/// array's storage.
67+
///
68+
/// - Note: The pointer passed as an argument to `body` is valid only for the
69+
/// lifetime of the closure. Do not escape it from the closure for later
70+
/// use.
71+
///
72+
/// - Parameter body: A closure with an `UnsafeBufferPointer` parameter
73+
/// that points to the contiguous storage for the array.
74+
/// If `body` has a return value, that value is also
75+
/// used as the return value for the `withUnsafeBytes(_:)` method. The
76+
/// argument is valid only for the duration of the closure's execution.
77+
/// - Returns: The return value, if any, of the `body` closure parameter.
78+
public func withUnsafeBytes<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R {
79+
let bytesLength = lengthInBytes
80+
let rawBuffer = malloc(bytesLength)!
81+
defer { free(rawBuffer) }
82+
_load_typed_array(jsObject.id, rawBuffer.assumingMemoryBound(to: UInt8.self))
83+
let length = lengthInBytes / MemoryLayout<Element>.size
84+
let boundPtr = rawBuffer.bindMemory(to: Element.self, capacity: length)
85+
let bufferPtr = UnsafeBufferPointer<Element>(start: boundPtr, count: length)
86+
let result = try body(bufferPtr)
87+
return result
88+
}
5789
}
5890

5991
// MARK: - Int and UInt support

Diff for: ‎Sources/JavaScriptKit/XcodeSupport.swift

+4
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ import _CJavaScriptKit
8282
_: UnsafePointer<T>,
8383
_: Int32
8484
) -> JavaScriptObjectRef { fatalError() }
85+
func _load_typed_array(
86+
_: JavaScriptObjectRef,
87+
_: UnsafeMutablePointer<UInt8>!
88+
) { fatalError() }
8589
func _release(_: JavaScriptObjectRef) { fatalError() }
8690

8791
#endif

Diff for: ‎Sources/_CJavaScriptKit/_CJavaScriptKit.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ void swjs_cleanup_host_function_call(void *argv_buffer) {
3636
/// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`.
3737
__attribute__((export_name("swjs_library_version")))
3838
int swjs_library_version(void) {
39-
return 703;
39+
return 704;
4040
}
4141

4242
int _library_features(void);

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

+8
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,14 @@ __attribute__((__import_module__("javascript_kit"),
247247
extern JavaScriptObjectRef _create_typed_array(const JavaScriptObjectRef constructor,
248248
const void *elements_ptr, const int length);
249249

250+
/// Copies the byte contents of a typed array into a Swift side memory buffer.
251+
///
252+
/// @param ref A JavaScript typed array object.
253+
/// @param buffer A Swift side buffer into which to copy the bytes.
254+
__attribute__((__import_module__("javascript_kit"),
255+
__import_name__("swjs_load_typed_array")))
256+
extern void _load_typed_array(const JavaScriptObjectRef ref, unsigned char *buffer);
257+
250258
/// Decrements reference count of `ref` retained by `SwiftRuntimeHeap` in JavaScript side.
251259
///
252260
/// @param ref The target JavaScript object.

0 commit comments

Comments
 (0)
Please sign in to comment.