Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ The build system supports multiple platforms simultaneously:

The `configure` script is used to select which platforms to build. This script must be run before `make`.

## MSBuild code

Non-standard patterns in .targets files:

- Always use `$(DeviceSpecificIntermediateOutputPath)` instead of `$(IntermediateOutputPath)`.

## Binding System

### bgen (Binding Generator)
Expand Down Expand Up @@ -335,4 +341,4 @@ try {
} catch (Exception e) {
// Code here
}
```
```
22 changes: 22 additions & 0 deletions docs/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,28 @@ See also:
* The [AlternateAppIcon](build-items.md#alternateappicon) item group.
* The [AppIcon](#appicon) property.

## InlineDlfcnMethods

Controls whether the build system replaces runtime calls to `ObjCRuntime.Dlfcn` methods with direct native symbol lookups at build time, eliminating the overhead of `dlsym` at runtime.

The valid options are:

* `compatibility`: Inlines dlfcn method calls but only creates native references for symbols used in `[Field]` attributes. This is more conservative and avoids link errors for symbols that don't exist at build time.
* `strict`: Inlines dlfcn method calls and creates native references for all symbols. This is more aggressive and may cause link errors if referenced native symbols don't exist.
* (empty): Disables inlining of dlfcn method calls.

Default value:
* .NET 11+: `strict` when using NativeAOT (`PublishAot=true`), `compatibility` otherwise.
* .NET 10 and earlier: not set (disabled).

Example:

```xml
<PropertyGroup>
<InlineDlfcnMethods>compatibility</InlineDlfcnMethods>
</PropertyGroup>
```

## iOSMinimumVersion

Specifies the minimum iOS version the app can run on.
Expand Down
69 changes: 69 additions & 0 deletions docs/code/native-symbols.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Native symbols

Native symbols can be referenced from managed code in several ways:

* P/Invokes (DllImports)
* Calls to `dlsym`, which can happen through:
* The various APIs in `ObjCRuntime.Dlfcn`
* The various APIs in `System.Runtime.InteropServices.NativeLibrary`
* A P/Invoke directly into `dlsym`

It's highly desirable to use a direct native reference to native symbols when building a mobile app, for a few reasons:

* It's faster at runtime, and the app is smaller.
* If the referenced native symbol comes from a third-party static library, the
native linker can remove it if it's configured to remove unused code
(because the native linker can't see that the native symbol is in fact used
at runtime) unless there's a direct native reference to the symbol.

On the other hand there's one scenario when a direct native reference is not desirable: when the native symbol does not exist.

In order to create a direct native reference to native symbols, we need to know the names of those native symbols.

## The `InlineDlfcnMethods` property

This behavior is controlled by the `InlineDlfcnMethods` MSBuild property, which
has two modes:

* `strict`: all calls to `ObjCRuntime.Dlfcn` APIs are inlined.
* `compatibility`: only calls that reference symbols from `[Field]` attributes are inlined.

See the [build properties documentation](../building-apps/build-properties.md) for default values.

## How it works

During the build we try to collect the following:

* Any property or field with the `[Foundation.Field]` attribute: we collect the symbol name.
* Any calls to the `ObjCRuntime.Dlfcn` APIs: we try to collect the symbol name (this might not always succeed, if the symbol name is not a constant).
* We don't process calls to `System.Runtime.InteropServices.NativeLibrary` at the moment (this may change in the future, if there's need).

This is further complicated by the fact that we only want to create native
references for symbols that survive trimming.

So we do the following:

1. Before or during trimming, we run the inlining steps:

* `ProcessExportedFields`: collects all members with `[Field]` attributes.

* `InlineDlfcnMethodsStep`: inspects all calls to `ObjCRuntime.Dlfcn`, and
inlines them depending on the selected mode. If inlined, the step creates
a P/Invoke to a native method that will return the address for that symbol
(using a direct native reference), and modifies the code that fetches that
symbol to call said P/Invoke.

2. After trimming, we figure out which of those symbols survived:

* For ILTrim: the `_CollectPostILTrimInformation` MSBuild target inspects
the trimmed assemblies and collects all the inlined dlfcn P/Invokes that
survived. Per-assembly results are cached to speed up incremental builds.

* For NativeAOT: the `_CollectPostNativeAOTTrimInformation` MSBuild target
inspects the native object file (or static library) produced by NativeAOT,
collects all unresolved native references, and filters them against the
inlined dlfcn symbols to determine which survived.

3. The `_PostTrimmingProcessing` MSBuild target takes the surviving symbols
from either path, generates the corresponding native C code, and adds it to
the list of files to compile and link into the final executable.
6 changes: 6 additions & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.props
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,12 @@
<AccelerateBuildsInVisualStudio Condition="'$(AccelerateBuildsInVisualStudio)' == ''">false</AccelerateBuildsInVisualStudio>
</PropertyGroup>

<!-- Set default value for InlineDlfcnMethods based on .NET version and NativeAOT -->
<PropertyGroup Condition="'$(InlineDlfcnMethods)' == '' And $([MSBuild]::VersionGreaterThanOrEquals($(TargetFrameworkVersion), '11.0'))">
<InlineDlfcnMethods Condition="'$(_UseNativeAot)' == 'true'">strict</InlineDlfcnMethods>
<InlineDlfcnMethods Condition="'$(InlineDlfcnMethods)' == ''">compatibility</InlineDlfcnMethods>
</PropertyGroup>

<!-- Set the default RuntimeIdentifier if not already specified. -->
<PropertyGroup Condition="'$(_RuntimeIdentifierIsRequired)' == 'true' And '$(RuntimeIdentifier)' == '' And '$(RuntimeIdentifiers)' == '' ">
<!-- The _<platform>RuntimeIdentifier values are set from the IDE -->
Expand Down
108 changes: 108 additions & 0 deletions dotnet/targets/Xamarin.Shared.Sdk.targets
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@
</PropertyGroup>

<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.CompileNativeCode" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.CollectPostILTrimInformation" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.CollectUnresolvedNativeSymbols" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.ComputeNativeAOTSurvivingNativeSymbols" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.FindAotCompiler" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.GetFullPaths" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.InstallNameTool" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.LinkNativeCode" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.MergeAppBundles" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.MobileILStrip" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.MacDevMessage" AssemblyFile="$(_TaskAssemblyName)" />
<UsingTask Runtime="$(_TaskRuntime)" TaskName="Xamarin.MacDev.Tasks.PostTrimmingProcessing" AssemblyFile="$(_TaskAssemblyName)" />

<!-- Project types and how do we distinguish between them

Expand Down Expand Up @@ -620,7 +624,9 @@
@(_BundlerEnvironmentVariables -> 'EnvironmentVariable=Overwrite=%(Overwrite)|%(Identity)=%(Value)')
@(_XamarinFrameworkAssemblies -> 'FrameworkAssembly=%(Filename)')
Interpreter=$(MtouchInterpreter)
InlineDlfcnMethods=$(InlineDlfcnMethods)
IntermediateLinkDir=$(IntermediateLinkDir)
IntermediateOutputPath=$(DeviceSpecificIntermediateOutputPath)
InvariantGlobalization=$(InvariantGlobalization)
HybridGlobalization=$(HybridGlobalization)
ItemsDirectory=$(_LinkerItemsDirectory)
Expand Down Expand Up @@ -764,6 +770,7 @@
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.ApplyPreserveAttributeStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseLinkDescriptionForApplyPreserveAttribute)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkForStaticRegistrarStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkStaticRegistrar)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.MarkNSObjectsStep" Condition="'$(_AreAnyAssembliesTrimmed)' == 'true' And '$(_UseDynamicDependenciesForMarkNSObjects)' == 'true'" />
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="Xamarin.Linker.Steps.InlineDlfcnMethodsStep" Condition="'$(InlineDlfcnMethods)' != ''" />
<!-- The final decision to remove/keep the dynamic registrar must be done before the linking step -->
<_TrimmerCustomSteps Include="$(_AdditionalTaskAssembly)" BeforeStep="MarkStep" Type="MonoTouch.Tuner.RegistrarRemovalTrackingStep" />
<!-- TODO: these steps should probably run after mark. -->
Expand Down Expand Up @@ -1633,6 +1640,87 @@
</ItemGroup>
</Target>

<Target Name="_ComputeTrimmedAssemblies"
DependsOnTargets="_LoadLinkerOutput"
>
<ItemGroup>
<_TrimmedAssembly Include="$(IntermediateLinkDir)*.dll" />
</ItemGroup>
</Target>

<Target Name="_ComputePostTrimmingPaths">
<PropertyGroup>
<_ILTrimSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)inlined-dlfcn\iltrim-surviving-native-symbols.txt</_ILTrimSurvivingNativeSymbolsFile>
<_NativeAOTUnresolvedSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-unresolved-symbols.txt</_NativeAOTUnresolvedSymbolsFile>
<_NativeAOTSurvivingNativeSymbolsFile>$(DeviceSpecificIntermediateOutputPath)nativeaot-surviving-native-symbols.txt</_NativeAOTSurvivingNativeSymbolsFile>
</PropertyGroup>
</Target>

<!-- See docs/code/native-symbols.md for an overview of native symbol handling. -->
<Target Name="_CollectPostILTrimInformation"
Condition="'$(InlineDlfcnMethods)' != ''"
DependsOnTargets="_ComputeTrimmedAssemblies;_ComputePostTrimmingPaths"
Inputs="@(_TrimmedAssembly)"
Outputs="$(_ILTrimSurvivingNativeSymbolsFile)"
>
<CollectPostILTrimInformation
TrimmedAssemblies="@(_TrimmedAssembly)"
SurvivingNativeSymbolsFile="$(_ILTrimSurvivingNativeSymbolsFile)"
CacheDirectory="$(DeviceSpecificIntermediateOutputPath)posttrim-info\cache"
/>
<ReadLinesFromFile File="$(_ILTrimSurvivingNativeSymbolsFile)">
<Output TaskParameter="Lines" ItemName="_AllExecutableSymbols" />
</ReadLinesFromFile>
<ItemGroup>
<FileWrites Include="$(_ILTrimSurvivingNativeSymbolsFile)" />
</ItemGroup>
</Target>

<!-- See docs/code/native-symbols.md for an overview of native symbol handling. -->
<Target Name="_PostTrimmingProcessing"
DependsOnTargets="_CollectPostILTrimInformation;_CollectPostNativeAOTTrimInformation;_ComputeTargetArchitectures;_ComputePostTrimmingPaths"
>
<ItemGroup>
<_SurvivingNativeSymbolsFile Include="$(_ILTrimSurvivingNativeSymbolsFile)" Condition="Exists('$(_ILTrimSurvivingNativeSymbolsFile)')" />
<_SurvivingNativeSymbolsFile Include="$(_NativeAOTSurvivingNativeSymbolsFile)" Condition="Exists('$(_NativeAOTSurvivingNativeSymbolsFile)')" />
</ItemGroup>
<PostTrimmingProcessing
Architecture="$(TargetArchitectures)"
OutputDirectory="$(DeviceSpecificIntermediateOutputPath)inlined-dlfcn"
ReferenceNativeSymbol="@(ReferenceNativeSymbol)"
SurvivingNativeSymbolsFiles="@(_SurvivingNativeSymbolsFile)"
>
<Output TaskParameter="NativeSourceFiles" ItemName="_PostTrimmingSourceFiles" />
</PostTrimmingProcessing>

<ItemGroup>
<FileWrites Include="@(_PostTrimmingSourceFiles)" />
<_PostTrimmingSourceFiles>
<OutputFile>$(DeviceSpecificIntermediateOutputPath)posttrim-info-compiled/%(Filename).o</OutputFile>
</_PostTrimmingSourceFiles>
</ItemGroup>

<CompileNativeCode
SessionId="$(BuildSessionId)"
Condition="'$(IsMacEnabled)' == 'true'"
CompileInfo="@(_PostTrimmingSourceFiles)"
DotNetRoot="$(_DotNetRoot)"
MinimumOSVersion="$(_MinimumOSVersion)"
IncludeDirectories="@(_XamarinMainIncludeDirectory)"
SdkDevPath="$(_SdkDevPath)"
SdkIsSimulator="$(_SdkIsSimulator)"
SdkRoot="$(_SdkRoot)"
TargetFrameworkMoniker="$(_ComputedTargetFrameworkMoniker)"
>
</CompileNativeCode>

<ItemGroup>
<_CompiledPostTrimmingFiles Include="@(_PostTrimmingSourceFiles -> '%(OutputFile)')" />
<_NativeExecutableObjectFiles Include="@(_CompiledPostTrimmingFiles)" />
<FileWrites Include="@(_CompiledPostTrimmingFiles)" />
</ItemGroup>
</Target>

<PropertyGroup>
<_CompileNativeExecutableDependsOn>
$(_CompileNativeExecutableDependsOn);
Expand Down Expand Up @@ -1689,13 +1777,33 @@
_ReadAppManifest;
_WriteAppManifest;
_CompileNativeExecutable;
_PostTrimmingProcessing;
_ReidentifyDynamicLibraries;
_AddSwiftLinkerFlags;
_ComputeLinkNativeExecutableInputs;
_ForceLinkNativeExecutable;
</_LinkNativeExecutableDependsOn>
</PropertyGroup>

<!-- See docs/code/native-symbols.md for an overview of native symbol handling. -->
<Target Name="_CollectPostNativeAOTTrimInformation"
Condition="'$(_UseNativeAot)' == 'true'"
DependsOnTargets="_ComputePostTrimmingPaths;_CompileNativeExecutable"
Inputs="$(NativeObject)"
Outputs="$(_NativeAOTSurvivingNativeSymbolsFile)"
>
<CollectUnresolvedNativeSymbols
SessionId="$(BuildSessionId)"
StaticLibrary="$(NativeObject)"
OutputFile="$(_NativeAOTUnresolvedSymbolsFile)"
/>
<ComputeNativeAOTSurvivingNativeSymbols
SessionId="$(BuildSessionId)"
UnresolvedSymbolsFile="$(_NativeAOTUnresolvedSymbolsFile)"
SurvivingNativeSymbolsFile="$(_NativeAOTSurvivingNativeSymbolsFile)"
/>
</Target>

<Target Name="_AddSwiftLinkerFlags" DependsOnTargets="_DetectSdkLocations">
<PropertyGroup>
<!-- Runtime now requires swift https://github.com/dotnet/runtime/commit/2c70e36356e8dfb50e6b32c8b7c9ce1a8e9f1331 -->
Expand Down
Loading
Loading