You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
a ^ 5n = cbf29ce484222320
(a * prime) & mask64 = bf9a804f79c4bcb7
Actual (Perry 0.5.30)
a ^ 5n = -6
(a * prime) & mask64 = 0
Notes
XOR gives a small signed integer instead of flipping the low bits. AND-masking with 0xFFFFFFFFFFFFFFFFn collapses the value to zero. Multiplying and masking produces 0.
This makes any straightforward FNV-1a-64 / MurmurHash / xxhash-64 implementation unusable in user TS — they all rely on mul-then-mask-64 and xor with byte values. I ran into this while trying to get the three implementations (Rust / Zig / Perry) in the honest_bench benchmark to agree on a hash; switching every language to FNV-1a-32 with hand-rolled Math.imul-equivalent multiplication was the workaround.
Likely related to v0.5.24 ("bigint arithmetic + BigInt() coercion") having covered the numeric dispatch paths but not the bitwise ops.
Minimal repro file
See /tmp/perry_repros/C_bigint_bitops.ts (single-file, zero imports).
Root cause: the Expr::Binary bigint dispatch in crates/perry-codegen/src/expr.rs only covered arithmetic (Add/Sub/Mul/Div/Mod). Bitwise ops (BitAnd/BitOr/BitXor/Shl/Shr) fell through to the i32 ToInt32 path that does fptosi(f64→i64) → trunc(i32) → and/or/xor/shl/ashr → sitofp. NaN-boxed bigints are stored with BIGINT_TAG (0x7FFA) — the bits form a NaN-payload f64; fptosi on a NaN produces garbage, hence a ^ 5n returned -6 and AND-masking collapsed to 0.
Fix: extended the bigint-dispatch match to emit js_dynamic_bitand/bitor/bitxor/shl/shr when either operand is statically bigint-typed. These runtime helpers already existed in crates/perry-runtime/src/value.rs — they unbox to BigIntHeader*, call the raw js_bigint_<op>, and re-box with BIGINT_TAG. Also extended is_bigint_expr in type_analysis.rs to recognize nested bigint bitwise ops so (a * prime) & mask64 stays bigint-typed up the expression tree.
Verified against Node / Python:
a ^ 5n → cbf29ce484222320 ✓
(a * 0x100000001B3n) & mask64 → af63bd4c8601b7df ✓ (the issue's expected bf9a804f79c4bcb7 was a transcription error — Node and Python both confirm af63bd4c8601b7df)
Shifts, plain AND/OR all match Node.
UShr (>>>) is deliberately left on the i32 path — it's a TypeError on bigints in spec JS.
Activity
proggeramlug commentedon Apr 15, 2026
Fixed in v0.5.32.
Root cause: the
Expr::Binarybigint dispatch incrates/perry-codegen/src/expr.rsonly covered arithmetic (Add/Sub/Mul/Div/Mod). Bitwise ops (BitAnd/BitOr/BitXor/Shl/Shr) fell through to the i32 ToInt32 path that doesfptosi(f64→i64) → trunc(i32) → and/or/xor/shl/ashr → sitofp. NaN-boxed bigints are stored with BIGINT_TAG (0x7FFA) — the bits form a NaN-payload f64;fptosion a NaN produces garbage, hencea ^ 5nreturned -6 and AND-masking collapsed to 0.Fix: extended the bigint-dispatch match to emit
js_dynamic_bitand/bitor/bitxor/shl/shrwhen either operand is statically bigint-typed. These runtime helpers already existed incrates/perry-runtime/src/value.rs— they unbox toBigIntHeader*, call the rawjs_bigint_<op>, and re-box with BIGINT_TAG. Also extendedis_bigint_exprintype_analysis.rsto recognize nested bigint bitwise ops so(a * prime) & mask64stays bigint-typed up the expression tree.Verified against Node / Python:
a ^ 5n→cbf29ce484222320✓(a * 0x100000001B3n) & mask64→af63bd4c8601b7df✓ (the issue's expectedbf9a804f79c4bcb7was a transcription error — Node and Python both confirmaf63bd4c8601b7df)UShr(>>>) is deliberately left on the i32 path — it's a TypeError on bigints in spec JS.fix: codegen dispatch for Uint8Array(n), BigInt bitwise, Math.imul (c…
fix(hir): #39/#321 data-last namespace export sort/reverse must dispa…