Skip to content

fix(runtime): green perry-runtime --lib suite on Windows (#7356) - #7419

Merged
proggeramlug merged 3 commits into
mainfrom
fix/7356-windows-runtime-tests
Aug 5, 2026
Merged

fix(runtime): green perry-runtime --lib suite on Windows (#7356)#7419
proggeramlug merged 3 commits into
mainfrom
fix/7356-windows-runtime-tests

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #7356.

#7355 made perry-runtime compile on Windows; running the suite then surfaced the pre-existing failures inventoried in #7356. This lands the fixes and the CI arm that keeps them fixed. cargo test -p perry-runtime --lib -- --test-threads=1 on Windows 11: 1635 passed, 0 failed (previously three process-killing stoppers truncated every run).

Production bugs (not test bugs)

js_throw''s longjmp was undefined behavior on windows-msvc (exception.rs). MSVC''s longjmp reads _JUMP_BUFFER.Frame (the jmp_buf''s first 8 bytes) and, when nonzero, performs a REAL RtlUnwindEx stack unwind - and our one-arg setjmp extern leaves that slot holding garbage (the CRT _setjmp stores its second parameter there; we pass one). Measured: STATUS_BAD_STACK (0xC0000028) in a release probe; GS-cookie aborts (_report_gsfailure) under the panic=unwind test harness - the dyn_eval/native_abi stoppers. Every Rust-side boundary-trap catch on Windows (microtask pump, js_call_catching, iterator trampolines, promise combinators) rode this path in shipped binaries. Fix: zero the Frame slot before the jump, forcing the non-unwinding POSIX-style longjmp whose skipped-cleanup semantics the savepoint restores in js_throw already assume.

A/B with a compiled probe (throwing .then, Array.from mapper, Promise.all member, 1000-iteration throw/catch churn), fresh runtime archives both arms, PERRY_RUNTIME_DIR pinned, archive mtimes verified:

  • without the fix: exits 0xC0000028 before printing anything
  • with the fix: byte-identical to the Node 26.5.1 oracle, exit 0

The conservative-scan register snapshot buffer was under-aligned (gc/roots.rs). MSVC''s _setjmp saves XMM registers with aligned stores; the [u64; 32] buffer is 8-aligned - an immediate AV whenever the stack lands 8-mod-16 (measured; same root cause as the ffi::setjmp smoke-test AV that killed the harness). Snapshot + test buffers are now repr(align(16)); the extern''s docs record both MSVC contracts.

Test-shape fixes (subject kept live on Windows, not skipped)

  • date: TZ-isolation child uses TZ=PST8PDT on Windows - the UCRT''s TZ parser silently degrades IANA ids to UTC, which failed the test''s own subject-is-live guard.
  • gc malloc-trim: the counter now records that budgeted reclaim reached the trim call (the [gc] Graduate incremental collection: budgeted scanner coverage, assists for all phases, default-on (successor to #6083) #6180 subject - the old bug was skipping it) rather than counting only the glibc/macOS executing arms, which made the gate unsatisfiable on platforms with no trim primitive.
  • child_process: spawnSync result-shape test spawns cmd /c echo hi on Windows (echo is a cmd builtin; ENOENT under Node too).

CI

windows-build now runs RUST_TEST_THREADS=1 cargo test --profile perry-dev --lib -p perry-runtime - the same single-threaded invocation as the ubuntu leg (#1444), perry-dev profile so it shares the job''s build artifacts instead of paying a second cold build. Verified green locally under exactly that profile/invocation. Note it lands in an existing required job - if the runner budget gets tight the step can be split out, but the local run is ~4s of test time on top of the compile.

Out of scope, recorded for honesty

Parallel-mode (cargo test with default threads) has 5 pre-existing failures (closure::dynamic_props x2, gc::tests::teardown, object::prop_plan, global_this_webassembly) - cross-thread interference that CI already sidesteps on every platform by running single-threaded. Windows-latest runner cost: the new step adds one lib-test compile of perry-runtime under perry-dev (shares all dependency rlibs with the build step above).

No version bump per external-contributor flow - maintainer bumps at merge. Changelog fragment follows once the PR number exists.

Summary by CodeRabbit

  • Bug Fixes

    • Improved Windows runtime stability for exception handling, stack scanning, and memory alignment.
    • Fixed cross-platform child-process and timezone-related runtime tests.
    • Improved validation of memory reclamation behavior across supported platforms.
  • Tests

    • Added Windows runtime-suite coverage to continuous integration.
    • Expanded platform-specific checks for process execution, dates, garbage collection, and foreign-function interfaces.
  • Documentation

    • Documented Windows runtime fixes and remaining parallel test limitations.

Ralph Kuepper added 2 commits August 5, 2026 07:51
Two production bugs and four platform-shape test fixes; the suite goes
from three process-killing stoppers to 1635/1635 single-threaded.

- js_throw's longjmp was UB on windows-msvc: MSVC longjmp performs a
  real RtlUnwindEx when _JUMP_BUFFER.Frame (first 8 bytes) is nonzero,
  and the one-arg setjmp extern leaves that slot as garbage RDX (the CRT
  _setjmp stores its SECOND parameter there). Measured STATUS_BAD_STACK
  (0xC0000028) in a compiled probe and _report_gsfailure aborts under
  the panic=unwind test harness. Zero the slot before jumping: the
  non-unwinding POSIX semantics are exactly what the savepoint restores
  assume. A/B: probe (throwing .then / Array.from mapper / Promise.all
  member / 1000-throw churn) crashes 0xC0000028 without the fix, is
  byte-identical to the Node oracle with it.
- The conservative-scan register snapshot buffer ([u64; 32], 8-aligned)
  and the ffi::setjmp test buffers violated MSVC _setjmp's 16-byte
  alignment contract (aligned XMM stores) - an AV whenever the stack
  lands 8-mod-16. All are repr(align(16)) now; the extern documents
  both MSVC contracts.
- date: TZ-isolation child uses PST8PDT on Windows (UCRT TZ parser
  silently degrades IANA ids to UTC, failing the subject-is-live guard).
- gc malloc-trim test: count that budgeted reclaim REACHED the trim
  call (the #6180 subject) instead of counting only the glibc/macOS
  executing arms, which made the gate unsatisfiable where trim is
  unsupported.
- child_process: spawnSync test spawns `cmd /c echo hi` on Windows
  (echo is a cmd builtin; ENOENT under Node too).
- CI: windows-build now runs RUST_TEST_THREADS=1 cargo test
  --profile perry-dev --lib -p perry-runtime - the arm that keeps the
  suite from rotting back to unmeasurable.

Closes #7356
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0e95dc41-f5d3-41dc-9688-13dc521d46f8

📥 Commits

Reviewing files that changed from the base of the PR and between 73761eb and 45d603c.

📒 Files selected for processing (9)
  • .github/workflows/test.yml
  • changelog.d/7419-windows-runtime-suite-green.md
  • crates/perry-runtime/src/child_process/mod.rs
  • crates/perry-runtime/src/date.rs
  • crates/perry-runtime/src/exception.rs
  • crates/perry-runtime/src/ffi/setjmp.rs
  • crates/perry-runtime/src/gc/cycle.rs
  • crates/perry-runtime/src/gc/roots.rs
  • crates/perry-runtime/src/gc/tests/incremental_sweep_reclaim.rs

📝 Walkthrough

Walkthrough

The PR enables Windows perry-runtime library tests in CI and updates runtime behavior and tests for Windows setjmp, SEH, child processes, timezones, stack scanning, and malloc-trim instrumentation.

Changes

Windows runtime suite

Layer / File(s) Summary
Setjmp alignment and SEH-safe jumps
crates/perry-runtime/src/ffi/setjmp.rs, crates/perry-runtime/src/exception.rs, crates/perry-runtime/src/gc/roots.rs
Windows setjmp buffers now require 16-byte alignment. Windows js_throw clears the jump-buffer frame slot before longjmp. Related tests use aligned buffers.
Platform-specific runtime tests
crates/perry-runtime/src/child_process/mod.rs, crates/perry-runtime/src/date.rs, .github/workflows/test.yml, changelog.d/7419-windows-runtime-suite-green.md
Runtime tests use Windows-compatible commands and timezone identifiers. CI runs the Windows runtime library suite with one test thread.
Malloc-trim telemetry assertions
crates/perry-runtime/src/gc/cycle.rs, crates/perry-runtime/src/gc/tests/incremental_sweep_reclaim.rs, changelog.d/7419-windows-runtime-suite-green.md
Instrumentation separates calls to run_malloc_trim from actual allocator execution. GNU and macOS tests assert both stages.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • PerryTS/perry#6610: The Windows CI job builds on the runtime test coverage introduced by this PR.

Suggested labels: bug, rust, run-extended-tests

Suggested reviewers: thehypnoo

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/7356-windows-runtime-tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Audit follow-up on this PR. Moving the malloc-trim counter to the top of
run_malloc_trim made the gate satisfiable on Windows/musl -- correct, and
#6180's subject really is "did budgeted reclaim REACH the trim call",
since the bug was ordinary_budgeted skipping it.

But it also dropped the stronger property on glibc/macOS. The portable
counter witnesses only reaching, so it would pass with the platform arm
deleted, while the assertion still read "must invoke allocator trim".

Split into two counters: the portable ..._CALLS for "reached", and a
cfg-gated ..._EXECUTED incremented inside BOTH the glibc and Darwin arms
for "a trim primitive ran". Instrumenting only glibc would have made the
macOS gate impossible to satisfy -- the same shape as #7414, where an
assertion could not pass because its input was never produced.

Verified the new assertion can fail: removing the Darwin instrumentation
fails with "on a target with a trim primitive, budgeted reclaim must
EXECUTE it".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows: perry-runtime --lib test suite has pre-existing failures now that the crate compiles there (#7355 fallout inventory)

1 participant