Skip to content

wasm runtime: recurring TypeError: Cannot convert X to a BigInt at i64 boundaries — tracking issue #1049

Description

@proggeramlug

Summary

Perry --target web has multiple sites where a JS Number leaks into a WASM i64 boundary, throwing TypeError: Cannot convert X to a BigInt. Following the #1037 fix, hone-editor now boots past the keyword tokenizer and mounts the editor surface via FFI — but every i64-typed FFI return and several internal class-method dispatches still throw this. There is no single repro because the same root pattern recurs at multiple codegen sites.

This is a tracking issue for the class of bugs; reproduce by stripping the editor workaround at a time.

Environment

Known instances (in encounter order)

1. wrapFfiForI64 returns plain Number for small i64 returns

crates/perry-codegen-wasm/src/wasm_runtime.js line ~2221:

function wrapFfiForI64(fn) {
  ...
  if (typeof result === 'number') {
    if (Number.isInteger(result) && Math.abs(result) < 2147483648) return result;  // ← Number, not BigInt
    _f64[0] = result;
    return _u64[0];
  }
  ...
}

hone_editor_create is declared returns: "i64" in our package.json. Our JS stub returns the editor handle as Number (return h;). Wrapper returns Number 1 → WASM caller expects BigInt for i64 return → Cannot convert 1 to a BigInt.

Workaround: have the FFI stub return BigInt(h) as any; — works, but every consumer has to know the per-function ABI.

The wrapper has no way to know the declared return type of the FFI function. Fix should plumb the declared returns from perry.nativeLibrary.functions into the wrapping decision, or always coerce Number → BigInt for any "could-be-i64" return.

2. Same class — TS class-method dispatch passes Number where i64 is expected

After workaround #1, the editor mounts the surface. Then this:

[log] HONE_EDITOR_MOUNTED handle=1 size=900x600
[log] mem_call: object_get_dynamic args: [Array(1), 0]
[log]   result: {__class__: __AnonShape_e1be8ee3b9e58854, line: 0, column: 0, selectionAnchor: null, desiredColumn: 0}
[error] Boot error: TypeError: Cannot convert 0 to a BigInt
    at wasm-function[875]:0x109654
    at wasm-function[1074]:0x1515ef
    at bootPerryWasm

The desiredColumn: 0 cursor state struct triggers a downstream WASM call where the value 0 is passed as i64. Different code path than the FFI wrapper — this is internal to the WASM module's body.

3. Earlier — TreeSitterEngine.parse(text, langId) (avoided by switching language to 'markdown')

Even with all FFI args properly BigInt-encoded at the __classDispatch boundary (verified by instrumentation showing fn.length=3, all three args bigint(0x7ffd...)):

[error] CRASH m=parse cls=TreeSitterEngine fn.length=3 fn.name=596
   allBits=bigint(0x7ffd000000000373),bigint(0x7ffd000000000372),bigint(0x7ffc000000000001)
[error] Boot error: TypeError: Cannot convert 0 to a BigInt
    at wasm://wasm/...wasm-function[596]:0xb6db5

The call args are valid BigInts. The throw originates inside the WASM function body at offset 0xb6db5, where the codegen-emitted code makes an import call (likely to hone_editor_ts_parse(text: string, langId: number)) with the text arg passed as Number 0 instead of as a string-handle BigInt for the i64 param.

Worked around by routing the editor to KeywordSyntaxEngine instead (language='markdown').

Pattern

All three sites share the same shape: a WASM-side i64 import or call gets a JS Number where it needs a BigInt. The fix points are different but the root cause is uniform: codegen / runtime doesn't track which numeric values cross i64 vs f64 boundaries.

This is the same family of bug as #1037 (String.fromCharCode returning undefined for string-handle decode) and the INT32_TAG-decode gap I flagged in #1037 comment. Three open BigInt/handle-boundary bugs now — worth a focused pass on the WASM↔JS value-encoding layer.

Repro

git clone https://github.com/HoneIDE/editor
cd editor
bun run examples/web/build.ts
python3 -m http.server -d examples/web/dist 8765 &
open http://localhost:8765/index.html

DevTools console shows the BigInt error. Comment out the BigInt(h) as any workaround in native/web/dom-ffi.ts to see instance #1; switch language: 'markdown' back to 'typescript' in examples/web/entry.ts to see instance #3.

What would help

A targeted audit of crates/perry-codegen-wasm/src/:

  1. Wherever JS code calls into WASM with declared-i64 args, ensure encoding is BigInt.
  2. Wherever WASM calls into JS imports with declared-i64 args, ensure WASM-side codegen emits BigInt at the call site.
  3. Wherever JS returns to WASM as declared-i64, coerce to BigInt at the wrapper.

Each of these is roughly the same shape; a single pass through codegen + runtime should knock them out.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions