[CoreCLR] Exclude RCPC2 from ARM64 ReadyToRun defaults - #12722
simonrozsival wants to merge 8 commits into
Conversation
Default ARM64 Android ReadyToRun to conservative instruction selection. Crossgen2's optimistic RCPC2 code is rejected on CPUs without RCPC2, forcing otherwise precompiled startup methods through Tier 0 JIT. Resolve the default after package imports and before R2R preparation. Preserve explicit normal/composite instruction-set arguments and leave other architectures, Mono, NativeAOT, and runtime tiering unchanged. Add policy coverage and document the override behavior. Fixed-profile untraced sample startup improved about 10% on Samsung A16; S23 results do not establish sub-percent non-regression. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Copilot review overview
🟢 Approval recommended
The behavioral change is narrowly scoped, documented, and accompanied by targeted MSBuild/test coverage to validate override and composite/non-composite scenarios.
Review tier: Lite
Findings: 1
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs — 💡 suggestion Resource management — CreateApkBuilder() returns a disposable ProjectBuilder;… |
What changed in this PR
This PR adjusts the .NET for Android CoreCLR ReadyToRun (R2R) defaults for android-arm64 so crossgen2 uses a conservative instruction-set policy (--instruction-set:-optimistic) unless the build already specifies an explicit --instruction-set option. This aims to reduce R2R entry rejection (and resulting Tier 0 JIT fallback) on devices that lack some optional ARM64 instruction features.
Changes:
- Add an MSBuild target in the CoreCLR targets to append
--instruction-set:-optimisticto$(PublishReadyToRunCrossgen2ExtraArgs)for Android apps targetingandroid-arm64when no explicit instruction-set policy is provided. - Extend test coverage to validate argument-policy behavior across composite/non-composite and explicit override combinations, plus assert the flag appears only for arm64 in the basic publish test.
- Document the new default and override behavior in the build properties documentation.
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest2.cs | Adds assertions and parameterized tests validating the new ARM64 R2R instruction-set default/override behavior. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.CoreCLR.targets | Introduces _AndroidSetReadyToRunInstructionSet to set the default crossgen2 instruction-set policy for CoreCLR android-arm64 R2R. |
| Documentation/docs-mobile/building-apps/build-properties.md | Documents the ARM64 --instruction-set:-optimistic default and how explicit instruction-set args override it. |
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
The ARM64 policy is otherwise scoped appropriately and the default/non-default paths are documented and tested, but a composite-only override currently leaves excluded assemblies on crossgen2's optimistic default.
CI build #1587516 is still in progress and already reports failures in Package Tests macOS > Tests > APKs 2 and MAUI Tests MAUI Integration. Azure log inspection was unavailable because the runner could not initialize its Azure CLI configuration, so I could not determine whether those failures are related to this change.
Generated by Android PR Reviewer for #12722 · gpt56 · 289.4 AIC · ⌖ 5.8 AIC · ⊞ 26.5K
Comment /review to run again
Preserve build-wide opt-out for composite-only ISA overrides: the SDK concatenates both argument lists, and crossgen2 rejects duplicate instruction-set options. Document how to apply a policy to composite exclusions via the common arguments and include exclusions in the policy regression cases. Dispose the ReadyToRun application test builder as requested in review. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
CI follow-up for build 1587516:
Pushed 472af43 with the review follow-up. Fresh validation 1587685 is running; this is not yet a green-CI claim. |
Remove the ARM64-only guard and simplify explicit ISA override detection. Add the private _AndroidUseConservativeReadyToRunInstructionSet=false escape hatch without disabling R2R or changing existing compiler arguments. Extend argument-policy coverage to x64 and ARM, composite/non-composite overrides and the private escape hatch. Keep measured startup claims scoped to the ARM64 phones. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
|
Can you try to get a signoff from somebody on the JIT team (or close by)? Just want to make sure there's no unintended consequence of the change. Otherwise it looks great :-) |
|
/review |
|
✅ Android PR Reviewer completed successfully!
|
There was a problem hiding this comment.
⚠️ Needs Changes
Found 1 warning: a composite-only instruction-set argument currently disables the conservative default even when PublishReadyToRunComposite=false, although the .NET SDK does not consume composite arguments in that mode. This leaves the effective non-composite crossgen2 invocation optimistic.
The late target placement, preservation of normal arguments, regression coverage, resource disposal, and performance evidence are otherwise well handled. CI is still running: 25 checks have passed, 18 are in progress, and 1 is queued, with no failures reported so far.
Generated by Android PR Reviewer for #12722 · gpt56 · 228.2 AIC · ⌖ 22.2 AIC · ⊞ 25.7K
Comment /review to run again
Keep the tested ARM64 instruction set except RCPC2, without encoding an RCPC2-absence assertion. Preserve other architectures, explicit overrides and the private opt-out, and document the higher baseline requirements. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove the primary-constructor parameter list from the integration tests' `MockBuildEngine`, fixing CS0111 on `main`. #12724 (`ecef743e91614754e6e1ab0ed7ed1834ecdedf5c`) added that signature after #12736 had introduced an explicit constructor with the same optional warning-list parameter. The merged combination left both constructors in place. This one-line repair keeps the explicit constructor and warning collection unchanged, including the existing no-XA4326 assertions; it does not change the production generator. The failure appears in the Linux and macOS builds of [Azure DevOps build 1588988](https://dev.azure.com/dnceng-public/public/_build/results?buildId=1588988): MockBuildEngine.cs(15,9): error CS0111: Type 'MockBuildEngine' already defines a member called 'MockBuildEngine' with the same parameter types. Based directly on current `main` (`d907cd735615d5d17616cbba9e1a4aeef99ab61d`), independently of #12722. Rechecked live main and open PRs before editing and immediately before opening this PR; no equivalent fix was found. ### Validation - Compiled the actual `MockBuildEngine.cs` using an isolated .NET SDK `11.0.100-rc.1.26431.118`, its net11.0 reference assemblies, and its real `Microsoft.Build.Framework.dll`. The same command reproduced CS0111 before the fix and succeeded without diagnostics after it. - An artifacts-only host driver compiled together with the fixed source passed default construction, explicit `null`, and supplied-list cases. Logging with default/null was harmless; successive warnings appended to the supplied list with object identity preserved. - The fixed file is byte-for-byte identical to its last working main version, immediately before #12724. <details> <summary>Direct compiler command</summary> `SDK` points to the private SDK copy and `OUT` to the session artifact directory; run from the repository root: ```bash refs=() for ref in "$SDK"/packs/Microsoft.NETCore.App.Ref/*/ref/net11.0/*.dll; do refs+=("-r:$ref") done "$SDK/dotnet" exec \ "$SDK/sdk/11.0.100-rc.1.26431.118/Roslyn/bincore/csc.dll" \ -nologo -target:library -nullable:enable -langversion:latest \ "-out:$OUT/MockBuildEngine.dll" "${refs[@]}" \ "-r:$SDK/sdk/11.0.100-rc.1.26431.118/Microsoft.Build.Framework.dll" \ tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/MockBuildEngine.cs ``` </details> **Integration-test limit:** Attempted `dotnet test tests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests/Microsoft.Android.Sdk.TrimmableTypeMap.IntegrationTests.csproj --no-restore -p:BuildProjectReferences=false --filter 'FullyQualifiedName~JniAssemblyRewriterTests|FullyQualifiedName~GeneratedTypeMapRewriterTests' -v minimal`, but no tests executed in the unprepared worktree. A subsequent `dotnet build` of the same project with `--no-restore -p:BuildProjectReferences=false -v minimal` confirmed NETSDK1004 (missing `project.assets.json`). The local Android build outputs are also absent. No full bootstrap or device tests were run, and older installed generator binaries were not substituted for current-main integration validation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Remove the android-x86 and android-arm cases from the CoreCLR argument-policy test. Keep the matrix on android-arm64 and android-x64, and clarify the documented x64 default. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use the checked-in NDK support metadata: CoreCLR remains enabled for ARM, ARM64 and x64; only x86 is disabled. Restore the ARM case and retain the existing architecture-neutral documentation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Use _AndroidUseOptimisticReadyToRunInstructionSet=true to skip the default ISA override. Remove the dedicated ReadyToRunInstructionSet test while retaining the existing ReadyToRun integration assertion. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
| [`$(MauiEnableFullReadyToRun)`](#mauienablefullreadytorun) to opt in to | ||
| full ReadyToRun. | ||
|
|
||
| For ARM64, .NET for Android defaults crossgen2 to: |
There was a problem hiding this comment.
We have these defaults in crossgen2 for all other OSes. If the defaults in crossgen2 do not work well for Android, we should fix it in crossgen2. I do not think we want to have these low-level defaults spread over multiple places.
There was a problem hiding this comment.
We may want to drop rcpc2 from the optimistic set for all OSes. It is relatively minor optimization that makes a lot of R2R methods rejected on older hardware.
|
Can you check the disassembly of the methods that declare dependency on rcpc2 and verify that they actually depend on rcpc2 instruction? I suspect that there may be a bug in the JIT that declares dependency on rcpc2 even when the code does not actually contain rcpc2 instruction. |
|
@jkotas Yes, you were right. In the stock MAUI sample, 117 of the 564 RCPC2-guarded method bodies contain no RCPC2 instructions. The other 447 really do use them. The problem is in For example, add x0, x0, #0x153
ldaprb w0, [x0]That's RCPC1, not RCPC2. The offset is too large for the RCPC2 form. I also reproduced this with a small volatile-field example using the same compiler. Stock and So there are unnecessary dependencies to fix here, though the genuine RCPC2 users will still need fallback on older devices. I haven't measured the startup impact of a JIT fix yet. Posted from the Copilot App on Simon's behalf. |
Can you open an issue on this in dotnet/runtime?
I think this PR can be closed. The fix is going to come from dotnet/runtime. |
Testing on android (dotnet/android#12722) showed that older devices don't have rcpc2 instruction set available which leads to significant r2r code rejection. This is a simpler, cross platform fix for the issue, compared to exposing knobs to tweak the optimistic set (#133514) and have dotnet/android use them. --------- Co-authored-by: Jan Kotas <jkotas@microsoft.com>

Summary: current policy
Default Android CoreCLR ARM64 ReadyToRun to the explicit ISA list tested on Samsung A16 and S23:
This disables automatic optimistic additions and explicitly enables the listed features, retaining the queried ARM64 features except RCPC2 without asserting that RCPC2 must be absent. Other Android CPU architectures retain
--instruction-set:-optimistic. Mono, NativeAOT and runtime JIT hardware-specific/tiered optimization are unchanged.This is a higher baseline, not a selective optimistic set. The eight explicit features become eager image-level requirements. A device missing a required effective runtime capability can reject the composite's R2R code and fall back to JIT. The A16 and S23 advertise all eight; these two-device results are not a universal Android compatibility guarantee.
Current evidence: the exact explicit-list APKs launch and retain R2R on both phones, with 65 pre-Ready JIT compilations in each explicit-list census. This is functional/R2R evidence, not a demonstrated 10% speedup for the new default. The larger historical benchmark below measured stock versus
-optimisticand an RCPC1-only sensitivity policy, not this new explicit list. The explicit-list A16 first-use Ready observations were slower than the controls; equal performance/non-regression is not established.Implementation and overrides
RuntimeIdentifier=android-arm64, before_PrepareForReadyToRunCompilation, after project/package imports have supplied their arguments. Android's per-RID inner builds supply this property.--instruction-setoption in eitherPublishReadyToRunCrossgen2ExtraArgsorPublishReadyToRunCrossgen2CompositeExtraArgssuppresses the default, independently of composite mode. Both properties are preserved, and command-line global properties retain MSBuild precedence.PublishReadyToRunCompositeExclusions. The SDK concatenates normal and composite arguments for the composite image; crossgen2 rejects duplicate ISA options. Put one override in the normal arguments to cover both the composite and excluded assemblies._AndroidUseOptimisticReadyToRunInstructionSet=trueescape hatch to skip the default ISA override. The target runs when this property is nottrue: blank/false keeps the override enabled. Opting out leaves supplied arguments unchanged and does not disable R2R. No new public property is introduced.BasicApplicationPublishReadyToRunintegration test. The separateReadyToRunInstructionSetparameterized test has been removed.No custom profile retraining, runtime feature masking or application rewrite is included in this PR.
What the ISA experiments established
Stock RCPC2 code was present, but rejected on A16
The measured A16 is SM-A165F / MT6789; the S23 is SM-S911B / SM8550. Both run Android 16/API 36. The A16 advertises RCPC but not RCPC2; the S23 advertises both.
The actual
ConcurrentDictionary<__Canon,__Canon>.TryGetValueInternalnative body containedLDAPRandLDAPUR, with an RCPC2 entry dependency. On A16, the runtime rejects the unsatisfied fixup and falls back to Tier 0 JIT. The profile already contained 21 canonical reference/reference owner records, including constructors,GetOrAdd, lookup, insertion, growth and lock helpers: missing profile coverage was not the cause of these rejections.An optimized crossgen2 replay recovered from the binlog's actual
TaskCommandLineEventArgs, with verbose/maps/dependency output, reproduced that diagnostic composite byte-for-byte. It attempted 20,230 methods, emitted 20,162 native method nodes and explicitly skipped 68 (51 always-throw and 17 no-IL declarations); none overlapped the original Tier 0 target set's owner/member stems. An initial strings-based extraction that dropped-Owas excluded.ARM64 volatile lowering / native-entry fixup failure.
Literal
-rcpc2is not the chosen solutionThe negative switch creates a compatibility condition, not an ARM hardware limitation. The parser calls
RemoveInstructionSetSupport("rcpc2"), which removes supported membership and adds explicitly unsupported membership:For this JIT-enabled target, crossgen2 writes
ExplicitlyUnsupportedFlagsinto the baseline ISA signature and roots it inEagerImports. Per-method dependency generation trusts that global baseline. Parser / builder / eager baseline.The actual negative image contains:
30is the ISA-support fixup;04is its entry count. Entries encode(feature ID << 1) | expectedSupport. RCPC2 is ID 42:0x54means expected unsupported, whereas0x55would mean expected supported. The other entries require ArmBase, AdvSimd and Atomics. Signature writer.CoreCLR compares its effective CPU-flag boolean with the expected boolean, not just a minimum architecture version. On an RCPC2-enabled S23,
true != false; failed eager fixups disable R2R through the composite. IL/JIT fallback keeps the app running. "Absent" means unsupported in CoreCLR's effective flags, not necessarily absent from the silicon; no runtime feature masking was introduced. Runtime comparison / composite disablement.-optimisticcontrol-rcpc2Zero explicit rejection flags do not mean R2R succeeded. The disabled-image path returns before per-method lookup/fixup checks. The negative trace has zero lookup-start events; its 14,578 null results before Ready cover 14,188 distinct runtime method identities. Over the complete recording it has 14,586 null results covering 14,196 methods, with zero nonzero results. The control has 8,779 lookup-start events and 8,717 nonzero / 66 null results over its complete recording. Null results are not individually proven ISA rejection events; ordinary missing bodies can return null too.
The negative image's startup JIT work included 4,243 CoreLib, 3,170 MAUI Controls, 1,612 MAUI, 1,009 Mono.Android and 917 Syncfusion Toolkit events. Its one OSR compilation was
Element.OnResourcesChangedKeys, not relabeled Tier 0.Four separate untraced fresh-install C/N/C/N launches all rendered the sample:
-optimistic-rcpc2-optimistic-rcpc2These are compatibility observations, not a statistical slowdown estimate: two per policy, USB charging, battery temperatures 38.5-39.9 C, no filtering or confidence interval. The first control's secondary bounded crash-log query was incomplete; its successful launch/Ready/screenshot were retained.
Why negative dependencies exist, and why this does not dismiss selective optimism
Feature availability can affect observable behavior, not only instruction choice. As an x86 illustration, a method returning
Avx2.IsSupportedcan be folded under an absent-feature assumption and disagree with a JIT version on an AVX2-capable process. This is not an RCPC2 API or an observed Android sample method.The source exempts ordinary reviewed CoreLib fallbacks from many negative per-method dependencies, but keeps
get_IsSupportedandget_IsHardwareAcceleratedas exceptions. This supports a generic dependency policy, not proof that a blanket RCPC2-absence assertion is necessary. We have not shown that the sample's weaker bodies intrinsically require the CPU to lack RCPC2. Dependency recording / per-method handling.The tested crossgen2 has no separate optimistic-only exclusion switch. Its help advertises
-featureas disabling optimistic light-up but omits the additional eager absence condition.-optimisticis special-cased asallowOptimistic = false, without marking all omitted features explicitly unsupported. Positive additions then become baseline requirements.Consequently, the selected explicit list is not equivalent to Vlad's narrower suggestion to remove only RCPC2 from automatic optimistic additions. That would require a compiler policy change or new option. It remains unimplemented/unmeasured here; broader code-quality and
IsSupportedconcerns are not disproven.The selected explicit list: host proof and two-device confirmation
The exact compiler replay uses
-optimistic,aes,crc,dotprod,lse,rcpc,rdma,sha1,sha2, with all 188 linked inputs, eight profiles and other arguments fixed. The new eager signature is:The only other ISA signature requires ArmBase/AdvSimd/VectorT128. RCPC2 appears in neither a positive nor a negative requirement. The image is 7,443,968 bytes, SHA-256
99973c94cb9fb2f70c0d0365515caaa0b584a12c5bb8e543a7dad104ad99a58a.The exact same plain and native-census APKs were used on A16 and S23. Each census variant preserves the corresponding plain variant's managed/R2R/profile/runtime-feature bytes; only native diagnostic configuration and signing members differ. The native app compression descriptor was unchanged versus the corresponding control because the composite size was unchanged. All 1,460 ZIP entries and 223 managed payloads plus 223 empty lookup descriptors were audited, with v2/v3 signatures and 16 KB alignment.
All four captures have zero explicit per-method rejection flags and no startup Tier 1/OSR/MinOpt/unknown tiers. Null results are S23 control/explicit 67/62 and A16 62/62. Complete-recording nonzero results are S23 8,721/8,721 and A16 8,727/8,724. These are result-event counts, not invocations. The small count differences do not establish a repeatable benefit.
Each phone had an untraced seed launch before these censuses. Both this comparison and the literal-negative comparison used native provider
Microsoft-Windows-DotNETRuntime:0x6000080018:5for 15 seconds, without periodic sampling or allocation recording; managed EventSource/Metrics remained disabled. ActualEntryPointpayloads were decoded using the registered CLR parser. All captures had zero raw/ETLX loss, matching event totals, complete rundown, no truncation or payload errors, and 1 GHz monotonic-clock alignment. No JIT/R2R count changes within Ready's 1 ms quantization interval.Separate untraced fresh-install observations:
All listed launches rendered the sample. The S23's planned fourth launch was not run: secondary bounded log export hit invalid UTF-8 after the third successful control launch. The original blocked record and all three successful run records were retained; no successful launch was discarded or rerun. The private log decoder was then corrected and checked with a malformed-byte host fixture; all four later A16 trial/log records completed. The native census was separate and completed on both phones.
No speedup or equivalence claim is made from these first-use observations. A16's explicit-list Ready times were slower in both observed pairs. USB charging was active; S23 prelaunch temperatures were 32.1-33.7 C, A16 28.6-28.7 C. These few observations are not pooled with the 480 historical seeded timings below; traced durations are not substituted for untraced timings.
Candidate APK SHA-256s: plain
077e88e16bae6e6774b670e35c10a074e018d151c9691fd9c6d967e14dd3fd0e; native census2f9450326eb88ba9d251b9c3f1c7f636dd40f7a373d69b5da778993ae99c10a9. The executable section is not byte-identical to the earlier RCPC1-only sensitivity control, so its timing data are not reused as measurements of this new image.Older-phone coverage and the baseline tradeoff
The measured A16 advertises
aes crc32 asimddp atomics lrcpc asimdrdm sha1 sha2, but notilrcpc; the S23 advertises all nine queried features.rdmais Arm RDM SIMD arithmetic, not networking RDMA;sha2maps to SHA-256 support.Public AArch64 captures show why this cannot be generalized to all Android phones:
"Missing" means not advertised by that captured OS/kernel, not proven physical absence or a guarantee for every SKU. The 665 capture is official Android 9/MIUI 11.0.9; the G85 is Android 11/MIUI 12.5.4; the 888 fixture does not specify OS/kernel. Kernel backports and the common safe capability set across heterogeneous cores matter. No .NET startup measurements were made on these public devices. Linux capability reporting.
LSE is already part of the pinned Android ARM64 R2R baseline (
armv8-apluslse), even with-optimistic. The historical stock and conservative images both require Atomics eagerly. A non-LSE target can therefore fail the old baseline too; the new explicit list additionally makes RCPC/dotprod/RDM/crypto/CRC requirements eager. This is an intentional stronger-baseline tradeoff, not a portable replacement for optional optimism. Baseline source.The frozen historical stock sample's guards reference only LSE/RCPC/RCPC2 among the queried nine. No AES/CRC/dotprod/RDM/SHA ISA guards occur in that exact image. There is no demonstrated independent startup benefit from enabling those six for this sample; other workloads can differ.
Related experiments: background, not additional product changes
Further A16 compiler studies used the historical conservative baseline, not the new explicit list. Across 288 measured launches, filtered paired Ready savings were
--method-layout:hotcold-7.54 ms (descriptive 95% interval -24.24 to +9.14),--Os+8.39 ms (-5.71 to +22.68), and--Ot+12.90 ms (-6.06 to +32.39). Every interval includes zero; none established extra startup improvement or +/-1% equivalence.--hot-cold-splittingcrashed before Ready, including both candidates in a separate C/H/C/H reproduction while both controls succeeded. No flag combinations or additional defaults were adopted.Profile inspection also separated method coverage from richer PGO: custom/generated profiles were coverage-only, while six shipped MAUI profiles contained edge/type instrumentation; none of the eight inputs had nonzero exclusive/call layout weights. A separate host lifecycle control showed that stopping EventPipe before managed exit loses shutdown PGO export: default/custom rundown alone yielded zero schemas, while exit with capture active yielded 908 chunks and 603 schema-bearing methods. EventPipe sample-type selection in the converter is a separate layout-weight issue. No profile-pipeline/runtime implementation change is part of this PR.
Historical evidence boundary
Everything in the measurement setup, statistical timing tables and eight-launch census below compares the earlier stock, RCPC1-only and conservative policies. The historical approximately 10% A16 gain is stock versus
-optimistic, not a benchmark result for the current explicit ARM64 list. The original observations, filtering and S23 non-regression limitations remain intact.Measurement setup
Workload: unchanged
dotnet new maui --sample-content; SDK11.0.100-rc.1.26431.118, CoreCLR11.0.0-rc.1.26431.118, Android37.0.0-rc.1.2257, MAUI11.0.0-rc.1.26451.6; Release ARM64, partial trimming and composite/partial R2R. Measured Android source:b65b55d5357abb23960a999c7c5ffaceb75c4515; MAUI source:484132f9e51f4d1eae72dba038575d6102639730.Within each typemap study, all managed compiler inputs (188 trimmable / 91 LLVM-IR), eight profile inputs, package graph, runtime configuration, DEX, resources and manifest match exactly across ISA policies. Only native assembly-store/application-descriptor payloads differ. Trimmable uses its completed 15,322-record custom profile; LLVM-IR uses its independently trained 14,912-record profile. The same profile is used on both sides of each ISA comparison. The custom profiles add method coverage rather than block/edge counts; the six shipped MAUI MIBCs already supply instrumentation data, including edge counts and type histograms. They are not a direct trimmable-versus-LLVM benchmark.
Diagnostics, managed EventSource and MAUI layout meters are disabled during timing. The same native Java endpoint reports useful content after initial data loading and two committed frames. TTID is Android
am start -WTotalTime. The dedicated study app is seeded during conditioning and force-stopped between measured launches; existing app data is untouched. Every installation is non-incremental, and launches start from HOME after one second of settling.There are 48 measured launches per policy per phone. Trimmable uses all six permutations of three policies; LLVM-IR uses six AB/BA-alternating paired blocks. Each treatment/block has eight measured launches plus one excluded post-install conditioning launch: 480 successful timing measurements and 60 conditioning launches overall.
--instruction-set:rcpc,-optimistic--instruction-set:-optimisticThe control policies disable other optimistic additions as well; in the actual frozen images the decoded requirement sets differ only in RCPC/RCPC2, while baseline/VectorT128 requirements remain. RCPC1-only has a positive RCPC1 requirement, not an RCPC2-absence assertion.
Statistics: positive savings mean baseline minus candidate. The primary estimator is the mean of paired-block mean differences; 10,000 fixed-seed hierarchical bootstrap replicates resample paired blocks and then launches within each arm. Intervals are descriptive, unadjusted for multiple comparisons and based on one device/session per study. With filtering, each block retains equal weight even when arm sample counts differ, so paired estimates can differ from pooled mean differences.
At the user's request, a post-hoc 1.5 x IQR Tukey rule was applied identically within each device/policy to TTID and useful-content time. A whole launch is excluded if either metric lies outside its fence; fixed fences are used during bootstrap. Unfiltered data and exact exclusions remain below. Outliers are not automatically measurement errors. The interpretation margins declared before these runs were 5% for a substantial saving and +/-1% for negligible startup difference; non-significance is not equivalence.
Untraced timing results
All successful launches, before outlier filtering
With the requested outlier filter
Historical conservative policy versus stock: paired effects
All intervals below are descriptive 95% saving intervals. Negative saving means slower.
Useful-content Ready
Initial display (TTID)
Interpretation: A16 improves robustly with either typemap; every stock/conservative paired block favors conservative codegen. S23 useful-content estimates remain inconclusive. The filtered S23 trimmable TTID contrast indicates about 11.3 ms / 2.10% slower initial display, with a slowdown interval of approximately 1.0-21.1 ms; this should not be hidden behind the inconclusive Ready result. The filtered Ready intervals still permit approximately 5.9% slowdown with trimmable or 1.8% with LLVM-IR. No universal high-end non-regression or steady-state-throughput claim is made.
RCPC1-only sensitivity comparisons (trimmable)
Outliers, interruptions and environment
Trimmable S23 filtering removes eight unusually fast conservative runs as well as slow outliers, shifting its Ready estimate from 0.34% slower raw to 2.21% slower filtered. LLVM-IR S23 filtering removes one slow run per policy. The two views are retained rather than choosing whichever supports the hypothesis.
S23 installer timeouts interrupted the final block of each study before the affected launch measurements. Completed observations were preserved. Active-user/non-streaming installs were used on recovery; the last APK had committed despite the client timeout and was hash-verified before reuse. Conditioning was retained, and first-five-block sensitivity was evaluated. The LLVM incremental build initially had a stale after-link composite; it was detected and excluded before device measurement, then the postprocessing stamps were invalidated and the packaged image checked against fresh crossgen2 output.
Benchmark-matched single-launch JIT census
One separate seeded traced launch per typemap/policy/phone (eight total), cut at Ready. Each census APK has the exact timed build's managed inputs, R2R composite, profiles and runtime feature switches. Only the native diagnostic-port configuration changes.
AndroidEnableProfiler=trueenables transport whileEnableDiagnostics=false,EventSourceSupport=falseandMetricsSupport=falsekeep the managed diagnostic features disabled. No allocation sampler or sampling profiler is used.Provider:
Microsoft-Windows-DotNETRuntime:0x4000080018:5. Native Java Ready/Idle timestamps are aligned with the runtime's CLOCK_MONOTONIC counter; its inferred frequency is 1 GHz and no count changes within the marker's 1 ms quantization interval. All eight traces report zero lost events. Traced elapsed times are not added to the untraced performance cohorts.Startup compilation-event counts equal distinct runtime-method counts in these launches. Tier classification comes from matched method-load flags, not thread, duration or code-version ID.
QuickJitted/instrumented quick JIT are Tier 0; plainOptimizedis not Tier 1; instrumented Tier 1 and OSR remain separate. No MinOpt or unknown-tier startup events were observed.Expected compilation after Ready, through the approximately three-second Idle marker
These are compilation events, not distinct methods across tiers, and are excluded from startup counts. Tier 1 is expected runtime optimization, not a startup profile-coverage failure.
Earlier compiler/profile isolation experiments: why the illustrative two-JIT result differs
The earlier trimmable diagnostic/managed-marker build had 379/380/380 Tier 0 starts, 34 other Optimized starts per run, and no Tier 1/OSR before Ready. A fresh control had 380 Tier 0, including 343 rejected R2R entries. Device-specific RCPC2 exclusion produced 37/37/37; conservative optimistic-ISA policy produced 50/37/37, retaining the initial finalizer/lock-path extra work.
Of the stable 37, 36 were absent from all eight original profile inputs and one was explicitly bypassed by R2R. Targeted compiler probes could emit the AndroidRuntime constructor, a generic runtime helper and a JNI creation-option setter. A new MethodDetails/R2R-entrypoint-enabled capture (
0x6000080019:5) and profile merge added 51 records, 15,271 -> 15,322. Conservative codegen plus that completed profile yielded 2/2/2 Tier 0 in that diagnostic variant, about 0.826 ms combined compilation spans, plus 27 other Optimized compilations per run.The two exceptions were
ProbabilisticMap.SetCharBit([BypassReadyToRun]for representation correctness) andSingle.MultiplyAddEstimate(mandatory nondeterministic estimate-intrinsic expansion explicitly blocked in R2R; an explicit single-method probe reportsJIT implementation limitation). Do not remove these safety/semantic restrictions to make a counter zero.That two-method count is not the timed APK's count. The later matched census above preserves the feature-off/native-Java-Ready timing binaries, including their additional JNI readiness, feature-off first-use and UI/scroll paths. It supersedes the earlier diagnostic counts for interpreting the timing tables. The custom profile changes and runtime exceptions are not modified by this PR.
Representation constraint | Estimate-intrinsic R2R restriction
Reproducible per-block timing observations and exclusion audit
All values are milliseconds. Lists contain measured launches 1-8 in each block; conditioning is omitted.
excludedlists the 1-based launch indices removed by the rule above. Trimmable block order: S/R/C, R/C/S, C/S/R, C/R/S, R/S/C, S/C/R. LLVM-IR order alternates S/C and C/S. S=stock, R=RCPC1-only, C=conservative. These observations reproduce both raw and filtered summaries and paired-block contrasts.Custom-profile SHA-256s:
5a4c8b28d00e3bc35df21172aa8452f4e4d93e03d6224ef087c8bc26d7c3e5841ee28cfdf8d327b9db792669d5b38018d412e35b7896cf6c5ed70147d3363f97Signed timing APK sizes: 24,733,775 B for each trimmable ISA policy; 24,098,895 B for each LLVM-IR ISA policy. Native composites were frozen and shared across phones. Raw captures, complete method/event records, compiler logs/maps and hash manifests are retained in the investigation artifacts; the block data above are embedded here so timing summaries do not depend on private filesystem links.
Validation
ReadyToRunInstructionSettest was removed; the earlier invalid x86 case had exposed that property-only checks cannot establish runtime support.AndroidApplication=true,PublishReadyToRun=true, ARM64 selects the complete explicit list and x64 retains-optimistic, preserving--mapcsv. These are SDK property/hook checks, not new full application builds.BasicApplicationPublishReadyToRuntest still checks the full ARM64 argument in x64/ARM64 composite/non-composite coverage for CI.All device windows were cleaned up and released; only dedicated study packages and owned diagnostic routes/processes were used. Raw traces, complete method/event records, compiler maps, packaging audits and hash manifests are retained. Public runtime source links identify the inspected base
ab19415702aa8139d5369e47c73edb47343c34ad; actual emitted bytes/device results are primary evidence, not an assumption that every file in the packaged VMR is identical to that base.No issue is linked; this change follows the startup profiling investigation.