Skip to content

Trace grace subtrees on the legacy heap, and gate heap integrity in CI - #5471

Merged
shai-almog merged 9 commits into
masterfrom
gc-heap-integrity-verifier
Jul 26, 2026
Merged

Trace grace subtrees on the legacy heap, and gate heap integrity in CI#5471
shai-almog merged 9 commits into
masterfrom
gc-heap-integrity-verifier

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #5442 (issue #5425). Two parts: the grace-subtree guarantee was only implemented on one of the two heaps, and nothing in the suite asserted the guarantee itself.

The gap

codenameOneGCSweep grants a fresh (gcMark == -1) legacy object exactly the BiBOP grace rule -- it promotes the object to the current epoch instead of freeing it:

if(o->__codenameOneGcMark != -1) { ...maybe free... }
else { o->__codenameOneGcMark = currentGcMarkValue; }   // grace, subtree never traced

but the grace-subtree pass added in #5442 walks the page registry only. An older object reachable ONLY through a graced legacy object was therefore freed while still referenced -- the same defect, on the other heap.

That heap is not a corner case for this issue. Everything above CN1_BIBOP_MAX_OBJECT lives there (the retained large byte[] blocks and Hashtable bucket arrays of the reporter's dictionary), as does every allocation the adaptive survivor-heavy bypass diverts off the page heap, and every matured survivor. Measured with the new audit half on a bypass-heavy workload:

[GRACE-AUDIT-LEGACY] epoch=65 missedFresh=65792 doomedChildren=256

Fix: mirror the page walk over allObjectsInHeap. Only entries already migrated into the table can be fresh at that point (pending allocations are not swept until the mark that migrates them, and migration happens with the owning thread paused, upstream of the pass), so the pass is exact. CN1_DISABLE_LEGACY_GRACE is the A/B escape hatch, mirroring CN1_DISABLE_SATB.

Why no test caught either half

Checksums are structurally blind to this failure. When a sweep frees memory a survivor still references, nothing diverges at the point of the bug -- the dangling reference reads whatever object recycled the slot, so the damage surfaces later, elsewhere, as corrupted data. That is how #5436's regression reached a user as "non word" dictionary entries and an impossible NPE rather than as a red test.

Three mechanisms make it worse, all measured while building the driver for this PR:

  1. Nearly all page reclamation goes through the O(1) all-dead page path, which drops a whole page without writing a single slot. Dead objects keep intact-looking headers, so a dangling read finds a plausible object instead of crashing.
  2. The conservative native-stack scan resurrects dropped garbage every cycle -- a word left behind by a returned frame marks whatever it points at. The first version of the driver came back green because its own dead frames pinned the hazard it had just built.
  3. SATB covers the hazard during a mark, so a driver that keeps the collector busy tests nothing.

GraceAudit also grades the grace pass against itself (it marks what it finds, so the audit build masks the defect it detects on later cycles) and covers only BiBOP.

The gate: -DCN1_GC_VERIFY

Makes the invariant -- no object the sweep kept may reference memory the sweep reclaimed -- directly observable, by destroying the plausible replacement:

  • Poison every reclaimed page slot and legacy block, the O(1) page reclaim included.
  • Quarantine freed legacy blocks in a ring instead of returning them to the C allocator, so a poisoned block stays mapped and recognizable.
  • Verify after every sweep: walk each survivor through its own generated mark function with the collector in verify mode, classifying every reference field against the page registry, the live-extent index and the quarantine set.

A violation aborts at the cycle that created it, naming the holder, the victim and the field:

[GC-VERIFY] DANGLING REFERENCE after sweep at epoch 15
            holder  = 0xd38070050 class=com.bench.GraceAudit.Node mark=15 (epoch+0) heapPos=-3
            field   -> 0xd3806cfe0 class=com.bench.GraceAudit.Node mark=-7 heapPos=-3
            victim  = RECYCLED page slot (above bump cursor) (page-resident)

The gate holds current-epoch survivors to the invariant, where a dangling field is unambiguous: the sweep either marked the object reachable (marking traces children) or promoted it by the grace rule (tracing the subtree was the grace pass's job). References it cannot place are skipped, so a violation is never a false alarm. CN1_GC_VERIFY_AGING extends it to previous-epoch survivors as a census rather than a gate.

The self-test is the point of the script. run-gc-verify.sh finishes by re-injecting the exact defect #5442 fixed (CN1_GC_FAULT=nograce) and requires the verifier to catch it -- a gate nobody has watched fail is not a gate, and a build where the verification silently compiled out would otherwise report a permanent, meaningless pass. GcHeapIntegrityIntegrationTest is the CI twin and asserts both halves.

Validation

check result
run-gauntlet.sh, both stop modes GREEN
run-gc-verify.sh (9 drivers + fault self-test) GREEN
GcHeapIntegrityIntegrationTest passes, 25s
GraceAudit under -DCN1_GRACE_AUDIT doomedChildren=0
StormAB / LoadLoop wall time and RSS unchanged
LargeArrayLoad collection cycles 5 (budget 10), unchanged

Known, deliberately not gated

With CN1_GC_VERIFY_AGING=1, LargeArrayLoad (the final issue-5425 shape) reports ~30k-60k references per cycle from previous-epoch survivors into memory the sweep already reclaimed -- Hashtable.Entry holders pointing at freed byte[] payloads. The two heaps age out of the one-cycle safety margin on different schedules: the O(1) page path drops a page before the legacy table gives up on its entries.

Strictly, unreachable objects are entitled to dangle, so this is not gated. But this collector resurrects unreachable objects routinely (mechanism 2 above), and resurrecting one of those entries makes the drain follow a field into recycled memory. Filed here as the next thing to chase rather than fixed blind.

🤖 Generated with Claude Code

The grace-subtree pass added in #5442 covers BiBOP pages only. The legacy
sweep grants a fresh (gcMark == -1) legacy object exactly the same one-cycle
grace -- codenameOneGCSweep promotes it to the current epoch instead of
freeing it -- but nothing traced its subtree, so an older object reachable
ONLY through such an object was freed while it was still referenced. That is
the same defect #5442 fixed, on the other heap. Everything above
CN1_BIBOP_MAX_OBJECT lands there (the retained large byte[] blocks and
Hashtable bucket arrays of issue 5425), as does every allocation the adaptive
survivor-heavy bypass diverts off the page heap, and every matured survivor,
whose table entry is what the sweep consults. Measured with the audit half
added below: ~65,800 untraced fresh legacy objects per cycle on a
bypass-heavy workload, with children reachable only through them.

Mirror the page walk over allObjectsInHeap. Only entries already migrated
into the table can be fresh at that point -- pending allocations are not
swept until the mark that migrates them, and migration happens with the
owning thread paused, upstream of this pass -- so the pass is exact. Cost is
one extra walk of an array the sweep already walks in full, and only fresh
entries are traced: StormAB and LoadLoop wall time and RSS are unchanged and
LargeArrayLoad still collects in 5 cycles. CN1_DISABLE_LEGACY_GRACE is the
A/B escape hatch, mirroring CN1_DISABLE_SATB.

Add the gate that would have caught both halves. Checksums are structurally
blind to this failure: when a sweep frees memory a survivor still
references, nothing diverges at the point of the bug -- the dangling
reference reads whatever object recycled the slot, so the damage surfaces
later, elsewhere, as corrupted data. That is how #5436's regression reached
a user as "non word" dictionary entries and an impossible NPE instead of as
a failing test. -DCN1_GC_VERIFY makes the invariant observable by destroying
the plausible replacement:

- POISON every reclaimed page slot and legacy block. This includes the O(1)
  all-dead page reclaim, which is where nearly all page memory is actually
  reclaimed and which normally drops a page without writing a single slot,
  leaving every dead object with an intact-looking header -- the reason a
  dangling read in this VM finds plausible data rather than crashing.
- QUARANTINE freed legacy blocks in a ring instead of returning them to the
  C allocator, so a poisoned block stays mapped and recognizable.
- VERIFY after every sweep: walk each survivor through its own generated
  mark function with the collector in verify mode, classifying every
  reference field against the page registry, the live-extent index and the
  quarantine set. A field pointing into reclaimed memory is reported with
  the holder's class, the victim's class and the field's mark call site,
  then aborts at the cycle that created it.

The gate holds CURRENT-EPOCH survivors to the invariant, where a dangling
field is unambiguous: the sweep either marked the object reachable (marking
traces children) or promoted it by the grace rule (tracing the subtree was
the grace pass's job). References it cannot place are skipped, so a
violation is never a false alarm. CN1_GC_VERIFY_AGING extends it to
previous-epoch survivors as a census rather than a gate.

run-gc-verify.sh runs it over nine drivers and then re-injects the #5442
defect (CN1_GC_FAULT=nograce disables the grace pass) and REQUIRES the
verifier to catch it -- a gate nobody has watched fail is not a gate, and a
build where the verification silently compiled out would otherwise report a
permanent, meaningless pass. GcHeapIntegrityIntegrationTest is the CI twin
and asserts both halves.

Two supporting pieces, both born from the same investigation:

- LegacyGrace is the legacy-path twin of GraceAudit, plus the
  [GRACE-AUDIT-LEGACY] half of -DCN1_GRACE_AUDIT. Writing it exposed why
  drivers in this area come back green while the defect is present: the
  hazard has to be built with no mark in flight (during a mark the SATB
  barriers cover the very reference move under test) and the driver has to
  scrub its own native stack afterwards, because the conservative root scan
  marks whatever a returned frame's leftover word still points at. Both are
  documented at the driver and in the README.
- CN1_GC_TRACE_MARK names the mark pass that keeps a class alive, which is
  how that retention was identified; CN1_GC_VERIFY_CENSUS reports whether a
  driver's hazard set actually ages out instead of being pinned.

Validation: gauntlet GREEN in both stop modes, run-gc-verify GREEN over all
nine drivers with the fault self-test firing, GraceAudit clean under
-DCN1_GRACE_AUDIT, and the new integration test green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 03:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends ParparVM’s concurrent GC correctness guarantees by (1) tracing grace subtrees for legacy-heap objects (mirroring the existing BiBOP/page-heap grace-subtree tracing), and (2) adding a QA-only heap integrity verifier (-DCN1_GC_VERIFY) plus an integration-test gate that exercises and self-validates the verifier in CI.

Changes:

  • Add legacy-heap grace-subtree tracing during mark to prevent sweeping objects reachable only through graced legacy objects.
  • Introduce -DCN1_GC_VERIFY QA mode: poison/quarantine freed memory and post-sweep verification that survivors don’t reference reclaimed memory (with fault-injection support).
  • Add new benchmark drivers/scripts and a JUnit integration test (GcHeapIntegrityIntegrationTest) that runs the verified build and asserts the verifier can also catch a re-injected known defect.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
vm/tests/src/test/resources/com/codename1/tools/translator/GcVerifyApp.java New workload app used by the heap-integrity integration test to exercise GC hazard shapes.
vm/tests/src/test/java/com/codename1/tools/translator/GcHeapIntegrityIntegrationTest.java New CI gate test that builds/runs ParparVM with -DCN1_GC_VERIFY and includes a fault-injection self-test.
vm/ByteCodeTranslator/src/cn1_globals.m Implements legacy grace-subtree tracing and the CN1_GC_VERIFY poison/quarantine/verify infrastructure (plus fault injection and audit hooks).
vm/benchmarks/src/com/bench/LegacyGrace.java New benchmark driver to reproduce and audit the legacy-heap grace hazard under -DCN1_GRACE_AUDIT.
vm/benchmarks/run-gc-verify.sh New script to run the verifier gate across multiple drivers and enforce a fault-injection self-test.
vm/benchmarks/README.md Documentation updates covering LegacyGrace and the -DCN1_GC_VERIFY gate and diagnostics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Both halves of the gate are decided by environment variables, and anyone
debugging the collector has exactly those exported: CN1_GC_VERIFY_SOFT
downgrades the abort the faulted half asserts on, and CN1_GC_FAULT injects the
defect the clean half asserts is absent. An inherited knob would invert a
result rather than fail loudly. Drop CN1_* from the child environment in the
integration test and unset the knobs at the top of run-gc-verify.sh, so both
start from a known state and see only what they set themselves.

Verified by running each with CN1_GC_FAULT=nograce and CN1_GC_VERIFY_SOFT=1
exported: the test passes and the script reports GC-VERIFY GREEN.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 03:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread vm/ByteCodeTranslator/src/cn1_globals.m Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

cn1GcVerifyChild read __builtin_return_address(0) itself, which resolves to
the return address in gcMarkObject -- one frame too deep, and in a different
object file from the generated mark function the label claimed it pointed
into. Anyone following a violation back to a field would have landed in the
collector rather than at the field read.

Capture the address in gcMarkObject instead, where this frame's return address
IS the instruction inside the generated mark function (or gcMarkArrayObject for
an element), and pass it down. Report it as an offset from the holder's own
mark function so the line is self-verifying without symbols and survives ASLR:

  markSite= 0x102fa66c0 = markFn+36 (the field read, inside the holder's mark function)

A small positive offset means the frame is right; add it to the mark
function's symbol to reach the exact field.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 03:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comment thread vm/ByteCodeTranslator/src/cn1_globals.m
Comment thread vm/ByteCodeTranslator/src/cn1_globals.m
@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 146 screenshots: 146 matched.
Native Windows port (x64 / Intel-AMD): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, SSE2 SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 62ms / native 4ms = 15.5x speedup
SIMD float-mul (64K x300) java 81ms / native 5ms = 16.2x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 206.000 ms
Base64 CN1 decode 124.000 ms
Base64 SIMD encode 103.000 ms
Base64 encode ratio (SIMD/CN1) 0.500x (50.0% faster)
Base64 SIMD decode 88.000 ms
Base64 decode ratio (SIMD/CN1) 0.710x (29.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 28.000 ms
Image createMask (SIMD on) 24.000 ms
Image createMask ratio (SIMD on/off) 0.857x (14.3% faster)
Image applyMask (SIMD off) 73.000 ms
Image applyMask (SIMD on) 62.000 ms
Image applyMask ratio (SIMD on/off) 0.849x (15.1% faster)
Image modifyAlpha (SIMD off) 140.000 ms
Image modifyAlpha (SIMD on) 61.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.436x (56.4% faster)
Image modifyAlpha removeColor (SIMD off) 68.000 ms
Image modifyAlpha removeColor (SIMD on) 56.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.824x (17.6% faster)

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 146 screenshots: 146 matched.
Native Windows port, REAL shipping pipeline: the hellocodenameone screenshot suite rendered by a binary CROSS-COMPILED on Linux (clang-cl + xwin, WebView2 linked) and RUN on a Windows x64 runner. Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 63ms / native 4ms = 15.7x speedup
SIMD float-mul (64K x300) java 63ms / native 4ms = 15.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 200.000 ms
Base64 CN1 decode 129.000 ms
Base64 SIMD encode 101.000 ms
Base64 encode ratio (SIMD/CN1) 0.505x (49.5% faster)
Base64 SIMD decode 86.000 ms
Base64 decode ratio (SIMD/CN1) 0.667x (33.3% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 27.000 ms
Image createMask (SIMD on) 23.000 ms
Image createMask ratio (SIMD on/off) 0.852x (14.8% faster)
Image applyMask (SIMD off) 68.000 ms
Image applyMask (SIMD on) 62.000 ms
Image applyMask ratio (SIMD on/off) 0.912x (8.8% faster)
Image modifyAlpha (SIMD off) 62.000 ms
Image modifyAlpha (SIMD on) 60.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.968x (3.2% faster)
Image modifyAlpha removeColor (SIMD off) 47.000 ms
Image modifyAlpha removeColor (SIMD on) 113.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 2.404x (140.4% slower)

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 147 screenshots: 147 matched.
Native Linux port (x64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub x64 runner. Baseline: scripts/linux/screenshots.

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 147 screenshots: 147 matched.
Native Linux port (arm64), GTK3/Cairo/Pango, ParparVM bytecode-to-C (no JVM): the hellocodenameone screenshot suite rendered by a native ELF built + run on the GitHub arm64 runner. Baseline: scripts/linux/screenshots-arm.

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 148 screenshots: 148 matched.
✅ Native Mac screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 174 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 54ms / native 2ms = 27.0x speedup
SIMD float-mul (64K x300) java 53ms / native 3ms = 17.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 149.000 ms
Base64 CN1 decode 112.000 ms
Base64 native encode 461.000 ms
Base64 encode ratio (CN1/native) 0.323x (67.7% faster)
Base64 native decode 194.000 ms
Base64 decode ratio (CN1/native) 0.577x (42.3% faster)
Base64 SIMD encode 46.000 ms
Base64 encode ratio (SIMD/CN1) 0.309x (69.1% faster)
Base64 SIMD decode 42.000 ms
Base64 decode ratio (SIMD/CN1) 0.375x (62.5% faster)
Base64 encode ratio (SIMD/native) 0.100x (90.0% faster)
Base64 decode ratio (SIMD/native) 0.216x (78.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 6.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.167x (83.3% faster)
Image applyMask (SIMD off) 47.000 ms
Image applyMask (SIMD on) 35.000 ms
Image applyMask ratio (SIMD on/off) 0.745x (25.5% faster)
Image modifyAlpha (SIMD off) 37.000 ms
Image modifyAlpha (SIMD on) 35.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.946x (5.4% faster)
Image modifyAlpha removeColor (SIMD off) 39.000 ms
Image modifyAlpha removeColor (SIMD on) 33.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.846x (15.4% faster)

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 181 screenshots: 181 matched.
✅ JavaScript-port screenshot tests passed.

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 146 screenshots: 146 matched.
Native Windows port (arm64 / Apple Silicon - Arm): full hellocodenameone screenshot suite rendered offscreen with Direct2D/DirectWrite, plus the real benchmarks (base64 native/CN1/SIMD, image createMask/applyMask/modifyAlpha/PNG/JPEG, NEON SIMD kernels). Compared against the in-repo baseline in scripts/windows/screenshots.

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 56ms / native 3ms = 18.6x speedup
SIMD float-mul (64K x300) java 54ms / native 3ms = 18.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 native bridge unavailable (CN1 + SIMD + image benchmarks only)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path gated to scalar (CPU autovectorizes scalar; explicit SIMD not beneficial here)
Base64 CN1 encode 246.000 ms
Base64 CN1 decode 126.000 ms
Base64 SIMD encode 64.000 ms
Base64 encode ratio (SIMD/CN1) 0.260x (74.0% faster)
Base64 SIMD decode 58.000 ms
Base64 decode ratio (SIMD/CN1) 0.460x (54.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 12.000 ms
Image createMask (SIMD on) 8.000 ms
Image createMask ratio (SIMD on/off) 0.667x (33.3% faster)
Image applyMask (SIMD off) 24.000 ms
Image applyMask (SIMD on) 19.000 ms
Image applyMask ratio (SIMD on/off) 0.792x (20.8% faster)
Image modifyAlpha (SIMD off) 154.000 ms
Image modifyAlpha (SIMD on) 12.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.078x (92.2% faster)
Image modifyAlpha removeColor (SIMD off) 18.000 ms
Image modifyAlpha removeColor (SIMD on) 14.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.778x (22.2% faster)

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 217 screenshots: 217 matched.
✅ Native Apple Watch (watchOS, Core Graphics) screenshot tests passed.

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 143 screenshots: 143 matched.
✅ Native iOS screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 284 seconds

Build and Run Timing

Metric Duration
Simulator Boot 59000 ms
Simulator Boot (Run) 2000 ms
App Install 10000 ms
App Launch 2000 ms
Test Execution 762000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 55ms / native 3ms = 18.3x speedup
SIMD float-mul (64K x300) java 53ms / native 3ms = 17.6x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 331.000 ms
Base64 CN1 decode 274.000 ms
Base64 native encode 304.000 ms
Base64 encode ratio (CN1/native) 1.089x (8.9% slower)
Base64 native decode 367.000 ms
Base64 decode ratio (CN1/native) 0.747x (25.3% faster)
Base64 SIMD encode 69.000 ms
Base64 encode ratio (SIMD/CN1) 0.208x (79.2% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.164x (83.6% faster)
Base64 encode ratio (SIMD/native) 0.227x (77.3% faster)
Base64 decode ratio (SIMD/native) 0.123x (87.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 16.000 ms
Image createMask (SIMD on) 6.000 ms
Image createMask ratio (SIMD on/off) 0.375x (62.5% faster)
Image applyMask (SIMD off) 66.000 ms
Image applyMask (SIMD on) 36.000 ms
Image applyMask ratio (SIMD on/off) 0.545x (45.5% faster)
Image modifyAlpha (SIMD off) 31.000 ms
Image modifyAlpha (SIMD on) 28.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.903x (9.7% faster)
Image modifyAlpha removeColor (SIMD off) 44.000 ms
Image modifyAlpha removeColor (SIMD on) 30.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.682x (31.8% faster)

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 144 screenshots: 144 matched.
✅ Native Apple TV (tvOS, Metal) screenshot tests passed.

@shai-almog

shai-almog commented Jul 26, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 149 screenshots: 149 matched.
✅ Native iOS Metal screenshot tests passed.

Benchmark Results

  • VM Translation Time: 0 seconds
  • Compilation Time: 377 seconds

Build and Run Timing

Metric Duration
Simulator Boot 66000 ms
Simulator Boot (Run) 1000 ms
App Install 16000 ms
App Launch 2000 ms
Test Execution 959000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 95ms / native 4ms = 23.7x speedup
SIMD float-mul (64K x300) java 63ms / native 3ms = 21.0x speedup
SIMD kernel correctness PASS (native result == scalar reference)
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 SIMD byte path active (NEON-accelerated)
Base64 CN1 encode 191.000 ms
Base64 CN1 decode 167.000 ms
Base64 native encode 480.000 ms
Base64 encode ratio (CN1/native) 0.398x (60.2% faster)
Base64 native decode 309.000 ms
Base64 decode ratio (CN1/native) 0.540x (46.0% faster)
Base64 SIMD encode 52.000 ms
Base64 encode ratio (SIMD/CN1) 0.272x (72.8% faster)
Base64 SIMD decode 50.000 ms
Base64 decode ratio (SIMD/CN1) 0.299x (70.1% faster)
Base64 encode ratio (SIMD/native) 0.108x (89.2% faster)
Base64 decode ratio (SIMD/native) 0.162x (83.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 2.000 ms
Image createMask ratio (SIMD on/off) 0.200x (80.0% faster)
Image applyMask (SIMD off) 47.000 ms
Image applyMask (SIMD on) 34.000 ms
Image applyMask ratio (SIMD on/off) 0.723x (27.7% faster)
Image modifyAlpha (SIMD off) 44.000 ms
Image modifyAlpha (SIMD on) 31.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.705x (29.5% faster)
Image modifyAlpha removeColor (SIMD off) 37.000 ms
Image modifyAlpha removeColor (SIMD on) 97.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 2.622x (162.2% slower)

@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [HTML preview] [Download]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 1 findings (Normal: 1)
    • codenameone-maven-plugin: 0 findings (no issues)
    • core-unittests: 0 findings (no issues)
    • ios: 0 findings (no issues)
  • PMD: 0 findings (no issues) [Report archive]
  • Checkstyle: 0 findings (no issues) [Report archive]

Generated automatically by the PR CI workflow.

…est on Linux

Three problems, all the same shape: a check that reports success without
having checked anything.

1. The verifier never reported whether it RAN. MtStress, MapTorture and
   SbTorture exit before any sweep completes, so their "clean" results in
   run-gc-verify.sh meant only that nothing was ever verified -- exactly the
   hollow-gate failure the fault self-test exists to prevent, sitting inside
   the same script. Count completed passes, print them at exit, and fail any
   driver reporting zero. Those three now end with one collection over the
   heap they built (printing nothing, so the gauntlet's byte-identical
   comparison is unaffected) and contribute real coverage.

2. The CI self-test failed on Linux: the fault was injected and the workload
   completed, but no dangling reference appeared, so the test declared the
   gate inert. Reproduced in a linux/amd64 container -- the platform is fine
   (GraceAudit detects the fault there), the app's hazard was too weak. Its
   during-mark phase now follows the shape that provably breaks a missing
   grace pass: refill, kick a mark, then allocate dropped fresh nodes in
   sleep-separated slices so they land across the mark rather than racing
   past it, then go quiet for a full cycle so the untraced children age past
   the free threshold. Verified in the container: clean 0 violations / 23
   passes, faulted aborts with 20 reports, three runs, no variance.

3. The integration test now also requires a nonzero pass count, so a
   workload that stops driving collection fails instead of passing silently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 05:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Comment thread vm/ByteCodeTranslator/src/cn1_globals.m Outdated
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 411 total, 0 failed, 14 skipped

Benchmark Results

  • Execution Time: 21567 ms

  • Hotspots (Top 20 sampled methods):

    • 14.99% java.util.ArrayList.indexOf (274 samples)
    • 6.89% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (126 samples)
    • 5.53% com.codename1.tools.translator.BytecodeMethod.equals (101 samples)
    • 3.45% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (63 samples)
    • 3.28% java.lang.StringBuilder.append (60 samples)
    • 2.68% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (49 samples)
    • 2.46% org.objectweb.asm.tree.analysis.Analyzer.analyze (45 samples)
    • 2.46% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (45 samples)
    • 2.13% com.codename1.tools.translator.Parser.addToConstantPool (39 samples)
    • 1.81% com.codename1.tools.translator.Parser.classIndex (33 samples)
    • 1.81% com.codename1.tools.translator.BytecodeMethod.optimize (33 samples)
    • 1.81% org.objectweb.asm.ClassReader.readCode (33 samples)
    • 1.59% java.lang.System.identityHashCode (29 samples)
    • 1.48% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (27 samples)
    • 1.48% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (27 samples)
    • 1.26% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (23 samples)
    • 1.20% java.lang.String.equals (22 samples)
    • 1.15% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (21 samples)
    • 1.09% sun.nio.fs.UnixNativeDispatcher.open0 (20 samples)
    • 1.04% java.lang.Object.hashCode (19 samples)
  • ⚠️ Coverage report not generated.

Static Analysis

  • ✅ SpotBugs: no findings (report was not generated by the build).
  • ⚠️ PMD report not generated.
  • ⚠️ Checkstyle report not generated.

Generated automatically by the PR CI workflow.

MapTorture, MtStress and SbTorture predate the copyright gate and carried no
header at all. The gate only inspects files a PR adds or modifies, so giving
them a trailing collection in the previous commit is what pulled them into
scope. Header text copied verbatim from LegacyGrace.java, added in this PR
and already passing.

Comment-only: scripts/check-copyright-headers.sh passes over the branch, and
the gauntlet still matches the host JVM byte for byte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 06:33
The comment described an earlier design in which the function handed the
evicted block back for the caller to release. It returns a boolean and frees
the displaced block itself, and the call site in codenameOneGcFree already
relies on that boolean -- so the comment was the only thing out of date, and
the one part a future caller would have read first.

State what the return value means instead: TRUE when the block was
quarantined and must NOT be freed, FALSE when the quarantine could not be
allocated and the caller should free normally.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 8 out of 9 changed files in this pull request and generated 1 comment.

Comment thread vm/ByteCodeTranslator/src/cn1_globals.m
cn1GcFaultNoGrace was initialized only by cn1GcVerifyHeap, which runs after
the sweep. The first cycle of every faulted run therefore traced grace
subtrees normally, and a workload completing a single GC cycle never had the
defect injected at all -- the self-test would have reported a gate that
"cannot fail" purely because nothing ever faulted it. Resolve the switch at
its first use in the mark instead; cn1GcFaultInit is idempotent and
GC-thread only, so the call in cn1GcVerifyHeap stays as a harmless second
one. Observable: [GC-FAULT] now prints before the first verify pass rather
than after a sweep.

Checking that turned up a matching gap in GcStress, which reported 1 verify
pass on one run and 0 on the next: its churn triggers collections, but
whether the last one reaches its sweep before the process exits is a race,
so the new vacuity check would have flaked in CI. It gets the same trailing
collection as the other drivers (prints nothing, so the gauntlet comparison
is unaffected).

Two consecutive gate runs now report identical pass counts across all nine
drivers, and the gauntlet stays byte-identical to the host JVM.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 06:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.

Comment thread vm/benchmarks/src/com/bench/LegacyGrace.java Outdated
The hazard comment still described the driver's first shape -- 256 parents of
~800 bytes -- after it was scaled to KEEP=10000 arrays of 65 references. The
number matters rather than being decoration: the window only works while
nothing in it starts a collection, so a reader checking that property was
being handed the wrong figure by an order of magnitude.

Give the real one (about 560 bytes each, 5.6 MB total, against the 24 MB
allocation-volume trigger) and say which constants would break the window if
raised. Same for the other precondition, that no mark is in flight, since
both are equally easy to lose when editing the driver.

Comment-only; LegacyGrace still reports 120 clean verify passes with the
fault self-test firing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 06:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

GcStress is the fourth torture in this tree without a header, and the
trailing collection added for the vacuity check pulled it into the copyright
gate's scope. Missed on the first pass because the earlier commit added
headers to the three drivers touched at that point, and GcStress was edited
after that check ran.

Audited every source file this branch touches rather than fixing one report
at a time: all eight now carry the header and
scripts/check-copyright-headers.sh passes over the full branch range.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 07:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 9 out of 10 changed files in this pull request and generated no new comments.

@shai-almog
shai-almog merged commit e637fc8 into master Jul 26, 2026
46 checks passed
@shai-almog
shai-almog deleted the gc-heap-integrity-verifier branch July 26, 2026 09:41
shai-almog added a commit that referenced this pull request Jul 26, 2026
…#5474)

* Stop the O(1) page reclaim from freeing slots the per-slot walk keeps

Follow-up to #5471, which flagged kept objects referencing reclaimed memory
on the issue-5425 workload and left the cause open.

The O(1) whole-page reclaim tests gcLastMarkedEpoch != V, but the per-slot
walk it claims to reproduce ("byte-identical outcome") frees on m < V-1 -- so
a slot marked at V-1 SURVIVES the walk while the shortcut drops the entire
page holding it. Measured on LargeArrayLoad, the final issue-5425 shape:
26,924 slots freed a full cycle early in one run. The other drivers report
zero, which fits: it needs a large retained survivor set sharing pages with
garbage, which is exactly what that workload builds.

That is what produced the dangling pairs. The legacy sweep ages on the same
m < V-1 rule, so a matured Hashtable.Entry kept at V-1 was left pointing at a
page-resident byte[] payload the shortcut had already reclaimed -- the
reporter's dictionary shape precisely.

Test gcLastMarkedEpoch < V-1 instead. Both bounds are then exact and neither
implies the other: gcLastMarkedEpoch covers slots marked since the last full
walk, gcGraceEpoch covers slots the sweep itself promoted out of grace.
earlyFreed goes 26,924 -> 0 across the suite, and it is free: LoadLoop and
StormAB wall time and RSS unchanged, LargeArrayLoad still 5 cycles, peak RSS
93.1 vs 92.6 MB.

On the resurrection half of that report, the measurement says the hazard is
real but essentially never fires: across all nine drivers, 1 resurrection
total and 0 of them holding a dangling reference. For the conservative scan
to revive an object a stale stack word must point at it, and such a word
generally keeps marking it EVERY cycle -- retention, not resurrection. The
audit stays in as a gate so that stops being an assumption.

Three things keep this honest:
- resurrected / resurrectedDangling / earlyFreed are reported at exit, and
  run-gc-verify.sh fails on a nonzero earlyFreed or resurrectedDangling.
- CN1_GC_FAULT=earlyfree restores the old bound, and a second self-test
  requires the gate to reject it (26,924 slots), so the new check cannot go
  inert the way an unexercised assertion does.
- CN1_GC_VERIFY_AGING is documented as PERTURBING: it doubles the post-sweep
  walk and the extra GC-thread time moves page ageing by orders of magnitude
  (early-freed 0 vs 205,958 from one binary). The 30k-60k figures quoted in
  #5471 came from that mode and overstated the problem; every number here is
  from the default configuration.

Validation: gauntlet GREEN in both stop modes, run-gc-verify GREEN over nine
drivers with both self-tests firing, GcHeapIntegrityIntegrationTest green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fix the stale precondition comment and cache the debug env lookup

The bullet list above the O(1) page decision still documented
gcLastMarkedEpoch != V -- the exact bound this PR replaces -- so the file
stated both the old rule and the new one a few lines apart, with the wrong
one first. The bullet now carries the real criterion and says why != V is
insufficient; the paragraph below no longer restates it.

getenv("CN1_GC_DEBUG_EARLY") also ran once per early-freed slot, and the
earlyfree self-test drives 26,924 of them through that loop. Cached in a
static, like the other QA env reads in this file.

Gate re-run: clean, with both self-tests firing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Name the phase in the dangling-reference report

The resurrection audit runs at the end of the mark but drives the same
per-field reporter as the post-sweep gate, so anything it found announced
itself as happening "after sweep" -- pointing a reader at the wrong phase of
the collector while they try to reconstruct what freed the memory.

Carry the calling context in the report instead of suppressing the per-field
detail, which is the part worth having: the holder and victim classes and the
mark site are what map a resurrected object's dangling field back to source.
The audit also now says which resolver snapshot it classifies against (the
mark's, correct there because the memory it looks for was reclaimed by
earlier cycles).

Post-sweep reports still read "after sweep"; verified against the nograce
self-test, and the gate stays green with both self-tests firing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants