Skip to content

[NativeAOT] Use NativeLinker and invoke lld directly for linking#11256

Open
sbomer wants to merge 14 commits intomainfrom
dev/sbomer/nativelink-task
Open

[NativeAOT] Use NativeLinker and invoke lld directly for linking#11256
sbomer wants to merge 14 commits intomainfrom
dev/sbomer/nativelink-task

Conversation

@sbomer
Copy link
Copy Markdown
Member

@sbomer sbomer commented Apr 30, 2026

Replace the clang Exec invocation with the LinkNativeAotSharedLibrary task
that calls ld.lld directly from the NDK. The task uses NativeLinker.cs which
handles response files, ABI-specific flags, and debug symbol stripping.

Compute NDK paths for CRT objects (crtbegin_so.o/crtend_so.o), compiler-rt
builtins, and libunwind. Pass system libraries (dl, z, log, m, c) and
library search paths explicitly.

Fix NativeLinker.cs quoting for soname with spaces, and guard against empty
runtime pack library directory.

Builds on #11148.
Contributes to #10697

sbomer and others added 10 commits April 17, 2026 10:27
Set NativeLib=static so ILC produces a .a archive via ar instead of invoking
the linker directly. Add _AndroidLinkNativeAotSharedLibrary target that runs
after LinkNative and links the ILC .o output into a .so using the NDK clang
wrapper. This gives Android full control over the native linker invocation,
following the same approach used by macios.

Reproduce the flags that LinkNative and SetupOSSpecificProps would have
provided for NativeLib=Shared:
- -shared, -Wl,-e,0x0, -Wl,-z,max-page-size=16384 (from LinkerArg)
- --version-script, --export-dynamic, --discard-all, --gc-sections (from
  CustomLinkerArg inside LinkNative)
- -fuse-ld=lld (from LinkerArg via LinkerFlavor)
- sections.ld linker script to retain the __modules section

Set IlcExportUnmanagedEntrypoints=true so ILC exports [UnmanagedCallersOnly]
methods as native symbols, required for JNI entry points.

Clear LinkerFlavor inside _AndroidBeforeIlcCompile to work around an ILC
targets bug where _LinkerVersion detection is skipped for NativeLib=Static
but the numeric comparison in LinkNative still evaluates.
Context: dotnet/runtime#126978

The resulting linker command line is identical to the original.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add Inputs/Outputs to _AndroidLinkNativeAotSharedLibrary so incremental
builds can skip relinking when inputs haven't changed. Add FileWrites
for the .so and sections.ld so Clean can account for generated files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
On Windows, ILC's LinkNative writes linker args to a response file instead
of passing them inline — cmd.exe has quoting issues with spaces in paths.
Match that behavior in _AndroidLinkNativeAotSharedLibrary.

Fixes NativeAOT build failures for projects with spaces or special characters
in their names (e.g. CheckProjectWithSpaceInNameWorks tests).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ILC's LinkNative target defaults CppLibCreator to the host 'ar', which
does not understand ELF objects when cross-compiling for Android. On
macOS this caused Xcode's ranlib to emit spurious 'empty table of
contents' warnings for every ABI.

Set CppLibCreator to llvm-ar (from the NDK toolchain, already on PATH)
so the archiver can correctly process ELF .o files.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ILC's LinkNative target strips symbols via llvm-objcopy after linking, but
those steps are skipped when NativeLib=Static. Add the same three objcopy
invocations to _AndroidLinkNativeAotSharedLibrary:
1. Extract debug info to .dbg file
2. Strip debug symbols from the .so
3. Add gnu-debuglink back to the .so

Without stripping, the .so was ~8MB larger than the original, causing
BuildReleaseArm64 apkdiff regression tests to fail.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add opt-in properties to NativeLinker.cs for NativeAOT-specific flags:
ExportDynamic, UseEhFrameHdr, DiscardAll, AsNeeded, HashStyleBoth,
LittleEndian, VersionScript, LinkerScript, EntryPoint,
CompressDebugSections, AdditionalSearchPaths.

Move --export-dynamic from standardArgs to an opt-in ExportDynamic
property (default true for back-compat with existing consumers).

Add LinkNativeAotSharedLibrary task that uses NativeLinker to link the
ILC .o output into a .so using ld.lld directly, replacing the clang
wrapper invocation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace the clang Exec invocation with the LinkNativeAotSharedLibrary task
that calls ld.lld directly from the NDK. The task uses NativeLinker.cs which
handles response files, ABI-specific flags, and debug symbol stripping.

Compute NDK paths for CRT objects (crtbegin_so.o/crtend_so.o), compiler-rt
builtins, and libunwind. Pass system libraries (dl, z, log, m, c) and
library search paths explicitly.

Fix NativeLinker.cs quoting for soname with spaces, and guard against empty
runtime pack library directory.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 30, 2026 17:33
@sbomer sbomer changed the title Use NativeLinker for Native AOT linking [NativeAOT] Use NativeLinker and invoke lld directly for linking Apr 30, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR shifts NativeAOT shared-library linking to use the existing NativeLinker infrastructure (response files, ABI flags, symbol stripping) instead of invoking the NDK clang wrapper, and wires this into the NativeAOT MSBuild targets.

Changes:

  • Extend NativeLinker with NativeAOT-focused linker options (export-dynamic, version scripts, linker scripts, debug section compression, extra -L paths, etc.).
  • Add a new MSBuild task (LinkNativeAotSharedLibrary) that drives NativeLinker to produce a .so from ILC output + runtime/static archives.
  • Update Microsoft.Android.Sdk.NativeAOT.targets to set NativeLib=static, enable unmanaged entrypoint exports, and run the new link step after LinkNative.
Show a summary per file
File Description
src/Xamarin.Android.Build.Tasks/Utilities/NativeLinker.cs Adds configurable linker options needed for NativeAOT and fixes argument quoting/guards.
src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs New task orchestrating NativeAOT .so linking via NativeLinker.
src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets MSBuild integration: produce .a from ILC then link final .so via the new task, plus NDK CRT/rt/unwind inputs.

Copilot's findings

Comments suppressed due to low confidence (1)

src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs:140

  • Directory.CreateDirectory (Path.GetDirectoryName (LinkerScript)!) can throw if LinkerScript is just a filename (no directory component), because Path.GetDirectoryName() would return null. Avoid the null-forgiving operator here and handle the null case explicitly (or use IntermediateOutputPath as the directory when none is provided).
		if (!LinkerScriptContent.IsNullOrEmpty () && !LinkerScript.IsNullOrEmpty ()) {
			Directory.CreateDirectory (Path.GetDirectoryName (LinkerScript)!);
			File.WriteAllText (LinkerScript, LinkerScriptContent);
		}
  • Files reviewed: 3/3 changed files
  • Comments generated: 4

Comment thread src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs Outdated
Comment thread src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs Outdated
Comment thread src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs
Comment thread src/Xamarin.Android.Build.Tasks/Tasks/LinkNativeAotSharedLibrary.cs Outdated
# Conflicts:
#	src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.NativeAOT.targets
@sbomer sbomer self-assigned this May 1, 2026
sbomer and others added 3 commits May 1, 2026 17:21
don't strip and save debug symbols separately. Matches the pattern used by
LinkApplicationSharedLibraries.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use TaskItem copy constructor instead of creating from ItemSpec alone,
so metadata like NativeLinkWholeArchive and NativeDontExportSymbols is
preserved. NativeLinker checks these for --whole-archive and --exclude-libs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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.

2 participants