Skip to content

Fix #3108: don't rely on C arg eval order in PUTFIELD/MULTIANEWARRAY#4985

Merged
shai-almog merged 1 commit into
masterfrom
fix-3108-ios-putfield-multianewarray
May 19, 2026
Merged

Fix #3108: don't rely on C arg eval order in PUTFIELD/MULTIANEWARRAY#4985
shai-almog merged 1 commit into
masterfrom
fix-3108-ios-putfield-multianewarray

Conversation

@shai-almog
Copy link
Copy Markdown
Collaborator

Summary

ParparVM's bytecode-to-C translator emitted C code that relied on the order of evaluation of function arguments, which C leaves unspecified. Clang on iOS evaluates such arguments right-to-left, so two opcodes were silently miscompiled:

  • PUTFIELD with a primitive value -- emitted set_field_X(POP_DOUBLE(), POP_OBJ()) (or POP_FLOAT/LONG/INT analogues) when neither operand could be folded into a literal. With right-to-left evaluation, POP_OBJ() runs first, pops the double's raw bits, interprets them as an object pointer, and the subsequent field write dereferences garbage. This is the NPE reported in #3108. The trigger reported there -- a.x = a.y = (int)expr -- produces bytecode with dup2_x1 + two putfields, which is exactly the shape that defeats Field.tryReduce and so falls through to the buggy fallback.
  • MULTIANEWARRAY for 3D/4D arrays -- emitted alloc3DArray(td, POP_INT(), POP_INT(), POP_INT()) (and the 4D equivalent). With right-to-left evaluation the dimensions silently swap: new int[a][b][c] allocates as if it were new int[c][b][a]. (The 2D case already used the safe SP -= dims; ...(*(SP+1)).data.i, (*SP).data.i pattern.)

Both sites now use PEEK semantics with an explicit SP -= N adjustment, mirroring the pattern already used by the object PUTFIELD path and the 2D MULTIANEWARRAY case. The MultiArray fix also corrects the (currently dead, but defensive) dims<actualDim arms so unspecified inner dimensions land in the right alloc?DArray slots, not just by luck of POP order.

Files changed

  • vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/Field.java -- primitive PUTFIELD fallback now uses PEEK_TYPE(1), PEEK_OBJ(2) + SP -= 2.
  • vm/ByteCodeTranslator/src/com/codename1/tools/translator/bytecodes/MultiArray.java -- 3D and 4D MULTIANEWARRAY use SP -= dims; alloc?DArray(td, (*(SP+N)).data.i, ..., (*SP).data.i, ...).
  • vm/tests/src/test/java/com/codename1/tools/translator/BytecodeInstructionIntegrationTest.java -- two new regression tests.

Test plan

  • Added putfieldUnfoldedPathUsesPeekNotPop -- constructs Field directly for each primitive descriptor + the object case, asserts emitted C uses PEEK_*(1), PEEK_OBJ(2) + explicit pop, and explicitly forbids the buggy POP_X(), POP_OBJ() shape.
  • Added multiArrayEmissionIsArgumentOrderSafe -- covers 2D/3D/4D MULTIANEWARRAY. Asserts the emitted C reads dimensions with PEEK after a single SP -= N, and explicitly forbids POP_INT(), POP_INT() chains.
  • Verified each new test fails on the pre-fix code with the old buggy emission visible in the failure diagnostic (set_field_MyClass_myField(POP_DOUBLE(), POP_OBJ()); and alloc3DArray(threadStateData, POP_INT(), POP_INT(), POP_INT(), ...)).
  • Verified each new test passes after the fix.
  • All 18 unit tests in BytecodeInstructionIntegrationTest pass.
  • Suggested follow-up validation: run the original repro from IOS miscompilation, results in nullPointerException #3108 (chained boardScaleIndex = newBoardScaleIndex = Math.min(...)) on a physical iOS build, confirm no NPE.
  • Suggested follow-up validation: exercise new int[a][b][c] on a physical iOS build and confirm a.length == a, a[0].length == b, a[0][0].length == c (this was likely broken before too, just less visibly than the PUTFIELD crash).

Refs: #3108

🤖 Generated with Claude Code

ParparVM emitted C of the form set_field_X(POP_DOUBLE(), POP_OBJ()) for
primitive instance-field writes when the value/target operands could not
be folded into literals, and alloc3DArray/alloc4DArray(td, POP_INT(),
POP_INT(), ...) for MULTIANEWARRAY. C does not specify the order of
function-argument evaluation, so clang on iOS evaluated the POPs
right-to-left and:

- PUTFIELD: popped the double's bits as an object reference, dereferenced
  garbage, and crashed with the NPE reported in #3108. Reproduced via
  chained assignment with widening conversion (a.x = a.y = (int)expr),
  which forces a dup2_x1 sequence that defeats the existing operand
  folding in Field.tryReduce.
- MULTIANEWARRAY: silently swapped the array dimensions, so
  new int[a][b][c] could be allocated as if it were new int[c][b][a].

Both sites now use PEEK semantics with an explicit SP adjustment, mirror-
ing the pattern already used by the object PUTFIELD path and the 2D
MULTIANEWARRAY case. The MultiArray fix also corrects the (currently
dead, but defensive) dims<actualDim arms so unspecified inner dimensions
land in the right alloc?DArray slots, not just by luck of POP order.

Regression tests cover both code paths: putfieldUnfoldedPathUsesPeekNotPop
and multiArrayEmissionIsArgumentOrderSafe. Both assert the emitted C uses
PEEK + SP-=N (or POP_MANY), and explicitly forbid POP_X(), POP_Y() chains.
Verified each fails on the pre-fix code with the buggy emission visible
in the failure diagnostic.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

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

@github-actions
Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [Report archive]
    • ByteCodeTranslator: 0 findings (no issues)
    • android: 0 findings (no issues)
    • 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.

@github-actions
Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 646 total, 0 failed, 2 skipped

Benchmark Results

  • Execution Time: 10459 ms

  • Hotspots (Top 20 sampled methods):

    • 20.58% java.lang.String.indexOf (379 samples)
    • 19.54% com.codename1.tools.translator.Parser.isMethodUsed (360 samples)
    • 17.16% java.util.ArrayList.indexOf (316 samples)
    • 5.05% java.lang.Object.hashCode (93 samples)
    • 4.67% com.codename1.tools.translator.ByteCodeClass.markDependent (86 samples)
    • 3.42% java.lang.System.identityHashCode (63 samples)
    • 3.09% com.codename1.tools.translator.BytecodeMethod.addToConstantPool (57 samples)
    • 2.28% com.codename1.tools.translator.ByteCodeClass.calcUsedByNative (42 samples)
    • 1.74% com.codename1.tools.translator.Parser.getClassByName (32 samples)
    • 1.68% com.codename1.tools.translator.ByteCodeClass.updateAllDependencies (31 samples)
    • 1.52% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (28 samples)
    • 0.98% com.codename1.tools.translator.BytecodeMethod.optimize (18 samples)
    • 0.92% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (17 samples)
    • 0.92% java.lang.StringCoding.encode (17 samples)
    • 0.81% java.lang.StringBuilder.append (15 samples)
    • 0.76% com.codename1.tools.translator.BytecodeMethod.appendMethodSignatureSuffixFromDesc (14 samples)
    • 0.76% com.codename1.tools.translator.Parser.cullMethods (14 samples)
    • 0.76% com.codename1.tools.translator.ByteCodeClass.isDefaultInterfaceMethod (14 samples)
    • 0.60% com.codename1.tools.translator.BytecodeMethod.appendMethodC (11 samples)
    • 0.54% com.codename1.tools.translator.ByteCodeClass.markDependencies (10 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.

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 79000 ms
Simulator Boot (Run) 1000 ms
App Install 14000 ms
App Launch 6000 ms
Test Execution 289000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 2112.000 ms
Base64 CN1 encode 2023.000 ms
Base64 encode ratio (CN1/native) 0.958x (4.2% faster)
Base64 native decode 931.000 ms
Base64 CN1 decode 1387.000 ms
Base64 decode ratio (CN1/native) 1.490x (49.0% slower)
Base64 SIMD encode 623.000 ms
Base64 encode ratio (SIMD/native) 0.295x (70.5% faster)
Base64 encode ratio (SIMD/CN1) 0.308x (69.2% faster)
Base64 SIMD decode 508.000 ms
Base64 decode ratio (SIMD/native) 0.546x (45.4% faster)
Base64 decode ratio (SIMD/CN1) 0.366x (63.4% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 62.000 ms
Image createMask (SIMD on) 10.000 ms
Image createMask ratio (SIMD on/off) 0.161x (83.9% faster)
Image applyMask (SIMD off) 232.000 ms
Image applyMask (SIMD on) 140.000 ms
Image applyMask ratio (SIMD on/off) 0.603x (39.7% faster)
Image modifyAlpha (SIMD off) 218.000 ms
Image modifyAlpha (SIMD on) 89.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.408x (59.2% faster)
Image modifyAlpha removeColor (SIMD off) 191.000 ms
Image modifyAlpha removeColor (SIMD on) 77.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.403x (59.7% faster)
Image PNG encode (SIMD off) 1393.000 ms
Image PNG encode (SIMD on) 1025.000 ms
Image PNG encode ratio (SIMD on/off) 0.736x (26.4% faster)
Image JPEG encode 728.000 ms

@shai-almog
Copy link
Copy Markdown
Collaborator Author

shai-almog commented May 19, 2026

Compared 110 screenshots: 110 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 77000 ms
Simulator Boot (Run) 2000 ms
App Install 23000 ms
App Launch 9000 ms
Test Execution 408000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 2083.000 ms
Base64 CN1 encode 1854.000 ms
Base64 encode ratio (CN1/native) 0.890x (11.0% faster)
Base64 native decode 1126.000 ms
Base64 CN1 decode 1408.000 ms
Base64 decode ratio (CN1/native) 1.250x (25.0% slower)
Base64 SIMD encode 617.000 ms
Base64 encode ratio (SIMD/native) 0.296x (70.4% faster)
Base64 encode ratio (SIMD/CN1) 0.333x (66.7% faster)
Base64 SIMD decode 633.000 ms
Base64 decode ratio (SIMD/native) 0.562x (43.8% faster)
Base64 decode ratio (SIMD/CN1) 0.450x (55.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 105.000 ms
Image createMask (SIMD on) 19.000 ms
Image createMask ratio (SIMD on/off) 0.181x (81.9% faster)
Image applyMask (SIMD off) 260.000 ms
Image applyMask (SIMD on) 104.000 ms
Image applyMask ratio (SIMD on/off) 0.400x (60.0% faster)
Image modifyAlpha (SIMD off) 166.000 ms
Image modifyAlpha (SIMD on) 106.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.639x (36.1% faster)
Image modifyAlpha removeColor (SIMD off) 272.000 ms
Image modifyAlpha removeColor (SIMD on) 125.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.460x (54.0% faster)
Image PNG encode (SIMD off) 1556.000 ms
Image PNG encode (SIMD on) 1800.000 ms
Image PNG encode ratio (SIMD on/off) 1.157x (15.7% slower)
Image JPEG encode 953.000 ms

@shai-almog shai-almog merged commit b609274 into master May 19, 2026
21 of 22 checks passed
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.

1 participant