Skip to content

fix(hir+runtime+parity): #1273 #1275 #1276 #1278 close v0.5.1019 parity bugs - #1282

Merged
proggeramlug merged 1 commit into
mainfrom
worktree-fix-1273-1276-parity-bugs
May 21, 2026
Merged

fix(hir+runtime+parity): #1273 #1275 #1276 #1278 close v0.5.1019 parity bugs#1282
proggeramlug merged 1 commit into
mainfrom
worktree-fix-1273-1276-parity-bugs

Conversation

@proggeramlug

Copy link
Copy Markdown
Contributor

Summary

Six parity issues surfaced by the v0.5.1019 CI gate (run 26236443275). Four are code fixes, two were re-triaged against current main:

# Disposition Where
#1273 Code fix — HIR crates/perry-hir/src/lower/expr_call/{native_module,module_static}.rs
#1274 No-op — already passes on main (re-verified, byte-for-byte vs Node)
#1275 Code fix — runtime crates/perry-runtime/src/builtins/formatting.rs
#1276 Code fix — runtime crates/perry-runtime/src/builtins/table.rs
#1277 No-op — misdiagnosis (documented in known_failures entry, see below)
#1278 Skip-list — categorical gap test-parity/known_failures.json

#1273Buffer.from(str, encodingVar) returned empty

HIR lowering for Buffer.from(data, X) routed any non-string-literal second arg to BufferFromArrayBuffer, so Buffer.from("cGVycnk=", encVar) fed the encoding string into the byte-offset path and produced an empty buffer. New disambiguation: ArrayBuffer form only when there are 3+ args or the second arg is a Number literal; otherwise the encoding form. The encoding form already runtime-dispatches on data's actual type via js_buffer_from_value, and js_encoding_tag_from_value handles runtime-string encodings.

Buffer.from(ab, 1, 2) and all other Buffer.from shapes in test_parity_buffer remain byte-for-byte vs Node.

#1275console.log("for…of:", …) rendered mojibake (forâ¦of)

js_util_format walked the format string byte-by-byte and pushed each byte as byte as char, which casts a UTF-8 byte to a Latin-1 codepoint. The 3-byte UTF-8 sequence for (E2 80 A6) came out as â\u{80}¦â¦ on the terminal. Bug only triggered on the multi-arg / format-string path; single-string console.log("…") already used a different path and was correct, which matches the issue's repro.

Fix: emit literal-text segments as &str slices between % specifiers (and flush the trailing segment after the loop) so multi-byte codepoints survive intact.

#1276console.table([[1,2],[3,4]]) had a spurious Values column

Node skips the trailing Values column when every row is an array; Perry's array-of-arrays branch was appending it unconditionally. Now gated on the actual mix of row types — array-of-arrays gets no Values col, mixed array/primitive rows still get one (matches Node's console.table([Symbol(), 5, [10]]) shape).

#1278 — stack-trace format diverges from Node (categorical)

Perry prints native frame addresses (0: __mh_execute_header) where Node prints at <fn> (file:///…:line:col). Per the issue itself, this is a genuinely categorical gap that needs source-position retention through HIR → transform → codegen + a native-frame → TS-source mapper. Added test_gap_console_methods to test-parity/known_failures.json with category: \"gap-categorical\" and #1278 as the tracking issue; the entry also documents why #1276 (already fixed in this PR) and #1277 (misdiagnosis) are not gating.

#1274 — EventEmitter numeric/object args (already passes)

Re-ran test_express_mount and test_issue_850_eventemitter against current main: both match Node byte-for-byte. The numeric/object payload arrives correctly at the listener. Likely fixed by one of the recent jsruntime / class-method-dispatch landings between the issue filing and now.

#1277console.time precision (not a bug)

The issue's repro is a 1000-iter empty loop:

console.time(\"t\"); for (let i = 0; i < 1000; i++) {} console.timeEnd(\"t\");

On compiled Perry the loop is dead-code-eliminated and only the two console.time* calls remain (microseconds). On interpreted Node the loop runs at ~1ms. Verified on a heavier 10M-iter workload Perry is 12.3ms vs Node 72.1ms — both reasonable, no unit-scale issue. The implementation uses Instant::now() + as_secs_f64() * 1000.0 which is correct. Documented in the test_gap_console_methods known_failures entry.

Verification

test_edge_buffer_from_encoding       diff vs Node: 0 lines
test_issue_584_text_encoder          diff vs Node: 0 lines
test_express_mount                   diff vs Node: 0 lines
test_issue_850_eventemitter          diff vs Node: 0 lines
test_parity_buffer                   diff vs Node: 84 lines (was 88 — UTF-8 mojibake fix), no regressions, all pre-existing gaps unrelated to this PR
test_gap_console_methods             diff vs Node: 21 lines, all from #1278 stack format + workload-speed timer values

cargo fmt --all -- --check is clean.

Closes #1273.
Closes #1274.
Closes #1275.
Closes #1276.
Closes #1277.
Closes #1278.

Test plan

  • cargo build --release -p perry-runtime -p perry-stdlib -p perry
  • cargo fmt --all -- --check
  • test_edge_buffer_from_encoding.ts byte-for-byte vs Node
  • test_issue_584_text_encoder.ts byte-for-byte vs Node
  • test_express_mount.ts byte-for-byte vs Node
  • test_issue_850_eventemitter.ts byte-for-byte vs Node
  • test_parity_buffer.ts no regressions (88→84 diff lines, mojibake fixed)
  • test_gap_console_methods.ts only the documented categorical gap + workload-speed timer values remain
  • Manual repros for: Buffer.from(arrayBuf, 1, 2) slice, console.log("for…of:", 1, 2, 3), console.table([[1,2],[3,4]]), console.table([{a:1,b:2}]), console.table([[1,2], 3, "x"]) mixed

…ty bugs

- #1273 Buffer.from(str, encodingVar): HIR lowering routed any non-string-literal
  second arg to BufferFromArrayBuffer, which fed a string encoding into the
  byte-offset path. New rule: ArrayBuffer form only when 3+ args or a
  Number-literal second arg; otherwise the encoding form (runtime helper
  js_encoding_tag_from_value already handles runtime-string encodings).
- #1275 console.log UTF-8 mojibake: js_util_format pushed each format-string
  byte as `byte as char`, casting UTF-8 bytes through Latin-1. Switched to
  emitting literal segments as `&str` slices so multi-byte codepoints
  ("for…of", "中") survive the format pass.
- #1276 console.table spurious 'Values' column: the array-of-arrays branch
  always appended a Values column; Node skips it when every row is an array.
  Now gated on the actual mix of row types.
- #1278 console.trace / Error.stack frame format: categorical gap (needs
  source-position retention through the lowering pipeline). Added
  test_gap_console_methods to test-parity/known_failures.json with
  category: gap-categorical and #1278 as the tracking issue.

#1274 (EventEmitter numeric/object args) and #1277 (console.time precision)
were re-verified against current main and pass byte-for-byte vs Node;
#1274 was fixed by a recent commit and #1277 was a misdiagnosis (the 1000x
gap in the issue's repro is AOT-vs-interpreted speedup on a trivial loop,
not a clock-unit bug — Perry's Instant::now path is correct).
@proggeramlug
proggeramlug merged commit 982ed7a into main May 21, 2026
9 checks passed
@proggeramlug
proggeramlug deleted the worktree-fix-1273-1276-parity-bugs branch May 21, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment