[Xamarin.Android.Build.Tasks] more LLVM IR generator build perf - #12285
Open
jonathanpeppers wants to merge 3 commits into
Open
[Xamarin.Android.Build.Tasks] more LLVM IR generator build perf#12285jonathanpeppers wants to merge 3 commits into
jonathanpeppers wants to merge 3 commits into
Conversation
`dotnet-trace` of a C#-only incremental build of a MAUI app shows `TypeUtilities.IsIRStruct()` accounting for 345 ms of the 550 ms spent inside `TypeMapGenerator.GenerateNativeAssembly()`, 282 ms of which is `Type.IsStructure()` walking attributes. `IsIRStruct()` was an extension method evaluated on every call, but its result depends only on the member's `Type` and the attributes declared on its `MemberInfo` -- both fixed for the lifetime of the `StructureMemberInfo`. `StructureMemberInfo` is created once per member per structure *type* and then reused for every structure *instance*, so the predicate was recomputed tens of thousands of times per build. Compute it once in the constructor and expose it as a property. Generated output is unchanged: `typemaps.arm64-v8a.ll` and `typemaps.arm64-v8a.o` are both byte-identical (SHA256) before and after, and after the change `TypeUtilities.IsIRStruct` / `TypeUtilities.IsStructure` no longer appear anywhere in the trace. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fb6a74f2-a06f-4528-bc46-70fa9dee8ba9
Comments in the generated LLVM IR are purely descriptive: `llc` ignores
them entirely. They are, however, more than half of the generated file.
For a `dotnet new maui -sc` app on a Debug CoreCLR build:
| File | Comments on | Comments off |
| ------------------------ | ----------- | ------------ |
| `typemaps.arm64-v8a.ll` | 15,181,829 | 7,631,959 |
| `typemaps.arm64-v8a.o` | 4,161,824 | 4,161,824 |
Add a `$(_AndroidEmitLlvmIrComments)` MSBuild property, blank (off) by
default, which flows into `<GenerateTypeMappings/>`,
`<GenerateNativeApplicationConfigSources/>` and
`<GenerateCompressedAssembliesNativeSourceFiles/>`. Setting it to `true`
restores the previous output for anyone debugging the generators.
Where a comment terminated a line of real content, an explicit newline is
written instead, and the hot paths skip *building* the comment string, not
just writing it.
`GenerateTypeMappings` on the same app, interleaved on/off runs, raw ms:
on 733, 793, 807, 839, 841, 848
off 679, 689, 689, 696, 721, 746
That's roughly 100 ms, consistently in the same direction across all
pairs. It is not visible in wall clock (~6.1 s either way) on this
machine.
Correctness, from a matched pair built with the same SDK binary and only
the property flipped:
* `typemaps.arm64-v8a.o` is byte-identical (SHA256
`230894AF63A583A8D2FCAC18C9AA962131A815EFCC307704B6D1E96887A837A0`).
* All three string blobs are byte-identical.
* 132,537 `i32` values appear in an identical sequence.
* Every non-sentinel offset still resolves to the start of a
nul-terminated string in its blob; the 4,909 that don't are all the
`0xFFFFFFFF` sentinel.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fb6a74f2-a06f-4528-bc46-70fa9dee8ba9
Contributor
There was a problem hiding this comment.
Pull request overview
Improves inner-loop build performance for the LLVM IR typemap/native-source generators in Xamarin.Android.Build.Tasks by (1) caching per-member structure classification and (2) allowing LLVM IR comment emission to be disabled (smaller .ll, less string-building work), while plumbing the new switch from MSBuild into the generators.
Changes:
- Cache
StructureMemberInfo.IsIRStructat construction time and replace the previous per-call type-walk extension method. - Add an
EmitComments/EmitLlvmIrCommentsswitch and thread it through MSBuild → tasks → composers →LlvmIrGenerator, gating comment string creation/writes in hot paths. - Update MSBuild targets to pass the new property to relevant tasks (
GenerateTypeMappings,GenerateNativeApplicationConfigSources,GenerateCompressedAssembliesNativeSourceFiles).
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets | Wires _AndroidEmitLlvmIrComments into tasks invoked from legacy/common targets. |
| src/Xamarin.Android.Build.Tasks/Utilities/TypeMapGenerator.cs | Adds EmitComments and passes it into the LLVM IR composer for typemap generation. |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/TypeUtilities.cs | Removes the old StructureMemberInfo.IsIRStruct(...) extension method. |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureMemberInfo.cs | Computes and stores IsIRStruct once per member (perf). |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/StructureInfo.cs | Uses cached IsIRStruct when deciding whether to recurse into embedded structs. |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrModule.cs | Switches struct preparation recursion to cached IsIRStruct. |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrInstructions.cs | Gates end-of-line comment emission on the new EmitComments setting. |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrGenerator.cs | Introduces EmitComments plumbing and gates multiple comment hot paths. |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrFunctionBody.cs | Gates per-instruction comment emission on EmitComments. |
| src/Xamarin.Android.Build.Tasks/Utilities/LlvmIrGenerator/LlvmIrComposer.cs | Adds EmitComments to composers and propagates it into LlvmIrGenerator. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateTypeMappings.cs | Adds task parameter EmitLlvmIrComments and passes it to TypeMapGenerator. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateNativeApplicationConfigSources.cs | Adds task parameter EmitLlvmIrComments and propagates it to the IR generator. |
| src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs | Adds task parameter EmitLlvmIrComments and propagates it to the IR generator. |
| src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.TypeMap.LlvmIr.targets | Wires _AndroidEmitLlvmIrComments into GenerateTypeMappings for SDK builds. |
…nts are off Review feedback on the previous commit: two call sites write indentation *before* calling into the comment writers, so with `$(_AndroidEmitLlvmIrComments)` off they emitted an indent-only line instead of nothing at all. * `LlvmIrFunctionBodyComment` is an item whose entire content is a comment. `LlvmIrFunctionBodyItem.Write()` writes the indent, then the comment, then unconditionally terminates the line, so the item turned into a blank line. Add a `IsCommentOnly` hook so such items are skipped outright rather than writing an empty line. * `WriteGlobalVariables()` writes a newline and the current indent before a group delimiter's comment. Fold `EmitComments` into the surrounding condition so the whole block is skipped. Note this is *not* the same as making `WriteCommentLine()` always terminate the line, which was the other option considered. The vast majority of comments are on lines of their own, so that would turn ~182,000 comment lines into ~182,000 blank lines and give back most of the size win. Also fix a "Constrict" -> "Construct" typo in a nearby exception message. The generated type maps do not exercise either path, so this is defensive: `typemaps.arm64-v8a.ll` is byte-identical to before this commit both on (SHA256 `A67E964C...B3E2ED`) and off (`E0A1F539...D152BD`), and `typemaps.arm64-v8a.o` remains identical between on and off (`230894AF...A837A0`). Neither `.ll` contains any whitespace-only line. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: fb6a74f2-a06f-4528-bc46-70fa9dee8ba9
This was referenced Jul 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #12280. Squash on merge.
Two more LLVM IR generator wins found by profiling a
dotnet new maui -scapp (Debug, CoreCLR,arm64-v8a) withdotnet-traceagainst the MSBuild worker node.1. Cache
StructureMemberInfo.IsIRStructIsIRStruct()was an extension method that walked the member's type on every call, and it is called once per member per array element. In the trace it was 345 ms of the 550 ms spent inTypeMapGenerator.GenerateNativeAssembly:The answer depends only on the member, which is immutable, so it's now computed once in the
StructureMemberInfoconstructor.TypeUtilities.IsIRStructandTypeUtilities.IsStructureare entirely absent from the frame table of a trace captured after the change..lland.oare byte-identical.2.
$(_AndroidEmitLlvmIrComments), off by defaultComments in the generated IR are purely descriptive —
llcignores them — but they are more than half the file:typemaps.arm64-v8a.lltypemaps.arm64-v8a.oNew private property
$(_AndroidEmitLlvmIrComments), blank (off) by default, flows into<GenerateTypeMappings/>,<GenerateNativeApplicationConfigSources/>and<GenerateCompressedAssembliesNativeSourceFiles/>. Set it totrueto get the old output back when debugging the generators.Where a comment used to terminate a line of real content, an explicit newline is written instead, and the hot paths skip building the comment string rather than just skipping the write.
GenerateTypeMappings, 12 interleaved on/off runs on the same app, raw ms:Roughly 100 ms, same direction in every pair. It is not visible in wall clock (~6.1 s either way) — this machine can't resolve 100 ms end to end, so please don't read more into it than the paired ordering.
Correctness
Matched pair, same SDK binary, only the property flipped:
typemaps.arm64-v8a.obyte-identical — SHA256230894AF63A583A8D2FCAC18C9AA962131A815EFCC307704B6D1E96887A837A0i32values in an identical sequence0xFFFFFFFFsentinelWorth calling out: the
; from:/; to:annotations that go away by default are exactly what that offset check reads. It was run before flipping the default, and$(_AndroidEmitLlvmIrComments)=trueexists so it can be run again.Tests
LlvmIrGeneratorTests9/9GenerateNativeApplicationConfigSourcesTests4/4 (runs realllcand parses the.s)Microsoft.Android.Build.BaseTasks-Tests129/129