Skip to content

Switch initializr JavaScript build to local ParparVM target#5200

Open
shai-almog wants to merge 22 commits into
masterfrom
initializr-local-javascript-build
Open

Switch initializr JavaScript build to local ParparVM target#5200
shai-almog wants to merge 22 commits into
masterfrom
initializr-local-javascript-build

Conversation

@shai-almog

Copy link
Copy Markdown
Collaborator

Summary

Switches the initializr's JavaScript build from the remote cloud build target to the local ParparVM-based translator.

CN1BuildMojo already routes any local--prefixed target locally: local-javascriptdoJavaScriptLocalBuild()JavaScriptBuilder (ParparVM bytecode → JavaScript). The plain javascript target went remote (Ant codeNameOne task → build server → download result.zip). The local path was already fully wired, so this change is purely a build-target switch in the initializr — no Mojo/builder changes.

Automate mode still logs in with the server secret, so the pipeline behaves the same as before.

Changes (all under scripts/initializr/)

  • javascript/pom.xmlcodename1.defaultBuildTarget: javascriptlocal-javascript
  • build.sh / build.bat — these pass -Dcodename1.buildTarget=javascript explicitly (overriding the pom default), so both flipped to local-javascript
  • README.adoc + bundled skill docs (SKILL.md, references/build-and-run.md, references/native-interfaces.md) — document the JS build as the local ParparVM target, with the cloud javascript target noted as the fallback

Notes

  • The maven/cn1app-archetype template (shipped to generated user projects) is intentionally not changed here — it still defaults to the cloud javascript target.

🤖 Generated with Claude Code

shai-almog and others added 2 commits June 8, 2026 20:34
The initializr javascript module previously built via the remote
`javascript` build target (cloud build server). Switch it to the
local `local-javascript` target, which routes through
CN1BuildMojo#doJavaScriptLocalBuild -> JavaScriptBuilder (ParparVM
bytecode -> JavaScript translator). Automate mode still authenticates
with the server secret, so the build pipeline is unaffected.

Changes (all under scripts/initializr/):
- javascript/pom.xml: codename1.defaultBuildTarget javascript -> local-javascript
- build.sh / build.bat: explicit -Dcodename1.buildTarget -> local-javascript
  (these override the pom default)
- README.adoc and bundled skill docs (SKILL.md, build-and-run.md,
  native-interfaces.md): document the JS build as local ParparVM, with
  the cloud `javascript` target noted as the fallback

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The local-javascript build failed in the initializr/website pipeline
because JavaScriptBuilder.checkUserLevel() only accepted an explicit
javascript.userLevel / CN1_USER_LEVEL / codename1.userLevel property.
The website build (build_initializr_for_site -> set_cn1_user_token)
authenticates by writing the CN1 user+token to the /com/codename1/ui
preferences node (SetUserTokenMojo / cn1:set-user-token), not by
setting a userLevel property, so the gate rejected an authenticated
build.

Honor that login directly: if a CN1 user+token is present in prefs the
local JS build is authorized, the same way the cloud build is. The
explicit userLevel property remains a fallback for logged-out CLI use.

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

shai-almog commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Compared 128 screenshots: 128 matched.

Native Android coverage

  • 📊 Line coverage: 14.26% (8658/60736 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 11.53% (42637/369783), branch 5.07% (1763/34775), complexity 6.08% (2025/33332), method 10.53% (1641/15583), class 17.23% (378/2194)
    • 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.26% (8658/60736 lines covered) [HTML preview] (artifact android-coverage-report, jacocoAndroidReport/html/index.html)
    • Other counters: instruction 11.53% (42637/369783), branch 5.07% (1763/34775), complexity 6.08% (2025/33332), method 10.53% (1641/15583), class 17.23% (378/2194)
    • 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 321ms / native 147ms = 2.1x speedup
SIMD float-mul (64K x300) java 144ms / native 195ms = 0.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 307.000 ms
Base64 CN1 decode 284.000 ms
Base64 native encode 762.000 ms
Base64 encode ratio (CN1/native) 0.403x (59.7% faster)
Base64 native decode 808.000 ms
Base64 decode ratio (CN1/native) 0.351x (64.9% faster)
Image encode benchmark status skipped (SIMD unsupported)

@github-actions

github-actions Bot commented Jun 8, 2026

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.

shai-almog and others added 2 commits June 8, 2026 21:37
The website CI builds the initializr against the released codenameone
plugin (cn1.plugin.version=7.0.250), whose JavaScriptBuilder license
gate only reads the javascript.userLevel / CN1_USER_LEVEL /
codename1.userLevel inputs -- it does not yet honor the logged-in CN1
account. set_cn1_user_token performs the login, but the released gate
still rejected the build ("JavaScript build failed").

Declare codename1.arg.javascript.userLevel=Enterprise in the shared
initializr settings so the released plugin's gate is satisfied. The
javascript module's getCN1ProjectDir() resolves to ../common, so this
flows into the build request as the javascript.userLevel arg. The
companion plugin change lets newer plugins accept the login directly,
at which point this declaration is redundant but harmless.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With codename1.buildTarget=local-javascript the ParparVM builder emits
a zip whose entries are nested under a single top-level directory
(e.g. Initializr-js/index.html), whereas the cloud result.zip is flat
with index.html at the root. The website extraction therefore failed
its "missing index.html after extraction" check.

After unzip, if index.html isn't at the root, flatten a single
top-level wrapper directory so the served initializr-app layout is
identical for both the local and cloud builders. No-op for the flat
cloud bundles (Playground/Skin Designer still build via the cloud
javascript target).

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

shai-almog commented Jun 8, 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: 226 seconds

Build and Run Timing

Metric Duration
Simulator Boot 66000 ms
Simulator Boot (Run) 1000 ms
App Install 11000 ms
App Launch 4000 ms
Test Execution 255000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 498.000 ms
Base64 CN1 encode 1343.000 ms
Base64 encode ratio (CN1/native) 2.697x (169.7% slower)
Base64 native decode 257.000 ms
Base64 CN1 decode 945.000 ms
Base64 decode ratio (CN1/native) 3.677x (267.7% slower)
Base64 SIMD encode 383.000 ms
Base64 encode ratio (SIMD/native) 0.769x (23.1% faster)
Base64 encode ratio (SIMD/CN1) 0.285x (71.5% faster)
Base64 SIMD decode 382.000 ms
Base64 decode ratio (SIMD/native) 1.486x (48.6% slower)
Base64 decode ratio (SIMD/CN1) 0.404x (59.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 63.000 ms
Image createMask (SIMD on) 12.000 ms
Image createMask ratio (SIMD on/off) 0.190x (81.0% faster)
Image applyMask (SIMD off) 133.000 ms
Image applyMask (SIMD on) 62.000 ms
Image applyMask ratio (SIMD on/off) 0.466x (53.4% faster)
Image modifyAlpha (SIMD off) 144.000 ms
Image modifyAlpha (SIMD on) 150.000 ms
Image modifyAlpha ratio (SIMD on/off) 1.042x (4.2% slower)
Image modifyAlpha removeColor (SIMD off) 191.000 ms
Image modifyAlpha removeColor (SIMD on) 67.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.351x (64.9% faster)
Image PNG encode (SIMD off) 1338.000 ms
Image PNG encode (SIMD on) 984.000 ms
Image PNG encode ratio (SIMD on/off) 0.735x (26.5% faster)
Image JPEG encode 482.000 ms

The Initializr page template references /initializr-app/icon.png. The
cloud build's bundle shipped that icon; the local ParparVM bundle does
not, so the link/image validator failed ("Cannot find file ...
initializr-app/icon.png").

Copy scripts/initializr/common/icon.png into the extracted bundle when
it is absent, so the served layout matches the cloud build.

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

shai-almog commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

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

Benchmark Results

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

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 648.000 ms
Base64 CN1 encode 1198.000 ms
Base64 encode ratio (CN1/native) 1.849x (84.9% slower)
Base64 native decode 326.000 ms
Base64 CN1 decode 910.000 ms
Base64 decode ratio (CN1/native) 2.791x (179.1% slower)
Base64 SIMD encode 397.000 ms
Base64 encode ratio (SIMD/native) 0.613x (38.7% faster)
Base64 encode ratio (SIMD/CN1) 0.331x (66.9% faster)
Base64 SIMD decode 374.000 ms
Base64 decode ratio (SIMD/native) 1.147x (14.7% slower)
Base64 decode ratio (SIMD/CN1) 0.411x (58.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 57.000 ms
Image createMask (SIMD on) 10.000 ms
Image createMask ratio (SIMD on/off) 0.175x (82.5% faster)
Image applyMask (SIMD off) 138.000 ms
Image applyMask (SIMD on) 73.000 ms
Image applyMask ratio (SIMD on/off) 0.529x (47.1% faster)
Image modifyAlpha (SIMD off) 161.000 ms
Image modifyAlpha (SIMD on) 79.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.491x (50.9% faster)
Image modifyAlpha removeColor (SIMD off) 165.000 ms
Image modifyAlpha removeColor (SIMD on) 91.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.552x (44.8% faster)
Image PNG encode (SIMD off) 1041.000 ms
Image PNG encode (SIMD on) 809.000 ms
Image PNG encode ratio (SIMD on/off) 0.777x (22.3% faster)
Image JPEG encode 430.000 ms

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Cloudflare Preview

@shai-almog

shai-almog commented Jun 8, 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: 311 seconds

Build and Run Timing

Metric Duration
Simulator Boot 88000 ms
Simulator Boot (Run) 1000 ms
App Install 16000 ms
App Launch 12000 ms
Test Execution 344000 ms

Detailed Performance Metrics

Metric Duration
Base64 payload size 8192 bytes
Base64 benchmark iterations 6000
Base64 native encode 960.000 ms
Base64 CN1 encode 1612.000 ms
Base64 encode ratio (CN1/native) 1.679x (67.9% slower)
Base64 native decode 435.000 ms
Base64 CN1 decode 1245.000 ms
Base64 decode ratio (CN1/native) 2.862x (186.2% slower)
Base64 SIMD encode 489.000 ms
Base64 encode ratio (SIMD/native) 0.509x (49.1% faster)
Base64 encode ratio (SIMD/CN1) 0.303x (69.7% faster)
Base64 SIMD decode 412.000 ms
Base64 decode ratio (SIMD/native) 0.947x (5.3% faster)
Base64 decode ratio (SIMD/CN1) 0.331x (66.9% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 77.000 ms
Image createMask (SIMD on) 14.000 ms
Image createMask ratio (SIMD on/off) 0.182x (81.8% faster)
Image applyMask (SIMD off) 160.000 ms
Image applyMask (SIMD on) 96.000 ms
Image applyMask ratio (SIMD on/off) 0.600x (40.0% faster)
Image modifyAlpha (SIMD off) 141.000 ms
Image modifyAlpha (SIMD on) 75.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.532x (46.8% faster)
Image modifyAlpha removeColor (SIMD off) 213.000 ms
Image modifyAlpha removeColor (SIMD on) 109.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.512x (48.8% faster)
Image PNG encode (SIMD off) 3288.000 ms
Image PNG encode (SIMD on) 1138.000 ms
Image PNG encode ratio (SIMD on/off) 0.346x (65.4% faster)
Image JPEG encode 686.000 ms

@shai-almog

shai-almog commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

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

Native-interface stubs (com_<pkg>_<Class>.js) generated by StubGenerator
end with `})(cn1_get_native_interfaces());` and self-register into that
registry. The accessor is defined only in fontmetrics.js, which loads on
the main thread; the worker imports the stubs via importScripts but never
loads fontmetrics.js, so the IIFE throws "ReferenceError:
cn1_get_native_interfaces is not defined" and aborts worker startup before
jvm.start(). Any app with a JS-port NativeInterface (e.g. the Initializr's
WebsiteThemeNative) therefore never boots; HelloCodenameOne has none, so
the screenshot suite never caught it.

Define a worker-local registry before the imports, mirroring the existing
getParameterByName worker-local shim.

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

github-actions Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

✅ ByteCodeTranslator Quality Report

Test & Coverage

  • Tests: 387 total, 0 failed, 11 skipped

Benchmark Results

  • Execution Time: 23124 ms

  • Hotspots (Top 20 sampled methods):

    • 25.10% java.lang.String.indexOf (664 samples)
    • 20.34% com.codename1.tools.translator.Parser.isMethodUsed (538 samples)
    • 13.16% com.codename1.tools.translator.Parser.addToConstantPool (348 samples)
    • 8.62% java.util.ArrayList.indexOf (228 samples)
    • 2.91% com.codename1.tools.translator.ByteCodeClass.fillVirtualMethodTable (77 samples)
    • 2.00% com.codename1.tools.translator.ByteCodeClass.calcUsedByNative (53 samples)
    • 1.70% com.codename1.tools.translator.ByteCodeClass.updateAllDependencies (45 samples)
    • 1.44% com.codename1.tools.translator.BytecodeMethod.appendMethodSignatureSuffixFromDesc (38 samples)
    • 1.29% com.codename1.tools.translator.ByteCodeClass.markDependent (34 samples)
    • 1.25% java.lang.StringBuilder.append (33 samples)
    • 1.10% com.codename1.tools.translator.BytecodeMethod.appendCMethodPrefix (29 samples)
    • 0.98% java.lang.Object.hashCode (26 samples)
    • 0.95% com.codename1.tools.translator.bytecodes.Invoke.addDependencies (25 samples)
    • 0.87% com.codename1.tools.translator.BytecodeMethod.optimize (23 samples)
    • 0.64% com.codename1.tools.translator.Parser.cullMethods (17 samples)
    • 0.60% com.codename1.tools.translator.bytecodes.CustomInvoke.appendInstruction (16 samples)
    • 0.53% sun.nio.fs.UnixNativeDispatcher.open0 (14 samples)
    • 0.53% com.codename1.tools.translator.BytecodeMethod.isMethodUsedByNative (14 samples)
    • 0.49% com.codename1.tools.translator.Parser.generateClassAndMethodIndexHeader (13 samples)
    • 0.45% java.util.TreeMap.getEntry (12 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 and others added 2 commits June 9, 2026 04:22
The local ParparVM JavaScript builder (codename1.buildTarget=
local-javascript) and its fixes live in the repo's 8.0-SNAPSHOT plugin,
not the pinned 7.0.250 release the initializr was building against. So
the preview never exercised the repo's builder/translator/JS-port — the
native-interface worker fix, the login-aware license gate, etc. could
not take effect.

Mirror the Playground/Skin Designer path for the initializr:
- cn1-local-workspace profile now overrides cn1.version AND
  cn1.plugin.version to 8.0-SNAPSHOT (was a vestigial 7.0.250 re-pin).
- build_initializr_for_site bootstraps the local snapshots and builds
  with -Dcn1.localWorkspace=true under the bootstrapped JDK 17.
- bootstrap_local_cn1_snapshots is now idempotent (the full reactor
  build runs once even though all three site apps call it).
- website-docs.yml sets WEBSITE_BOOTSTRAP_CN1_SNAPSHOTS=true so the
  preview builds the site apps against repo HEAD.

Validated locally: the initializr JS bundle builds against 8.0-SNAPSHOT
and its worker.js carries the cn1_get_native_interfaces registry shim.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
NativeLookup.create() returned null on the ParparVM JS port: there was
no <Iface>Impl, the cn1_native_interfaces registry was never read, and
the worker imported the JS stub (which crashed on the undefined
cn1_get_native_interfaces). Native interfaces simply weren't wired.

Implement them end to end, modeled on the cloud builder's
JSStubGenerator/NativeLookup.register flow but adapted to the
worker/host-call model so the developer's JS impl runs on the MAIN
thread (DOM access), as it must:

- JavaScriptBuilder: scan staged classes for NativeInterface subtypes
  and generate a <Iface>Impl per interface whose methods box their args
  and delegate to NativeInterfaceBridge.call*. The launcher emits
  NativeLookup.register(iface, impl) so create() resolves and the
  optimizer keeps the impl (otherwise reached only reflectively).
- NativeInterfaceBridge (com.codename1.impl.platform.js -> HOST_HOOK):
  call{Boolean,Int,...,String,Object,Void}(iface, method, args). The
  worker suspends and the call replays on the main thread.
- browser_bridge.js: main-thread handlers look up
  cn1_native_interfaces[iface][method_], invoke it with (args..., {
  complete, error }) and resume the worker via the host callback.
- JavascriptBundleWriter: native-interface stubs (identified by the
  cn1_get_native_interfaces marker) load on the MAIN thread (index.html)
  and are excluded from the worker importScripts.
- parparvm_runtime: marshal Java String -> JS string in
  toHostTransferArg so the iface/method names reach the host correctly.
- worker.js: drop the earlier worker-local registry shim (the
  degrade-in-worker approach); stubs no longer load in the worker.

The existing JS impl format (cn1_native_interfaces["<pkg>_<Iface>"]
["<method>_<paramTypes>"](args..., callback)) is preserved so stubs
work as-is. Validated locally: the initializr bundle's
WebsiteThemeNativeImpl is reachable and emits callBoolean/callVoid
host-hooks; browser_bridge registers the handlers; index.html loads the
stub and the worker does not. boolean/void/numeric returns are complete;
String/Object/long return wrapping is a follow-up.

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

shai-almog commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Native Windows port

Compared 125 screenshots: 124 matched, 1 updated.

  • StatusBarTapDiagnosticScreenshotTest — updated screenshot. Screenshot differs (784x561 px, bit depth 8).

    StatusBarTapDiagnosticScreenshotTest
    Preview info: JPEG preview quality 70; JPEG preview quality 70.
    Full-resolution PNG saved as StatusBarTapDiagnosticScreenshotTest.png in workflow artifacts.

shai-almog and others added 4 commits June 9, 2026 08:01
Extend native-interface support to every NativeInterface type (all
primitives, String, the primitive arrays byte[]/int[]/long[]/double[]/
float[]/boolean[]/char[]/short[], and String[]) in both directions.

- NativeInterfaceBridge: per-primitive call* + callString/callVoid/
  callObject, plus a typed callArray(iface, method, args, componentToken)
  for array returns.
- JavaScriptBuilder: <Iface>Impl return-type dispatch routes arrays to
  callArray with the element's component token (JAVA_INT.../
  java_lang_String) and boxes primitive args into the Object[].
- JavascriptNativeRegistry: the call* symbols are RUNTIME_IMPLEMENTED so
  the translator defers to the worker-side wrappers.
- parparvm_runtime.js: bindNative wrappers funnel through the single
  __cn1_native_interface_call__ host hook and coerce the resolved JS
  value to the declared Java type -- createJavaString (String/String[]),
  _LfromNumber (long/long[]), jvm.newArray(token) (typed arrays),
  int/short/byte/char/float/double normalisation. toHostTransferArg now
  unboxes argument values too (boxed Integer/Long/.../Boolean -> JS
  primitive, long {__l} -> number) so primitive/long/array arguments
  marshal correctly to the host.
- browser_bridge.js: one __cn1_native_interface_call__ handler dispatches
  to cn1_native_interfaces[iface][method](args..., callback).

Verified in the local initializr bundle: no "Missing javascript native
method" stubs remain, the bindNative wrappers/array builder are present,
and WebsiteThemeNativeImpl is reachable. PeerComponent returns route
through callObject (best-effort) pending dedicated peer wrapping.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Complete the supported-type set with com.codename1.ui.PeerComponent in
both directions:

- Return: the generated <Iface>Impl wraps the call result with
  PeerComponent.create(callObject(...)). The host returns the native
  element; cn1InvokeNativeInterface now host-ref-wraps any non-array
  object result (hostResult) so the un-cloneable DOM element survives
  postMessage to the worker and works as an HTMLElement JSO receiver (the
  JSO bridge dispatches on __cn1HostRef). PeerComponent.create routes to
  HTML5Implementation.createNativePeer -> new HTML5Peer((HTMLElement)..).
- Argument: a PeerComponent param is passed as peer.getNativePeer() (the
  underlying native element / host-ref), not the Java peer wrapper;
  toHostTransferArg already marshals the host-ref. The method key uses the
  _com_codename1_ui_PeerComponent suffix.

Validated by temporarily adding int[]/String[]/PeerComponent methods to a
native interface and rebuilding: the generated impl compiles (callArray
with JAVA_INT/java_lang_String tokens, PeerComponent.create, getNativePeer)
-- malformed codegen would have failed javac. Reverted the temp methods;
the initializr's WebsiteThemeNative (boolean/void) still builds clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The translator emits translated_app.js one statement per line with
generous indentation for readability; in a deployed bundle that
indentation + blank lines is ~20% dead weight the browser must download
and (more importantly) parse. Strip per-line leading/trailing whitespace
and drop blank lines before writing each chunk -- safe regardless of ASI
or // comments since newlines are preserved (one statement per line).

Initializr translated_app.js: 11.93 MB -> 9.58 MB raw (-2.36 MB, ~20%),
cutting parse time. Set -Dparparvm.js.pretty=true to keep the readable
form for debugging the generated code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shai-almog shai-almog force-pushed the initializr-local-javascript-build branch from 4a5cfcf to 5259c7d Compare June 10, 2026 09:33
shai-almog and others added 3 commits June 10, 2026 18:40
…all sites

Drop the over-conservative hasVirtualDispatch seed in the suspension
analysis: a method containing virtual calls no longer becomes a
generator unless one of the dispatched signatures actually has a
suspending impl. The emitter now selects a synchronous dispatcher
family (cn1_ivs0..4/N, no yield*) at every INVOKEVIRTUAL /
INVOKEINTERFACE whose signature the analysis proved entirely
synchronous -- ~8.5k of ~40k virtual call sites skip generator
ceremony, and ~660 methods become plain functions.

Correctness rails (each closes a gap found by the screenshot suite):
- cn1_ivs* drives an unexpected generator ONE step and throws a NAMED
  CHA-unsound error instead of letting the raw generator object leak
  as a value (the silent-corruption mode that sank prior attempts).
- The interpreter-path virtual emission (arity 0-4 fast paths) now
  consults isInvokeSuspending instead of hardcoding yield* cn1_iv*,
  which previously emitted yield inside plain functions
  (ReferenceError: yield is not defined in HashMap.<init> at boot).
- JSO-bridge sigs are suspending even when only declared abstractly
  (Window.getDocument has no translated impl; the runtime installs a
  function* override that the concrete-impl scan cannot see).
- Any method whose identifier / __impl / dispatch-id appears as a
  string literal in port.js / parparvm_runtime.js / browser_bridge.js
  is suspending: bindNative / bindCiFallback replace those bodies with
  generators at runtime.
- Diag-only: periodic WORKER_HB_THREADS dump so a single parked thread
  (EDT still ticking) is observable, not just total freezes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Headless/hidden Chromium intensively throttles re-armed setTimeout chains
(and under wake-up budgeting, worker timers generally) down to ~one firing
per minute. ParparVM scheduled EVERY Thread.sleep / Object.wait(timeout) /
java.util.Timer wakeup through a single re-armed setTimeout chain, so in
quiet phases the whole VM stalled in 12-60s bursts: every green thread
parked past its wake deadline (WORKER_HB_THREADS dumps showed sleeps 27-53s
overdue with the queue intact and the armed timer never firing) while the
suite crawled or wedged. This is the latent stall behind the screenshot
suite's late-cluster flakiness -- any change to execution timing (ident
minification, sync dispatch) merely moved which test sat inside a
throttling window.

Defense in depth, all layers verified on the full screenshot suite
(SUITE:FINISHED, 0 stranded wakeups):
- parparvm_runtime: deliver due timed wakeups opportunistically at every
  outermost drain() (each host event), bounded-latency 1s backstop pump,
  distrust an armed wakeup timer whose target is already past (re-arm),
  per-entry guard in the expired-batch resume loop (one throwing resume
  can no longer strand the rest of the batch), re-entrancy guard, and
  diag-only stranded-sleep / thread-dump / arm-fire instrumentation.
- browser_bridge: __cn1NudgeVm hook -- posts 'timer-wake' to the worker
  so an un-throttled external context can keep the VM clock honest.
- run-javascript-headless-browser.mjs: disable Chromium background-timer
  throttling at launch and drive __cn1NudgeVm every 250ms via CDP
  Runtime.evaluate, which is exempt from visibility throttling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shai-almog shai-almog force-pushed the initializr-local-javascript-build branch from bb757e1 to b283804 Compare June 10, 2026 18:35
shai-almog and others added 4 commits June 10, 2026 21:44
The translator emitted full cn1_<pkg>_<Class>_<method>_<sig> identifiers
(avg ~45 chars, ~3.45MB / 35% of the Initializr bundle) at every
definition and call site, because esbuild minification isn't in the
pipeline. Rename them to short $M* symbols across all chunks.

Crucially this is bridge-aware: Codename One has no reflection/
serialization, so name-based resolution IS the JS<->worker bridge. The
worker-side runtime/port override native methods by reassigning the
global of that exact name (global[name]=nativeFn) AND the constructed
global[name+"__impl"] static-body variant. So a renamed native stub
would bypass its override and return its placeholder (null/0) -> NPE.
We therefore EXCLUDE from the rename every cn1_ name referenced as a
string in the runtime sources (parparvm_runtime.js, browser_bridge.js,
port.js) plus their "__impl" variants, plus any name referenced as a
string in the app bundle (setMain's main method, serialized field
manifests), plus constructors/clinit (reconstructed by string).
renameTokens never rewrites inside string literals.

Initializr translated_app.js: 9.37MB -> 7.50MB raw (gzip 1.33->~1.2MB).
Validated by a headless-browser boot of the bundle: boots with no errors,
matching the pre-rename baseline (initialized:true, error:""). Kill
switch: -Dparparvm.js.minify.idents.off.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 0a7a804)
(cherry picked from commit 7035577)
The previous ident-minification commit broke HelloCodenameOne at runtime
(0 screenshots rendered -> the screenshot suite hung): another bridge-
overridden native whose stub got renamed, so global[name]=nativeFn
override was bypassed. Inferring native names from runtime/port JS text
was incomplete.

Use the translator's authoritative knowledge instead: every method.isNative()
emitted is recorded (with its "__impl" static-body variant) in
JavascriptMethodGenerator.NATIVE_METHOD_IDENTIFIERS, and the bundle-writer
excludes that set from the rename. Natives are the ONLY functions the
JS<->worker bridge overrides by name, so this is complete by construction.

Validated by building+booting HelloCodenameOne (the app that regressed):
initialized:true, started:true, error:"" -- fully boots and starts (the
broken build produced no render). Initializr unchanged (7.50MB, 6241
renamed). No framework or test changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 85a8e01)
(cherry picked from commit f1fcc54)
…tion

The screenshot runner (port.js) dispatches the suite's test lambdas by
building their identifier at runtime via string concatenation, e.g.

  "cn1_..._Cn1ssDeviceRunner_lambda_" + methodName + "_" + i + "_" + sig

so the FULL generated identifier (".._lambda_runNextTest_2_<sig>") never
appears as a string literal -- only the 77-char stem does. The exact-match
string-literal exclusion therefore missed these, the minifier renamed the
lambda run methods to $M*, and the bridge's name lookup failed:
`lambda2RunBridge:missingDispatch=1`. With no runnable dispatch the
cooperative scheduler parked every thread (runnable=0) and the suite wedged
~28 tests in -> the javascript-screenshots hang/broken-pipe at 85a8e01.

Fix: treat every scanned cn1_ string token as a PREFIX and protect any
generated def that extends it at an identifier-segment boundary, so the
constructed name keeps its canonical identifier. A length floor (16) keeps
the generic construction roots "cn1_" (-> ___INIT__/___CLINIT__, already
skipped) and "cn1_s_" (dispatch-ids in the _qX table, never a def) out of
the prefix set -- "cn1_" as a prefix would match every def and disable all
minification. Over-protecting only forgoes a little size; under-protecting
breaks a name-resolved bridge.

Validated: HelloCodenameOne JS bundle keeps all 30 Cn1ssDeviceRunner lambda
identifiers verbatim while still minifying 12,836 generated functions
(translated_app.js 11.93MB -> 7.69MB, -36%); the local Playwright screenshot
run progresses past the prior wedge with sinceStepMs resetting (no parked
scheduler) instead of hanging.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
(cherry picked from commit 1af5e29)
(cherry picked from commit bb757e1)
…back

Three fixes that make the re-landed identifier minification safe and cure
the Initializr preview's font 404s:

- JavascriptBundleWriter: the string-literal exclusion scanner tracked
  quote state character-by-character, which desyncs on apostrophes inside
  port.js comments ("doesn't") -- from there it misses most of the ~479
  quoted cn1_* literals, so bindCiFallback override targets lost their
  rename protection (lambda2RunBridge:missingDispatch, suite dead at
  index 45). Chunks (machine-generated, no prose) keep the in-string
  scanner; the hand-written bridge JS now uses the regex quoted-token
  collector that already guards the suspension analysis.
- build-javascript-port-hellocodenameone.sh: pass
  -Dcodename1.javascriptport.webapp explicitly -- the locate walk-up from
  a staging CWD can miss the repo, silently degrading the port.js-derived
  protections.
- browser_bridge: loadTrueTypeFont falls back from assets/<font> to the
  bundle root -- app-resource fonts (Initializr's Inter-*.ttf) are copied
  top-level by the translator and 404'd under assets/, dropping the whole
  UI to system fonts. Also resolve pending afterPaint frame ticks from
  __cn1NudgeVm so hidden-page rAF/timer starvation can't stall settle
  waits when an external driver is nudging.

Measured on the test app: raw 10.59MB -> 7.79MB (-26%) with the full
screenshot suite reaching SUITE:FINISHED and zero dispatch failures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
shai-almog and others added 3 commits June 11, 2026 07:33
… transfers

Reproduced the reported Initializr symptoms locally (loads extremely
slowly, ignores scroll/drag) and fixed three independent causes:

- ArrayBufferInputStream.read() dispatched a JSO-bridge call PER BYTE;
  Resources.load(theme.res) issues hundreds of thousands of single-byte
  reads through DataInputStream, so boot ran 90+ seconds of bridge
  round-trips (worker event loop pegged, CDP-paused stack showed
  parseJsoBridgeMethod under Resources.<init>). A lazily materialised
  worker-local copy (one bulk readBulkImpl) makes every subsequent read
  a plain array access. Measured boot: 90s+ (never started) -> 1.0s.
- Cooperative budget-yield is now ON by default (opt out with
  parparvm.js.preemptYield=0) and ALSO checked inside cn1_iv0..N: the
  per-method-entry check alone cannot slice a loop that stays inside one
  method and only calls runtime functions. No synchronous stretch can
  starve the worker's event loop any more.
- The canvas booted pointer-events:none and relied on a worker round-trip
  to restore it on the first event, so the initial pointer DOWN was
  always lost and the first gesture was swallowed. It now boots
  pointer-events:auto; the per-event peer toggling still applies when
  native peers are active. Verified: hit-test lands on the canvas and a
  synthesized drag crosses the bridge (13 worker-callback events).

Regression guard (BridgeBulkTransferGuardTest, runs in the screenshot
suite on every platform): the runtime counts JSO dispatches + host calls
(jvm.__cn1JsoDispatchCount/__cn1HostCallCount, exposed to tests via a
port.js-bound helper), and the test asserts large-volume operations cost
bridge calls proportional to OPERATIONS not BYTES -- budgets two orders
of magnitude below the per-element cost. First run already measured the
fixed stream at 1 bridge call for a 47KB single-byte read-through, and
flagged a separate pre-existing gap: a JS-port Storage write is not
readable back within 5s (async localforage commit invisible to the
synchronous facade) -- that leg logs CN1SS:WARN and self-arms once the
round-trip is fixed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The collapse rules only matched the generator spelling
(yield* cn1_iv<N>(...)), so the ~8.5k call sites the CHA proved
non-suspending stayed on the verbose stack-shuffle path. Each rule now
also applies in the synchronous spelling via applyVirtualRule, which
derives the sync pattern/replacement textually (yield\* cn1_iv ->
cn1_ivs) -- no capture groups added, so group numbering is identical.

-131KB raw on the test app; full screenshot suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
{ let __result = <call>; X = __result; } -> X = <call>;  (RHS fully
evaluates before the store, so the rewrite is sound even when X appears
in the call expression), plus the adjacent X = Y; Y = X; identity pair.
Modest raw win (-13KB; esbuild already collapses most of these
downstream) but it also shrinks the pre-esbuild chunks the whitespace
budget and chunk splitter operate on. Suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@shai-almog

shai-almog commented Jun 11, 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 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300) java 67ms / native 3ms = 22.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 324.000 ms
Base64 CN1 decode 182.000 ms
Base64 SIMD encode 134.000 ms
Base64 encode ratio (SIMD/CN1) 0.414x (58.6% faster)
Base64 SIMD decode 130.000 ms
Base64 decode ratio (SIMD/CN1) 0.714x (28.6% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 33.000 ms
Image createMask (SIMD on) 18.000 ms
Image createMask ratio (SIMD on/off) 0.545x (45.5% faster)
Image applyMask (SIMD off) 55.000 ms
Image applyMask (SIMD on) 27.000 ms
Image applyMask ratio (SIMD on/off) 0.491x (50.9% faster)
Image modifyAlpha (SIMD off) 57.000 ms
Image modifyAlpha (SIMD on) 30.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.526x (47.4% faster)
Image modifyAlpha removeColor (SIMD off) 74.000 ms
Image modifyAlpha removeColor (SIMD on) 29.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.392x (60.8% faster)

@shai-almog

shai-almog commented Jun 11, 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 65ms / native 4ms = 16.2x speedup
SIMD float-mul (64K x300) java 65ms / native 3ms = 21.6x 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 325.000 ms
Base64 CN1 decode 182.000 ms
Base64 SIMD encode 137.000 ms
Base64 encode ratio (SIMD/CN1) 0.422x (57.8% faster)
Base64 SIMD decode 127.000 ms
Base64 decode ratio (SIMD/CN1) 0.698x (30.2% faster)
Image encode benchmark iterations 100
Image createMask (SIMD off) 32.000 ms
Image createMask (SIMD on) 13.000 ms
Image createMask ratio (SIMD on/off) 0.406x (59.4% faster)
Image applyMask (SIMD off) 54.000 ms
Image applyMask (SIMD on) 22.000 ms
Image applyMask ratio (SIMD on/off) 0.407x (59.3% faster)
Image modifyAlpha (SIMD off) 53.000 ms
Image modifyAlpha (SIMD on) 23.000 ms
Image modifyAlpha ratio (SIMD on/off) 0.434x (56.6% faster)
Image modifyAlpha removeColor (SIMD off) 57.000 ms
Image modifyAlpha removeColor (SIMD on) 23.000 ms
Image modifyAlpha removeColor ratio (SIMD on/off) 0.404x (59.6% faster)

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