Skip to content

Commit

Permalink
[NativeAOT] Support exporting methods from referenced assemblies in a…
Browse files Browse the repository at this point in the history
… formal way (#83396)


Fixes #65755
  • Loading branch information
ivanpovazan authored Mar 22, 2023
1 parent 2e8148c commit b69b359
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@
<Target Name="_ComputeAssembliesToCompileToNative" DependsOnTargets="$(IlcDynamicBuildPropertyDependencies)">

<Warning Condition="Exists($(UserRuntimeConfig))" Text="The published project has a runtimeconfig.template.json that is not supported by PublishAot. Move the configuration to the project file using RuntimeHostConfigurationOption." />

<!-- Fail with descriptive error message for common mistake. -->
<Error Condition="$([MSBuild]::VersionLessThan('$(NETCoreSdkVersion)', '6.0.0'))" Text=".NET SDK 6+ is required for native compilation." />
<Error Condition="$([MSBuild]::VersionLessThan('$(TargetFrameworkVersion)', '6.0'))" Text="For native compilation, the project needs to target .NET 6 or higher." />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ The .NET Foundation licenses this file to you under the MIT license.

<NativeObject>$(NativeIntermediateOutputPath)$(TargetName)$(NativeObjectExt)</NativeObject>
<NativeBinary>$(NativeOutputPath)$(TargetName)$(NativeBinaryExt)</NativeBinary>
<IlcExportUnmanagedEntrypoints Condition="'$(NativeLib)' == 'Shared'">true</IlcExportUnmanagedEntrypoints>
<IlcExportUnmanagedEntrypoints Condition="'$(IlcExportUnmanagedEntrypoints)' == '' and '$(NativeLib)' == 'Shared'">true</IlcExportUnmanagedEntrypoints>
<ExportsFile Condition="$(IlcExportUnmanagedEntrypoints) == 'true' and $(ExportsFile) == ''">$(NativeIntermediateOutputPath)$(TargetName)$(ExportsFileExt)</ExportsFile>

<IlcCompileOutput>$(NativeObject)</IlcCompileOutput>
Expand All @@ -98,6 +98,7 @@ The .NET Foundation licenses this file to you under the MIT license.
</PropertyGroup>

<ItemGroup Condition="$(IlcSystemModule) == ''">
<UnmanagedEntryPointsAssembly Include="System.Private.CoreLib" />
<AutoInitializedAssemblies Include="System.Private.CoreLib" />
<AutoInitializedAssemblies Include="System.Private.StackTraceMetadata" Condition="$(IlcDisableReflection) != 'true' or $(IlcGenerateStackTraceData) == 'true'" />
<AutoInitializedAssemblies Include="System.Private.TypeLoader" />
Expand Down Expand Up @@ -241,6 +242,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Include="@(_IlcNoSingleWarnAssemblies->'--nosinglewarnassembly:%(Filename)')" />
<IlcArg Condition="'$(TrimmerDefaultAction)' == 'copyused' or '$(TrimmerDefaultAction)' == 'copy' or '$(TrimMode)' == 'partial'" Include="--defaultrooting" />
<IlcArg Include="--resilient" />
<IlcArg Include="@(UnmanagedEntryPointsAssembly->'--generateunmanagedentrypoints:%(Identity)')" />

<IlcArg Condition="$(IlcDisableReflection) == 'true'" Include="--feature:System.Reflection.IsReflectionExecutionAvailable=false" />

Expand Down Expand Up @@ -310,6 +312,7 @@ The .NET Foundation licenses this file to you under the MIT license.
<CustomLinkerArg Include="/LIBPATH:&quot;%(AdditionalNativeLibraryDirectories.Identity)&quot;" Condition="'$(_targetOS)' == 'win' and '@(AdditionalNativeLibraryDirectories->Count())' &gt; 0" />
<CustomLinkerArg Include="-exported_symbols_list &quot;$(ExportsFile)&quot;" Condition="'$(_IsApplePlatform)' == 'true' and $(ExportsFile) != ''" />
<CustomLinkerArg Include="-Wl,--version-script=$(ExportsFile)" Condition="'$(_targetOS)' != 'win' and '$(_IsApplePlatform)' != 'true' and $(ExportsFile) != ''" />
<CustomLinkerArg Include="-Wl,--export-dynamic" Condition="'$(_targetOS)' != 'win' and '$(_IsApplePlatform)' != 'true' and '$(IlcExportUnmanagedEntrypoints)' == 'true' and '$(NativeLib)' == ''" />
<CustomLinkerArg Condition="Exists('$(_Win32ResFile)')" Include="&quot;$(_Win32ResFile)&quot;" />
<CustomLinkerArg Include="@(LinkerArg)" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ namespace ILCompiler
/// <summary>
/// Computes a set of roots based on managed and unmanaged methods exported from a module.
/// </summary>
public class ExportedMethodsRootProvider : ICompilationRootProvider
public class UnmanagedEntryPointsRootProvider : ICompilationRootProvider
{
private EcmaModule _module;

public ExportedMethodsRootProvider(EcmaModule module)
public UnmanagedEntryPointsRootProvider(EcmaModule module)
{
_module = module;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -572,7 +572,7 @@
<Compile Include="Compiler\DependencyAnalysis\Target_LoongArch64\LoongArch64ReadyToRunHelperNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\Target_LoongArch64\LoongArch64ReadyToRunGenericHelperNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\Target_LoongArch64\LoongArch64UnboxingStubNode.cs" />
<Compile Include="Compiler\ExportedMethodsRootProvider.cs" />
<Compile Include="Compiler\UnmanagedEntryPointsRootProvider.cs" />
<Compile Include="Compiler\GenericDictionaryLookup.cs" />
<Compile Include="Compiler\IRootingServiceProvider.cs" />
<Compile Include="Compiler\JitHelper.cs" />
Expand Down
3 changes: 3 additions & 0 deletions src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,8 @@ internal sealed class ILCompilerRootCommand : RootCommand
new(new[] { "--singlemethodgenericarg" }, "Single method compilation: generic arguments to the method");
public Option<string> MakeReproPath { get; } =
new(new[] { "--make-repro-path" }, "Path where to place a repro package");
public Option<string[]> UnmanagedEntryPointsAssemblies { get; } =
new(new[] { "--generateunmanagedentrypoints" }, Array.Empty<string>, "Generate unmanaged entrypoints for a given assembly");

public OptimizationMode OptimizationMode { get; private set; }
public ParseResult Result;
Expand Down Expand Up @@ -232,6 +234,7 @@ public ILCompilerRootCommand(string[] args) : base(".NET Native IL Compiler")
AddOption(SingleMethodName);
AddOption(SingleMethodGenericArgs);
AddOption(MakeReproPath);
AddOption(UnmanagedEntryPointsAssemblies);

this.SetHandler(context =>
{
Expand Down
21 changes: 13 additions & 8 deletions src/coreclr/tools/aot/ILCompiler/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,6 @@ public int Run()
{
// Either single file, or multifile library, or multifile consumption.
EcmaModule entrypointModule = null;
bool systemModuleIsInputModule = false;
foreach (var inputFile in typeSystemContext.InputFilePaths)
{
EcmaModule module = typeSystemContext.GetModuleFromPath(inputFile.Value);
Expand All @@ -178,10 +177,7 @@ public int Run()
entrypointModule = module;
}

if (module == typeSystemContext.SystemModule)
systemModuleIsInputModule = true;

compilationRoots.Add(new ExportedMethodsRootProvider(module));
compilationRoots.Add(new UnmanagedEntryPointsRootProvider(module));
}

bool nativeLib = Get(_command.NativeLib);
Expand Down Expand Up @@ -209,8 +205,6 @@ public int Run()
if (entrypointModule == null && (!nativeLib || SplitExeInitialization))
throw new Exception("No entrypoint module");

if (!systemModuleIsInputModule)
compilationRoots.Add(new ExportedMethodsRootProvider((EcmaModule)typeSystemContext.SystemModule));
compilationGroup = new SingleFileCompilationModuleGroup();
}

Expand Down Expand Up @@ -238,6 +232,17 @@ public int Run()
}
}

foreach (var unmanagedEntryPointsAssembly in Get(_command.UnmanagedEntryPointsAssemblies))
{
if (typeSystemContext.InputFilePaths.ContainsKey(unmanagedEntryPointsAssembly))
{
// Skip adding UnmanagedEntryPointsRootProvider for modules that have been already registered as an input module
continue;
}
EcmaModule module = typeSystemContext.GetModuleForSimpleName(unmanagedEntryPointsAssembly);
compilationRoots.Add(new UnmanagedEntryPointsRootProvider(module));
}

foreach (var rdXmlFilePath in Get(_command.RdXmlFilePaths))
{
compilationRoots.Add(new RdXmlRootProvider(typeSystemContext, rdXmlFilePath));
Expand Down Expand Up @@ -531,7 +536,7 @@ void RunScanner()
ExportsFileWriter defFileWriter = new ExportsFileWriter(typeSystemContext, exportsFile);
foreach (var compilationRoot in compilationRoots)
{
if (compilationRoot is ExportedMethodsRootProvider provider)
if (compilationRoot is UnmanagedEntryPointsRootProvider provider)
defFileWriter.AddExportedMethods(provider.ExportedMethods);
}

Expand Down
1 change: 1 addition & 0 deletions src/coreclr/tools/aot/ILCompiler/repro/repro.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
<ReproResponseLines Include="-r:$(MicrosoftNetCoreAppRuntimePackRidLibTfmDir)*.dll" />
<ReproResponseLines Include="-g" />
<ReproResponseLines Include="-O" Condition="'$(Optimize)' == 'true'" />
<ReproResponseLines Include="--generateunmanagedentrypoints:System.Private.CoreLib" />
<ReproResponseLines Include="--initassembly:System.Private.CoreLib" />
<ReproResponseLines Include="--initassembly:System.Private.StackTraceMetadata" />
<ReproResponseLines Include="--initassembly:System.Private.TypeLoader" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public ILScanResults Trim (ILCompilerOptions options, ILogWriter logWriter)
entrypointModule = module;
}

compilationRoots.Add (new ExportedMethodsRootProvider (module));
compilationRoots.Add (new UnmanagedEntryPointsRootProvider (module));
}

compilationRoots.Add (new MainMethodRootProvider (entrypointModule, CreateInitializerList (typeSystemContext, options), generateLibraryAndModuleInitializers: true));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace GenerateUnmanagedEntryPoints
{
unsafe class Program
{
[UnmanagedCallersOnly(EntryPoint = "MainAssemblyMethod")]
static void MainAssemblyMethod() => Console.WriteLine($"Hello from {nameof(MainAssemblyMethod)}");

static int Main()
{
IntPtr methodAddress = IntPtr.Zero;
IntPtr programHandle = IntPtr.Zero;

programHandle = NativeLibrary.GetMainProgramHandle();
if (programHandle == IntPtr.Zero)
{
return 1;
}

if (NativeLibrary.TryGetExport(programHandle, "MainAssemblyMethod", out methodAddress))
{
var MainAssemblyMethodPtr = (delegate* unmanaged <void>) methodAddress;
MainAssemblyMethodPtr();
}
else
{
return 2;
}

if (NativeLibrary.TryGetExport(programHandle, "ReferencedAssembly1Method", out methodAddress))
{
var ReferencedAssembly1MethodPtr = (delegate* unmanaged <void>) methodAddress;
ReferencedAssembly1MethodPtr();
}
else
{
return 3;
}

if (NativeLibrary.TryGetExport(programHandle, "ReferencedAssembly2Method", out methodAddress))
{
// must not be exposed from ReferencedAssembly2 assembly
return 4;
}

return 100;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<CLRTestKind>BuildAndRun</CLRTestKind>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<IlcExportUnmanagedEntrypoints>true</IlcExportUnmanagedEntrypoints>
</PropertyGroup>

<ItemGroup>
<Compile Include="GenerateUnmanagedEntryPoints.cs" />
<ProjectReference Include="$(MSBuildThisFileDirectory)ReferencedAssembly1\ReferencedAssembly1.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)ReferencedAssembly2\ReferencedAssembly2.csproj" />
</ItemGroup>

<!-- Expose additional unmanaged entry points from ReferencedAssembly1 -->
<ItemGroup>
<UnmanagedEntryPointsAssembly Include="ReferencedAssembly1" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace ReferencedAssembly1
{
public class ClassLibrary
{
[UnmanagedCallersOnly(EntryPoint = "ReferencedAssembly1Method")]
public static void ReferencedAssembly1Method() => Console.WriteLine($"Hello from {nameof(ReferencedAssembly1Method)}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>

<ItemGroup>
<Compile Include="ReferencedAssembly1.cs" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace ReferencedAssembly2
{
public class ClassLibrary
{
[UnmanagedCallersOnly(EntryPoint = "ReferencedAssembly2Method")]
public static void ReferencedAssembly2Method() => Console.WriteLine($"Hello from {nameof(ReferencedAssembly2Method)}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
</PropertyGroup>

<ItemGroup>
<Compile Include="ReferencedAssembly2.cs" />
</ItemGroup>

</Project>

0 comments on commit b69b359

Please sign in to comment.