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) {