Repository navigation
Backend: Spring-style beans, transactions, scheduling, metrics and MCP, resolved at build time - #5908
Conversation
…P, resolved at build time The backend gains Spring's programming model under com.codename1.backend.annotations -- @Service/@Component/@repository, @Autowired, @value, @ConfigurationProperties, @bean, scopes, profiles and conditions, @transactional, @async, @scheduled, @ManagedResource/@Timed/@counted and @mcptool -- with every decision made by the build: a generated BackendWiring constructs and injects the beans with plain code, and aspects are woven into the annotated methods. No container, scan, proxy or reflection at run time. Runtime: thread-bound transactions that the pool, daos and sessions join, a cron and fixed-rate scheduler with an optional database lock, task executors including fd-less virtual-thread tasks with a per-host wake pipe, HTTP sessions (memory or JDBC store), OTLP metrics and management endpoints, and an MCP endpoint with development tools. Also fixes the sticky virtual-thread yield reason (yieldNow from a handler hung), a handler taking HttpServer.Request being refused, and adds the backend and full-stack references to the generated agent skill. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Developer Guide build artifacts are available for download from this workflow run:
Developer Guide quality checks: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6dd51f4064
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 172 screenshots: 172 matched. Benchmark ResultsDetailed Performance Metrics
ParparVM vs HotSpot (JDK 25): Windows x64Runner CPU: AMD64 Family 25 Model 17 Stepping 1, AuthenticAMD (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A regression is a ratio more than 15% (time) / 15% (RAM) above its baseline in
Result: no regression |
|
Compared 172 screenshots: 172 matched. Benchmark ResultsDetailed Performance Metrics
|
Cloudflare Preview
|
|
Compared 172 screenshots: 172 matched. ParparVM vs HotSpot (JDK 25): Linux x64Runner CPU: AMD EPYC 7763 64-Core Processor (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A regression is a ratio more than 15% (time) / 15% (RAM) above its baseline in
Result: no regression |
|
Compared 172 screenshots: 172 matched. ParparVM vs HotSpot (JDK 25): Linux arm64Runner CPU: Neoverse-N2 (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A regression is a ratio more than 15% (time) / 15% (RAM) above its baseline in
Result: no regression |
|
Compared 172 screenshots: 172 matched. Benchmark ResultsDetailed Performance Metrics
ParparVM vs HotSpot (JDK 25): Windows arm64Runner CPU: ARMv8 (64-bit) Family 8 Model D49 Revision 0, MICROSOFT CORPORATION (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A regression is a ratio more than 15% (time) / 15% (RAM) above its baseline in
Result: no regression |
✅ Continuous Quality ReportTest & Coverage
Static Analysis
Generated automatically by the PR CI workflow. |
✅ ByteCodeTranslator Quality ReportTest & Coverage
Benchmark Results
Static Analysis
Generated automatically by the PR CI workflow. |
|
Compared 193 screenshots: 193 matched. |
- Sessions: the session cookie goes on a copy of the handler's Response, never into it (it may be a shared constant); cn1.session.secure refuses anything but auto/true/false. - TaskExecutor: virtual submissions are refused after shutdown and counted active before the hand-off; @async accessors re-fetch a shut-down executor. - MCP: only loopback or listed origins pass (the Host match let DNS rebinding through); byte/short tool arguments are range-checked; backend_call uses https against a TLS server. - Request metrics are recorded in a finally, so failures count and the route label is cleared. - Factory beans inherit their configuration class's @Profile and @ConditionalOnProperty; request-scoped beans' destroyMethod runs. - Scheduler lock: an INSERT failure with no existing row is rethrown. - OTLP histogram bucket_counts stays fixed64 (per metrics.proto), now held by a test decoding with the generated opentelemetry-proto classes. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Compared 157 screenshots: 157 matched. Native Android coverage
✅ Native Android screenshot tests passed. Native Android coverage
Benchmark ResultsDetailed Performance Metrics
|
|
Compared 150 screenshots: 150 matched. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e31da12f35
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 155 screenshots: 155 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
…lit the guide Review fixes: - Sessions, MCP tools and managed beans are per server (Backend.getSessions, Environment.registerTool/registerManaged, Builder.mcpTool); two servers in one process no longer share cookies, sessions or tools. - @SessionScope beans are kept by the server for the session's lifetime and destroyed on invalidate, expiry and stop; a negative session timeout fails. - Transactions: no process-wide default pool; a NESTED method before the first statement sets its savepoint after BEGIN. setRollbackOnly in the method that began the transaction rolls back without throwing. - Backend.stop runs destroy callbacks once; a failed start destroys built beans; management routes precede application handlers; session-store failures are logged as 500s; metrics shutdown is bounded by its timeout. - Weaving keeps synchronized on the body, so a synchronized @async method holds its monitor where it runs. - Injection points and lifecycle methods inherited from base classes are wired; @ConditionalOnMissingBean matches the bean's exposed types and takes explicit ones. Docs: the backend chapter is split into nine chapters (web, beans, data and transactions, sessions, scheduling, observability, MCP, operations) with diagrams and compiled samples. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Compared 223 screenshots: 223 matched. |
|
Compared 166 screenshots: 166 matched. Benchmark Results
Detailed Performance Metrics
ParparVM vs HotSpot (JDK 25): macOS arm64Runner CPU: Apple M1 (Virtual) (baseline Ratios are ParparVM / JDK 25: below 1.00x ParparVM is faster (time) or smaller (RAM). Median of 5 interleaved, paired rounds; every run's output was verified. A regression is a ratio more than 15% (time) / 15% (RAM) above its baseline in
Result: no regression |
|
Compared 155 screenshots: 155 matched. Benchmark Results
Build and Run Timing
Detailed Performance Metrics
|
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 28cad305bb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Compared 154 screenshots: 154 matched. Benchmark Results
Detailed Performance Metrics
|
- Executors belong to the server that opened them: request, task, scheduler and start-up threads carry it, stopping one server no longer shuts down another's, and generated @async code looks the executor up per call. At the shutdown deadline queued tasks are dropped rather than run against destroyed beans, and running ones are interrupted; a virtual task a host cannot run falls back to its own executor. - Every server applies cn1.session.* (handler-only ones sent a TLS session cookie without Secure); a failing request still stores its session so its session beans are kept or destroyed. - Request beans are destroyed while their request is still current; @PreDestroy runs subclass before superclass; factory beans run inherited lifecycle methods. - Health reports STARTING until the start-up hook returns. - A cron time inside a DST gap is skipped instead of firing an hour late. - The metrics exporter can be reopened; histograms copy and validate their bounds and labels. - A managed resource must be a singleton and cannot overload operations. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4d761e707
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…as JSON A route may now return an entity or DTO (and collections or String-keyed maps of them) and take one as @RequestBody, as a Spring controller does through Jackson. The build writes a <Name>Cn1Json codec per class -- plain code, no reflection -- in the app's @mapped JSON form, so a class shared by app and backend round-trips: fields (public directly, others via bean accessors), @JsonProperty/@JsonIgnore (now shared with the backend), Date as epoch millis (read from millis or ISO-8601), byte[] base64, enums by name, subclasses written as themselves, unknown members ignored. - A body the codec refuses is a 400 naming the path ($.lines[0].quantity: expected ...); a response nesting past 64 objects (a cycle) is a 500 pointing at @JsonIgnore. - Generic type-variable fields, body classes without a no-arg constructor, interfaces, arrays other than byte[], and runtime types such as HttpServer.Response inside a list stay build errors, with the reason. - HttpServer renders a deferred JSON body before the write, into the same buffer, so a throw there (a Writable, a codec) is answered 500 and recorded as the handler's failure instead of dropping the connection unanswered. - The guide's order example is compiled from the guide's own files and run end to end by BackendBeansTest. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…host A virtual thread used to park only on the socket it served. An outbound read -- a PostgreSQL or MySQL query, a Web call, a TLS handshake -- did a blocking recv() on the host pthread, and with one host per core that many slow calls stopped the server answering anything. - Native: a per-virtual-thread wait record (descriptors, events, timeout) and cn1BackendVtWait/cn1BackendAwaitFd; resume answers WAITING (3) for it. Outbound Tcp descriptors stay non-blocking for life and every wait (connect, read, write, TLS handshake/read/write) parks on a virtual thread and polls anywhere else, so platform threads behave as before. SO_RCVTIMEO/SO_SNDTIMEO become the wait's deadline. Web drives libcurl through the multi interface on a virtual thread. - Host: WAITING registers the descriptors with the host's own poller for exactly the wait, wakes on readiness or deadline, and the stop drain keeps pumping tasks that wait on outbound I/O. - Docs/javadoc/skill: network databases, Web and TLS are fine on VIRTUAL; SQLite, file access, host-name resolution and Object.wait still block. - Tests: SelfTest (Tcp, Web, TLS handshake, TLS read, read deadline) and DbCheck (pg_sleep / SLEEP against a one-host server) prove another request is served while the call waits; both fail with the old blocking wait. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The seven AvoidUsingHardCodedIP findings are loopback and wildcard literals that recognise or bind the local interface and dial nothing; each carries a //NOPMD with that reason, the convention ServerSocket and Credentials already use. The OwnRoute overrides get @OverRide. - SelfTest exercises JsonCodec on the translated runtime: range and fraction refusals with their paths, dates from millis and ISO-8601 with offsets and fractions, an impossible date refused, base64, writeDate. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 903a18fb09
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…and paths - An Object or raw Map/List field is written by its run-time class through a generated cn1app.JsonValues: a class the build writes goes through its codec, Date as millis, enums by name, JDK shapes through Json; any other class is a 500 instead of its toString(). - A TreeSet body needs a Comparable element; refused at build time otherwise. - readLong bounds a fractional-free double by 2^63 before converting, so 9223372036854775808.0 is refused instead of clamped to Long.MAX_VALUE. - MCP and management match the canonical path (pathFrom), as the routers, static files and relay do: /%6dcp is /mcp. - MCP answers an allowed origin's CORS preflight before authentication and names the origin on every reply to it (new Response.header). - The guide's order example uses one source block per include, which the snippet validator requires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8a415127f0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…s, lease check - PINNING. ParparVM identifies a monitor's owner by its OS thread, which every virtual thread on a host shares, so a virtual thread that parked inside `synchronized` let the next one on that host into the same critical section. With outbound I/O now parking, that became likely. A virtual thread now counts the monitors it holds (one TLS load per monitorEnter/ Exit, zero off a virtual thread) and never switches out while the count is above zero: every park -- socket wait, collector handshake, nap -- asks cn1BackendCanPark/cn1VirtualThreadPinned and waits like a platform thread instead. SelfTest reproduces the interleave on one host: two requests waiting on a slow socket inside one monitor; with pinning disabled it sees two inside at once. - DataSource reserves pool capacity under its monitor and opens the connection outside it, and a virtual thread waits for a free connection by napping rather than wait(); DbCheck runs concurrent lazy opens on one host against real PostgreSQL. - @mcptool parameters other than scalars are read through the shared JSON codecs (List<Integer> holds Integers, DTOs are built, a bad value is a tool error naming its path), results are written through them, and shapes they cannot serve are build errors. The codec set is shared between the bean and router processors. - A scheduler claim that lands after its lease ran out is not run. - Response.header no longer fully qualifies an imported class (forbidden PMD). - The DB session purge's cross-replica limit is recorded as a Spring-matching decision. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
…stay parkable With virtual threads pinned while they hold a monitor, Database's synchronized methods pinned every query, and a PostgreSQL or MySQL wait held its host again -- DbCheck's "another request is served while a query waits" failed on one host. The exclusion a connection needs (one operation at a time, a transaction reserved to its caller) is now a reentrant gate: a flag taken and returned under a monitor held only for that instant. A virtual thread waiting for it naps; anything else waits on the monitor as before. Transaction ownership is keyed by the virtual thread, not Thread.currentThread(), which every virtual thread on a host shares, and a caller waiting on another's transaction gives the gate up meanwhile, as wait() gave up the monitor. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fbe9a07fc5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…rved - A body value read into float or Float is refused (400) when a float cannot hold it, instead of narrowing 1e100 to infinity or 1e-100 to zero; the same rule MCP float arguments already had. - @Timed/@counted names are checked against the server's own instruments at build time, by name and by Prometheus series. Taken at run time, the clash surfaced in the woven finally after the body's work had committed. - An Object or raw collection field holding a class with no codec stays a run-time 500 by design; the reasoning is in BackendJsonCodecs.check. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e86694ec3a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ntry fixes - Two classes whose JSON codec names fold together (Outer_Inner and Outer.Inner) are a build error instead of one codec silently replacing the other. - Every class the bean processor generates -- aspects, proxies, tool and managed adapters, codecs -- is checked against the project's own classes before it is compiled; an existing class of that name is a build error instead of being overwritten in the output directory. - Subclass dispatch walks ancestors on the compile classpath too, so a project subclass reaching the declared type through a dependency's class is written with its own fields. - A module whose only request is @enablemanagement gets an entry point. Each has a test asserting the failure or the outcome; the dispatch and entry-point tests fail with their fixes reverted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
CodenameOne/vm/backend/src/com/codename1/backend/Config.java
Lines 441 to 445 in 3666deb
When a lower-priority properties file defines a string setting and deployment deliberately supplies an empty system-property override, this treats the empty value as absent and silently reactivates the file value. For example, -Dcn1.static.root= cannot disable a static root compiled into application.properties, so files continue to be served despite the highest-priority layer explicitly clearing the setting; accept a present empty property here and let each setting's reader decide what empty means.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…cket fallbacks - An @ManagedOperation returning anything beyond a scalar or String is written through the JSON codecs; one returning a shape they cannot write is a build error. A DTO result was sent as its quoted toString(). - A websocket fallback router sees the server's sessions on the handshake (the upgrade bypasses the request wrapper), and what the handshake did to them is stored and released afterwards. - perf-baseline: calibrate linux-x64@amd-epyc-9v45-96-core-processor, a runner CPU model the gate had no row for (ratios within the spread of the existing AMD/Intel linux rows). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 114c900cd0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…ounted servers - An @ManagedAttribute that is not a number or a boolean is a build error; it was listed under /manage/managed and silently missing from metrics. - A module that only serves static files (@StaticFilesConfig or cn1.static.root in a properties file) gets an entry point. - cn1.management.path=/ mounts the endpoints at the server root instead of being refused as not starting with /. Each has a test; the two outcome tests fail with their fix reverted. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Swish! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
The arm64 gate flagged stringBuilding RAM at 0.32x against a 0.26x baseline. Not a regression: the VM, JavaAPI and benchmark are identical between this head and 3666deb, which read 0.27x, and master reads 0.26x on every run -- the same code, two readings, one row's RAM moving between runs. The baseline stays 0.26x; the row gets the 0.3 memory tolerance other noisy RAM rows carry, so +30% is still a regression. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…plates Master's Spring-style backend beans (#5908) land in build-engine, where the annotation processors moved: the dev-tools switch goes through BackendPackager for both plugins (-Pcn1.backend.devTools for Gradle), the bean tests run there with their SQLite driver, and the Gradle backend templates gain the Greeter service the archetype now injects into Api. Conversion's untouched-skeleton check expects that pair, and the Gradle backend checks call /greet so the wiring is exercised, not just /healthz. The skill's new backend references give the Gradle tasks beside the Maven goals. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* fidelity: capture only once every tile has painted where it will be cropped
The crop rectangle is read from each tile's current layout, and a capture that
has stopped changing does not prove the screen shows that layout: a tile that
was laid out again after its last paint, with nothing asking for a repaint, sits
still in its old place. Android's disabled text field was captured 50px right of
where it sits twice -- dark once, light once, 81% against a 96-97% baseline --
with the settle-until-stable capture already in place, on commits that changed
nothing on Android. Tiles now record where they were last painted, and the
capture waits, repainting, until every one was painted at its laid-out position.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Mac port: take the window's first size from its saved frame and fixed-size hint
The fitted default was right for a first launch only. The main window carries
setFrameAutosaveName, so every later launch has AppKit restore the frame the
previous one left, and macos.fixedWindowSize resizes it after construction too --
either way the early size query answered the default and the tree was laid out
twice. On the benchmark runner that was launches two to five: the first settled
at 68MB, the rest at 90MB. The expected size now comes from the fixed-size hint,
else the saved frame, else the default, then fitted to the screen as before.
Reproduced locally by writing a 900x600 saved frame: the first size query said
2048x1370 and the form settled at 1800x1136; it now says 1800x1136 from the start.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* parparvm-parallel-mark CI: retry transient Central failures
Both Maven steps called mvn bare. A TLS handshake to Central that did not
complete while fetching maven-clean-plugin failed the 4-marker x64 arm before
anything under test was built. They go through scripts/ci/retry.sh now,
transient failures only, like the rest of the repository's Maven steps; a test
that fails is not retried.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Windows screenshot CI: restore the Maven repository from the nearest earlier run
setup-java's cache: 'maven' keys on the hash of every pom.xml and restores nothing
on a miss, so any pom change started screenshot-capture from an empty repository.
Central rate-limits Windows runners hard: the first run after a master merge got
429 Too Many Requests on junit-bom through all five attempts of the retry loop,
over ten minutes, before building anything. actions/cache with a restore-keys
prefix brings back the nearest earlier repository, so only the new artifacts come
from Central.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* iOS simulator tests: build the embedded watch app for the runner's arch only
The packaging job hit its 120-minute cap with every step green. A third of
the UI-smoke compile was an x86_64 watch-simulator slice (3206 units beside
the arm64 3206) that nothing on an arm64 runner can run: the watch target's
ARCHS_STANDARD + ONLY_ACTIVE_ARCH has no active arch when the destination
is an iPhone, so it compiles every standard arch. Measured locally: without
the pin the watch compiles x86_64, with it only arm64.
Both scripts write the same EXCLUDED_ARCHS[sdk=watchsimulator*] so the
notification-test step keeps reusing the UI step's watch objects.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM: read DartLongList's array through CN1_ARRAY_DATA
#5903 moved array elements behind a data offset in a 4-byte header, so
struct JavaArrayPrototype no longer has a data member. The Dart List<int>
intrinsics still read arr->data and every C-target build of a transpiled
Flutter app stopped compiling after the merge.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* CI: skip the translator-selected DLL intrinsics arm; fix the TelemetryTest flake
check-ablation-arms swept CN1_HAVE_DLL_INTRINSICS as a -D arm, but like
CN1_HAVE_SB_INTRINSICS it is defined by cn1_intrinsics.h when the translation
carries dart_core_DartLongList.h, and forcing it without that header cannot
compile.
TelemetryTest: the same change as 98e65a7ec3 on backend-spring-model (#5908).
An earlier test's queued export reached the collector mock after the
connections were cleared, and the traceparent test accepted any span.
Applied identically so the two branches merge cleanly.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* macOS: mark idle present surfaces purgeable
The Metal view keeps three full-window IOSurfaces so a frame always has one
the window server is not reading, but once the app is idle only the surface
under the layer is ever looked at again. The other two stayed resident and
counted in the footprint. They are now marked volatile once the window server
lets go of them, and made non-volatile again, under a lock shared with the
selection and the rebuild, before the next frame's full blit overwrites one.
Measured on the transpiled Flutter gallery at rest (local, 2048x1370 backing,
four interleaved launches): 141-142 MB -> 120-121 MB phys_footprint.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM: cull what nothing reachable calls, not only what nothing calls
The method cull keeps a method while some surviving method calls it. That
is a statement about callers, not reachability: dead cycles keep each
other alive forever, and the tail of a dead chain longer than the cull's
four rounds survives. A feature package an app never touches (health,
home, intents, call, nearby...) is exactly such a web.
ReachabilityCull walks forward from the cull's own roots (main, clinit,
finalize, natives, runtime delegate targets, methods the native sources
name) along the cull's own edges -- a call is desc.name and reaches every
method with that signature, and an override is kept when the declaration
it overrides is called -- and eliminates the rest. It can only remove
methods the cull kept, never keep one it removed.
Transpiled Flutter gallery, macOS: 1,065 methods + 697 in the class cull
that follows, universal "Executable code" 55.3MB -> 48.6MB, arm64 __text
23.3MB -> 20.3MB. Compute checksums identical to the unmodified build;
first frame renders. -Dcn1.reachabilityCull=false restores the old
answer; -Dcn1.reachWhy=<class prefix> prints the caller chain that kept a
class, for the next "why is this in my binary".
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM: inline threshold 50 on optimized C builds
The translator emits one small C function per Java method plus per-call
helpers, and at clang's default threshold (225 at -O2, 250 at -O3) they
are inlined into every caller -- most of a translated app's __text is
those copies. Threshold 50, compile and LTO link, on every C target:
- Xcode templates (iOS, macOS): OTHER_CFLAGS on both Release
configurations; OTHER_LDFLAGS -Wl,-mllvm on the target level only, with
$(inherited), because IPhoneBuilder injects OTHER_LDFLAGS = -ObjC into
the project-level block.
- CMake (Linux clang, Windows clang-cl): SHELL:-mllvm on the Release
compile; Linux ThinLTO link gets -Wl,-plugin-opt=-inline-threshold=50,
since the clang driver does not forward a link-time -mllvm. Verified in
a Linux clang 18 image with GNU ld, gold and lld: the flag moves a
cross-TU inline decision in all three.
Transpiled Flutter gallery, macOS, with the reachability cull: arm64
__text 20.3MB -> 16.4MB, universal "Executable code" 48.6MB -> 40.4MB
(Flutter: 47.9MB). Compute checksums identical, geomean within noise;
cold start unchanged. -Os reached a similar size but cost 9% of compute.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Desktop: honour desktop.width/height on native Linux and Windows; bench at Flutter's window size
The ParparVM Linux and Windows ports opened every main window at a
hard-coded 800x600, and the declared desktop.width/desktop.height hints
reached only the JavaSE wrapper. The generated stubs now pass them to the
implementation before Display.init, which is what creates the window.
The flutter-bench's own first rule is the same surface size on both sides,
and it did not hold: Flutter's templates open 800x600 on macOS and 1280x720
on Linux and Windows, while Codename One opened 1024x685 on macOS and 800x600
on the other two. build_apps.sh now builds Codename One at the Flutter
template's size on each platform -- larger than before on Linux and Windows,
smaller on macOS.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* macOS: purgeable screen texture at rest, memoryless stencil
At rest the persistent render target duplicates the surface under the layer
(every presented frame blits all of it there). Once a frame's GPU work
completes and no newer frame or readback has started, the texture is marked
volatile; the next user makes it non-volatile under cn1PresentLock and, if the
kernel reclaimed it, copies the displayed surface back before reading it.
The Stencil8 attachment is cleared every pass and never stored, so on Apple
GPUs it is now memoryless.
Also moves cn1RetireIdleSurfaces above cn1PayDeferredPresent's doc comment,
which the previous commit had split from its method.
Measured on the 800x600 gallery at rest (local, three interleaved launches):
99.9-107.0 MB -> 91.3-97.1 MB phys_footprint.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM: keep the size changes building under self-hosting and zig cc
ReachabilityCull's -Dcn1.reachWhy diagnostic used String.split, which the
translator's own JavaAPI does not have, so the self-hosting compile failed.
The ThinLTO -Wl,-plugin-opt=-inline-threshold=50 link option failed every
zig cc link ("unsupported linker arg: -plugin-opt"): zig identifies as Clang
but its linker driver accepts a fixed argument set. Skipped when the compiler
is zig; the compile-side threshold still applies there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Metal: round overflowing pictures at the box instead of building a rounded copy
A picture scaled to overflow its box (BoxFit.cover, the gallery's carousel
cards) could not use the hardware rounded draw: rounding the whole picture put
the corners outside the box, where the clip cut them off square. So the Flutter
runtime read the pixels back, cut the corners in, and uploaded a second image --
every card held as the decoded original, a full ARGB bitmap on the CPU, a second
texture on the GPU, and CoreGraphics' converted copy of that bitmap from the
upload.
Graphics.drawImageRegionRounded draws only the region of the picture inside the
box and rounds the box's corners; on Metal the rounded fragment shader samples
that region of the one texture that already exists. Other ports draw the region
square, clipped, and FittedImage keeps the copy wherever the platform cannot
round.
Measured on the transpiled Flutter gallery, macOS, 800x600 window, at rest,
three interleaved launches each: 100.9-101.4 MB -> 63.8-64.5 MB phys_footprint
(malloc 42 -> 18 MB, GPU 33 -> 23 MB, CG raster data 3.1 MB -> 0). Screen
capture against the old build: cards identical to the eye; 18,545 of 7.7M
pixels differ, none by more than 128 levels, all on card edges -- analytic
antialiasing of the corners against the copy's cut ones, and one bilinear
sample of the region against a pre-scaled bitmap.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* flutter-bench: attribute compute failures against a host JVM reference
A compute workload whose checksums differed, or that one app did not run, was
dropped from the table and the mean without saying which side failed. On the
web that silently removed intArithmetic and arrayRandom (Flutter web returns
a wrong result: Dart on JavaScript does integer arithmetic in doubles) and the
64-bit workloads (Flutter web produces none) -- and a Codename One failure
would have disappeared the same way.
Each leg now runs the same CommonWorkloads.java on the host JVM. A
disagreement or a missing result is attributed: the row says which side got a
wrong result or did not run, the geometric mean is still taken over the
workloads both sides got right, and any workload Codename One gets wrong or
does not run fails the gate.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: build objects from a per-class cached layout
Every translated new went through jvm.newObject, which on every allocation
re-walked the class chain to collect field defaults, re-parsed each field's
descriptor, created a monitor with two arrays, and ran a breadth-first
ancestor search to decide whether the class is a Throwable -- whose negative
answer was never cached, so every ordinary object paid for it.
The field defaults and the Throwable flag are now computed once per class
(instanceLayout, cached on the classDef behind an epoch defineClass bumps),
and the monitor is left null: every monitor user already attaches one on
first use.
Interleaved A/B over one translation of vm/benchmarks' CommonWorkloads under
node, each workload in its own process, best of 3: objectAllocation 5.77x,
valueEscape 3.83x, hashMapChurn 2.02x, stringBuilding 1.18x; the workloads
that do not allocate are 0.99-1.01x. Checksums identical to a JVM.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: hold a long that fits in 53 bits as a plain number
Every Java long was a {__l, l, h} hi/lo record and every long operation
allocated one, so any loop with a long in it -- a long sum over an int array,
a recursive function returning long, a long checksum, a timestamp delta --
paid an allocation and a helper call per operation.
A long now has two canonical forms chosen by magnitude: a JS number when it
is a safe integer (|v| < 2^53), the hi/lo record otherwise. Every helper
returns the canonical form, and each has a number/number fast path that is
exact: IEEE add, subtract and multiply of safe integers are exact whenever
the true result is safe, and a true result outside the range cannot round
back into it. Everything else takes the exact hi/lo path, which is also
where 64-bit wrap-around happens. Division uses a % b (exact on doubles)
rather than a rounded a / b. The translator emits a safe long constant as a
number literal instead of an _Llit call.
NativeStorage uses a long as an opaque handle and hangs the storage off it,
so its handles are built as records on purpose.
Checked differentially against BigInt with Java semantics: 596,641 checks
over boundary and random values, every result also checked for canonical
form; the checker was confirmed to fail against the old representation and
against an injected division bug.
Interleaved A/B under node over the CommonWorkloads translation, each
workload isolated, best of 3, against the previous commit: arraySequential
3.29x, valueEscape 2.06x, recursion 1.83x, arrayRandom 1.72x, intArithmetic
1.10x, others flat. longArithmetic -- every value a full 64-bit number, so
every operation takes the slow path and results alternate between forms --
is 0.70x; each helper alone is at parity on 64-bit operands, the cost is the
form changes between them. vm/tests JavaScript group: 374 run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: emit collection and autobox natives as calls to their Java twins
HashMap's get/put/remove/containsKey/clear/areEqualKeys, HashSet's seven
table natives and the six boxed types' valueOf are natives for the C
targets (probe loops, tagged immediates). On JavaScript each one only ever
forwarded to a pure-Java twin, through a function* binding in
parparvm_runtime.js. A generator binding is a native the suspension analysis
cannot see into, so every one was classified suspending: every map lookup
and every autobox paid for a generator and a yield*, and so did every method
that called one.
The translator now emits these natives itself as a direct call to the twin
(JavascriptNativeRegistry.TRANSLATED_DELEGATES), and the suspension analysis
records the call as an edge, so the native is a plain function wherever the
twin is. The runtime bindings are gone; areEqualKeys gains a Java twin
(areEqualKeysImpl) because its binding was hand-written.
Two other things kept the whole HashMap chain suspending:
- Object.getClass was bound as a generator only so it could throw a
NullPointerException; every boxed type's equals calls it, so Object.equals
dispatch suspended. It now throws through the existing synchronous
cn1_ivsNpe.
- port.js replaced HashMap.computeHashCode three ways with a null-key
fallback, and a replaced method is suspending by definition. Its only
caller, cn1Marker, has answered a null key itself since the compact
HashMap, so the fallback could not fire; it is removed.
Suspension report on the CommonWorkloads translation: 222 -> 286
synchronous methods of 414, and HashMap get/put, cn1Marker, cn1FindSlotImpl
and the benchmark method itself are synchronous. hashMapChurn under node:
2186 -> 679 ms (4665 before this series), checksum identical to a JVM.
vm/tests JavaScript group: 374 run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: stop 64-bit long values flipping form on every operation
a7ceeb6401 made a long a plain number whenever its value was a safe integer,
and normalized every slow-path result to one. On full 64-bit arithmetic
(vm/benchmarks longArithmetic, the shape of Random, SHA-512 and hashing) that
measured 0.70x against the all-record representation it replaced: a 40-60
bit result came back from a helper as a double, which V8 boxes on return,
and the next 64-bit operation converted it back into a record, so values
changed form on almost every operation and every number operand of a slow
path was converted through an allocation.
Two changes:
- A slow-path result is a number only when it is an int32 (the high word is
the low word's sign extension), a record otherwise. The number/number fast
paths still return any safe integer as a number. A value between int32 and
2^53 can therefore be in either form, which nothing depends on: every
helper reads a value through its own typeof test or the new word
accessors, and Java code only compares longs through _Lcmp.
- The slow paths read an operand's words directly (_LwL/_LwH): x | 0 and
x >> 31 for an int32, the fields of a record, an exact split for any other
safe number. They no longer build a record per number operand.
Checked against BigInt with Java semantics: 596,641 checks, 0 failures, with
the representation rule asserted (numbers always safe and never -0, an int32
slow-path result always a number, anything past 2^53 always a record); the
checker still fails the old all-record runtime.
Interleaved A/B under node, CommonWorkloads translation, each workload
isolated, best of 3, load average 6-11: longArithmetic 1.90x against the
previous commit and 1.32x against the all-record representation (best of 5);
every other workload 0.99-1.05x. vm/tests JavaScript group: 374 run,
0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: allocate through a per-class function V8 can inline
Every translated new went through jvm.newObject, which builds the object by
assigning each field default through a computed key. V8 cannot see through
that -- the allocation is a call it will not inline and the stores are
keyed -- so every new in a loop allocated for real. On the valueEscape shape
(allocate, read two fields, drop) in node: 220-250ms through that path,
~80ms from a literal with computed keys, 14ms from a function returning a
literal with the field names as literal keys, which V8 inlines and removes,
as it does for dart2js's own output.
The translator now emits that function, cn1_<class>___NEW__, for every class
translated code allocates with a plain new, and calls it at the NEW site.
Its literal has newObject's property order, filtered by the same field-level
RTA the class's f: list uses, so either path builds the same shape. Keys are
bare identifiers, each after a comma and a space, which the string hoisting
leaves alone (it rewrites ,KEY: into a computed key), and they are excluded
from the whole-token function-name minifier. The functions go after every
class registration and capture their classDef once at load (_Od, which fails
loudly on an unregistered class). Not for a Throwable (newObject captures
its stack), an abstract class, an interface, a class whose chain is not
fully known, or under -Dparparvm.js.manglefields=1; -Dparparvm.js.allocfn.off
disables it. The NEW site's class-initialization guard already runs any
static initializer.
Interleaved A/B, production-mode translation (minifier and aliasing on),
each workload isolated, best of 3, load average 7-10: valueEscape 5.16x,
objectAllocation 4.43x, hashMapChurn 1.38x, stringBuilding 1.06x, others
0.99-1.02x; checksums identical to a JVM in both arms. Bundle: +7.7KB raw,
+1.1KB gzip on this 34-class translation. vm/tests JavaScript group: 374
run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: call a virtual method directly when the receiver type allows one body
The JavaScript emitter devirtualized a call only when its signature was
declared once in the whole program (monomorphicDispatch), which almost
nothing is: Integer.intValue() shares its signature with every Number,
HashMap.get(Object) with every Map, StringBuilder.append(String) with every
Appendable. Every such call went through cn1_ivResolve, a methods-map probe
per call, even where the receiver's type leaves one possible body.
The suspension analysis already resolves each virtual call site against its
receiver type (RTA's instantiated cone) to classify it. DispatchModel
.directTarget exposes that answer -- the one body, or null for a JSO/bridge
signature, an unresolvable cone, more than one body or an abstract one --
and the emitter calls that body directly through the existing _dv/_dw
helpers (null check, generator drive), so the call and the site's
classification come from one resolver. A native qualifies: its m: entry
names the same global the direct call does, and bindNative replaces both.
The m: entries stay, since only monomorphicDispatch prunes them.
-Dparparvm.js.sitedevirt.off disables it.
Interleaved A/B, production-mode translations, each workload isolated, best
of 3, load average 7-15: hashMapChurn 1.37x, stringBuilding 1.13x, the rest
0.96-1.01x; checksums identical to a JVM in both arms. vm/tests JavaScript
group: 374 run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: runtime strings are not interned; cheaper class-init guard, arrays, StringBuilder
Four runtime changes, measured together:
- createJavaString -- every string the runtime builds: concatenation,
Integer.toString, StringBuilder.toString, stack traces -- went through the
string-literal table, which never evicts. Every string a program ever built
stayed reachable for good, and two separately built equal strings were the
same object, which Java forbids (String.intern() has its own pool in the
Java code). It now builds a fresh String (jvm.newJavaString); only literals
are interned, and a literal hit is one table probe instead of two.
- _I, the class-initialization guard in front of every static call, static
field access and NEW on a class with an initializer, always called the full
ensureClassInitialized. It now answers the already-initialized case itself,
small enough to inline. It was 42% of hashMapChurn.
- newArray built the array's class name twice by concatenation, probed the
class table and allocated a monitor per array; the class is now cached per
(component, dimensions) and the monitor left for monitorEnter, as newObject
does.
- StringBuilder's latin1 test concatenated the byte[] class name on every
call, per character in the append and toString loops; it is a constant, and
the loops test once.
Interleaved A/B over one production-mode translation, runtime swapped, each
workload isolated, best of 3, load average 8-12: stringBuilding 2.24x,
hashMapChurn 1.35x, the rest 0.97-1.02x; checksums identical to a JVM in both
arms. vm/tests JavaScript group: 374 run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: allocation-free identity counter; direct calls skip the generator drive
Two costs that kept V8 from removing an allocation that never escapes:
- Every allocation incremented jvm.nextIdentity. jvm carries hundreds of
properties, so V8 keeps it in dictionary mode and the increment was a
hashed load and store per object. The counter now lives on its own
one-field object, cn1Ids (jvm.nextIdentity is an accessor onto it), and the
translator's allocation functions use it.
- A direct call to a devirtualized body went through _dw*, which passes the
result to cn1_ivsDrive, whose typeof r.next test is a property load on
whatever the method returned, at every direct call site. That guards
against a generator reaching a synchronous caller, which a target the
analysis classified synchronous cannot return (a sync native is a
plain-function binding; a bridge-replaced method is seeded suspending). The
emitter now uses _dn* -- the null check and the call -- for exactly those
targets, including the peephole that folds operand pushes into them.
Interleaved A/B, production-mode translation, each workload isolated, best
of 3; the A arm is the same translation with these two emissions reverted
textually plus the previous runtime, byte-identical to the previous commit's
output. Load average 30-50 (other work on the host): valueEscape 3.50x,
objectAllocation 2.33x, hashMapChurn 1.11x, stringBuilding 1.09x, the rest
0.97-1.01x; checksums identical to a JVM. vm/tests JavaScript group: 374
run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS screenshots: reseed chart-time, whose golden recorded "Invalid Date"
The committed chart-time reference labelled every time-axis tick "Invalid
Date": a Java long reached the browser's Date as a boxed object and did not
convert. With longs that fit in 53 bits held as plain numbers (da0e5341e8),
the axis now reads Mar 2 .. Mar 8, 2024 -- the golden recorded the bug.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* perf gate: calibrate Linux x64 AMD EPYC 9V45
The hosted Linux x64 pool now also hands out AMD EPYC 9V45 runners, which
had no rows, so the gate failed on any job that landed on one. Calibrated
from that job's perf-results.json as calibrate-perf-baseline.py documents;
only the new model's rows are added. On the runners that do have rows, the
same head measured within tolerance of the baseline on every benchmark.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: match bridge-built name stems only against the bridge sources
The function-name minifier protects any generated function whose name
extends a quoted cn1_ string of 16+ characters, so a name the bridge JS
builds by concatenation (the screenshot runner's lambda stem in port.js)
still resolves. It took those stems from the bundle's own string literals
too -- and the bundle quotes every instance field's property name in its
class f: lists. A field strokeWidth therefore protected strokeWidth_R_double
and strokeWidth_double, the getter and setter every transpiled Dart property
has: 5,211 functions of the transpiled Flutter gallery kept their full
names.
Stems now come from the bridge sources only. The bundle's literals are
complete names (jvm.setMain's main method, the field properties) and keep
their exact-match protection.
Transpiled Flutter gallery, same translator input, script bytes: -151KB
(-1.2%) with this alone. vm/tests JavaScript group: 374 run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: allocation functions only where an allocation is hot
f3770384c9 gave every class translated code allocates with new its own
allocation function. On the transpiled Flutter gallery that was 3,153
functions and +2.2MB (+17%) of script, which the javascript benchmark leg
gates at zero tolerance against its baseline.
An allocation function pays off where V8 sees the allocation many times:
- a new inside a loop -- between a backward branch and its target (jumps
and switches) -- which is where V8 removes one that never escapes;
- the boxed types, always: their instances are built in their own valueOf
factories, never in the loop that autoboxes, and hashMapChurn spent 21%
of its time building Integers through jvm.newObject without them.
Every other new -- a listener, a UI component, a lambda built once per call
-- uses jvm.newObject again. A class with more than 16 instance fields never
gets one: the literal lists every inherited field, and 63 wide classes were
a third of the functions' size. The per-class classDef constant is numbered
rather than named after the class, which the minifier never shortened.
Tried and dropped: treating new in any method of <= 16 instructions as hot
(to catch small factories): +490KB and it did not reach valueOfHeap.
Transpiled Flutter gallery, same translator input, against the translator
before this series: script 12,855,430 -> 12,705,943 bytes (-1.16%, with the
previous commit), deflated download -0.03%. Compute workloads under node,
production-mode translation: objectAllocation 24ms, valueEscape 46ms,
hashMapChurn 236ms, stringBuilding 234ms -- unchanged from the previous
head; checksums identical to a JVM. vm/tests JavaScript group: 374 run,
0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: run java.lang.System's static initializer instead of replacing it
The runtime bound System.<clinit> to a native that only created the console
print streams, so every other static of java.lang.System was never
assigned -- LOCK above all, which System.gc() and the GC thread synchronize
on. Any app that reached System.gc() (the Codename One core does, from the
EDT) died on "Cannot read properties of undefined (reading '__monitor')";
the transpiled Flutter gallery did, right after its first frame, showing an
internal application error dialog.
The binding now runs the translated initializer (installNativeBindings keeps
it in jvm.translatedMethods) and then installs the console print streams as
before. It stays a plain function so System's initializer, and every
class-init guard in front of System, does not become suspending; a
translated initializer that returns a generator is handed to the class-init
driver with the stream swap after it.
Verified on the transpiled Flutter gallery in headless Chromium: the
__monitor error and the dialog are gone, with the old and the new translator
alike (the defect predates this series). vm/tests JavaScript group: 374 run,
0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: start the application lifecycle on the EDT
ParparVMBootstrap called the lifecycle's init() and start() inline on the
worker's main thread, where every other port runs them on the EDT (iOS
hands its stub to Display.init). Code that checks threads broke: a
transpiled Flutter app's runApp asserts it is on the EDT, so the gallery
threw IllegalStateException("Flutter framework mutation off the EDT") and
never left its loading screen, with the old translator as with the new.
The bootstrap now queues itself with callSerially after Display.init and
the afterInit hook, so the hardening stamp still runs before init/start.
Verified: the transpiled Flutter gallery reaches its first frame in
headless Chromium (BENCH:FIRSTFRAME ~760ms) where it used to stop at the
loading screen; vm/tests JavaScript group 374 run, 0 failures. NOT run
locally: the JavaScript screenshot suite (scripts-javascript.yml), whose
build uses this bootstrap and is the gate for this change -- it needs the
hellocodenameone build through ~/.m2, which this checkout does not use.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: hashCode/equals sites are synchronous; exact receiver type from new
In the transpiled Flutter gallery -- the build the javascript benchmark leg
measures -- hashMapChurn ran 1488ms in headless Chrome against 227ms for the
same workload in a small application. The suspension fixpoint had made the
whole java.util.HashMap API a generator: Vector, Hashtable, SimpleTimeZone and
the Collections.synchronized* wrappers have synchronized hashCode/equals, so
every hashCode()/equals() dispatch site that could reach them suspended, and
through computeHashCode/areEqualKeys every map operation and every caller of
a map did too. A small application never closes that cycle.
hashCode() and equals(Object) sites are now classified synchronous whatever
their implementations are (JavascriptSuspensionAnalysis.isDrivenSignature),
and a generator implementation is run to completion by cn1_ivsDrive, which
now steps through time-slice yields. A body that really blocks there fails
with a named error after withdrawing its monitor entrant. A devirtualized site
whose target is a generator but whose site is synchronous is emitted as _dw.
Kill switch: -Dparparvm.js.drivensigs.off.
A local whose every store is `new X(...)` now has exact type X, so a virtual
call on it goes straight to X's body even when the declared type has
overrides elsewhere (new HashMap() beside LinkedHashMap).
Kill switch: -Dparparvm.js.exactlocals.off.
Gallery, headless Chrome, ?benchCompute=1: hashMapChurn 1488 -> 440ms;
other workloads unchanged. Executable code 12,705,943 -> 12,691,168 bytes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Linux: cut three launch costs -- GDK GL probing, repeated font metrics, the blocking accessibility publish
Measured from the Linux leg of the Flutter benchmark (perf profile of the
transpiled gallery's launch) and fixed at the port, so every app gains:
- GDK_GL=disable before gtk_init, unless the environment sets GDK_GL. Opening
the display otherwise probes GLX for GL-capable visuals (epoxy_glx_version,
glXQueryServerString, a dlopen of the Mesa driver): ~9% of the launch. The
port draws with cairo and its 3D backend makes its own EGL context; WebKitGTK
still renders with it off (checked with WebKitGTK 2.50 under Xvfb). gtk_init
alone went from 290-630 ms to 90-140 ms in the emulated container.
- Font metrics cached per PangoFontDescription. pango_context_get_metrics
shapes a sample string for character widths the port never reads, and a
theme asks for the same descriptions over and over (~7.7% of the launch).
The cache stores Pango's own first answer, so nothing moves; it is cleared
when a TrueType font is registered, since a family may then resolve anew.
- The accessibility tree is applied on the GTK thread after the frame, at low
priority, instead of while the event dispatch thread waits. It builds a GTK
widget per node (~5% of the launch, all before the first frame). Each
publication replaces the whole tree, so one still pending when a newer
arrives is freed unapplied.
A/B on the same translated gallery build (x86_64 under emulation, Xvfb,
1280x720, 10 interleaved launches, load average 53-153): first frame median
860 -> 707 ms, best 846 -> 688 ms. Captures of both, 4 s after the first
frame, are pixel-identical.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: direct calls at the site, literal long constants, indexed native overrides
Three costs found profiling the transpiled Flutter gallery in headless Chrome,
plus the test the synchronous hashCode/equals sites were missing.
- A direct call to a synchronous body is now written `impl(_nn(t), ...)` when
every argument is a local or a literal. Through _dn* the body was a parameter
of a helper every direct call shares, so its inner call was megamorphic, the
body was never inlined and the receiver escaped into it: on valueEscape's
shape (allocate, read two fields through getters) 107ms against 7ms in node.
The null check now runs before the arguments, which is unobservable only when
they are pure, so impure argument lists keep _dn*.
- LCONST_0/LCONST_1 emit the literals 0 and 1 rather than _L0/_L1. A long is a
plain number in the safe range, and a top-level const read is a context load
V8 did not fold: an accumulator seeded from it kept every _Ladd's type tests,
2.5x slower on arraySequential's reduction in node.
- installNativeBindings' overrideMethodMaps scanned every class for every
native, concatenating a prefix per class to find the owner and probing each
methods map for the key. On the gallery that was 9% of the worker's CPU
profile at start-up and deoptimized the function 808 times. The owner is now
found at the name's underscore boundaries and the holders of a key come from
an index built once.
- JsDrivenHashContentionApp: a synchronized hashCode() whose monitor another
green thread holds, reached through HashMap.put. It must throw the driver's
"sync virtual dispatch reached a blocking method" error, never proceed
unlocked, and leave the monitor and map usable. Verified to fail in all five
compiler configurations when the driver returns instead of throwing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Linux: draw scaled images directly; cache text widths
Scaled drawing. LinuxImplementation never overrode
isScaledImageDrawingSupported, although the port has always had
drawImageScaled, so Graphics.drawImage(img, x, y, w, h) scaled first: for an
EncodedImage that is EncodedImage.scaled -- decode, scale, re-encode to PNG
(lossy JPEG for an opaque picture), decode again -- on every paint, with
nothing keeping the result. The transpiled Flutter gallery ran 13 PNG encodes
before its first frame (5 cards, 6 icons, the logo twice), all from
PaintSurface.paintDirty. With the override: 0. A capture 4 s after the first
frame differs from the old one in 743 pixels by at most 3 levels -- the JPEG
round trip that no longer happens.
Text widths. cn1MeasureUtf8 now caches (font, exact UTF-8 bytes) -> width,
bounded at 8192 entries (cleared when full) and cleared with the metrics
cache when a TrueType font is registered. 84% of lookups before the gallery's
first frame hit (1508 of 1800), and captures are pixel-identical.
Neither moved the median first frame measurably in the emulated container
(x86_64 under emulation, Xvfb, 14 interleaved launches: 911 / 900 / 917 ms,
spread +-100 ms), where the launch is dominated by other costs.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Windows: draw scaled images directly
The same gap 13cbaf7a8f closed on Linux: WindowsImplementation never
overrode isScaledImageDrawingSupported, although the port has always had
drawImageScaled, so Graphics.drawImage(img, x, y, w, h) pre-scaled the
picture -- for an EncodedImage a decode, scale, re-encode to PNG (lossy JPEG
when opaque) and decode on every paint.
drawImageScaled uses the same cn1WinEnsureBitmap, clip push and linear
interpolation as the unscaled drawImage, only into a w x h destination, so
mutable and encoded images and alpha behave exactly as they do unscaled. The
native has no guard for a degenerate rectangle, so the Java override returns
early for w or h <= 0.
Not built here (no Windows toolchain): the Java compiles with JDK 8, the
native signature matches its declaration, and the C-linkage gate passes. CI
verifies. Windows screenshot goldens that draw scaled encoded images may
shift by a few levels, since the lossy re-encode no longer happens -- on
Linux the same change moved 743 pixels by at most 3.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: numeric array stores stay numeric; inline bounds test in loops
The transpiled Flutter gallery's arraySequential ran 412ms in headless Chrome
against ~180 for the same code in a small application. A natives-syntax probe
in the gallery showed why: its 8M-element int[] was HOLEY_ELEMENTS. V8 records
on each allocation site the most general elements kind an array it made ever
reached and starts later arrays there, and jvm.newArray had ONE `new Array`
site for every array in the program -- so after the first Object[] every int[]
held a boxed HeapNumber per element outside the small-integer range, and every
store allocated. Each element family (int; byte/char/short; boolean; float and
double; long; references) now has its own site.
Array loads and stores inside a loop are emitted as a bounds test statement
followed by a plain element access, `if(a==null||i>>>0>=a.length)_A(a,i);`,
instead of a call to the one _A/_T helper every access in the program shares,
whose element-access feedback is megamorphic in a real application. The
failing case still goes through _A/_T, so NullPointerException and
ArrayIndexOutOfBoundsException are exactly what they were; the value of a
store is still evaluated once, after array and index. Only pure array and
index operands qualify (they are re-read), and only sites between a backward
branch and its target, since the form costs ~30 bytes: +30KB on the gallery.
A conditional expression was measured and rejected -- merging the unboxed
element with _A's tagged result boxed every load (212ms against 38ms for the
statement form in a Chrome micro-benchmark, 98ms for the shared helper).
The non-deferred direct-call path now also writes `impl(_nn(t), ...)`.
Gallery, headless Chrome, 3 interleaved rounds at load 8-12 (ms, origin ->
this): arraySequential 412 -> 206, arrayRandom 835 -> 749, quicksortBench
341 -> 322, stringBuilding 285 -> 275; valueEscape unchanged at 124; the rest
within noise. Checksums identical. Executable code 12,704,437 bytes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS tests: copyright header on the contended-monitor fixture
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* JS port: string literals read in a loop keep their String in a slot
_L("text") looks the text up in the runtime's literal table on every read, a
dictionary lookup over every literal in the program -- 4,675 distinct ones in
the transpiled Flutter gallery -- repeated per iteration wherever a loop
builds strings. A literal read between a backward branch and its target is now
`(_Lc[n]||(_Lc[n]=_L("text")))`, one slot per distinct text; elsewhere the
lookup runs once and the slot would not pay for its bytes (+11.7KB on the
gallery for the loop sites alone).
Gallery, headless Chrome, stringBuilding, 6 interleaved rounds at load 5-8:
min 272 -> 261ms, median 272 -> 266ms. Small, and inside what this host can
resolve on one workload; kept because it cannot cost anything else.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Windows screenshots: reseed the two scaled-image goldens
8c3d4c1041 draws scaled images through the port's drawImageScaled native
instead of decode, scale, re-encode and decode. The old goldens carried the
re-encode path's nearest-neighbour stair steps on upscaled edges; every other
port (Linux, macOS, JavaScript) draws those edges smooth, and so does Windows
now. Native and cross-compiled captures are pixel-identical.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Linux screenshots: reseed the two scaled-image goldens (x64 and arm64)
074cf54f43 draws scaled images through drawImageScaled instead of decode,
scale, re-encode and decode; only the edges of scaled images move. The x64
and arm64 captures are pixel-identical.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* perf gate: keep a failed benchmark's run logs where the job uploads them
The Linux arm64 gate's hello row crashed (exit 139, round r00) on
737e3b7985 and left nothing to autopsy: its stdout and stderr logs --
which carry the ParparVM fault handler's backtrace -- were written under
vm/selfhost/target/perf-gate, which no workflow uploads. The job summary
said "exited with 139" and named a path on a runner that no longer
existed.
A failed row now copies its logs beside perf-results.json (the uploaded
raw/perf directory) and names the copy in the failure reason.
Not reproduced locally: the cull-on self-hosted translator (-O3, thin
LTO, clang 19, arm64 Linux) ran a larger clean translation 120 times and
the cull-off build 90 times without a crash, and a CN1_GC_VERIFY build
reported 0 violations over 207M references. The 56 methods the
reachability cull stubs in the translator binary are called only from
interface thunks that nothing calls; the inline threshold does not reach
the self-hosted binary (compile-dist.sh builds it directly, not through
the generated CMake).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM CMake: inline threshold on generated sources only
The Windows x64 perf gate measured objectAllocation +33% time and +23%
peak memory after the inline threshold went target-wide. The runtime's
hand-written C -- allocator, collector, write barriers, nativeMethods.c
-- was compiled with it too, and it is five files of a few thousand: not
where the size is, and where a lost inline costs on every allocation.
The generated CMake now reads cn1-source-manifest.txt (written beside
it, after every source exists) at configure time and puts the threshold
in the COMPILE_OPTIONS of the sources the manifest lists as generated.
No file-name guessing: java_io_File_runtime.c is hand-written and named
like a class. List form rather than SHELL:, which per-source options do
not accept.
Checked on the generated Bench project with clang: 147 of 152 compile
commands carry the flag -- exactly the generated set -- and none of
cn1_globals.c, nativeMethods.c, cn1_virtual_thread.c, cn1_win_compat.c,
java_io_File_runtime.c do. CleanTargetIntegrationTest green. The arm64
Linux proxy (clang 19, no LTO) did not reproduce the Windows regression
in any variant (47.5 / 46.2 / 47.3 ms), so the Windows figure has to come
from CI.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM CMake: a GCC inline limit, so a gcc-built Linux app shrinks too
The Flutter benchmark's Linux build uses the runner's host cc (no zig
there), which is GCC 13 -- and the clang-only -mllvm threshold never
reached it. CI measured Linux executable code 17.8MB before the size work
and 17.9MB after: no change at all. The reachability cull did run (809
methods + 1,439), but GCC's --gc-sections was already dropping what it
removes; the Mac gained from it because DEAD_CODE_STRIPPING is off there.
GCC's knob is max-inline-insns-single, applied like the clang flag to
the sources the manifest lists as generated. Measured on the transpiled
Flutter gallery's Linux translation:
- object .text (arm64, gcc 12): -O3 26.45MB, single=20 23.90MB.
- linked .text (x86-64, gcc 12, gc-sections, the build as CI runs it):
18.51MB -> 15.55MB (-16%); Flutter's Linux figure is 16.1MB.
- vm/benchmarks, runtime at plain -O3, 3 interleaved rounds: geomean
54.91 -> 55.07ms, every workload within noise.
Rejected, each measured: max-inline-insns-auto (shrinks more, but stops
-O3 inlining recursion into itself: recursion 76 -> 116ms), -O2 (same
recursion loss, objectAllocation +9%), turning off cloning, unswitching,
peeling and loop versioning (0.2%).
Checked in the generated build.ninja: all 3,793 generated sources carry
the flag, none of the 28 hand-written runtime and port sources do.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Metal: load the default library once per pipeline cache
pipelineFor: called newDefaultLibrary for every pipeline it built, which opens
and parses default.metallib each time -- several times on the first frame --
and, in the non-ARC build, leaked each library object. The cache now keeps the
one library it loaded.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* iOS bundle: ship PNGs as they are, and not the port's design notes
The iOS gallery app was 5.7MB larger than Flutter's before counting a
byte of code, all of it in two places:
- PNGs. Xcode's copypng rewrites every loose PNG into Apple's CgBI form,
which for photographic images is LARGER: the gallery's 145 PNGs went
from 14.40MB to 19.98MB. The template now sets COMPRESS_PNG_FILES = NO
and STRIP_PNG_TEXT = NO on both configurations -- both, because copypng
treats -strip-PNG-text as "compress" and runs pngcrush -iphone anyway,
so the first setting alone changed nothing. Nothing here needs CgBI:
images decode through ImageIO, which reads a standard PNG equally well,
and Image.isPNG only checks the signature both share.
- WATCHOS_PORT.md and TVOS_PORT.md, which sit beside the port's natives
and were zipped into nativeios.jar with them, so Xcode put them in the
resources phase of every iOS app. Excluded at the packaging step (an
app's own .md resources are untouched).
Gallery, device Release, stripped, as flutter-bench builds it: installed
123.91MB -> 118.24MB (Flutter 115.85MB locally); every asset is now
byte-identical to Flutter's copy.
Cloud builder note (BuildDaemon, separate repo, not changed here): it
builds from the same generated project, so it inherits the template
settings; any IPhoneBuilder path there that rewrites COMPRESS_PNG_FILES or
STRIP_PNG_TEXT, or that archives with an xcconfig setting them, would
undo this.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM cull: type-aware reachability for the C targets, with a CI trap
ReachabilityCull matched calls by lookup signature only, so x.area() kept every
area() in the program. An instance method is now live only once its class, or a
subclass that inherits it, is allocated. Until then it is held, and it is
released as soon as an allocation happens. Static methods and initializers keep
the signature rule.
Allocation seeds (ReachabilityCull.Allocation):
- NEW in a live method;
- class literals in a live method (NativeLookup.register, Util.register,
getMenuBarClass...);
- string constants spelling a class's binary name;
- every class named in the hand-written native sources or headers;
- the runtime roots the C side creates (String, the boxes, Thread, the VM's
exceptions);
- every dependency of a live method holding a fused or custom instruction;
- once Class.newInstance is reachable, every concrete class with a no-arg
constructor. Class.forName takes names built at run time: NativeLookup's
"Impl", UIBuilder's resource names, stored geofence/background listener
names. newInstance can only run a no-arg constructor, so this bounds what
reflection can create.
A held method that live code names directly (a guarded
((Database) o).close(), for example) keeps its dispatcher. Its body becomes the
culled stub, and its callees stop counting as called
(BytecodeMethod.cullBody, MethodDependencyGraph.removeCalls).
-Dcn1.cullRta=false restores the signature-only answer.
Safety net: with CN1_CULL_TRAP=1 (or -Dcn1.cull.trap=true), every culled stub
aborts and names the method, instead of returning 0. It aborts rather than
throwing an Error, because a catch (Throwable) would swallow an Error. Our
translating workflows set it at workflow level, as CN1_NATIVE_VERIFY is set.
The size and perf benchmarks do not set it, and neither do customer builds.
Measured on the transpiled Flutter gallery, same sources, with and without the
gate:
- iOS (device Release, as flutter-bench builds it): code 21.93 -> 21.23 MB
(-3.2%); installed 118.24 -> 117.54 MB.
- macOS universal: __TEXT arm64 18.28 -> 17.96 MB, x86_64 19.20 -> 18.85 MB
(-1.8% each).
- Compute checksums are identical to the baseline.
- The cull removes 2891 methods on iOS (2946 on macOS), against 1031 (1065)
before; 826 bodies are culled.
The cap is the reflective seed: 1315 of 3866 allocated classes come from it,
because IOSImplementation's background location and fetch paths make
Class.forName reachable in every app.
Also, for clang without LTO (zig cc on Linux), generated sources build at -O2
instead of CMake Release's -O3. Measured with clang at threshold 50: .text
22.43 -> 21.49 MB (-4.2%); vm/benchmarks compute geomean 59.44 -> 57.58 ms;
recursion 161 -> 118 ms. clang-cl already builds /O2, and GCC is untouched,
because -O2 costs recursion ~52% there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* ParparVM cull: seed reflection per Class.forName site, not per newInstance
Until now, the moment Class.newInstance was reachable, every concrete class
with a no-arg constructor counted as allocated. On the transpiled gallery
that was 1315 of 3866 classes. It fired in every app, because the iOS
port's background location, geofence and background-processing paths reach
Class.forName.
newInstance can only act on a Class object the program holds, and there
are three ways to hold one:
- a class literal (already an allocation seed);
- getClass() on an instance (its class made the instance);
- Class.forName, the only one that can name a class nothing allocated.
getSuperclass() yields an ancestor of an allocated class, whose methods the
subtree rule already admits. So the seed now belongs to each live method
that calls forName, and applies once newInstance is live too.
Audit of every forName in the core, the translated ports and JavaAPI
(Mac, Linux, Windows and the flutter/dart runtimes have none). Each site
feeds exactly one type:
- NativeLookup.create -> NativeInterface
- GeofenceManager.getListenerClass -> GeofenceListener
- DeviceRunner.runTest -> UnitTest
- IOSImplementation.Loc.getBackgroundLocationListener -> LocationListener
- IOSImplementation.Loc.getGeofenceListener -> GeofenceListener
- IOSImplementation.runBackgroundProcessing -> BackgroundWorker
A known site seeds only the concrete no-arg classes assignable to its type.
Any other caller (an app's, a cn1lib's) keeps the old answer, every concrete
no-arg class, and the translator prints which method did it. Each call site
now carries a comment pointing at ReachabilityCull.FOR_NAME_SITES.
Every other reflective creation in core was checked and takes a literal or
getClass():
- UIBuilder's component registry;
- Util.register;
- the PropertyBusinessObject types;
- MenuBar via LookAndFeel;
- Facebook/GoogleConnect, AdsService, Socket, CommonProgressAnimations and
InstantUI;
- DynamicImage and PropertyIndex, which use getClass().
Tests (vm/tests ForNameSeedIntegrationTest):
- For each site, a stand-in with the real class and method name is the
only live forName, and a listener is created only by a name built at run
time. The program is built with CN1_CULL_TRAP and run: the listener runs,
and the other seed classes' methods are culled stubs.
- An unlisted site falls back to every no-arg class and is reported.
- The real core and iOS port are translated with all six sites live, and
no forName may be reported unnarrowed.
Probes: dropping the BackgroundWorker entry fails the per-site test and the
real-core test. Ignoring the site's type fails the "others are culled"
assertions.
Measured on the gallery, same sources, headless builds:
- iOS code 21.23 -> 20.42 MB (Flutter 20.00). Before type-aware reachability
it was 21.93. Installed 117.54 -> 116.73 MB.
- macOS __TEXT arm64 17.96 -> 17.83 MB, x86_64 18.85 -> 18.72 MB.
- Allocated classes 3866 -> 3545 on iOS. The cull removes 4323 methods
(+919), against 2891 (+771).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* macOS: build the main window before [NSApp run], overlapping the VM boot
The window was queued onto the main queue, so it could not be built until
[NSApp run] had finished launching -- on the GitHub macOS runner ~264 ms into
the process -- and the first frame waited behind it. The generated main now
calls CN1MacBuildMainWindowBeforeRun right after dispatching the VM boot, so
the build overlaps the boot instead of delaying it (the reason the old comment
gave for not building inline). Weakly linked: a port without the function keeps
the queued build.
flutter-bench macOS on CI (diagnostic run 36613510954): window built at
~130 ms instead of ~264, the Java first paint 125 ms -> 7-32 ms, cold start
344 ms vs Flutter 382 ms (1.11x; was 0.87x).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* iOS size: cull what only dead native C names; drop the loose legacy icons
Measured on the transpiled Flutter gallery, same sources, headless device builds
(iOS: Release, iphoneos, as flutter-bench builds it):
| | before | after | Flutter |
|---|---|---|---|
| iOS code | 20.42 MB | 19.37 MB | 20.00 MB |
| iOS installed | 116.73 MB | 115.03 MB | 115.85 MB |
| macOS __TEXT arm64 | 17.83 MB | 16.70 MB | |
| macOS __TEXT x86_64 | 18.72 MB | 17.52 MB | |
The reachability cull removes 5614 methods (+1290 after the class cull) on
iOS, against 4323 (+919). The feature packages the gallery never touches are
gone from the binary: home, call, nearby and health.
Four narrowings in ReachabilityCull, active with the type-aware cull. Each
has its own switch, and each one, switched off, fails
NativeScopeIntegrationTest:
- NativeBodies (-Dcn1.nativeBodyScope=false). A name inside the C body of a
port's Java native, or inside a static C helper, is a call that body makes.
It is followed once the native is live, and is no longer a root. The
#else trampolines of every switched-off feature call their callbacks to
report NOT_SUPPORTED, so every app kept every feature. Static helpers
only, because their callers are all in the same file. Functions with
external linkage stay roots, as do the VM runtime's own natives.
#include/#import lines no longer count as naming a class.
- NativeFeatureFilter (-Dcn1.nativeFeatureFilter=false). A branch that a
CN1_INCLUDE_ switch compiles out names nothing. So does a CN1_ macro whose
every #define sits in such a branch, for example CN1_VPN_HAS_NE. Only OFF
is ever concluded; a condition it cannot evaluate keeps all its branches.
- Conditional <clinit> (-Dcn1.cullClinit=false). A static initializer runs
only once its class is touched: its code runs, its statics are read or
written, or it is allocated. The IOS*Callbacks dead-code guards call every
callback from <clinit>.
- StaticCalls (-Dcn1.cullStaticOwner=false). A static call reaches only the
class it names or that class's superclasses, not every static with the
same name and descriptor. A name reached some other way (a fused or
rewritten instruction, invokedynamic) keeps the signature rule.
A method that compiled C still names but that can never run stays declared, as
the culled stub, so the natives still link. With CN1_CULL_TRAP that stub
aborts.
The builder now puts the app icon only in the AppIcon asset catalog. Loose
copies of every size, Icon.png and iTunesArtwork (0.6 MB) went into the
bundle root for the top-level CFBundleIconFiles of iOS 6 and older. No
supported deployment target reads those files; they take the icon from
CFBundleIcons/CFBundleIconName, which actool writes. The key is dropped from
the Info.plist template, and IPhoneBuilderIconsTest pins it. The BuildDaemon
IPhoneBuilder still copies them and needs the same change.
The ZooZ strings bundle moves from nativeSources to the Zooz SDK directory.
Only a build that links ZooZ uses it, and only BuildDaemon ever enables ZooZ,
by unzipping zoozIosSources.jar, which now carries the bundle.
Verified with CN1_CULL_TRAP on:
- translator tests: 203 run, 0 failures, 11 skipped;
- the torture gauntlet is green;
- the Linux gallery, built in the amd64 container and run under Xvfb there,
went through 17 scripted click paths with no culled method called, and its
compute checksums match.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* flutter-bench: re-record the Android code-size baseline
Code grew 4,720,336 -> 4,772,660 bytes (+1.1%) with features added since the
baseline was recorded; still 4.5x under Flutter's 20.6 MB. Only code_bytes
moves: the start-up and memory rows stay at the multi-run envelope they were
tuned to.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* macOS: document why the main window is built before [NSApp run]
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Themes: custom mode ships and installs no native theme, on every port
ThemeMode.CUSTOM ("the theme the application ships, rather than a
platform one") is now honoured everywhere, generically -- nothing here
knows about Flutter:
- iOS / macOS: IOSImplementation.installNativeTheme() returns before the
chain and hasNativeTheme() answers false for custom. Custom used to fall
to the bottom of the chain and install the pre-flat iPhoneTheme.res.
- Android: the mode resolution moves into nativeThemeMode(), which also
recognizes custom from and.themeMode / cn1.androidTheme / nativeTheme;
hasNativeTheme() answers false and installNativeTheme() does nothing.
Custom used to fall to androidTheme.res.
- Builders: NativeThemes.themeFor answers null and androidThemesFor an
empty set for custom, so no theme .res is packaged. IPhoneBuilder maps
nativeTheme=custom to ios mode custom (it became auto, iOS 7), and the
macOS whitelist accepts custom (it became modern).
- Linux / Windows native: those ports install their bundled theme from
init() whenever the file exists, so the builders leave
linuxNativeTheme.res / windowsNativeTheme.res out for custom.
- JavaSE: ios/and.themeMode=custom resolve to no simulator theme, and
nativeTheme=custom reaches the desktop as "no platform theme" (the
packaged app and the desktop wrapper default), the way native does.
- Hint docs: custom added to the ios/and.themeMode value patterns and the
ThemeMode / Build / Mac / DesktopBuild wording.
flutter-bench: the generated app sets includeNativeBool: false in
theme.css, and every build passes nativeTheme / ios.themeMode /
and.themeMode = custom (the archetype's settings set the platform ones
to modern, and they outrank nativeTheme). The porting guide describes
both settings as the generic option they are.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Theme install: defer the desktop base theme, build control glyphs on first use
Two costs every Flutter-runtime launch paid before its first frame:
1. The Linux and Windows ports installed their native theme eagerly in
init(). FlutterUI then installs CN1FlutterMaterialTheme.res with
setThemeProps, which replaces the whole table (the theme sets
@includeNativeBool=false), so that install was thrown away. It is now
registered with the new UIManager.setDeferredBaseTheme and runs only if
something reads the theme before any other theme is installed. An app
with no theme of its own still gets it. setThemeProps drops it, and
addThemeProps layers on top of it.
2. DefaultLookAndFeel.refreshTheme ends every theme install on every
platform, and it built the check box, radio button and combo arrow
glyphs each time: it loaded the Material icon font and rendered eight
FontImages. These are now built the first time a getter, a paint or a
preferred-size call asks for them, using the same code and the same
theme. A setter called after a refresh wins over the late build.
Measured on the Flutter gallery, ParparVM-translated, Linux container
(x86 under emulation), with 8 interleaved launches per arm and medians:
A HEAD: native install at init 59.7 ms + Material install 22.3 ms
B deferred: Material install 55.8 ms (it is now the first install)
C B + glyphs: Material install 45.8 ms
Total theme work before the first frame: 82 -> 46 ms.
Cold start to BENCH:FIRSTFRAME (14 interleaved runs, median):
483 / 475 / 468 ms.
Captures 4 s after the first frame: A == B == C, 0 pixels differ.
Headless JVM (JDK 8, 10 fresh JVMs per arm), setThemeProps of
CN1FlutterMaterialTheme.res:
first install 13.4 -> 9.9 ms, warm 1.13 -> 0.73 ms.
On macOS and iOS the Material install is already the first install, so
only the glyph half applies there. It is the same share of refreshTheme.
Tests: UIManagerDeferredBaseThemeTest and DefaultLookAndFeelLazyGlyphsTest.
Full core-unittests: 7482 run, 0 failures.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Themes: nativeTheme=custom does not reach the JavaSE desktop
Keeps the rule GenerateDesktopAppWrapperMojoTest pins: of the cross-platform
nativeTheme values only native also selects the desktop theme, so an app that
set nativeTheme=custom for its phone builds does not lose its desktop theme
unasked. desktop.themeMode=custom already installs no framework theme there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* flutter-bench: Android code-size baseline after the custom theme mode (+656 bytes)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* perf gate: re-measure the Windows AMD Family 26 objectAllocation memory row
The row was calibrated from a single run at 0.26x, far below every other…
What
The backend now reads like a Spring application --
@Service,@Autowired,@Value,@ConfigurationProperties,@Bean, scopes,@Profile/@ConditionalOnProperty,@Transactional,@Async,@Scheduled,@ManagedResource/@Timed/@Counted,@McpTool-- all undercom.codename1.backend.annotations, and all resolved at build time:BackendBeanAnnotationProcessorresolves the bean graph and the entry point's generatedBackendWiringconstructs and injects every bean with straight-linenew/setter calls. Private@Autowiredfields get a woven setter. Missing, ambiguous or cyclic dependencies are build errors naming the injection point.@Transactional,@Async,@Timed,@Countedrewrite the method itself (body moved aside, the method delegates to a generated helper) -- no proxy, so self-calls, private methods andnew-built objects get them too.Transactions(all seven propagations, savepoints, read-only) thatDataSource, daos and managed sessions join;Scheduler(cron masks computed by the build, fixed rate/delay, DB lock across replicas);Tasks/TaskExecutorincluding fd-less virtual-thread tasks with a per-host wake pipe;HttpSessionwith memory and JDBC stores; OTLP metrics +/manageendpoints (health, JSON, Prometheus, jobs, managed beans); an MCP endpoint serving@McpToolmethods and, on dev profiles, development tools (backend_routes,backend_beans,backend_call,backend_requests,backend_sql, ...), excluded fromcn1:backend-packageby default.backend.mdandfull-stack-loop.mdreferences in the Initializr skill, archetype/Initializr backend sample with an injected service, new Backend guide sections with compiled snippets.Fixes found on the way
yieldNow()from a handler hung and IO parks after GC backpressure were misreported as runnable; each yield site now sets its reason.Future.get()on a virtual thread blocked the host and could wedge every host; it now yields cooperatively.HttpServer.Requestwas refused ($vs.in the nested type name).Verification
vm/testsbackend suites (HTTP 72/72, WebSocket, OTel, runtime self-test, DB/ORM on all three engines) pass; a sample packaged withcn1:backend-packageexercised every feature and survived a 7000-request concurrent soak.Known limits
Aspects apply to project classes only; request/session/lazy beans need a non-final class with a no-arg constructor (run once for the stand-in); no transaction isolation levels; session-bean
@PreDestroynot run on expiry;cn1app-archetype-test.shextended but not run locally.🤖 Generated with Claude Code