Skip to content

BridgeJS: Add opt-in TypedArray bridging for numeric arrays#8

Draft
krodak wants to merge 1 commit into
mainfrom
krodak/typed-array
Draft

BridgeJS: Add opt-in TypedArray bridging for numeric arrays#8
krodak wants to merge 1 commit into
mainfrom
krodak/typed-array

Conversation

@krodak
Copy link
Copy Markdown
Collaborator

@krodak krodak commented May 12, 2026

Overview

Optimize numeric array bridging with bulk TypedArray memory transfer. When a Swift function passes or receives a numeric array ([Int], [UInt8], [Float], [Double], etc.), BridgeJS now uses bulk memory copy instead of element-by-element stack serialization. TypeScript types remain number[] / bigint[]no user-facing API change.

This is a transparent performance optimization following the same pattern as bridgeJSStackPopAsOptional — the ABI changes internally but the type contract is unchanged.

How it works

Swift → JS (return values): withUnsafeBufferPointerswift_js_push_typed_array(ptr, count, kind) → JS copies bytes into a TypedArray → pre-allocated for-loop converts to number[].

JS → Swift (parameters): JS converts the input array to a TypedArray → retains in JS heap → passes (id, count) as WASM params → Swift allocates via Array(unsafeUninitializedCapacity:) → calls back to JS via swift_js_init_typed_array_memory to bulk copy.

Non-numeric arrays ([String], [MyStruct], etc.) are unaffected — they continue using the existing element-by-element stack protocol.

What changed

  • BridgeJSSkeleton.swiftisNumericScalar and typedArrayKind helpers on BridgeType
  • BridgeJSIntrinsics.swift_BridgeJSTypedArrayElement protocol, conformances for 12 numeric types, _bridgeJS_typedArrayPush and _bridgeJS_typedArrayLiftParameter bulk transfer functions, WASM externs for swift_js_push_typed_array and swift_js_init_typed_array_memory
  • JSGlueGen.swiftnumericArrayLift, numericArrayLower, numericArrayLowerReturn JS fragment generators; routing in lowerParameter, liftReturn, liftParameter, lowerReturn to detect numeric arrays
  • BridgeJSLink.swifttaStack, typedArrayConstructors, swift_js_push_typed_array, swift_js_init_typed_array_memory handlers
  • ExportSwift.swift — Numeric array parameters use (sourceId, count) WASM params + _bridgeJS_typedArrayLiftParameter; returns use _bridgeJS_typedArrayPush
  • ImportTS.swift — Numeric array parameters use _bridgeJS_typedArrayPush; returns use _bridgeJS_typedArrayLiftParameter
  • ClosureCodegen.swift — Numeric array closure parameters use bulk path

Benchmark results

Release build, 10K iterations × 5 runs, --filter=ArrayRoundtrip:

JS → Swift (take*)

Test Before After Speedup
takeIntArray (1K) 758 ms 13 ms 58× faster
takeDoubleArray (1K) 791 ms 13 ms 61× faster

Swift → JS (make*)

Test Before After Speedup
makeIntArray (1K) 221 ms 18 ms 12× faster
makeIntArrayLarge (10K) 2160 ms 146 ms 15× faster
makeDoubleArray (1K) 271 ms 36 ms 7.5× faster

Roundtrips

Test Before After Speedup
roundtripIntArray (1K) 964 ms 29 ms 33× faster
roundtripDoubleArray (1K) 1065 ms 29 ms 37× faster

Non-numeric — zero impact

Test Before After
takeStringArray 23 ms 25 ms
makeStringArray 7 ms 7 ms
takePointArray 6 ms 6 ms

Follow-up

For users who want actual Uint8Array / Float32Array in their JS API (e.g., for fetch body, WebGPU), a separate PR can add JSTypedArray<T> as a recognized BridgeJS type — per maintainer guidance. This would return TypedArrays directly without the Array conversion step.

@krodak krodak force-pushed the krodak/typed-array branch from 648ab81 to a1fc661 Compare May 12, 2026 09:39
@krodak krodak force-pushed the krodak/typed-array branch 5 times, most recently from c3c03ec to 8f7d9be Compare May 12, 2026 21:55
Use bulk TypedArray memory copy instead of element-by-element stack
serialization for numeric arrays ([Int], [UInt8], [Float], [Double],
etc.). TypeScript types remain number[]/bigint[] — no API change.

Swift->JS: withUnsafeBufferPointer passes (ptr, count, kind) to
swift_js_push_typed_array which copies bytes into a JS TypedArray,
then Array.from() converts to number[] for the caller.

JS->Swift: retains the array as a TypedArray in the JS heap, passes
(id, count) as WASM params, Swift allocates via
Array(unsafeUninitializedCapacity:) and calls back to JS to bulk copy.

Non-numeric arrays (String, structs, classes, enums) are unaffected
and continue using the existing element-by-element stack protocol.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant