Skip to content

Native-library descriptor params: object args aren't marshalled to JSON strings (descriptor bindings throw after #5621) #5626

Description

@proggeramlug

Summary

Follow-up to #5621 (now merged). With ergonomic camelCase exports routing to their js_<pkg>_* symbols, a whole class of bindings still can't run: those whose functions take descriptor objects marshalled to a "string" FFI param via JSON.stringify. #5621 rewrites the call site directly to the FFI symbol, bypassing the package's wrapper body — so the object never gets stringified and hits js_native_abi_check_string_ptr, which throws TypeError: Expected string for native string parameter.

This blocks @perryts/webgpu end-to-end: ~20 of its 69 functions take a GPUBufferDescriptor / GPURenderPipelineDescriptor / GPUCanvasConfiguration / etc. object and declare the corresponding manifest param as "string" (the binding's documented design is "the TS side JSON.stringifys the descriptor and we serde_json-deserialize on the Rust side").

Evidence

Built perry at the #5621 merge (7b15b680b), linked @perryts/webgpu's real staticlib, ran:

const adapter = await requestAdapter();                         // -> 1            ✓ routes
const { device } = await adapterRequestDevice(adapter);          // -> device 2     ✓ routes
const sh = deviceCreateShaderModule(device, "@compute ...");     // -> 4            ✓ STRING arg
const buf = deviceCreateBuffer(device, { size: 16, usage: 0x80 });// OBJECT arg
//   TypeError: Expected string for native string parameter

So #5621 routing is confirmed working for no-arg / handle-arg / real-string-arg functions. It's specifically object → "string" param that fails, because the wrapper body that would have done JSON.stringify(descriptor) is bypassed by the call-site rewrite.

crates/perry-runtime/src/native_abi.rs:137 (js_native_abi_check_string_ptr) validates rather than coerces — a non-string throws.

Root cause

Before #5621, descriptor bindings worked (when routing worked at all) because the camelCase wrapper had a real body: deviceCreateBuffer(d, desc) => js_webgpu_device_create_buffer(d, JSON.stringify(desc)). #5621's call-site rewrite (lower_call/extern_func.rs, ffi_aliases) replaces deviceCreateBuffer(...) with a direct call to js_webgpu_device_create_buffer(...) — the wrapper body never runs, so the descriptor reaches the boundary as a live object.

There's no longer any TS-side hook to marshal the object, so marshalling has to move into the FFI boundary itself.

Proposed fix

Add a manifest param descriptor that means "JSON-serialize this JS value to a string before the call":

{ "name": "js_webgpu_device_create_buffer", "params": ["i64", "json"], "returns": "i64" }

At the call site, a "json" param lowers the arg through JSON.stringify (the existing js_json_stringify runtime entry) and passes the resulting string pointer — the native side still receives a *const StringHeader and serde_json-deserializes it unchanged, so no binding Rust change is needed beyond the manifest edit. Objects, arrays, and primitives all serialize; the type is explicit and opt-in, so existing "string" params (real strings, e.g. WGSL source) keep their strict check.

Alternative (less preferred): make "string" auto-JSON.stringify a non-string arg. Works with zero manifest changes but is implicit and would mask "passed the wrong thing" bugs — a dedicated "json" type is cleaner.

Note on binding-side packaging (already handled, for context)

For @perryts/webgpu to even reach this point, two binding-side manifest fixes were needed and applied (not perry bugs):

With both in place + the #5621 build, the routing works perfectly up to the first descriptor-object call — which is where this issue picks up.

Environment

Local perry built from 7b15b680b (#5621 merge), macOS arm64.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugConfirmed defect or regression

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions