Skip to content

[Build] Add per-file up-to-date check in CompileNativeAssembly#11055

Draft
davidnguyen-tech wants to merge 3 commits intodotnet:mainfrom
davidnguyen-tech:feature/compile-native-assembly-skip-unchanged
Draft

[Build] Add per-file up-to-date check in CompileNativeAssembly#11055
davidnguyen-tech wants to merge 3 commits intodotnet:mainfrom
davidnguyen-tech:feature/compile-native-assembly-skip-unchanged

Conversation

@davidnguyen-tech
Copy link
Copy Markdown
Member

@davidnguyen-tech davidnguyen-tech commented Mar 30, 2026

Summary

Two related changes that together skip _CompileNativeAssemblySources on no-type-change incremental CoreCLR builds:

  1. Stabilize the typemap LLVM-IR output so that builds with no type changes produce a byte-identical typemaps.<abi>.ll, letting Files.CopyIfStreamChanged preserve its mtime → MSBuild's target-level Inputs/Outputs check skips _CompileNativeAssemblySources entirely.
  2. Per-file llc up-to-date check as defense-in-depth: when the target does run (because some .ll did change), skip individual files whose .o is already up to date.

Problem

On incremental CoreCLR builds, _CompileNativeAssemblySources was rebuilding every .ll.o even when nothing relevant had changed:

  • TypeMappingDebugNativeAssemblyGeneratorCLR embedded an mvid_hash and emitted entries in hash-bucket order, so any assembly rebuild flipped bytes inside typemaps.<abi>.ll even when the type set was identical. CopyIfStreamChanged then updated mtime, which busted the target's Inputs/Outputs check.
  • <CompileNativeAssembly> (the task) had no per-file granularity — once the target ran, it called llc for every source even if .o was newer than .ll.

Fix

Utilities/TypeMappingDebugNativeAssemblyGeneratorCLR.cs

  • Remove mvid_hash from emitted typemap entries.
  • Sort entries by managed type name so output is deterministic across runs.

Tasks/CompileNativeAssembly.cs

  • In RunAssembler(), compare File.GetLastWriteTimeUtc of the output .o against the input .ll. If .o exists and is ≥ .ll, skip the llc invocation and log:
    [LLVM llc] Skipping '<x>.ll' because '<x>.o' is up to date

These changes also benefit _CompileNativeAssemblySources callers in Microsoft.Android.Sdk.NativeRuntime.targets and Microsoft.Android.Sdk.NativeAOT.targets.

Verification

Verified end-to-end on Samsung SM-G960F (R58Y30J85VV) against dotnet new maui -sc (MauiVersion 11.0.0-preview.4.26221.9, net11.0-android, CoreCLR, arm64-v8a, Debug):

  • Typemap determinismtypemaps.arm64-v8a.ll byte-identical and mtime-preserved across a no-type-change edit.
  • Target skip — head-to-head binlog probes:
    • PR side: Skipping target "_CompileNativeAssemblySources" because all output files are up-to-date
    • main side: Building target "_CompileNativeAssemblySources" completely
  • Per-file llc skip — exercised by the integration test (see below).
  • App still works — deploys, launches, MainActivity resolves through the typemap name-iterate path.
  • Wall-time — 5 runs each side, ~1.0–1.6 s saved on this minimal app. Savings scale with typemap size; expect more on real apps.

Testing

CompileNativeAssemblySourcesSkipsUnchangedFiles (in IncrementalBuildTest.cs) verifies both behaviours in one build:

  • Forces typemap regeneration by appending a new [Activity]-attributed TypeMapProbeActivity class — GenerateJavaStubs adds a JCW, so typemaps.<abi>.ll content + mtime genuinely change.
  • Asserts _CompileNativeAssemblySources is not skipped at the target level.
  • Asserts the per-file skip log line fires for environment.<abi>.ll (which is unaffected by user types and so stays up-to-date).

Test runs at Detailed verbosity so LogDebugMessage (Low importance) is captured.

davidnguyen-tech and others added 2 commits March 30, 2026 19:16
When _CompileNativeAssemblySources runs, it recompiles ALL .ll files
even if only some have changed. This is because MSBuild's target-level
Inputs/Outputs is all-or-nothing — if any .ll file is newer than any
.o file, every file gets recompiled.

Add a per-file timestamp check in RunAssembler() — if the output .o is
newer than the input .ll, that file is skipped. This saves time on
incremental CoreCLR builds where upstream generators use
CopyIfStreamChanged to preserve .ll timestamps for unchanged files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add CompileNativeAssemblySourcesSkipsUnchangedFiles integration test
that verifies on incremental CoreCLR builds, unchanged .ll files are
not recompiled by the CompileNativeAssembly task while changed .ll
files are still correctly compiled.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidnguyen-tech davidnguyen-tech force-pushed the feature/compile-native-assembly-skip-unchanged branch from 41fdedd to f644fca Compare March 30, 2026 17:17
Remove build-specific MVID data from the CoreCLR debug typemap assemblies
array, making the generated .ll files byte-identical across incremental
builds when only C# code changes (no type additions/removals).

Generator changes (TypeMappingDebugNativeAssemblyGeneratorCLR.cs):
- Remove mvid_hash field from TypeMapAssembly struct
- Remove MVID field (was already NativeAssembler-ignored)
- Sort data.UniqueAssemblies by name before building the assembly
  names blob, ensuring deterministic blob offsets
- Sort uniqueAssemblies list by Name instead of mvid_hash

Runtime changes (typemap.cc, xamarin-app.hh):
- Remove mvid_hash from the native TypeMapAssembly struct
- Replace MVID-based binary search with assembly iteration:
  for each assembly, construct "TypeName, AssemblyName" and look up
  in the managed-to-java map. The array is small (~80-100 entries),
  so iteration cost is negligible vs the previous hash+binary-search.

This eliminates the root cause of typemaps.*.ll content changes during
C#-only incremental edits. Combined with the per-file skip from commit
d2f07a1, this saves ~2.85s per CoreCLR incremental build.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant