[linker] Add linker step to remove calls to Console.WriteLine - #26248
Open
Adham-Kiwan wants to merge 1 commit into
Open
[linker] Add linker step to remove calls to Console.WriteLine#26248Adham-Kiwan wants to merge 1 commit into
Adham-Kiwan wants to merge 1 commit into
Conversation
…otnet#16781) Adds a new opt-in linker optimization, `remove-console-writeline`, that strips calls to System.Console.WriteLine from application code during trimming, to help reduce shipped app size (these calls - and any string formatting feeding them - are typically dead weight once an app has shipped). - tools/dotnet-linker/RemoveConsoleWriteLineCallsStep.cs: new AssemblyModifierStep (same base class/conventions as OptimizeGeneratedCodeStep) that scans method bodies for calls to Console.WriteLine and nops them out. Call arguments are replaced with 'pop' instructions (one per argument) instead of also being nop'ed out, so that any side effects from evaluating them (e.g. a method call passed as an argument) are preserved - only the actual write to the console is removed. Scope is intentionally limited to Console.WriteLine (not the other Console.Write* members), to keep the initial change small; this can be broadened later if needed. - tools/common/Optimizations.cs: registers the new `remove-console-writeline` optimization (opt-in only, not enabled by default on any platform, since - unlike the other optimizations here - it changes the observable behavior of the app). - dotnet/targets/Xamarin.Shared.Sdk.targets: registers the new step in the linker pipeline (BeforeStep="MarkStep"), following the same pattern as OptimizeGeneratedCodeStep. - docs/website/optimizations.md: documents the new optimization, following the existing format for this file. - tests/mtouch/MTouch.cs: updates the MT0132 test's expected message to include the new optimization name in the list of valid optimizations. Modeled on tools/dotnet-linker/OptimizeGeneratedCodeStep.cs (registration/ base class conventions) and tools/linker/OptimizeGeneratedCode.cs (IL nop-ing pattern), which are the existing custom linker steps that rewrite method bodies in this codebase. See the PR description for the test plan and what could/couldn't be verified in this environment (the full dotnet/macios bootstrap - Xcode, the pinned preview .NET SDK, etc. - was not available).
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.
Fixes #16781.
Summary
Adds a new opt-in linker optimization,
remove-console-writeline, that strips calls toSystem.Console.WriteLinefrom application code during trimming/linking, to help reduce shipped app size. As noted in the issue, these calls (and any string-formatting logic used to build their arguments) are typically dead weight once an app has shipped, since there's usually nobody around to read a mobile app's standard output.What's included
tools/dotnet-linker/RemoveConsoleWriteLineCallsStep.cs(new): a custom linker step, subclassingAssemblyModifierStep, that walks method bodies looking forcalls toConsole.WriteLineand removes them:nop(rather than removed outright), so that any branches or exception handler regions that reference that exact instruction stay valid - this mirrors the existingNophelper pattern intools/linker/OptimizeGeneratedCode.cs.popinstruction instead of also being nop'ed out. This keeps the evaluation stack balanced and preserves any side effects from computing the arguments (e.g.Console.WriteLine(ComputeSomething())still callsComputeSomething(), it just no longer prints the result). Only the actual write to the console is removed.tools/common/Optimizations.cs: registersremove-console-writelineas a new named optimization, following the existing--optimize=[+|-]<name>mechanism shared by mtouch/mmp/dotnet builds. It's opt-in on every platform (not enabled by default anywhere), because - unlike the other optimizations in this file - it changes the observable behavior of the app (visible console output disappears). This can be revisited if maintainers would prefer a default-on-for-release behavior instead, per the issue's framing.dotnet/targets/Xamarin.Shared.Sdk.targets: registers the new step in the trimmer pipeline (BeforeStep="MarkStep", gated only on_AreAnyAssembliesTrimmed), right next toOptimizeGeneratedCodeStep. The step is always registered when assemblies are trimmed, but internally no-ops unless the optimization was explicitly turned on (same patternOptimizeGeneratedCodeStepuses for its own sub-optimizations).docs/website/optimizations.md: documents the new optimization in the same format as the existing entries on that page.tests/mtouch/MTouch.cs: updates theMT0132test's expected "valid optimizations" list to include the new name (it's an exhaustive list built fromOptimizations.opt_names, so adding a new optimization requires this one-line update).Prior art followed
tools/dotnet-linker/OptimizeGeneratedCodeStep.csandtools/dotnet-linker/Steps/AssemblyModifierStep.cs.tools/linker/OptimizeGeneratedCode.cs.--optimize=flag /Optimizationsmechanism:tools/common/Optimizations.cs._TrimmerCustomStepsitem group indotnet/targets/Xamarin.Shared.Sdk.targets.Scope decisions
Console.WriteLineis targeted, not the otherConsole.Write*members (Console.Write, writing directly viaConsole.Out, etc.), per the issue's suggestion to keep scope tight. Extending to the rest of the family should be a small, mechanical follow-up (themr.Name/DeclaringTypecheck would just need a couple more cases) if there's interest.Test plan - please read carefully
I was not able to build or run this repository's actual build/test suite in my environment, per the task constraints: this repo requires a full Xcode + mono/.NET-for-Apple bootstrap (
make bootstrap), which pins a specific preview .NET SDK viaglobal.json(10.0.400-preview.0.26371.110, fetched viamake dotnet -C builds) and isn't available here. I did not attempt it, and I want to be explicit that the actualdotnet-linker.csprojwas never compiled, and the new step was never run through real illink/the real trimmer pipeline, or against a real app.What I did do to gain confidence in the change, given that constraint:
tools/dotnet-linker/*.cs,tools/dotnet-linker/Steps/*.cs,tools/linker/OptimizeGeneratedCode.cs, and the_TrimmerCustomStepswiring inXamarin.Shared.Sdk.targets) to match the exact conventions (base class, namespace, file placement,Name/ErrorCodemembers, registration style,AreAnyAssembliesTrimmed/PrepareAssembliesgating).ProcessMethod/RemoveCalllogic from the new step into a standalone console app referencing theMono.CecilNuGet package directly (outside this repo, since a plaindotnet build/dotnet runwas usable there without the pinned SDK), then:Console.WriteLinecall shapes: zero args, one string arg, an interpolated string (which compiles to theDefaultInterpolatedStringHandlerpattern - the trickiest case, since the actualWriteLinecall only takes the final formatted string), a multi-argument format overload (WriteLine(string, object, object)), and an argument with an observable side effect (a method call that also writes to a log file).call→nop+pop-per-argument rewrite looked correct for every shape above.InvalidProgramException/verification failure (i.e. the stack balance is correct), produced zero console output (allConsole.WriteLinecalls were successfully elided), and that the side-effecting argument still ran exactly once (confirmed via its log file) - i.e. argument side effects are preserved as intended.AssemblyModifierStep,ConfigurationAwareStep,Mono.Tuner.Extensions.Is,Mono.Linker.AssemblyAction, etc., copied from what I read in this repo) to catch any compile-time mistakes in the class itself. This compiled cleanly, but it is not a substitute for compiling against the real base classes indotnet-linker.csproj, since I could have subtly misread a signature.What this means concretely: I'm confident the IL-rewriting logic is correct (it's been exercised end-to-end on real IL and a real runtime), and I'm confident the step follows this codebase's conventions closely (by close reading), but the integration point - the actual custom-step registration loading correctly into illink,
LinkerConfiguration/Optimizationsplumbing end-to-end, and a real app build with--optimize=+remove-console-writeline- has not been exercised, and I'd very much appreciate maintainer/CI validation on that front before merge. If CI surfaces a wiring mistake (e.g. a namespace/type-name typo in the_TrimmerCustomStepsentry), it should be a quick fix.dotnet-linker.csproj.--optimize=+remove-console-writelineactually removesConsole.WriteLineoutput from a real trimmed app build.🤖 Generated with Claude Code