diff --git a/docs/using-nativeaot/compiling.md b/docs/using-nativeaot/compiling.md
index 95ac427b2715..e6a8c7551276 100644
--- a/docs/using-nativeaot/compiling.md
+++ b/docs/using-nativeaot/compiling.md
@@ -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
+
+
+
+```
+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
diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
index dc49f836c7b1..c921aaa56eb3 100644
--- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
+++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets
@@ -266,6 +266,8 @@ The .NET Foundation licenses this file to you under the MIT license.
+
+
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs
index 60fe612fb5f0..b91f405c8cba 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs
@@ -24,6 +24,7 @@ partial class CompilationBuilder
protected bool _singleThreaded;
protected InstructionSetSupport _instructionSetSupport;
protected SecurityMitigationOptions _mitigationOptions;
+ protected ConfigurableWasmImportPolicy _wasmImportPolicy;
partial void InitializePartial()
{
@@ -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)
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ConfigurableWasmImportPolicy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ConfigurableWasmImportPolicy.cs
new file mode 100644
index 000000000000..a04bd4af3504
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ConfigurableWasmImportPolicy.cs
@@ -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 _wasmImports; // function names to module names
+
+ public ConfigurableWasmImportPolicy(IReadOnlyList wasmImports, IReadOnlyList wasmImportLists)
+ {
+ _wasmImports = new Dictionary();
+
+ 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 !");
+ }
+ }
+
+ public bool TryGetWasmModule(string realMethodName, out string wasmModuleName)
+ {
+ return _wasmImports.TryGetValue(realMethodName, out wasmModuleName);
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
index 4656f3d18401..dc411c82338c 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
@@ -180,6 +180,7 @@
+
diff --git a/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/ILToLLVMImporter.cs b/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/ILToLLVMImporter.cs
index 158e45fb0042..1671cab606f0 100644
--- a/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/ILToLLVMImporter.cs
+++ b/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/ILToLLVMImporter.cs
@@ -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;
@@ -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
{
diff --git a/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/LLVMSharpInterop.cs b/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/LLVMSharpInterop.cs
index 6ef911a33558..611324198beb 100644
--- a/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/LLVMSharpInterop.cs
+++ b/src/coreclr/tools/aot/ILCompiler.LLVM/CodeGen/LLVMSharpInterop.cs
@@ -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
{
///
@@ -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 nameSpan = name.AsSpan();
+ ReadOnlySpan 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 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(Value, Length);
+ return AsString(span);
+ }
+
+ public static string AsString(ReadOnlySpan self)
+ {
+ if (self.IsEmpty)
+ {
+ return string.Empty;
+ }
+
+ fixed (byte* pSelf = self)
+ {
+ return Encoding.UTF8.GetString(pSelf, self.Length);
+ }
+ }
+ }
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilation.cs
index ac2b236860ca..eda23b13a668 100644
--- a/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilation.cs
+++ b/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilation.cs
@@ -32,6 +32,7 @@ public sealed class LLVMCodegenCompilation : RyuJitCompilation
internal LLVMDIBuilderRef DIBuilder { get; }
internal Dictionary DebugMetadataMap { get; }
internal bool NativeLib { get; }
+ internal ConfigurableWasmImportPolicy ConfigurableWasmImportPolicy { get; }
internal LLVMCodegenCompilation(DependencyAnalyzerBase dependencyGraph,
LLVMCodegenNodeFactory nodeFactory,
@@ -43,7 +44,8 @@ internal LLVMCodegenCompilation(DependencyAnalyzerBase 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;
@@ -57,6 +59,7 @@ internal LLVMCodegenCompilation(DependencyAnalyzerBase dependencyGr
DebugMetadataMap = new Dictionary();
ILImporter.Context = Module.Context;
NativeLib = nativeLib;
+ ConfigurableWasmImportPolicy = configurableWasmImportPolicy;
}
private static IEnumerable GetCompilationRoots(IEnumerable existingRoots, NodeFactory factory)
diff --git a/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilationBuilder.cs
index 160cb9681e8e..8d8439518ce7 100644
--- a/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilationBuilder.cs
+++ b/src/coreclr/tools/aot/ILCompiler.LLVM/Compiler/LLVMCodegenCompilationBuilder.cs
@@ -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 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);
}
}
diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs
index 5ee358b8add8..ba44774b82f0 100644
--- a/src/coreclr/tools/aot/ILCompiler/Program.cs
+++ b/src/coreclr/tools/aot/ILCompiler/Program.cs
@@ -84,6 +84,10 @@ internal class Program
private IReadOnlyList _directPInvokeLists = Array.Empty();
+ private IReadOnlyList _wasmImports = Array.Empty();
+
+ private IReadOnlyList _wasmImportsLists = Array.Empty();
+
private IReadOnlyList _rootedAssemblies = Array.Empty();
private IReadOnlyList _conditionallyRootedAssemblies = Array.Empty();
@@ -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");
@@ -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> featureSwitches = new List>();
@@ -766,7 +774,8 @@ static string ILLinkify(string rootedAssembly)
.UseCompilationRoots(compilationRoots)
.UseOptimizationMode(_optimizationMode)
.UseSecurityMitigationOptions(securityMitigationOptions)
- .UseDebugInfoProvider(debugInfoProvider);
+ .UseDebugInfoProvider(debugInfoProvider)
+ .UseWasmImportPolicy(wasmImportPolicy);
if (scanResults != null)
{