Skip to content

Commit 00fa823

Browse files
committed
fix: workflow YAML parse error (v0.5.89)
The v0.5.88 CI run scheduled 0 jobs with "This run likely failed because of a workflow file issue". actionlint pinpointed the cause: .github/workflows/test.yml:126:0: could not parse as YAML: could not find expected ':' [syntax-check] Two `run: |` blocks in the parity job embedded multi-line `python3 -c "..."` scripts whose `import sys` / `import json, sys` lines started at column 0. YAML block scalars require every content line at >= the block's base indent (10 spaces here); a dedented line terminates the block. Rewrote "Check parity threshold" (awk compares jq-extracted floats) and "Check for new failures" (jq + sort + comm diff against known_failures.json). Same semantics, no embedded python — jq, awk, and comm are preinstalled on macos-14 runners. No runtime or codegen changes.
1 parent fc59775 commit 00fa823

4 files changed

Lines changed: 48 additions & 52 deletions

File tree

.github/workflows/test.yml

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -117,41 +117,36 @@ jobs:
117117
exit 1
118118
fi
119119
120-
ACTUAL=$(python3 -c "import json; print(json.load(open('$REPORT'))['summary']['parity_percentage'])")
121-
MIN=$(python3 -c "import json; print(json.load(open('$THRESHOLD_FILE'))['min_parity_pct'])")
120+
ACTUAL=$(jq -r '.summary.parity_percentage' "$REPORT")
121+
MIN=$(jq -r '.min_parity_pct' "$THRESHOLD_FILE")
122122
123123
echo "Parity: ${ACTUAL}% (threshold: ${MIN}%)"
124124
125-
python3 -c "
126-
import sys
127-
actual, minimum = float('$ACTUAL'), float('$MIN')
128-
if actual < minimum:
129-
print(f'FAIL: Parity {actual}% is below threshold {minimum}%')
130-
sys.exit(1)
131-
print(f'OK: Parity {actual}% meets threshold {minimum}%')
132-
"
125+
awk -v a="$ACTUAL" -v m="$MIN" 'BEGIN {
126+
if (a+0 < m+0) { print "FAIL: Parity " a "% is below threshold " m "%"; exit 1 }
127+
print "OK: Parity " a "% meets threshold " m "%"
128+
}'
133129
134130
- name: Check for new failures
135131
run: |
136132
REPORT="test-parity/reports/latest.json"
137133
KNOWN="test-parity/known_failures.json"
138134
139-
python3 -c "
140-
import json, sys
141-
report = json.load(open('$REPORT'))
142-
known = json.load(open('$KNOWN')) if __import__('os').path.exists('$KNOWN') else {}
143-
parity_fails = report['failures'].get('parity', [])
144-
compile_fails = report['failures'].get('compile', [])
145-
all_fails = parity_fails + compile_fails
146-
new_failures = [f for f in all_fails if f not in known]
147-
if new_failures:
148-
print('NEW FAILURES (not in known_failures.json):')
149-
for f in new_failures:
150-
print(f' - {f}')
151-
sys.exit(1)
152-
else:
153-
print(f'All {len(all_fails)} failures are known/triaged.')
154-
"
135+
jq -r '(.failures.parity // []) + (.failures.compile // []) | .[]' "$REPORT" | sort -u > /tmp/all_fails.txt
136+
if [[ -f "$KNOWN" ]]; then
137+
jq -r 'keys[]' "$KNOWN" | sort -u > /tmp/known.txt
138+
else
139+
: > /tmp/known.txt
140+
fi
141+
142+
TOTAL=$(wc -l < /tmp/all_fails.txt | tr -d ' ')
143+
comm -23 /tmp/all_fails.txt /tmp/known.txt > /tmp/new.txt
144+
if [[ -s /tmp/new.txt ]]; then
145+
echo "NEW FAILURES (not in known_failures.json):"
146+
sed 's/^/ - /' /tmp/new.txt
147+
exit 1
148+
fi
149+
echo "All ${TOTAL} failures are known/triaged."
155150
156151
- name: Upload parity report
157152
if: always()

CLAUDE.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
88

99
Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation.
1010

11-
**Current Version:** 0.5.88
11+
**Current Version:** 0.5.89
1212

1313
## TypeScript Parity Status
1414

@@ -150,6 +150,7 @@ First-resolved directory cached in `compile_package_dirs`; subsequent imports re
150150

151151
Keep entries to 1-2 lines max. Full details in CHANGELOG.md.
152152

153+
- **v0.5.89** — Fix `.github/workflows/test.yml` YAML parse error (the v0.5.88 workflow failed with "This run likely failed because of a workflow file issue" and scheduled 0 jobs). Two `run: |` blocks embedded multi-line `python3 -c "..."` scripts whose `import sys` / `import json, sys` lines started at column 0 — YAML block scalars require every content line at ≥ the block's base indent (10 spaces here), and a dedented line terminates the block. `actionlint` pinpointed it: `.github/workflows/test.yml:126:0: could not parse as YAML: could not find expected ':'`. Rewrote the "Check parity threshold" and "Check for new failures" steps to use `jq` + `awk` + `comm` — same semantics, no embedded python, preinstalled on `macos-14` runners. No runtime or codegen changes.
153154
- **v0.5.88** — Test/CI/benchmark infrastructure (no runtime or codegen changes). New GitHub Actions workflow (`.github/workflows/test.yml`) with five parallel jobs sharing a cached release build: `cargo-test` (workspace tests, iOS crate excluded), `parity` (runs `run_parity_tests.sh`, gates on `test-parity/threshold.json` @ 83% and diffs against `test-parity/known_failures.json` — currently 18 triaged entries), `compile-smoke` (every `test-files/*.ts` must compile clean), and `binary-size` (tracks `perry`/`libperry_runtime.a`/`libperry_stdlib.a` on main only). New `benchmarks/compare.sh` (regression detector: 15% speed / 25% memory thresholds vs `benchmarks/baseline.json`, writes updated JSON with git short-SHA + UTC timestamp, also supports `--update-baseline` and `--quick`) and `benchmarks/quick.sh` (5-bench ~15s smoke over fib/math/nested/factorial/matmul with Perry-vs-Node ratio + peak-RSS comparison). Seven new `.ts` microbenchmarks in `benchmarks/suite/` (`bench_array_grow`, `bench_buffer_readwrite`, `bench_gc_pressure`, `bench_int_arithmetic`, `bench_json_roundtrip`, `bench_object_property`, `bench_string_heavy`) covering the allocation and JSON paths recently tuned in v0.5.67–v0.5.81. Five new gap tests (`test_gap_buffer_ops`, `test_gap_closures`, `test_gap_map_set_extended`, `test_gap_typed_arrays`, `test_gap_typeof_instanceof` — the extra targets are called out in the gap-suite header but weren't checked in until now), four issue-#63 regression tests (`test_issue63_arr/asm/correctness/escape` — guard the array-literal scalar-replacement and inline-alloc paths from v0.5.69/v0.5.73/v0.5.74), five stress tests (`test_stress_buffer`, `test_stress_gc`, `test_stress_int_ops`, `test_stress_nanbox`, `test_stress_promises`), and a new `test-files/multi/test_stress_cross_module/` two-file module-resolution stress. New `test-coverage/` with `audit.sh` (scans `#[no_mangle] pub extern "C" fn` in perry-runtime + perry-stdlib, cross-references against `test-files/`) and the latest `COVERAGE.md` snapshot (1437 FFI functions, 42.8% covered). `issue60_writeup.md` post-mortem for the dynamic-property-write speedup shipped in v0.5.51/v0.5.55. No changes to `crates/`, runtime, or codegen — 108 runtime tests + gap suite + parity baseline all unchanged.
154155
- **v0.5.87** — Defer arena block reset for recent blocks (#73 final fix). v0.5.86 landed interior-pointer scan + FP register sweep which cut SIGSEGVs to 2% but NaN-sample rate was still 30% — `samples.slice()` occasionally returning empty because the backing store's block got reset while samples was mid-async-loop. Root cause: `arena_reset_empty_blocks` resets on the first sweep cycle that observes a block with zero live objects; a conservative-scan miss on a single cycle is enough to trigger reset, and the next allocation overwrites samples's header. Fix in `arena.rs::arena_reset_empty_blocks`: (1) never reset the current allocation block or the 4 blocks immediately preceding it — those hold the freshest allocations whose handles LLVM is most likely to be carrying in caller-saved registers the scan can't see. (2) Require two consecutive dead observations on older blocks before reset (`ArenaBlock.dead_cycles` counter). New block struct field `dead_cycles: u32` tracks consecutive dead observations; reaches 2 before block.offset flips to 0. Memory ceiling: up to 5 recent blocks × 8 MB = 40 MB of potentially-dead arena retention in the worst case. Negligible for benchmarks; acceptable for long-running programs where correctness matters more than aggressive block reuse. **Bench measurement (50-run @perry/postgres bench-this sweep):** 92% SUCCESS, 0% SIGSEGV, 6% NaN, 2% TIMEOUT — up from 64/2/30/0 in v0.5.86 and 0/90/0/10 in v0.5.82. The residual 6% NaN cases are now deep-GC-pressure scenarios where 5 blocks isn't enough retention; they can be investigated as a follow-up if they turn up in production workloads. 108 runtime tests pass; gap suite unchanged at 13/28 · 131 diffs.
155156
- **v0.5.86** — Root-cause fix for #73: interior-pointer GC + caller-saved FP register scan. Debugging v0.5.85's residual 10% SIGSEGV + 27% NaN led to the actual cause: runtime higher-order functions like `js_array_reduce` derive `elements_ptr = arr + 8` once and then invoke user callbacks in a loop holding ONLY the interior pointer. The conservative GC stack scan matches words against a `ValidPointerSet` populated with object-start pointers — an interior pointer `arr + 8` isn't in the set, so the scan fails to keep `arr` alive across the callback invocations and the next allocation-driven GC cycle sweeps the backing store mid-iteration. Compounds with Darwin's `setjmp` only capturing x19-x28 + d8-d15 (callee-saved) — any LLVM-allocated caller-saved FP register holding a NaN-boxed pointer across the async poll loop's internal calls is also invisible to the scan. **Two-place fix in `gc.rs`.** (1) `ValidPointerSet::enclosing_object(ptr)`: binary-search the sorted pointer table for the largest entry ≤ `ptr`, read the candidate's GC header to get its total size, and confirm `ptr` lies within `[start, start + size)` — returns the enclosing user pointer or None. Called from `try_mark_value_or_raw` on the raw-pointer path when direct lookup fails. NaN-boxed pointers always point at object starts (POINTER_TAG is stamped at NaN-box time on the user pointer, never an interior offset), so the enclosing check only applies to the raw/non-NaN-boxed path. Cost: one extra binary search + GC header load on stack words that don't match exactly — negligible vs the mark-trace work. (2) `mark_stack_roots` adds an inline-asm capture of d0-d31 (ARM64 only for now): setjmp's default callee-saved save list (d8-d15) misses d0-d7 and d16-d31, and LLVM will happily keep a NaN-box live in those caller-saved regs across the await-driven poll callbacks. The inline `str d0..d31, [buf, #N]` sequence writes every FP register unconditionally; whichever one happens to hold a NaN-box gets picked up by the subsequent `try_mark_value_or_raw` pass. **Bench measurement** (50-run @perry/postgres bench-this sweep): SIGSEGV rate 30% → 2%, SUCCESS 30% → 64%, NaN-sample 30% → 30%. NaN cases remain where `samples.slice()` returns an empty array because `samples`'s backing store got reused in an earlier allocator cycle; these are now a pure GC scheduling issue (scan didn't find the array before a sweep) rather than anything latent in the runtime or codegen. Dropping block-reset entirely (tested via a local env-var guard) gives 100% success but would grow arena memory without bound — a proper fix needs a smarter reset policy that defers resets for blocks containing fresh allocations. Deferred to a follow-up. 108 runtime tests pass; gap suite unchanged at 13/28 · 131 diffs.

Cargo.lock

Lines changed: 24 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ opt-level = "s" # Optimize for size in stdlib
102102
opt-level = 3
103103

104104
[workspace.package]
105-
version = "0.5.88"
105+
version = "0.5.89"
106106
edition = "2021"
107107
license = "MIT"
108108
repository = "https://github.com/PerryTS/perry"

0 commit comments

Comments
 (0)