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/:
- Wherever JS code calls into WASM with declared-i64 args, ensure encoding is BigInt.
- Wherever WASM calls into JS imports with declared-i64 args, ensure WASM-side codegen emits BigInt at the call site.
- 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.
Summary
Perry
--target webhas multiple sites where a JS Number leaks into a WASMi64boundary, throwingTypeError: 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
--target web)@honeide/editorboot path after wasm runtime: post-parse hang —object_get_dynamicrepeats indefinitely with identical args after keyword tokenizer enters its outer loop #1037 landed.Known instances (in encounter order)
1.
wrapFfiForI64returns plain Number for small i64 returnscrates/perry-codegen-wasm/src/wasm_runtime.jsline ~2221:hone_editor_createis declaredreturns: "i64"in ourpackage.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
returnsfromperry.nativeLibrary.functionsinto 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:
The
desiredColumn: 0cursor state struct triggers a downstream WASM call where the value0is 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
__classDispatchboundary (verified by instrumentation showingfn.length=3, all three argsbigint(0x7ffd...)):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 tohone_editor_ts_parse(text: string, langId: number)) with thetextarg passed as Number 0 instead of as a string-handle BigInt for the i64 param.Worked around by routing the editor to
KeywordSyntaxEngineinstead (language='markdown').Pattern
All three sites share the same shape: a WASM-side
i64import 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.fromCharCodereturning undefined for string-handle decode) and theINT32_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
DevTools console shows the BigInt error. Comment out the
BigInt(h) as anyworkaround innative/web/dom-ffi.tsto see instance #1; switchlanguage: 'markdown'back to'typescript'inexamples/web/entry.tsto see instance #3.What would help
A targeted audit of
crates/perry-codegen-wasm/src/:Each of these is roughly the same shape; a single pass through codegen + runtime should knock them out.