Skip to content

Playground: build via local ParparVM JavaScript target#5250

Open
shai-almog wants to merge 14 commits into
masterfrom
playground-local-javascript-target
Open

Playground: build via local ParparVM JavaScript target#5250
shai-almog wants to merge 14 commits into
masterfrom
playground-local-javascript-target

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Migrates the Playground off the pinned old release + cloud javascript build target onto the local ParparVM local-javascript target — the same path the initializr uses (#5200) — and removes the class-exclusion list and version pinning that worked around the lagging cloud TeaVM backend.

Playground (scripts/cn1playground/)

  • Build target: javascript/pom.xml codename1.defaultBuildTarget and build.sh/build.bat switch javascriptlocal-javascript. The cloud javascript target remains a fallback.
  • Un-pin: dropped the one-release cn1.registry.version (7.0.234) pin. The bean-shell access registry now tracks the current API directly. New cn1.accessRegistry.useLocalSources property (true under the cn1-local-workspace profile) generates the registry from the repo's own CN1 sources (8.0-SNAPSHOT has no source jars on Central); common/pom.xml passes it to the generator as CN1_ACCESS_USE_LOCAL_SOURCES.
  • Exclusion list (GenerateCN1AccessRegistry.java): removed the security.* / nfc.* exclusions — those were cloud-TeaVM-lag workarounds, and the local target builds current sources. Kept the structural exclusions: Accessor/IOAccessor internals, and Simd (its alloca scratch arrays may not escape the reflection bridge under the bytecode-compliance check, which the local target enforces too). Also fixed an array-component varargs codegen bug surfaced by re-including NFC: emit new byte[len][], not the malformed new byte[][len], for T[]... params.

JS-port translator (vm/ByteCodeTranslator/)

The Playground's reflective registry references nearly the whole API, exercising paths a normal app doesn't:

  • Array class literals: the JS backend only handled object ldc class literals and threw Unsupported ldc constant on array literals (byte[].class, String[].class), which the registry emits for every array-typed parameter. Now lowered to jvm.getArrayClass(component, dims).classObject (cached under the same name array instances use).
  • Error swallowing: Parser.writeOutput caught Throwable but only rethrew Exception, so an OutOfMemoryError mid-emit let the translator exit 0 with a truncated dist (parparvm_runtime.js but no worker.js/translated_app.js). Now rethrows Errors so the build fails loudly.

Plugin / build wiring

  • JavaScriptBuilder: a -Xmx in CN1_TRANSLATOR_OPTS now overrides the default 512m translator heap.
  • The Playground keeps nearly the whole API reachable, so the JS RTA tree-shaking pass can't prune yet runs 100min+. build.sh/build.bat and the website build_playground_for_site set CN1_TRANSLATOR_OPTS=-Dparparvm.js.rta.off -Xmx6g; the website extraction flattens the single wrapper dir so index.html lands at the served root (mirroring the initializr path).

Verification

Built locally with the local 8.0-SNAPSHOT plugin: the local-javascript build produces a complete bundle (index.html, worker.js, translated_app + 6 chunks, parparvm_runtime.js, browser_bridge.js, js/ assets, themes, Monaco editor html.tar, native-interface stub) that loads in headless Chromium with no fatal JS/VM errors. The regenerated registry now includes com.codename1.security.* / com.codename1.nfc.* and still excludes the internals + Simd.

Note: most of the diff is the regenerated bsh/cn1/GeneratedCN1Access*.java (checked-in generated source, regenerated each build).

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 390 total, 0 failed, 14 skipped

Benchmark Results

  • Execution Time: 18156 ms

  • Hotspots (Top 20 sampled methods):

    • 26.50% com.codename1.tools.translator.Parser.addToConstantPool (443 samples)
    • 16.03% java.util.ArrayList.indexOf (268 samples)
    • 4.25% com.codename1.tools.translator.ByteCodeClass.fillVirtualMethodTable (71 samples)
    • 3.47% java.lang.StringBuilder.append (58 samples)
    • 2.27% org.objectweb.asm.tree.analysis.Analyzer.analyze (38 samples)
    • 2.21% com.codename1.tools.translator.BytecodeMethod.optimize (37 samples)
    • 2.03% java.lang.System.identityHashCode (34 samples)
    • 1.73% org.objectweb.asm.tree.analysis.Analyzer.findSubroutine (29 samples)
    • 1.38% java.lang.Object.hashCode (23 samples)
    • 1.26% com.codename1.tools.translator.bytecodes.Invoke.addDependencies (21 samples)
    • 1.26% java.lang.StringCoding.encode (21 samples)
    • 1.14% com.codename1.tools.translator.Parser.classIndex (19 samples)
    • 1.14% java.util.HashMap.hash (19 samples)
    • 1.08% com.codename1.tools.translator.BytecodeMethod.equals (18 samples)
    • 1.02% java.io.UnixFileSystem.getBooleanAttributes0 (17 samples)
    • 0.90% com.codename1.tools.translator.Parser.cullMethods (15 samples)
    • 0.90% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (15 samples)
    • 0.90% com.codename1.tools.translator.Parser.resolveDupForms (15 samples)
    • 0.84% org.objectweb.asm.ClassReader.readCode (14 samples)
    • 0.78% com.codename1.tools.translator.BytecodeMethod.appendMethodSignatureSuffixFromDesc (13 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

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 128 screenshots: 128 matched.

Native Android coverage

  • 📊 Line coverage: 14.23% (8661/60877 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 11.52% (42650/370328), branch 5.06% (1763/34839), complexity 6.06% (2023/33383), method 10.51% (1640/15601), class 17.17% (378/2201)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

✅ Native Android screenshot tests passed.

Native Android coverage

  • 📊 Line coverage: 14.23% (8661/60877 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 11.52% (42650/370328), branch 5.06% (1763/34839), complexity 6.06% (2023/33383), method 10.51% (1640/15601), class 17.17% (378/2201)
    • Lowest covered classes
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysKt – 0.00% (0/6327 lines covered)
      • kotlin.collections.unsigned.kotlin.collections.unsigned.UArraysKt___UArraysKt – 0.00% (0/2384 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.ClassReader – 0.00% (0/1519 lines covered)
      • kotlin.collections.kotlin.collections.CollectionsKt___CollectionsKt – 0.00% (0/1148 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.MethodWriter – 0.00% (0/923 lines covered)
      • kotlin.sequences.kotlin.sequences.SequencesKt___SequencesKt – 0.00% (0/730 lines covered)
      • kotlin.text.kotlin.text.StringsKt___StringsKt – 0.00% (0/623 lines covered)
      • org.jacoco.agent.rt.internal_b6258fc.asm.org.jacoco.agent.rt.internal_b6258fc.asm.Frame – 0.00% (0/564 lines covered)
      • kotlin.collections.kotlin.collections.ArraysKt___ArraysJvmKt – 0.00% (0/495 lines covered)
      • kotlinx.coroutines.kotlinx.coroutines.JobSupport – 0.00% (0/423 lines covered)

Benchmark Results

Detailed Performance Metrics

Metric Duration
SIMD kernel backend scalar fallback (no native SIMD)
SIMD int-add (64K x300) java 260ms / native 117ms = 2.2x speedup
SIMD float-mul (64K x300) java 126ms / native 74ms = 1.7x speedup
SIMD kernel correctness PASS (native result == scalar reference)
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 189.000 ms
Base64 CN1 decode 232.000 ms
Base64 native encode 821.000 ms
Base64 encode ratio (CN1/native) 0.230x (77.0% faster)
Base64 native decode 767.000 ms
Base64 decode ratio (CN1/native) 0.302x (69.8% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

github-actions Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

✅ Continuous Quality Report

Test & Coverage

Static Analysis

  • SpotBugs [HTML preview] [Download]
    • ByteCodeTranslator: 2 findings (Normal: 2)
    • 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.

@shai-almog

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 125 screenshots: 125 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 71ms / native 5ms = 14.2x speedup
SIMD float-mul (64K x300) java 69ms / native 4ms = 17.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 281.000 ms
Base64 CN1 decode 211.000 ms
Base64 SIMD encode 130.000 ms
Base64 encode ratio (SIMD/CN1) 0.463x (53.7% faster)
Base64 SIMD decode 133.000 ms
Base64 decode ratio (SIMD/CN1) 0.630x (37.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 35.000 ms
Image createMask (SIMD on) 13.000 ms
Image createMask ratio (SIMD on/off) 0.371x (62.9% faster)
Image applyMask (SIMD off) 56.000 ms
Image applyMask (SIMD on) 30.000 ms
Image applyMask ratio (SIMD on/off) 0.536x (46.4% faster)
Image modifyAlpha (SIMD off) 58.000 ms
Image modifyAlpha (SIMD on) 22.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.379x (62.1% faster)
Image modifyAlpha removeColor (SIMD off) 63.000 ms
Image modifyAlpha removeColor (SIMD on) 23.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.365x (63.5% faster)

@shai-almog

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

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

@shai-almog

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 125 screenshots: 125 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 72ms / native 4ms = 18.0x speedup
SIMD float-mul (64K x300) java 71ms / native 4ms = 17.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 273.000 ms
Base64 CN1 decode 171.000 ms
Base64 SIMD encode 150.000 ms
Base64 encode ratio (SIMD/CN1) 0.549x (45.1% faster)
Base64 SIMD decode 132.000 ms
Base64 decode ratio (SIMD/CN1) 0.772x (22.8% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 33.000 ms
Image createMask (SIMD on) 10.000 ms
Image createMask ratio (SIMD on/off) 0.303x (69.7% faster)
Image applyMask (SIMD off) 53.000 ms
Image applyMask (SIMD on) 24.000 ms
Image applyMask ratio (SIMD on/off) 0.453x (54.7% faster)
Image modifyAlpha (SIMD off) 55.000 ms
Image modifyAlpha (SIMD on) 18.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.327x (67.3% faster)
Image modifyAlpha removeColor (SIMD off) 56.000 ms
Image modifyAlpha removeColor (SIMD on) 18.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.321x (67.9% faster)

@shai-almog

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 127 screenshots: 127 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 Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 127 screenshots: 127 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 Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

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 55ms / native 3ms = 18.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 306.000 ms
Base64 CN1 decode 198.000 ms
Base64 native encode 782.000 ms
Base64 encode ratio (CN1/native) 0.391x (60.9% faster)
Base64 native decode 449.000 ms
Base64 decode ratio (CN1/native) 0.441x (55.9% faster)
Base64 SIMD encode 54.000 ms
Base64 encode ratio (SIMD/CN1) 0.176x (82.4% faster)
Base64 SIMD decode 45.000 ms
Base64 decode ratio (SIMD/CN1) 0.227x (77.3% faster)
Base64 encode ratio (SIMD/native) 0.069x (93.1% faster)
Base64 decode ratio (SIMD/native) 0.100x (90.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 18.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.056x (94.4% faster)
Image applyMask (SIMD off) 79.000 ms
Image applyMask (SIMD on) 90.000 ms
Image applyMask ratio (SIMD on/off) 1.139x (13.9% slower)
Image modifyAlpha (SIMD off) 94.000 ms
Image modifyAlpha (SIMD on) 81.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.862x (13.8% faster)
Image modifyAlpha removeColor (SIMD off) 93.000 ms
Image modifyAlpha removeColor (SIMD on) 51.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.548x (45.2% faster)

@shai-almog

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 125 screenshots: 125 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 62ms / native 4ms = 15.5x speedup
SIMD float-mul (64K x300) java 61ms / native 3ms = 20.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 606.000 ms
Base64 CN1 decode 235.000 ms
Base64 SIMD encode 102.000 ms
Base64 encode ratio (SIMD/CN1) 0.168x (83.2% faster)
Base64 SIMD decode 135.000 ms
Base64 decode ratio (SIMD/CN1) 0.574x (42.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 22.000 ms
Image createMask (SIMD on) 7.000 ms
Image createMask ratio (SIMD on/off) 0.318x (68.2% faster)
Image applyMask (SIMD off) 38.000 ms
Image applyMask (SIMD on) 13.000 ms
Image applyMask ratio (SIMD on/off) 0.342x (65.8% faster)
Image modifyAlpha (SIMD off) 36.000 ms
Image modifyAlpha (SIMD on) 11.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.306x (69.4% faster)
Image modifyAlpha removeColor (SIMD off) 40.000 ms
Image modifyAlpha removeColor (SIMD on) 12.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.300x (70.0% faster)

@shai-almog shai-almog force-pushed the playground-local-javascript-target branch from 3e84cf7 to 0684ad9 Compare June 16, 2026 12:22
@shai-almog

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 73000 ms
Simulator Boot (Run) 1000 ms
App Install 18000 ms
App Launch 18000 ms
Test Execution 362000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 222ms / native 3ms = 74.0x speedup
SIMD float-mul (64K x300) java 271ms / native 8ms = 33.8x 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 559.000 ms
Base64 CN1 decode 305.000 ms
Base64 native encode 855.000 ms
Base64 encode ratio (CN1/native) 0.654x (34.6% faster)
Base64 native decode 425.000 ms
Base64 decode ratio (CN1/native) 0.718x (28.2% faster)
Base64 SIMD encode 110.000 ms
Base64 encode ratio (SIMD/CN1) 0.197x (80.3% faster)
Base64 SIMD decode 89.000 ms
Base64 decode ratio (SIMD/CN1) 0.292x (70.8% faster)
Base64 encode ratio (SIMD/native) 0.129x (87.1% faster)
Base64 decode ratio (SIMD/native) 0.209x (79.1% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 30.000 ms
Image createMask (SIMD on) 4.000 ms
Image createMask ratio (SIMD on/off) 0.133x (86.7% faster)
Image applyMask (SIMD off) 132.000 ms
Image applyMask (SIMD on) 88.000 ms
Image applyMask ratio (SIMD on/off) 0.667x (33.3% faster)
Image modifyAlpha (SIMD off) 103.000 ms
Image modifyAlpha (SIMD on) 182.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.767x (76.7% slower)
Image modifyAlpha removeColor (SIMD off) 222.000 ms
Image modifyAlpha removeColor (SIMD on) 77.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.347x (65.3% faster)

@shai-almog

shai-almog commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Build and Run Timing

Metric Duration
Simulator Boot 70000 ms
Simulator Boot (Run) 1000 ms
App Install 15000 ms
App Launch 18000 ms
Test Execution 265000 ms

Detailed Performance Metrics

Metric Duration
SIMD kernel backend SSE2 (x64) / NEON (arm64) native kernels
SIMD int-add (64K x300) java 82ms / native 3ms = 27.3x speedup
SIMD float-mul (64K x300) java 70ms / native 3ms = 23.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 491.000 ms
Base64 CN1 decode 319.000 ms
Base64 native encode 1009.000 ms
Base64 encode ratio (CN1/native) 0.487x (51.3% faster)
Base64 native decode 369.000 ms
Base64 decode ratio (CN1/native) 0.864x (13.6% faster)
Base64 SIMD encode 91.000 ms
Base64 encode ratio (SIMD/CN1) 0.185x (81.5% faster)
Base64 SIMD decode 59.000 ms
Base64 decode ratio (SIMD/CN1) 0.185x (81.5% faster)
Base64 encode ratio (SIMD/native) 0.090x (91.0% faster)
Base64 decode ratio (SIMD/native) 0.160x (84.0% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 18.000 ms
Image createMask (SIMD on) 1.000 ms
Image createMask ratio (SIMD on/off) 0.056x (94.4% faster)
Image applyMask (SIMD off) 66.000 ms
Image applyMask (SIMD on) 64.000 ms
Image applyMask ratio (SIMD on/off) 0.970x (3.0% faster)
Image modifyAlpha (SIMD off) 64.000 ms
Image modifyAlpha (SIMD on) 62.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.969x (3.1% faster)
Image modifyAlpha removeColor (SIMD off) 93.000 ms
Image modifyAlpha removeColor (SIMD on) 77.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.828x (17.2% faster)

Migrate the Playground off the pinned old release + cloud `javascript`
build target onto the local ParparVM `local-javascript` target, the same
path the initializr uses.

Playground changes (scripts/cn1playground/):
- javascript/pom.xml: codename1.defaultBuildTarget javascript -> local-javascript
- build.sh / build.bat: javascript target -> local-javascript
- pom.xml: drop the one-release `cn1.registry.version` (7.0.234) pin. The
  bean-shell access registry now tracks the current API directly. A new
  cn1.accessRegistry.useLocalSources property (true under the
  cn1-local-workspace profile) makes the registry generate from the repo's
  own CN1 sources, since 8.0-SNAPSHOT has no source jars on Maven Central.
- common/pom.xml: pass that property through to the registry generator as
  CN1_ACCESS_USE_LOCAL_SOURCES.
- tools/GenerateCN1AccessRegistry.java: remove the security.* / nfc.* class
  exclusions (they worked around the legacy cloud TeaVM backend lagging the
  release channel; the local target builds the current sources). Keep the
  structural exclusions (Accessor/IOAccessor internals, and Simd whose
  alloca scratch arrays cannot escape the reflection bridge under the
  bytecode-compliance check). Also exclude the com.codename1.testing.junit
  package: it is the JavaSE-port JUnit 5 test-extension API (not a runtime
  API a playground script can use, and not on the common compile classpath),
  which un-pinning the registry version newly pulled in and broke the build.
  Fix array-component varargs codegen: emit `new byte[len][]`, not the
  malformed `new byte[][len]`, for `T[]...`.
- tools/generate-cn1-access-registry.sh: drop the registry-version logic.
- README / tools/README: document the new path.

JS-port translator fixes (vm/ByteCodeTranslator/) needed for the Playground's
full-API reflective registry:
- JavascriptMethodGenerator: support array class literals (`ldc [B`,
  `String[].class`, ...) via jvm.getArrayClass(component, dims).classObject.
  The registry emits these for every array-typed parameter; the JS backend
  previously only handled object class literals and threw
  "Unsupported ldc constant".
- Parser: rethrow Errors (e.g. OutOfMemoryError) from writeOutput instead of
  swallowing them, which had let the translator exit 0 with a truncated dist.

Plugin / build wiring:
- JavaScriptBuilder: let a -Xmx in CN1_TRANSLATOR_OPTS override the default
  512m translator heap.
- The Playground keeps nearly the whole API reachable, so the JS RTA
  tree-shaking pass cannot prune much yet runs 100min+. build.sh/build.bat and
  the website build_playground_for_site set CN1_TRANSLATOR_OPTS to disable RTA
  (-Dparparvm.js.rta.off) and raise the heap (-Xmx6g); the website extraction
  flattens the single wrapper dir so index.html lands at the served root.

Verified locally: the local-javascript build produces a complete bundle
(index.html, worker.js, translated_app + chunks, parparvm_runtime.js,
browser_bridge.js, js/ assets, themes, editor html.tar, native-interface
stub) that loads in headless Chromium with no fatal JS/VM errors, and the
playground language smoke tests pass (registry regenerated in release mode,
325/325 syntax, 312/312 paint, 20/20 preview).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog force-pushed the playground-local-javascript-target branch from 0684ad9 to ebbb018 Compare June 16, 2026 13:16
@github-actions

Copy link
Copy Markdown
Contributor

Cloudflare Preview

shai-almog and others added 13 commits June 16, 2026 18:57
JavascriptBundleWriter.hoistStringConstants emits top-level
`const _q<alias>="..."` aliases for repeated string literals, but it
reset the alias counter to 0 for every chunk. The translated_app chunks
are concatenated into one worker scope via importScripts, so two chunks
each declaring e.g. `const _q0O` (with different string values) is a
`SyntaxError: redeclaration of const _q0O` that aborts worker startup.

This only triggered once a bundle was large enough to split into
multiple chunks (>20 MB) -- the Playground is the first such app; smaller
local-javascript apps (initializr, HelloCodenameOne) are single-chunk and
were unaffected.

Thread one shared alias counter through every chunk so each gets a
disjoint alias range -- exactly the naming a single un-split chunk would
produce. Verified: node --check on the concatenated 57 MB Playground
bundle parses cleanly (0 cross-chunk duplicate const names).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ParparVM localforage shim (synchronous-callback, localStorage-backed)
bailed out if a real `window.localforage` was already present:

    if (window.localforage && typeof window.localforage.setItem === "function") return;

But fontmetrics.js bundles a real localForage 1.7.3 and globally exposes
it, and it loads BEFORE the shim. So the shim bailed, CN1 Storage hit the
real localForage's Promise-based callback path, and the worker-bridged
callback failed with `TypeError: b is not a function` (localForage's
executeCallback invoking a non-function) right after the app's main()
ran -- the Playground is the first local-javascript app to touch Storage
on boot.

The worker can't pump the async microtask loop the real localForage
relies on, so the synchronous shim MUST own window.localforage. Install
unconditionally, overriding any real localForage; only skip re-installing
over ourselves (idempotent via a __cn1ShimInstalled marker). fontmetrics
keeps its own bundled instance (referenced through its module closure) for
font-metric caching.

Verified: the Playground bundle boots in headless Chromium with no
`b is not a function` (0 fatal JS errors) where it previously threw on
every Storage access.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ootstrap race

Getting the Playground editor (a Monaco BrowserComponent) working on the
local ParparVM JavaScript target surfaced several JS-port issues that
don't bite TeaVM (which runs on the main thread, not a worker):

- HTML5BrowserComponent.isCORSRestricted: was an @JSBody, which on the
  ParparVM port runs in the worker where the iframe arg is a host-ref proxy
  with no live DOM (iframe.contentWindow is undefined) -- so it always threw
  and reported EVERY BrowserComponent as CORS-restricted, blocking execute().
  Reimplemented via the JSO bridge (getContentWindow().getDocument()) so the
  probe runs on the main thread: same-origin -> not restricted; truly
  cross-origin -> caught and restricted. Verified: "Is NOT cors restricted".

- JavaScriptBuilder: BrowserComponent.setURLHierarchy loads from
  assets/cn1html/<path>, but the app's bundled HTML hierarchy only shipped
  packed in html.tar (the runtime unpacks it into FileSystemStorage, which an
  iframe can't fetch over HTTP). Unpack html.tar into assets/cn1html/ in the
  bundle so the editor iframe URL resolves to a real same-origin file.

- editor.js: Monaco worker baseUrl was relative ("monaco/min/vs/"), which is
  unparseable inside the data:-URL worker ("Failed to parse URL") and wrong
  (duplicated vs/vs/). Use the absolute parent of vs/. Also force a layout()
  via a ResizeObserver on the iframe body + after bootstrap, because the editor
  is created in a hidden 0x0 peer iframe and Monaco's automaticLayout misses
  the later resize (it stayed laid out at ~5x5 = blank pane).

- PlaygroundBrowserEditor.flush(): the BrowserComponent ready event can fire
  before editor.js has defined window.PlaygroundEditor, so the bootstrap
  no-oped. Inject a self-retrying bootstrap that waits inside the iframe until
  PlaygroundEditor exists. editor.js also self-signals readiness.

Verified in headless Chromium: the Playground boots, renders its full UI
(Code/CSS/Preview tabs, nav), the editor iframe loads same-origin, and Monaco
renders syntax-highlighted code when bootstrapped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
BrowserComponent.onMessage never fired on the ParparVM port, so the
Playground editor (Monaco in a BrowserComponent) could never be bootstrapped
or send edits back. Two root causes, both from the worker model:

- browser_bridge.js serializeEventForWorker() omitted MessageEvent fields, so
  the worker-side MessageEvent arrived with getDataAsString()==null and no
  source. Serialize data/dataAsString/origin/source (source as a host-ref).

- HTML5BrowserComponent.messageListener only forwarded a message when
  getEventSource(e) == iframe.getContentWindow(); on the worker those are two
  different wrappers for the same window (always false), so every message was
  dropped. The worker can't compare window identity, so forward any message
  that carries a source and let multi-frame apps disambiguate themselves.

Playground side (the editor hosts two BrowserComponents, Java + CSS, and the
port can't match a message to a specific iframe):
- editor.js tags "change" messages with the editor language and self-signals
  "ready" until bootstrapped (the host's one-time browser.ready can miss a
  freshly (re)created peer iframe).
- PlaygroundBrowserEditor routes "change" by language and injects a
  self-retrying bootstrap that waits for window.PlaygroundEditor.

Verified in headless Chromium: loading the Playground with ?sample=hello-world
boots, renders the full UI, and the Monaco editor displays the sample's code
with syntax highlighting at full size (0 page errors). Running the code
(Preview) still surfaces a separate beanshell-run error -- tracked separately.

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

The Playground editor is loaded into a peer BrowserComponent via a relative
"assets/cn1html/.." src. A relative iframe src is resolved against the host
document's base URL at the instant it is set, and the Playground mutates its
own location (share links, history.pushState), so the editor iframe
intermittently resolved against the wrong base and 404'd. Build the URL
absolutely from location.origin + current directory so the load is
deterministic. Verified reliable across repeated Playwright runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p poll

CN1 Storage on the JavaScript port is backed by LocalForage, which wrapped
every async localforage callback in a `while(!done){Thread.sleep(20)}`
busy-wait. The app runs in a Web Worker; the localforage shim (main thread,
synchronous localStorage) delivers its result back to the worker as a
`worker-callback` message. The tight Thread.sleep timer loop STARVES the
worker's self.onmessage, so the callback (and, fatally, all pointer events)
were never processed until the 10s poll timeout fired. The Playground reads 7
keys synchronously on the EDT at startup and keeps saving state, so the EDT was
permanently busy-waiting -> the whole app was frozen to input and boot took
~94s (7 reads x 10s timeouts).

Fix: the shim's localStorage ops are already synchronous, so add value-
returning *Sync methods (getItemSync/setItemSync/removeItemSync/clearSync/
lengthSync/keysSync) and call them as ordinary BLOCKING JSO host calls that
return the value directly. The worker parks on HOST_CALL and resumes on
HOST_CALLBACK -- a path that is not starved -- so there is no poll, no
Thread.sleep, and no message starvation. Removed awaitLocalForageDone.

Verified in Playwright: boot to editor-visible dropped 94s -> ~4s, clicks now
repaint (Samples/Inspector panels open, tabs switch), 0 page errors. Fixes the
JS-port input freeze that made the migrated Playground non-interactive.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…previews run

The live preview interprets the user's script with BeanShell. The runner wraps a
loose script as `Object build(ctx){...} build(ctx);` (and lifecycle samples
declare `void init(Object)` / `start()`). On the ParparVM JavaScript port bsh's
default imports (java.lang/java.util/java.io) were not applied to this eval
namespace, so resolving the `Object` return/param type threw "Class: Object not
found in namespace". The method declaration was skipped, the method was never
installed, `build(ctx);` became a no-op, no component was ever constructed or
captured, and resolveComponent reported "Script must return a Component..." —
the preview showed "Preview paused" for every sample.

Import the core JRE packages (java.lang/java.util/java.io) and java.lang.Object/
String explicitly in PlaygroundRunner.bindGlobals so script methods install and
run. Verified in Playwright: the hello-world/Welcome preview now renders the full
form (title, labels, buttons, switch, checkbox, text field, FAB) and the
lifecycle sample's init/start + form.show() path runs; 0 page errors.

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

The editor only ever uses Java (basic-language) and CSS (language service), but
the bundled Monaco shipped the full TypeScript compiler language service (5.5MB)
plus the HTML and JSON services and all 81 basic-languages. Monaco lazy-loads
these on demand via a generic worker, so removing the unused ones is safe — they
were never activated. Keep language/css and basic-languages/{java,css}.

Saves ~6MB raw / ~1.45MB gz of editor page weight. Verified in Playwright: Java
highlighting and the CSS editor both work, 0 Monaco 404s, 0 page errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r, GPU/physics demos

Four fixes to make the local-javascript Playground fully interactive:

1. Editor & panel clicks were dead. The peer pointer-events toggle yielded the
   canvas to a peer iframe whenever the iframe's BOX was under the cursor, but the
   Samples/Inspector sheets paint OPAQUELY on the canvas over the editor iframe --
   so every panel/list click was swallowed by the hidden editor. The toggle now
   also requires the canvas pixel under the cursor to be (near-)transparent
   (an actual punched hole) before yielding, so panels get their clicks and the
   editor still gets clicks where it shows through.

2. Deep links (?sample=, ?code=, ?css=) never worked. CN.getProperty(
   "browser.window.location.href") is inlined/devirtualised past
   HTML5Implementation.getProperty to the base impl (returns the default), so the
   override was never reached. Route the host-page URL through the WebsiteThemeNative
   native interface (host-call bridge, not devirtualised), reading window.location
   and falling back to the parent frame when embedded. Keeps CN.getProperty as the
   off-browser fallback. Also forward the full host href to the worker
   (__cn1LocationHref) and prefer it in getProperty's location branches.

3. Unified the two loaders. The app posts cn1-app-ready once its own boot splash
   clears; the website wrapper holds its overlay until then (with a generous
   timeout fallback) so the bare "Loading..." splash no longer flashes mid-boot.

4. Added Physics and 3D / GPU sample demos.

Verified on a locally built bundle (Playwright): selecting Physics via the panel
loads it; ?sample=physics / ?sample=3d-gpu load directly and via a parent-frame
query (production embed); cn1-app-ready hides the wrapper loader; Monaco is
editable; 0 page errors.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (typed chars erased)

Once the pointer-events toggle let clicks/keys actually reach the Monaco editor
peer, every interaction wiped the typed character. Root cause: HTML5Peer and
HTML5BrowserComponent both gate their peer DOM management on documentContains(),
which was an @JSBody. On the ParparVM worker the script runs in the worker where
the element argument is a host-ref proxy with no live DOM, so
doc.documentElement.contains(el) ALWAYS returned false. initComponent() then
re-appended the iframe on every call -- and appendChild() of an already-attached
iframe MOVES it, which reloads it: editor.js re-runs, Monaco re-bootstraps from the
stale source, and the user's edits vanish. A click drove initComponent repeatedly,
so it reloaded on every keystroke/click ("typed character erased immediately" /
"no interaction").

Fix: replace both documentContains() @JSBody copies with a JSO-bridge probe
(getParentNode() runs on the main thread and reliably reports detached vs attached),
mirroring the existing isCORSRestricted() worker-proxy fix. initComponent now sees
the peer is already in the DOM and skips the re-append, so the iframe is never
moved/reloaded. Verified (Playwright): click no longer detaches the frame or churns
the DOM, typing persists and accumulates, no NPE flood, and sample switching still
loads (the editor stays in the DOM; it's only hidden behind panels).

Also: guard the iframe eventRouter -- when the canvas is pointer-events:none the
event belongs to the peer, so don't re-dispatch a synthetic copy into CN1 (caused
relayout churn), and bail out when getBoundingClientRect() returns null on the
worker bridge (was an NPE on every event). And keep PlaygroundBrowserEditor's
pendingSource in sync with typed text so any future re-bootstrap can't regress to
stale content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…every keystroke

After the iframe-reload fix, typing still broke: a second after typing, the caret
jumped to line 1 col 1 and text could only be entered at the very start. Cause: the
editor re-signals "ready" every 400ms until bootstrapped, and on the JS port a
"ready" from ANY editor iframe is delivered to EVERY editor's host handler, so the
host kept calling flush() -> bootstrap() -> setSource() -> model.setValue(), which
resets Monaco's caret to 1:1 and (inside the 280ms change-debounce window) re-pushed
stale text -- a sync loop that reverted edits.

Fix:
- editor.js bootstrap(): only seed the source on the FIRST bootstrap. Later
  bootstraps (from repeated/ cross-editor "ready") refresh markers/messages/theme
  only and never call setValue, so the caret and content are preserved.
- PlaygroundBrowserEditor: ignore "ready" once already bootstrapped, so the host
  flushes exactly once.

Verified (Playwright): place caret at end, type, wait 3.2s past all retries/debounce
-> caret stays at end (line 33, not 1:1), further typing appends contiguously
(AAABBB), no revert, 0 NPE.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Benchmarked the edit->preview pipeline (instrumented PlaygroundRunner/executeRunScript):
the fixed debounce was the largest controllable chunk -- 280ms (editor change
notification) + 850ms (auto-run) = 1130ms before the BeanShell eval even starts.
The eval itself is ~450-540ms (warm) and is bsh-interpretation bound (amplified by
the JS port's per-method generator model -- architectural, not tunable here); render
is ~12ms and editor/css/persist ~80ms.

Cut the change-notification debounce 280->120ms and the auto-run debounce 850->300ms,
so the preview reacts ~700ms sooner after you stop typing (~1580ms -> ~870ms total)
without thrashing (the auto-run is still ticket-debounced to the last keystroke, and
the eval runs on the worker EDT so it never blocks Monaco typing on the main thread).
Verified the caret stays stable across the faster runs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in (~8x)

Benchmarked the ParparVM JavaScript translation (it was ~22 min, an order of
magnitude slower than TeaVM) by timing each phase and sampling the translator with
jstack. Breakdown for the Playground (4665 classes / 42,829 methods, RTA off):
  parse 8s, conservative cull 31s, suspension 0s, codegen+write 1266s.
Codegen was ~30ms/method and 29/36 stack samples sat in
JavascriptMethodGenerator.findSimpleGotoSource, called from the inline-goto fold
peephole (inlineUniqueSourceCases): each pass calls findSimpleGotoSource -- a full
O(regionLen) scan -- for every unique-source case and rebuilds the whole switch-body
string per fold, so the cost is ~O(folds * cases * regionLen). On the largest
generated state-machine methods this single peephole dominated the entire build.

The fold is a pure SIZE optimization (it never affects correctness), so cap it: skip
it once a method's switch body exceeds a budget (default 4000 chars, tunable via
-Dparparvm.js.inlineFold.maxRegion; <=0 restores unconditional folding). The few huge
methods stay slightly larger; everything else still folds.

Result (Playground translation): codegen 1266s -> 162s, total ~1310s -> ~186s. And
the bundle is actually marginally SMALLER (55.2MB raw / 6.54MB gz vs ~57MB / 6.7MB) --
the fold was barely helping gz size while costing ~18 min. Verified the bundle is
correct: editor typing + stable caret, ?sample= deep links, live preview, 0 errors.
This halves the website build's dominant cost (it translates Initializr + Playground).

Co-Authored-By: Claude Opus 4.8 (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.

1 participant