Skip to content

Fix GC storm on retained large-array loads (issue 5425, final Dtest shape)#5446

Open
shai-almog wants to merge 22 commits into
masterfrom
claude/array-handling-regressions-6ha9yj
Open

Fix GC storm on retained large-array loads (issue 5425, final Dtest shape)#5446
shai-almog wants to merge 22 commits into
masterfrom
claude/array-handling-regressions-6ha9yj

Conversation

@shai-almog

@shai-almog shai-almog commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Fixes the remaining slowdown in #5425 (final Dtest shape from the last comments: a persistent survivor set plus a pass that retains only large 10K byte[] blocks and produces no garbage, yet took 450s on iOS vs 4s on Android, with the reporter observing "allocating the large arrays is triggering a global multiple times"). Along the way, CI validation surfaced and this PR also fixes a second long-standing VM defect: Thread.sleep truncation by signals.

Fix 1: GC trigger asymmetry for large/legacy allocations

Root cause: arrays above CN1_BIBOP_MAX_OBJECT (512 bytes) always take the legacy allocation path. That path's only byte-based GC signal was the pre-BiBOP 1MB isHighFrequencyGC re-arm, which keeps the GC thread starting a full mark/sweep cycle every 200ms wait interval for as long as ≥1MB was allocated since the last cycle. BiBOP allocations got a modern 24MB (adaptive up to 192MB) trigger, but the legacy path was never re-tuned — it re-armed 24× more aggressively, keeping full collections running at wall-clock cadence over the whole survivor set.

Change (behavioral pieces #ifndef CN1_DISABLE_BIBOP):

  1. cn1LegacyBytesSinceGc — per-cycle byte counter (64-bit; long is 32-bit on the Windows LLP64 target) for legacy allocations, reset in cn1BibopBeginGcCycle (same atomic-exchange idiom as bibopBytesSinceGc).
  2. Level-triggered, per-cycle-latched async System.gc() in codenameOneGcMalloc's legacy tail when legacy volume crosses CN1_LEGACY_GC_TRIGGER_BYTES (24MB, -D overridable): the latch (cn1LegacyGcScheduled, cleared at cycle begin) bounds it to one schedule per cycle window, while level-triggering means a crossing suppressed by the native-bracket gate (mirrored from cn1BibopMaybeGc, applied when conservative roots are off) is retried by the next out-of-bracket legacy allocation instead of being lost.
  3. isHighFrequencyGC re-arms at max(1MB, bibopGcTriggerBytes) — collection scheduling is event-driven on both paths, so the 200ms loop only re-arms when volume would have crossed the trigger anyway.
  4. CN1_GC_LOG_CYCLES env-gated cycle tracer in codenameOneGCMark (deliberately available in CN1_DISABLE_BIBOP A/B builds too; behavior-identical when unset).

Measured: cycle count over the guarded workload — unfixed 15 (wall-clock cadence) vs fixed 6 (volume-driven), exactly stable across runs. On the fast-loop shape the storm showed as 10–15× inflated large_ms rounds and 4–8× higher sys CPU.

Fix 2: Thread.sleep truncated by signals (found via this PR's CI)

The quieter GC exposed a deterministic Linux-suite failure (ToastBarTopPosition missing on both arches, 5/5 rounds). Local reproduction on the translated GTK port showed Thread.sleep(3000) returning after ~20ms: usleep/nanosleep return EINTR on any signal and POSIX never restarts them (SA_RESTART excludes them), while the conservative-roots collector signal-stops sleeping threads every cycle to scan their stacks. java.util.Timer schedules through Thread.sleep, so every TimerTask fired almost immediately — ToastBar's 10-minute expiry dismissed the toast mid-test, which is exactly the observed re-show/poll loop timeout.

Change: Thread.sleep now sleeps on a monotonic deadline (QueryPerformanceCounter via the cn1_win_compat shim on the MSVC target) and resumes across early wakeups, chunked <1s (POSIX allows usleep(>=1e6) to fail with EINVAL on musl — long sleeps were silent no-ops there). The deadline saturates instead of overflowing for Long.MAX_VALUE-style arguments, negative millis throw IllegalArgumentException per the JDK contract (guarded in Java; the native is now sleepImpl), and the sleeper parks with CN1_GC_PARK_CAPTURE per the CN1_YIELD_THREAD contract so its native-stack roots are scannable on Windows, which has no signal-stop fallback. Interrupt semantics are deliberately unchanged: ParparVM's Thread.interrupt() has always been flag-only (sleep never threw InterruptedException and never woke early by design), and the resume loop preserves exactly that — it neither delivers the exception nor invents a silent early wake.

Measured on the translated Linux port under Xvfb: Thread.sleep(3000) → 3000/3000/3001/3000/3000ms (was 19–26ms); the ToastBar show/poll choreography runs to completion.

Benchmark + CI regression guard

  • vm/benchmarks/src/com/bench/LargeArrayLoad.java — models the final Dtest (persistent small survivors; retained large arrays only; allocation-free lookups; wall-stretched phases).
  • vm/tests/.../LargeArrayGcIntegrationTest.java (+ app resource, @Tag("benchmark")) — asserts RESULT= parity with the host JVM and gates on collection cycle count (≤10) via the tracer; load-independent, unlike wall-time assertions (verified flaky and rejected).
  • The Linux screenshot gate now dumps app-side CN1SS diagnostics on failure, and ToastBarTopPositionScreenshotTest traces its poll state during extended waits — both were what made the sleep defect findable.

Validation

  • Integration test passes on the fixed VM and fails on the unfixed VM (6 vs 15 cycles).
  • Full local gauntlet green after both fixes: MapTorture, SbTorture, StrCmp, FusedTest, IbpTest, ExcTest, ThreadChurn, SoeTest, TaggedSync, LoadLoop byte-identical to the host JVM; GcStress ×10 + MtStress ×6 across cooperative and CN1_GC_SIGNAL_STOP=1 stop modes; RESULT= parity across host JVM / fixed / unfixed.
  • Deeper structural GC costs identified during the audit (adoption densifying the legacy table, per-cycle extent qsort, placeObjectInHeapCollection NULL-slot scans) are intentionally untouched — tracked by the existing TODO in the extent-snapshot section of cn1_globals.m.

🤖 Generated with Claude Code

https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29

…ape)

Arrays above CN1_BIBOP_MAX_OBJECT always take the legacy allocation
path, whose only byte-based GC signal was the pre-BiBOP 1MB
isHighFrequencyGC re-arm -- 24x more aggressive than the BiBOP trigger.
Retaining a few hundred 10K byte[] blocks (no garbage at all) kept full
collection cycles starting every 200ms wait interval over the whole
survivor set: "allocating the large arrays is triggering a global
multiple times".

Fix, all gated on the BiBOP build so -DCN1_DISABLE_BIBOP keeps the old
shape verbatim:
- cn1LegacyBytesSinceGc: per-cycle legacy allocation byte counter,
  reset in cn1BibopBeginGcCycle alongside bibopBytesSinceGc.
- Event-driven trigger in codenameOneGcMalloc's legacy tail mirroring
  cn1BibopMaybeGc: async System.gc() when legacy volume crosses
  CN1_LEGACY_GC_TRIGGER_BYTES (24MB, -D overridable), so legacy-churn
  workloads still collect promptly.
- isHighFrequencyGC re-arms at max(1MB, bibopGcTriggerBytes): with both
  paths event-driven on volume, the 200ms loop only needs to re-arm
  when the volume budget would have been crossed anyway.
- CN1_GC_LOG_CYCLES env-gated cycle tracer in codenameOneGCMark (one
  stderr line per cycle) for CI assertions and on-device diagnosis.

Guard: com.bench.LargeArrayLoad models the final Dtest (persistent
small survivor set; retained large-array pass producing no garbage;
wall-stretched phases so re-arm windows are machine-independent), and
LargeArrayGcIntegrationTest gates on cycle count via the tracer --
measured 15 cycles unfixed vs 6 fixed, stable across runs, budget 10 --
plus RESULT parity with the host JVM. Cycle count is load-independent,
unlike phase wall times which only inflate under mutator contention.

Validated: integration test passes fixed / fails unfixed; gauntlet
green (tortures byte-identical to host JVM, GcStress/MtStress in both
cooperative and CN1_GC_SIGNAL_STOP modes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 23, 2026 23:46

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 addresses the GC “storm” regression in ParparVM on iOS for workloads that retain many large byte[] arrays (legacy allocation path), by aligning legacy GC triggering/scheduling with the BiBOP volume-based policy and adding deterministic cycle-count instrumentation plus CI regression coverage.

Changes:

  • Adds a legacy (non-BiBOP) allocation byte counter and an event-driven GC trigger for large/legacy allocations, and aligns the high-frequency GC re-arm threshold with the adaptive BiBOP trigger.
  • Introduces an env-gated per-GC-cycle tracer (CN1_GC_LOG_CYCLES) for deterministic cycle counting.
  • Adds a benchmark driver and a ParparVM integration test that gates on GC cycle count and validates RESULT= parity vs host JVM.

Reviewed changes

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

Show a summary per file
File Description
vm/ByteCodeTranslator/src/cn1_globals.m Adds legacy allocation volume tracking/trigger, aligns re-arm threshold with BiBOP trigger, and adds env-gated GC cycle tracing.
vm/tests/src/test/java/com/codename1/tools/translator/LargeArrayGcIntegrationTest.java New integration test that builds/runs a translated executable and asserts GC cycle count stays below a regression threshold.
vm/tests/src/test/resources/com/codename1/tools/translator/LargeArrayGcApp.java New test app resource modeling the “final Dtest shape” workload used by the integration test.
vm/benchmarks/src/com/bench/LargeArrayLoad.java New benchmark app for local/manual reproduction and cycle-count inspection.
vm/benchmarks/README.md Documents the new benchmark and how to use cycle tracing to detect regressions.

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

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

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 407 total, 0 failed, 14 skipped

Benchmark Results

  • Execution Time: 24518 ms

  • Hotspots (Top 20 sampled methods):

    • 13.86% java.util.ArrayList.indexOf (268 samples)
    • 7.81% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (151 samples)
    • 6.62% com.codename1.tools.translator.Parser.addToConstantPool (128 samples)
    • 3.72% com.codename1.tools.translator.Parser.cn1EnsureSubclassIndex (72 samples)
    • 3.67% java.lang.StringBuilder.append (71 samples)
    • 2.64% com.codename1.tools.translator.ByteCodeClass.hasDeclaredMethod (51 samples)
    • 2.64% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (51 samples)
    • 2.02% com.codename1.tools.translator.BytecodeMethod.equals (39 samples)
    • 1.86% com.codename1.tools.translator.BytecodeMethod.optimize (36 samples)
    • 1.81% com.codename1.tools.translator.Parser.classIndex (35 samples)
    • 1.71% org.objectweb.asm.tree.analysis.Analyzer.analyze (33 samples)
    • 1.65% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (32 samples)
    • 1.14% com.codename1.tools.translator.bytecodes.Invoke.resolveDirectTarget (22 samples)
    • 1.14% java.lang.String.equals (22 samples)
    • 1.14% java.util.HashMap.hash (22 samples)
    • 1.09% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (21 samples)
    • 1.03% java.lang.System.identityHashCode (20 samples)
    • 1.03% com.codename1.tools.translator.BytecodeMethod.addInstruction (20 samples)
    • 0.98% com.codename1.tools.translator.NativeSymbolIndex.<init> (19 samples)
    • 0.98% com.codename1.tools.translator.bytecodes.Invoke.findMethodUp (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.

@github-actions

github-actions Bot commented Jul 24, 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.

@shai-almog

shai-almog commented Jul 24, 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 63ms / native 4ms = 15.7x speedup
SIMD float-mul (64K x300) java 64ms / native 4ms = 16.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 195.000 ms
Base64 CN1 decode 125.000 ms
Base64 SIMD encode 101.000 ms
Base64 encode ratio (SIMD/CN1) 0.518x (48.2% faster)
Base64 SIMD decode 112.000 ms
Base64 decode ratio (SIMD/CN1) 0.896x (10.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 21.000 ms
Image createMask (SIMD on) 20.000 ms
Image createMask ratio (SIMD on/off) 0.952x (4.8% faster)
Image applyMask (SIMD off) 54.000 ms
Image applyMask (SIMD on) 47.000 ms
Image applyMask ratio (SIMD on/off) 0.870x (13.0% faster)
Image modifyAlpha (SIMD off) 45.000 ms
Image modifyAlpha (SIMD on) 193.000 ms
Image modifyAlpha ratio (SIMD on/off) 4.289x (328.9% slower)
Image modifyAlpha removeColor (SIMD off) 57.000 ms
Image modifyAlpha removeColor (SIMD on) 29.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.509x (49.1% faster)

@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Jul 24, 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 60ms / native 4ms = 15.0x speedup
SIMD float-mul (64K x300) java 63ms / native 3ms = 21.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 192.000 ms
Base64 CN1 decode 121.000 ms
Base64 SIMD encode 99.000 ms
Base64 encode ratio (SIMD/CN1) 0.516x (48.4% faster)
Base64 SIMD decode 83.000 ms
Base64 decode ratio (SIMD/CN1) 0.686x (31.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 21.000 ms
Image createMask (SIMD on) 16.000 ms
Image createMask ratio (SIMD on/off) 0.762x (23.8% faster)
Image applyMask (SIMD off) 44.000 ms
Image applyMask (SIMD on) 38.000 ms
Image applyMask ratio (SIMD on/off) 0.864x (13.6% faster)
Image modifyAlpha (SIMD off) 34.000 ms
Image modifyAlpha (SIMD on) 29.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.853x (14.7% faster)
Image modifyAlpha removeColor (SIMD off) 36.000 ms
Image modifyAlpha removeColor (SIMD on) 31.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.861x (13.9% faster)

@shai-almog

shai-almog commented Jul 24, 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 54ms / native 3ms = 18.0x speedup
SIMD float-mul (64K x300) java 55ms / native 3ms = 18.3x 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 245.000 ms
Base64 CN1 decode 127.000 ms
Base64 SIMD encode 65.000 ms
Base64 encode ratio (SIMD/CN1) 0.265x (73.5% faster)
Base64 SIMD decode 59.000 ms
Base64 decode ratio (SIMD/CN1) 0.465x (53.5% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 10.000 ms
Image createMask (SIMD on) 8.000 ms
Image createMask ratio (SIMD on/off) 0.800x (20.0% faster)
Image applyMask (SIMD off) 23.000 ms
Image applyMask (SIMD on) 17.000 ms
Image applyMask ratio (SIMD on/off) 0.739x (26.1% faster)
Image modifyAlpha (SIMD off) 14.000 ms
Image modifyAlpha (SIMD on) 11.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.786x (21.4% faster)
Image modifyAlpha removeColor (SIMD off) 17.000 ms
Image modifyAlpha removeColor (SIMD on) 12.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.706x (29.4% faster)

@shai-almog

shai-almog commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Native Linux port (x64)

Compared 147 screenshots: 146 matched, 1 missing actual.

  • ToastBarTopPosition — missing actual screenshot. Actual screenshot missing (test did not produce output).

    No preview available for this screenshot.

@shai-almog

shai-almog commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

Native Linux port (arm64)

Compared 147 screenshots: 146 matched, 1 missing actual.

  • ToastBarTopPosition — missing actual screenshot. Actual screenshot missing (test did not produce output).

    No preview available for this screenshot.

@shai-almog

shai-almog commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Jul 24, 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 24, 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 24, 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: 236 seconds

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 52ms / native 3ms = 17.3x speedup
SIMD float-mul (64K x300) java 52ms / native 3ms = 17.3x 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 148.000 ms
Base64 CN1 decode 112.000 ms
Base64 native encode 493.000 ms
Base64 encode ratio (CN1/native) 0.300x (70.0% faster)
Base64 native decode 214.000 ms
Base64 decode ratio (CN1/native) 0.523x (47.7% faster)
Base64 SIMD encode 46.000 ms
Base64 encode ratio (SIMD/CN1) 0.311x (68.9% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.402x (59.8% faster)
Base64 encode ratio (SIMD/native) 0.093x (90.7% faster)
Base64 decode ratio (SIMD/native) 0.210x (79.0% 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) 41.000 ms
Image applyMask (SIMD on) 30.000 ms
Image applyMask ratio (SIMD on/off) 0.732x (26.8% faster)
Image modifyAlpha (SIMD off) 33.000 ms
Image modifyAlpha (SIMD on) 29.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.879x (12.1% faster)
Image modifyAlpha removeColor (SIMD off) 37.000 ms
Image modifyAlpha removeColor (SIMD on) 27.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.730x (27.0% faster)

@shai-almog

shai-almog commented Jul 24, 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: 501 seconds

Build and Run Timing

Metric Duration
Simulator Boot 76000 ms
Simulator Boot (Run) 2000 ms
App Install 18000 ms
App Launch 1000 ms
Test Execution 953000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 57ms / native 4ms = 14.2x speedup
SIMD float-mul (64K x300) java 86ms / native 3ms = 28.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 223.000 ms
Base64 CN1 decode 169.000 ms
Base64 native encode 572.000 ms
Base64 encode ratio (CN1/native) 0.390x (61.0% faster)
Base64 native decode 510.000 ms
Base64 decode ratio (CN1/native) 0.331x (66.9% faster)
Base64 SIMD encode 75.000 ms
Base64 encode ratio (SIMD/CN1) 0.336x (66.4% faster)
Base64 SIMD decode 71.000 ms
Base64 decode ratio (SIMD/CN1) 0.420x (58.0% faster)
Base64 encode ratio (SIMD/native) 0.131x (86.9% faster)
Base64 decode ratio (SIMD/native) 0.139x (86.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 8.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.375x (62.5% faster)
Image applyMask (SIMD off) 60.000 ms
Image applyMask (SIMD on) 56.000 ms
Image applyMask ratio (SIMD on/off) 0.933x (6.7% faster)
Image modifyAlpha (SIMD off) 48.000 ms
Image modifyAlpha (SIMD on) 53.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.104x (10.4% slower)
Image modifyAlpha removeColor (SIMD off) 66.000 ms
Image modifyAlpha removeColor (SIMD on) 68.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.030x (3.0% slower)

@shai-almog

shai-almog commented Jul 24, 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: 616 seconds

Build and Run Timing

Metric Duration
Simulator Boot 112000 ms
Simulator Boot (Run) 3000 ms
App Install 23000 ms
App Launch 3000 ms
Test Execution 1323000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 134ms / native 6ms = 22.3x speedup
SIMD float-mul (64K x300) java 212ms / native 3ms = 70.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 246.000 ms
Base64 CN1 decode 161.000 ms
Base64 native encode 629.000 ms
Base64 encode ratio (CN1/native) 0.391x (60.9% faster)
Base64 native decode 422.000 ms
Base64 decode ratio (CN1/native) 0.382x (61.8% faster)
Base64 SIMD encode 68.000 ms
Base64 encode ratio (SIMD/CN1) 0.276x (72.4% faster)
Base64 SIMD decode 94.000 ms
Base64 decode ratio (SIMD/CN1) 0.584x (41.6% faster)
Base64 encode ratio (SIMD/native) 0.108x (89.2% faster)
Base64 decode ratio (SIMD/native) 0.223x (77.7% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 14.000 ms
Image createMask (SIMD on) 3.000 ms
Image createMask ratio (SIMD on/off) 0.214x (78.6% faster)
Image applyMask (SIMD off) 85.000 ms
Image applyMask (SIMD on) 87.000 ms
Image applyMask ratio (SIMD on/off) 1.024x (2.4% slower)
Image modifyAlpha (SIMD off) 152.000 ms
Image modifyAlpha (SIMD on) 76.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.500x (50.0% faster)
Image modifyAlpha removeColor (SIMD off) 79.000 ms
Image modifyAlpha removeColor (SIMD on) 126.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 1.595x (59.5% slower)

@ddyer0

ddyer0 commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

It would be interesting to run the current test against the legacy parpavm behavior, but I don't have the
ability to do that. The metering I do have is that old parpavm performed this task, with all the
garbage creation that has since been eliminated, and with all the small byte[] that have been
eliminated, in 14 seconds.

check-copyright-headers flagged the two new test files; use the
standard Codename One GPLv2 + Classpath Exception header.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 02: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 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread vm/ByteCodeTranslator/src/cn1_globals.m Outdated
Comment thread vm/ByteCodeTranslator/src/cn1_globals.m Outdated
Use the same atomic_exchange idiom as the BiBOP reset for
cn1LegacyBytesSinceGc (racing adds land before the swap or charge the
next cycle), and mirror cn1BibopMaybeGc's native-allocation-bracket
gate on the legacy trigger: without conservative roots a bracketed
thread must not trigger a cycle; with them, gating would starve the
trigger for native-bracket allocators.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 03:03

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 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread vm/ByteCodeTranslator/src/cn1_globals.m Outdated
Comment thread vm/ByteCodeTranslator/src/cn1_globals.m
cn1LegacyBytesSinceGc is file-local -- make it static. The
CN1_GC_LOG_CYCLES tracer stays outside the CN1_DISABLE_BIBOP guard on
purpose (A/B builds need the same cycle-count observable); document
that instead of gating it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 03:09
Copilot AI review requested due to automatic review settings July 24, 2026 06:53

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 10 out of 10 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

vm/ByteCodeTranslator/src/cn1_globals.m:3966

  • The legacy-volume trigger uses long prevLegacyBytes = atomic_fetch_add_explicit(&cn1LegacyBytesSinceGc, ...). On Windows, long is 32-bit, so both the accumulator and prevLegacyBytes can overflow and incorrectly miss the edge-trigger crossing (or trigger spuriously). Use a 64-bit accumulator type consistently here (and in the cn1LegacyBytesSinceGc declaration/reset).
    long prevLegacyBytes = atomic_fetch_add_explicit(&cn1LegacyBytesSinceGc, (long)size,
                                                     memory_order_relaxed);
    if(prevLegacyBytes <= CN1_LEGACY_GC_TRIGGER_BYTES
       && prevLegacyBytes + (long)size > CN1_LEGACY_GC_TRIGGER_BYTES
       && constantPoolObjects != 0

Comment thread vm/ByteCodeTranslator/src/nativeMethods.m Outdated
Comment thread vm/ByteCodeTranslator/src/cn1_globals.m
Two review findings on the Windows (LLP64 / no-signal-stop) target:

- cn1LegacyBytesSinceGc was _Atomic long, which is 32-bit under LLP64;
  the counter accumulates raw bytes between cycle resets, so it now uses
  long long (with the fetch_add operand and prevLegacyBytes widened to
  match) so the edge-trigger comparison can never see a wrapped value.

- Thread.sleep published threadActive=FALSE without a cooperative park
  capture (a pre-existing pattern this PR's rewrite kept). Under
  CN1_CONSERVATIVE_GC_ROOTS the collector scans a parked thread's native
  stack either via the capture or a signal stop, and Windows has no
  signal-stop fallback -- an uncaptured sleeper's native-stack roots
  went unscanned for the cycle. sleep now runs CN1_GC_PARK_CAPTURE
  before parking, per the CN1_YIELD_THREAD contract, and drops the
  capture on resume exactly like CN1_RESUME_THREAD.

Revalidated locally: LargeArrayLoad 6 GC cycles (x2), RESULT=1099 host
parity, SLEEP_EDGE_OK, TIMER_LATENCY_OK (max_early_ms=0,
min_sleep1500_ms=1500), MapTorture byte-identical to the host JVM,
GcStress x4 + MtStress x3 in cooperative and CN1_GC_SIGNAL_STOP=1 modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 07:06

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 10 out of 10 changed files in this pull request and generated 3 comments.

Comment thread vm/ByteCodeTranslator/src/nativeMethods.m Outdated
…umbs

- The sleep deadline on the MSVC/clang-cl target was computed from
  gettimeofday, a wall clock: a system time step mid-sleep stretched or
  cut the remaining duration. cn1_win_compat gains cn1_monotonic_micros
  (QueryPerformanceCounter, overflow-safe split divide) and
  cn1SleepNowMicros now uses it, keeping windows.h out of translated
  compilation units per the compat layer's rule.

- LargeArrayGcIntegrationTest's best-effort temp cleanup no longer fails
  silently: one stderr line per root reports the first failed deletion
  so leaked translated build trees leave a breadcrumb on self-hosted
  runners.

- LargeArrayLoad and its CI twin LargeArrayGcApp now carry explicit
  KEEP IN SYNC notes in both directions, documenting that the shared
  host-JVM-asserted RESULT= value turns one-sided edits into a visible
  parity break rather than silent drift.

Revalidated locally: LargeArrayLoad 6 GC cycles (x2), RESULT=1099,
SLEEP_EDGE_OK, TIMER_LATENCY_OK; vm/tests module test-compiles clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 07:29
The copyright gate checks every file a PR touches; these two predate the
gate and entered the diff via the monotonic-clock shim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29

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 12 out of 12 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

vm/ByteCodeTranslator/src/nativeMethods.m:2116

  • Thread.sleep(long) still doesn’t reject negative durations. In Java semantics (and in this repo’s JavaAPI, see Thread.sleep(long,int) and Thread.join), negative millis must throw IllegalArgumentException. With the new deadline arithmetic, negative values can also trigger signed overflow/UB in millis * 1000 (e.g. Long.MIN_VALUE) before the loop even runs, or silently return immediately, which is inconsistent and potentially unsafe.

Please add an early millis < 0 check in the native implementation that throws IllegalArgumentException (e.g. message "timeout value is negative"), and ensure the threadActive/gcParkCaptured state is restored appropriately when throwing.

    JAVA_LONG now = cn1SleepNowMicros();
    JAVA_LONG maxMicros = 0x7fffffffffffffffLL;
    JAVA_LONG deltaMicros = (millis > maxMicros / 1000) ? maxMicros : millis * 1000;
    JAVA_LONG deadline = (deltaMicros > maxMicros - now) ? maxMicros : now + deltaMicros;

Copilot AI review requested due to automatic review settings July 24, 2026 07:39

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 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread vm/ByteCodeTranslator/src/cn1_win_compat.c
The unsynchronized static was a formal C data race across concurrent
sleepers; QueryPerformanceFrequency is a cheap userspace read of a boot
constant, so just call it per invocation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 07: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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment thread vm/ByteCodeTranslator/src/cn1_globals.m
Comment thread vm/ByteCodeTranslator/src/nativeMethods.m Outdated
Two review findings:

- The edge-triggered legacy volume trigger could lose its edge for good:
  a crossing that landed while the allocating thread was inside a
  native-allocation bracket (gated when conservative roots are off) was
  skipped, and every later allocation saw prev already above the
  threshold, deferring collection to the GC thread's 30s idle wake. The
  trigger is now LEVEL-triggered on the counter and LATCHED
  (cn1LegacyGcScheduled, cleared at cycle begin after the counter reset)
  so a suppressed crossing is retried by the next out-of-bracket legacy
  allocation on any thread while System.gc() is still scheduled at most
  once per cycle window.

- Thread.sleep(long) now throws IllegalArgumentException for negative
  millis per the JDK contract (the (millis, nanos) overload already
  did). The guard lives in Java -- sleep(long) delegates to a private
  native sleepImpl -- so the exception class is retained by the
  translator's reachability pass wherever sleep is used. SleepEdge now
  asserts the negative case.

Revalidated locally: LargeArrayLoad 6 GC cycles (x2), RESULT=1099,
SLEEP_EDGE_OK (negativeThrew=true), TIMER_LATENCY_OK, MapTorture
parity, GcStress x3 + MtStress x2 in both stop modes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 08:02

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 13 out of 13 changed files in this pull request and generated 2 comments.

Comment thread vm/JavaAPI/src/java/lang/Thread.java
Comment thread .github/workflows/linux-build-run.yml Outdated
Thread.sleep(long) became a Java wrapper delegating to the native
sleepImpl(long), so the JS translator now emits calls to
cn1_java_lang_Thread_sleepImpl_*; the runtime binds those (keeping the
old sleep aliases for previously-translated bundles), the native
registry lists the new symbol, and the opcode coverage test asserts it.

Also: the Linux gate's diagnostic grep uses -E with standard
alternation instead of GNU basic-regex escapes.

JavascriptOpcodeCoverageTest passes (3/3); LargeArrayGcIntegrationTest
re-verified green at both e2a6c74 and 0a13eca on this machine (the
one local failure was CPU-starvation flake from a concurrent full
module build, not a code regression).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29
Copilot AI review requested due to automatic review settings July 24, 2026 08:17
The copyright gate checks every PR-touched file; this pre-existing test
entered the diff via the sleepImpl binding assertion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0133QDXUjHwX9uprkupEgB29

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 16 out of 16 changed files in this pull request and generated 1 comment.

Comment thread vm/ByteCodeTranslator/src/nativeMethods.m
Copilot AI review requested due to automatic review settings July 24, 2026 08:21
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.

4 participants