Skip to content

Add a way for Swift code to access raw contents of a Typed Array #151

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 24, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion IntegrationTests/TestSuites/Sources/PrimaryTests/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -424,8 +424,9 @@ try test("Closure Identifiers") {
}
#endif

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

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

func checkArrayUnsafeBytes<T>(_ array: [T]) throws where T: TypedArrayElement & Equatable {
let copyOfArray: [T] = JSTypedArray(array).withUnsafeBytes { buffer in
Array(buffer)
}
try expectEqual(copyOfArray, array)
}

try test("TypedArray") {
let numbers = [UInt8](0 ... 255)
let typedArray = JSTypedArray(numbers)
try expectEqual(typedArray[12], 12)
try expectEqual(numbers.count, typedArray.lengthInBytes)

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

try checkArray([0, .max, 127, 1] as [UInt8])
try checkArray([0, 1, .max, .min, -1] as [Int8])
Expand Down
12 changes: 9 additions & 3 deletions Runtime/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export class SwiftRuntime {
private instance: WebAssembly.Instance | null;
private heap: SwiftRuntimeHeap;
private _closureHeap: SwiftClosureHeap | null;
private version: number = 703;
private version: number = 704;

constructor() {
this.instance = null;
Expand Down Expand Up @@ -224,7 +224,7 @@ export class SwiftRuntime {
return this.heap.referenceHeap(ref);
};

const writeString = (ptr: pointer, bytes: Uint8Array) => {
const writeBytes = (ptr: pointer, bytes: Uint8Array) => {
const uint8Memory = new Uint8Array(memory().buffer);
uint8Memory.set(bytes, ptr);
};
Expand Down Expand Up @@ -445,7 +445,7 @@ export class SwiftRuntime {

swjs_load_string: (ref: ref, buffer: pointer) => {
const bytes = this.heap.referenceHeap(ref);
writeString(buffer, bytes);
writeBytes(buffer, bytes);
},

swjs_call_function: (
Expand Down Expand Up @@ -582,6 +582,12 @@ export class SwiftRuntime {
return this.heap.retain(array.slice());
},

swjs_load_typed_array: (ref: ref, buffer: pointer) => {
const typedArray = this.heap.referenceHeap(ref);
const bytes = new Uint8Array(typedArray.buffer);
writeBytes(buffer, bytes);
},

swjs_release: (ref: ref) => {
this.heap.release(ref);
},
Expand Down
32 changes: 32 additions & 0 deletions Sources/JavaScriptKit/BasicObjects/JSTypedArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,38 @@ public class JSTypedArray<Element>: JSBridgedClass, ExpressibleByArrayLiteral wh
public convenience init<S: Sequence>(_ sequence: S) where S.Element == Element {
self.init(Array(sequence))
}

/// Length (in bytes) of the typed array.
/// The value is established when a TypedArray is constructed and cannot be changed.
/// If the TypedArray is not specifying a `byteOffset` or a `length`, the `length` of the referenced `ArrayBuffer` will be returned.
public var lengthInBytes: Int {
Int(jsObject["byteLength"].number!)
}

/// Calls the given closure with a pointer to a copy of the underlying bytes of the
/// array's storage.
///
/// - Note: The pointer passed as an argument to `body` is valid only for the
/// lifetime of the closure. Do not escape it from the closure for later
/// use.
///
/// - Parameter body: A closure with an `UnsafeBufferPointer` parameter
/// that points to the contiguous storage for the array.
/// If `body` has a return value, that value is also
/// used as the return value for the `withUnsafeBytes(_:)` method. The
/// argument is valid only for the duration of the closure's execution.
/// - Returns: The return value, if any, of the `body` closure parameter.
public func withUnsafeBytes<R>(_ body: (UnsafeBufferPointer<Element>) throws -> R) rethrows -> R {
let bytesLength = lengthInBytes
let rawBuffer = malloc(bytesLength)!
defer { free(rawBuffer) }
_load_typed_array(jsObject.id, rawBuffer.assumingMemoryBound(to: UInt8.self))
let length = lengthInBytes / MemoryLayout<Element>.size
let boundPtr = rawBuffer.bindMemory(to: Element.self, capacity: length)
let bufferPtr = UnsafeBufferPointer<Element>(start: boundPtr, count: length)
let result = try body(bufferPtr)
return result
}
}

// MARK: - Int and UInt support
Expand Down
4 changes: 4 additions & 0 deletions Sources/JavaScriptKit/XcodeSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ import _CJavaScriptKit
_: UnsafePointer<T>,
_: Int32
) -> JavaScriptObjectRef { fatalError() }
func _load_typed_array(
_: JavaScriptObjectRef,
_: UnsafeMutablePointer<UInt8>!
) { fatalError() }
func _release(_: JavaScriptObjectRef) { fatalError() }

#endif
2 changes: 1 addition & 1 deletion Sources/_CJavaScriptKit/_CJavaScriptKit.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void swjs_cleanup_host_function_call(void *argv_buffer) {
/// this and `SwiftRuntime.version` in `./Runtime/src/index.ts`.
__attribute__((export_name("swjs_library_version")))
int swjs_library_version(void) {
return 703;
return 704;
}

int _library_features(void);
Expand Down
8 changes: 8 additions & 0 deletions Sources/_CJavaScriptKit/include/_CJavaScriptKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,14 @@ __attribute__((__import_module__("javascript_kit"),
extern JavaScriptObjectRef _create_typed_array(const JavaScriptObjectRef constructor,
const void *elements_ptr, const int length);

/// Copies the byte contents of a typed array into a Swift side memory buffer.
///
/// @param ref A JavaScript typed array object.
/// @param buffer A Swift side buffer into which to copy the bytes.
__attribute__((__import_module__("javascript_kit"),
__import_name__("swjs_load_typed_array")))
extern void _load_typed_array(const JavaScriptObjectRef ref, unsigned char *buffer);

/// Decrements reference count of `ref` retained by `SwiftRuntimeHeap` in JavaScript side.
///
/// @param ref The target JavaScript object.
Expand Down