Widen GC header size field from 16 to 31 bits (closes #484)#488
Conversation
`$alloc` has always stored object size as `(size << 1) | mark` in the full i32 header, but the GC's read sites were masking with `0xFFFF`, truncating any allocation >= 65536 bytes. The sweeper then interpreted middle-of-payload bytes as tiny zero-size headers and linked each 8-byte chunk into the free list, shredding the live object and returning corrupted pointers to the caller. Fix: strip the `i32.const 65535; i32.and` pair from all five read sites in vera/codegen/assembly.py (two in `$alloc` free-list walks, three in `$gc_collect` — phase 1 clear-marks advance, phase 2b scan, phase 3 sweep). Max single allocation is now ~2 GB, bounded by WASM's 4 GB memory ceiling and the leading mark bit. Stress tests for array_map and array_filter uncapped from 8K back to 10K elements (80,000-byte output allocations). Updated test docstrings to note the previous cap was #484 workaround, now resolved. Removed the 16-bit caveat text from the translator docstrings in calls_arrays.py. Removed the entry from KNOWN_ISSUES. Testing the fix surfaced a separate bug: `$alloc` grows memory by exactly 1 page regardless of how many pages the allocation needs, so single allocations more than ~64 KB larger than free heap space still trap. Filed as #487 and added to KNOWN_ISSUES. Closes #484. Co-Authored-By: Claude <noreply@anthropic.invalid>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughRemoved 16-bit masking of GC object-header size everywhere it was applied, treating the header size as a full 31-bit value (derived from Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsTimed out fetching pipeline failures after 30000ms Comment |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #488 +/- ##
=======================================
Coverage 90.40% 90.40%
=======================================
Files 58 58
Lines 19845 19845
Branches 225 225
=======================================
Hits 17941 17941
Misses 1900 1900
Partials 4 4
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@tests/test_codegen_monomorphize.py`:
- Around line 907-928: Add two new tests (e.g., test_array_map_exact_65535_bytes
and test_array_map_exact_65536_bytes) modeled on
test_array_map_large_input_no_stack_overflow that exercise the exact 65,535 and
65,536-byte output boundaries: construct an `@Array` of an element type with a
known fixed byte-size (or use a byte/UInt8 element) and choose the array length
so the serialized/output payload is exactly 65,535 bytes and exactly 65,536
bytes, map over it with array_map and assert the final element via
_run(fn="main"); keep names array_range and array_map and reuse the same
pattern/assert style as the existing test.
In `@vera/codegen/assembly.py`:
- Around line 332-339: The allocator must reject sizes with the high (31st) bit
set before constructing the header so header = size << 1 cannot wrap; in the
$alloc entry (the block that constructs header via "local.get $size; i32.const
1; i32.shl" or equivalent) add a guard that tests (size & 0x80000000) == 0
(e.g., i32.const 0x80000000; i32.and; i32.eqz) and trap or return an OOM/error
if the test fails, ensuring the 31-bit invariant the collector relies on; apply
the same guard to the other header-construction site referenced (the block
around lines 724-728) so both allocation paths enforce the ~2GB bound.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: e3ddba29-ad07-4568-ba8f-3f098f21f5f6
📒 Files selected for processing (6)
CHANGELOG.mdKNOWN_ISSUES.mdTESTING.mdtests/test_codegen_monomorphize.pyvera/codegen/assembly.pyvera/wasm/calls_arrays.py
💤 Files with no reviewable changes (1)
- vera/wasm/calls_arrays.py
Two findings, both addressed: - assembly.py: add a 31-bit invariant guard at the top of $alloc. Tests `(size & 0x80000000) != 0` and traps via `unreachable` if set. Prevents silent zero-size headers if a caller ever requests >= 2^31 bytes (impossible today given WASM's 4 GB memory cap, but makes the invariant the sweeper depends on explicit). Only added at the caller-input site (bump-alloc entry); the free-list reuse path reconstructs from node_size read back from a header that was already validated at its original $alloc, so it's safe by induction. - tests: add test_array_map_exact_65536_byte_boundary — 8,192 Int elements × 8 bytes = 65,536 bytes, the first size that was corrupted under the old mask. Tight regression guard: a future partial-mask regression could pass the 10K stress test but trip this one. The suggested matching 65,535-byte test isn't cleanly reachable with Int granularity (8-byte aligned), and Byte support in array_map is independently broken — int_to_byte(3) returns 32772 — so only the 65,536 case is tested. Co-Authored-By: Claude <noreply@anthropic.invalid>
Summary
i32.const 65535; i32.andpair from all 5 read sites invera/codegen/assembly.py— 2 in$allocfree-list walks, 3 in$gc_collect(phase 1 clear-marks advance, phase 2b conservative scan, phase 3 sweep). Header still stores(size << 1) | mark; we just stop throwing away the high 15 bits on readback.Discovered along the way
Filed #487:
$allocgrows memory by exactly 1 page regardless of need, so single allocations >~64 KB larger than free heap still trap. Orthogonal to this fix; stays under the existing 10K ceiling so it's not blocking.Test plan
mypy vera/clean (57 files)pytest tests/ -q— 3,321 passed, 11 skippedarray_mapandarray_filterstress tests now pass (previously compiled but returned corrupt data)Closes #484.
🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Documentation
Tests