Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NativeAOT-LLVM] Add support for controlling the import name used for PInvokes. #1845

Merged
merged 2 commits into from
Feb 8, 2022
Merged
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
19 changes: 19 additions & 0 deletions docs/using-nativeaot/compiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,25 @@ public static int Answer()
> dotnet publish /p:NativeLib=Static /p:SelfContained=true -r browser-wasm -c Debug /p:TargetArchitecture=wasm /p:PlatformTarget=AnyCPU /p:MSBuildEnableWorkloadResolver=false /p:EmccExtraArgs="-s EXPORTED_FUNCTIONS=_Answer -s EXPORTED_RUNTIME_METHODS=cwrap" --self-contained
```

#### WebAssembly module imports
Functions in other WebAssembly modules can be imported and invoked using `DllImport` e.g.
```cs
[DllImport("*")]
static extern int random_get(byte* buf, uint size);
```
Be default emscripten will create a WebAssembly import for this function, importing from the `env` module. This can be controlled with `WasmImport` items in the project file. For example
```xml
<ItemGroup>
<WasmImport Include="wasi_snapshot_preview1!random_get" />
</ItemGroup>
```
Will cause the above `random_get` to create this WebAssembly:
```
(import "wasi_snapshot_preview1" "random_get" (func $random_get (type 3)))
```

This can be used to import WASI functions that are in other modules, either as the above, in WASI, `wasi_snapshot_preview1`, or in other WebAssembly modules that may be linked with [WebAssembly module linking](https://github.com/WebAssembly/module-linking)

### Cross-compiling on Linux
Similarly, to target linux-arm64 on a Linux x64 host, in addition to the `Microsoft.DotNet.ILCompiler` package reference, also add the `runtime.linux-x64.Microsoft.DotNet.ILCompiler` package reference to get the x64-hosted compiler:
```xml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,8 @@ The .NET Foundation licenses this file to you under the MIT license.
<IlcArg Include="@(RuntimeHostConfigurationOption->'--appcontextswitch:%(Identity)=%(Value)')" />
<IlcArg Include="@(DirectPInvoke->'--directpinvoke:%(Identity)')" />
<IlcArg Include="@(DirectPInvokeList->'--directpinvokelist:%(Identity)')" />
<IlcArg Include="@(WasmImport->'--wasmimport:%(Identity)')" />
<IlcArg Include="@(WasmImportList->'--wasmimportlist:%(Identity)')" />
<IlcArg Include="@(_TrimmerFeatureSettings->'--feature:%(Identity)=%(Value)')" />
<IlcArg Condition="$(ServerGarbageCollection) == 'true'" Include="--runtimeopt:gcServer=1" />
<IlcArg Condition="$(IlcGenerateCompleteTypeMetadata) == 'true' and $(IlcDisableReflection) != 'true'" Include="--completetypemetadata" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ partial class CompilationBuilder
protected bool _singleThreaded;
protected InstructionSetSupport _instructionSetSupport;
protected SecurityMitigationOptions _mitigationOptions;
protected ConfigurableWasmImportPolicy _wasmImportPolicy;

partial void InitializePartial()
{
Expand Down Expand Up @@ -103,6 +104,12 @@ public CompilationBuilder UsePreinitializationManager(PreinitializationManager m
return this;
}

public CompilationBuilder UseWasmImportPolicy(ConfigurableWasmImportPolicy wasmImportPolicy)
{
_wasmImportPolicy = wasmImportPolicy;
return this;
}

protected PreinitializationManager GetPreinitializationManager()
{
if (_preinitializationManager == null)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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.Collections.Generic;
using System.IO;

namespace ILCompiler
{
public class ConfigurableWasmImportPolicy
{
private readonly Dictionary<string, string> _wasmImports; // function names to module names

public ConfigurableWasmImportPolicy(IReadOnlyList<string> wasmImports, IReadOnlyList<string> wasmImportLists)
{
_wasmImports = new Dictionary<string, string>();

foreach (var file in wasmImportLists)
{
foreach (var entry in File.ReadLines(file))
{
AddWasmImport(entry);
}
}

foreach (var entry in wasmImports)
{
AddWasmImport(entry);
}
}

private void AddWasmImport(string entry)
{
// Ignore comments
if (entry.StartsWith('#'))
return;

entry = entry.Trim();

// Ignore empty entries
if (string.IsNullOrEmpty(entry))
return;

int separator = entry.IndexOf('!');

if (separator != -1)
{
string wasmModuleName = entry.Substring(0, separator);
string entrypointName = entry.Substring(separator + 1);

if (_wasmImports.ContainsKey(entrypointName))
{
// this is an artificial restriction because we are using just the PInvoke function name to distinguish WebAssembly imports
throw new Exception("WebAssembly function imports must be unique");
}
_wasmImports.Add(entrypointName, wasmModuleName);
}
else
{
throw new Exception("WebAssembly import entries must be of the format <module name>!<function name>");
}
}

public bool TryGetWasmModule(string realMethodName, out string wasmModuleName)
{
return _wasmImports.TryGetValue(realMethodName, out wasmModuleName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
<Compile Include="Compiler\CompilerTypeSystemContext.Mangling.cs" />
<Compile Include="Compiler\CompilerTypeSystemContext.Sorting.cs" />
<Compile Include="Compiler\CompilerGeneratedInteropStubManager.cs" />
<Compile Include="Compiler\ConfigurableWasmImportPolicy.cs" />
<Compile Include="Compiler\Dataflow\DynamicallyAccessedMembersBinder.cs" />
<Compile Include="Compiler\Dataflow\EcmaExtensions.cs" />
<Compile Include="Compiler\Dataflow\FlowAnnotations.cs" />
Expand Down
13 changes: 13 additions & 0 deletions src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/ILToLLVMImporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3155,6 +3155,11 @@ private ExpressionEntry ImportRawPInvoke(MethodDesc method, StackEntry[] argumen
return null;
}

private static LLVMAttributeRef CreateImportAttr(LLVMContextRef context, string name, string value)
{
return LLVMSharpInterop.CreateAttribute(context, name, value);
}

private LLVMValueRef MakeExternFunction(MethodDesc method, string realMethodName, LLVMValueRef realFunction = default(LLVMValueRef))
{
LLVMValueRef nativeFunc;
Expand All @@ -3171,6 +3176,14 @@ private ExpressionEntry ImportRawPInvoke(MethodDesc method, StackEntry[] argumen
{
nativeFunc = Module.AddFunction(realMethodName, nativeFuncType);
nativeFunc.Linkage = LLVMLinkage.LLVMDLLImportLinkage;
if (_compilation.ConfigurableWasmImportPolicy.TryGetWasmModule(realMethodName, out string wasmModuleName))
{
unsafe
{
LLVM.AddAttributeAtIndex(nativeFunc, ~0u, CreateImportAttr(Context, "wasm-import-module", wasmModuleName));
LLVM.AddAttributeAtIndex(nativeFunc, ~0u, CreateImportAttr(Context, "wasm-import-name", realMethodName));
}
}
}
else
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using LLVMSharp.Interop;
using System;
using System.Runtime.InteropServices;
using System.Text;
using LLVMSharp.Interop;

namespace Internal.IL
{
//TODO-LLVM: delete this file when IL->LLVM module has gone
internal class LLVMSharpInterop
{
///
Expand All @@ -11,5 +15,81 @@ internal static unsafe void DISetSubProgram(LLVMValueRef function, LLVMMetadataR
{
LLVM.SetSubprogram(function, diFunction);
}

internal static unsafe LLVMAttributeRef CreateAttribute(LLVMContextRef context, string name, string value)
{
ReadOnlySpan<char> nameSpan = name.AsSpan();
ReadOnlySpan<char> valueSpan = value.AsSpan();

using var marshaledName = new MarshaledString(name);
using var marshaledValue = new MarshaledString(value);

return LLVM.CreateStringAttribute(context, marshaledName.Value, (uint)marshaledName.Length,
marshaledValue.Value, (uint)marshaledValue.Length);
}

internal unsafe struct MarshaledString : IDisposable
{
public MarshaledString(ReadOnlySpan<char> input)
{
if (input.IsEmpty)
{
var value = Marshal.AllocHGlobal(1);
Marshal.WriteByte(value, 0, 0);

Length = 0;
Value = (sbyte*)value;
}
else
{
var valueBytes = Encoding.UTF8.GetBytes(input.ToString());
var length = valueBytes.Length;
var value = Marshal.AllocHGlobal(length + 1);
Marshal.Copy(valueBytes, 0, value, length);
Marshal.WriteByte(value, length, 0);

Length = length;
Value = (sbyte*)value;
}
}

public int Length { get; private set; }

public sbyte* Value { get; private set; }

public void Dispose()
{
if (Value != null)
{
Marshal.FreeHGlobal((IntPtr)Value);
Value = null;
Length = 0;
}
}

public static implicit operator sbyte*(in MarshaledString value)
{
return value.Value;
}

public override string ToString()
{
var span = new ReadOnlySpan<byte>(Value, Length);
return AsString(span);
}

public static string AsString(ReadOnlySpan<byte> self)
{
if (self.IsEmpty)
{
return string.Empty;
}

fixed (byte* pSelf = self)
{
return Encoding.UTF8.GetString(pSelf, self.Length);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public sealed class LLVMCodegenCompilation : RyuJitCompilation
internal LLVMDIBuilderRef DIBuilder { get; }
internal Dictionary<string, DebugMetadata> DebugMetadataMap { get; }
internal bool NativeLib { get; }
internal ConfigurableWasmImportPolicy ConfigurableWasmImportPolicy { get; }

internal LLVMCodegenCompilation(DependencyAnalyzerBase<NodeFactory> dependencyGraph,
LLVMCodegenNodeFactory nodeFactory,
Expand All @@ -43,7 +44,8 @@ internal LLVMCodegenCompilation(DependencyAnalyzerBase<NodeFactory> dependencyGr
IInliningPolicy inliningPolicy,
DevirtualizationManager devirtualizationManager,
InstructionSetSupport instructionSetSupport,
bool nativeLib)
bool nativeLib,
ConfigurableWasmImportPolicy configurableWasmImportPolicy)
: base(dependencyGraph, nodeFactory, GetCompilationRoots(roots, nodeFactory), ilProvider, debugInformationProvider, logger, devirtualizationManager, inliningPolicy ?? new LLVMNoInLiningPolicy(), instructionSetSupport, null /* ProfileDataManager */, RyuJitCompilationOptions.SingleThreadedCompilation)
{
NodeFactory = nodeFactory;
Expand All @@ -57,6 +59,7 @@ internal LLVMCodegenCompilation(DependencyAnalyzerBase<NodeFactory> dependencyGr
DebugMetadataMap = new Dictionary<string, DebugMetadata>();
ILImporter.Context = Module.Context;
NativeLib = nativeLib;
ConfigurableWasmImportPolicy = configurableWasmImportPolicy;
}

private static IEnumerable<ICompilationRootProvider> GetCompilationRoots(IEnumerable<ICompilationRootProvider> existingRoots, NodeFactory factory)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public override ICompilation ToCompilation()
LLVMCodegenNodeFactory factory = new LLVMCodegenNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider, GetPreinitializationManager());
JitConfigProvider.Initialize(_context.Target, jitFlagBuilder.ToArray(), _ryujitOptions);
DependencyAnalyzerBase<NodeFactory> graph = CreateDependencyGraph(factory, new ObjectNode.ObjectNodeComparer(new CompilerComparer()));
return new LLVMCodegenCompilation(graph, factory, _compilationRoots, _ilProvider, _debugInformationProvider, _logger, _config, _inliningPolicy, _devirtualizationManager, _instructionSetSupport, _nativeLib);
return new LLVMCodegenCompilation(graph, factory, _compilationRoots, _ilProvider, _debugInformationProvider, _logger, _config, _inliningPolicy, _devirtualizationManager, _instructionSetSupport, _nativeLib, _wasmImportPolicy);
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/coreclr/tools/aot/ILCompiler/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ internal class Program

private IReadOnlyList<string> _directPInvokeLists = Array.Empty<string>();

private IReadOnlyList<string> _wasmImports = Array.Empty<string>();

private IReadOnlyList<string> _wasmImportsLists = Array.Empty<string>();

private IReadOnlyList<string> _rootedAssemblies = Array.Empty<string>();
private IReadOnlyList<string> _conditionallyRootedAssemblies = Array.Empty<string>();

Expand Down Expand Up @@ -215,6 +219,8 @@ private ArgumentSyntax ParseCommandLine(string[] args)
syntax.DefineOptionList("nosinglewarnassembly", ref _singleWarnDisabledAssemblies, "Expand AOT/trimming warnings for given assembly");
syntax.DefineOptionList("directpinvoke", ref _directPInvokes, "PInvoke to call directly");
syntax.DefineOptionList("directpinvokelist", ref _directPInvokeLists, "File with list of PInvokes to call directly");
syntax.DefineOptionList("wasmimport", ref _wasmImports, "WebAssembly import module names for PInvoke functions");
syntax.DefineOptionList("wasmimportlist", ref _wasmImportsLists, "File with list of WebAssembly import module names for PInvoke functions");

syntax.DefineOptionList("root", ref _rootedAssemblies, "Fully generate given assembly");
syntax.DefineOptionList("conditionalroot", ref _conditionallyRootedAssemblies, "Fully generate given assembly if it's used");
Expand Down Expand Up @@ -640,6 +646,8 @@ static string ILLinkify(string rootedAssembly)
else
pinvokePolicy = new ConfigurablePInvokePolicy(typeSystemContext.Target, _directPInvokes, _directPInvokeLists);

ConfigurableWasmImportPolicy wasmImportPolicy = new ConfigurableWasmImportPolicy(_wasmImports, _wasmImportsLists);

ILProvider ilProvider = new CoreRTILProvider();

List<KeyValuePair<string, bool>> featureSwitches = new List<KeyValuePair<string, bool>>();
Expand Down Expand Up @@ -766,7 +774,8 @@ static string ILLinkify(string rootedAssembly)
.UseCompilationRoots(compilationRoots)
.UseOptimizationMode(_optimizationMode)
.UseSecurityMitigationOptions(securityMitigationOptions)
.UseDebugInfoProvider(debugInfoProvider);
.UseDebugInfoProvider(debugInfoProvider)
.UseWasmImportPolicy(wasmImportPolicy);

if (scanResults != null)
{
Expand Down