Repository navigation
ParparVM: a for-each over a ?: walked the else branch - #6121
Conversation
javac joins the two branches of `for (x : c ? a : b)` at a label right before iterator(). prevExecutable skipped that label, so intrinsifyForEach folded the else branch's load in as the receiver of both paths: the loop always walked `b`, and threw NullPointerException when `b` was null and `c` true. The keySet()/values() fold had the same flaw. prevExecutable now stops at a jump target, and the loop reads a joined receiver off the stack. The bulk comparison also stops passing checks that prove nothing: a check that prints nothing, throws out of its body on HotSpot, or is skipped while ParparVM throws now fails, and the HotSpot run must reach Bulk.end(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cloudflare Preview
|
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 227 screenshots: 227 matched. |
|
Compared 206 screenshots: 206 matched. Benchmark ResultsDetailed Performance Metrics
|
|
Compared 206 screenshots: 206 matched. ParparVM vs HotSpot (JDK 25): Linux arm64Runner CPU: Neoverse-N2 (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in
Result: no regression |
|
Compared 206 screenshots: 206 matched. Benchmark ResultsDetailed Performance Metrics
ParparVM vs HotSpot (JDK 25): Windows x64Runner CPU: Intel64 Family 6 Model 207 Stepping 2, GenuineIntel (baseline 2 improvements past the baseline (not a failure; rebaseline to lock them in):
Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in
A change that moves performance ON PURPOSE records the new baseline in Result: no regression (improved past the baseline: consider rebaselining) |
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 206 screenshots: 206 matched. Benchmark ResultsDetailed Performance Metrics
ParparVM vs HotSpot (JDK 25): Windows arm64Runner CPU: ARMv8 (64-bit) Family 8 Model D49 Revision 0, MICROSOFT CORPORATION (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in
Result: no regression |
|
Compared 192 screenshots: 192 matched. Benchmark Results
Detailed Performance Metrics
ParparVM vs HotSpot (JDK 25): macOS arm64Runner CPU: Apple M1 (Virtual) (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in
Result: no regression |
|
Compared 189 screenshots: 189 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
Compared 184 screenshots: 184 matched. |
The only shipped loops whose translation the fix changes are JpqlQueryImpl's `for (Expr c : v.resultBranches == null ? v.children : v.resultBranches)`. An instance field read is not folded into the loop, so that shape was already right; it is kept as a guard. A static field read is folded, and the same shape over statics threw NullPointerException before the fix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Compared 206 screenshots: 206 matched. ParparVM vs HotSpot (JDK 25): Linux x64Runner CPU: AMD EPYC 7763 64-Core Processor (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A ratio more than 20% (time) / 25% (RAM) above its baseline in
Result: no regression |
Problem
On iOS (and native Linux/Windows),
for (String s : (all ? getOwn() : updated))always iterated the else branch; withupdated == nullandall == trueit threwNullPointerException. JavaSE and Android were correct.javac compiles it to
IFEQ L2; <a>; GOTO L3; L2: ALOAD updated; L3: INVOKE iterator.BytecodeMethod.prevExecutable()skipped the join labelL3, sointrinsifyForEach()foldedALOAD updatedinto the loop setup as the receiver for both paths and removed it from the else branch. TheHashMap.keySet()/values()fold used the same helper and had the same bug for(c ? m1 : m2).keySet().Fix
prevExecutablestops at a jump-target label (assrNextRealNoJoinalready does); a joined receiver is read withPOP_OBJ()and the loop stays intrinsified. Every caller pairs a consumer with its producer, so all of them were unsafe across a join.Tests
?:cases in the bulkCollectionSemanticsAppfixture, each isolated, with named expected values inBulkGeneratedCodeChecks. With the fix reverted, 7 fail (for-each and keySet/values); the iterator-call case is behaviour-only, as its comment says.BulkConformanceIntegrationTest.compare: a check that prints nothing, throws out of its body on HotSpot, or is skipped while ParparVM throws now fails, and the HotSpot run must reachBulk.end().BulkCompareTestcovers each rule. No existing check trips them.Locally: full bulk suite 330 checks / 0 failing;
BulkCompareTest,NativeTraversalTest,LocalReceiverTypesTestgreen.🤖 Generated with Claude Code