diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 7c362f411cec95..02046f9358fa97 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -309,7 +309,7 @@ private SortedSet GetUndefinedSymbols() return undefinedSymbolSet; } - public void EmitObject(Stream outputFileStream, IReadOnlyCollection nodes, IObjectDumper dumper, Logger logger) + public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection nodes, IObjectDumper dumper, Logger logger) { // Pre-create some of the sections GetOrCreateSection(ObjectNodeSection.TextSection); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs index 03920b49bd2bad..9965646738d72f 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionWriter.cs @@ -136,6 +136,15 @@ public readonly void WriteUtf8String(string value) bufferWriter.Advance(size); } + public readonly void WriteUtf8StringNoNull(string value) + { + IBufferWriter bufferWriter = _sectionData.BufferWriter; + int size = Encoding.UTF8.GetByteCount(value); + Span buffer = bufferWriter.GetSpan(size); + Encoding.UTF8.GetBytes(value, buffer); + bufferWriter.Advance(size); + } + public readonly void WritePadding(int size) => _sectionData.AppendPadding(size); public readonly long Position => _sectionData.Length; diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs new file mode 100644 index 00000000000000..aa4c4738ae26aa --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -0,0 +1,185 @@ +// 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.Diagnostics; +using System.Linq; +using System.Collections.Generic; + +namespace ILCompiler.ObjectWriter +{ + public enum WasmSectionType + { + Custom = 0, + Type = 1, + Import = 2, + Function = 3, + Table = 4, + Memory = 5, + Global = 6, + Export = 7, + Start = 8, + Element = 9, + Code = 10, + Data = 11, + DataCount = 12, + Tag = 13, + } + + public static class PlaceholderValues + { + // Wasm function signature for (func (params i32) (result i32)) + public static WasmFuncType CreateWasmFunc_i32_i32() + { + return new WasmFuncType( + paramTypes: new([WasmValueType.I32]), + returnTypes: new([WasmValueType.I32]) + ); + } + } + + // For now, we only encode Wasm numeric value types. + // These are encoded as a single byte. However, + // not all value types can be encoded this way. + // For example, reference types (see https://webassembly.github.io/spec/core/binary/types.html#reference-types) + // require a more complex encoding. + public enum WasmValueType : byte + { + I32 = 0x7F, + I64 = 0x7E, + F32 = 0x7D, + F64 = 0x7C + } + + public static class WasmValueTypeExtensions + { + public static string ToTypeString(this WasmValueType valueType) + { + return valueType switch + { + WasmValueType.I32 => "i32", + WasmValueType.I64 => "i64", + WasmValueType.F32 => "f32", + WasmValueType.F64 => "f64", + _ => "unknown", + }; + } + } + + #nullable enable + public readonly struct WasmResultType : IEquatable + { + private readonly WasmValueType[] _types; + public ReadOnlySpan Types => _types; + + /// + /// Initializes a new instance of the WasmResultType class with the specified value types. + /// + /// An array of WasmValueType elements representing the types included in the result. If null, an empty array is + /// used. + public WasmResultType(WasmValueType[]? types) + { + _types = types ?? Array.Empty(); + } + + public bool Equals(WasmResultType other) => Types.SequenceEqual(other.Types); + public override bool Equals(object? obj) + { + return obj is WasmResultType other && Equals(other); + } + + public override int GetHashCode() + { + if (_types == null || _types.Length == 0) + return 0; + + int code = _types[0].GetHashCode(); + for (int i = 1; i < _types.Length; i++) + { + code = HashCode.Combine(code, _types[i].GetHashCode()); + } + + return code; + } + + public int EncodeSize() + { + uint sizeLength = DwarfHelper.SizeOfULEB128((ulong)_types.Length); + return (int)(sizeLength + (uint)_types.Length); + } + + public int Encode(Span buffer) + { + int sizeLength = DwarfHelper.WriteULEB128(buffer, (ulong)_types.Length); + Span rest = buffer.Slice(sizeLength); + for (int i = 0; i < _types.Length; i++) + { + rest[i] = (byte)_types[i]; + } + return (int)(sizeLength + (uint)_types.Length); + } + } + + public static class WasmResultTypeExtensions + { + public static string ToTypeListString(this WasmResultType result) + { + return string.Join(" ", result.Types.ToArray().Select(t => t.ToTypeString())); + } + } + + public struct WasmFuncType : IEquatable + { + private readonly WasmResultType _params; + private readonly WasmResultType _returns; + + public WasmFuncType(WasmResultType paramTypes, WasmResultType returnTypes) + { + _params = paramTypes; + _returns = returnTypes; + } + + public readonly int EncodeSize() + { + return 1 + _params.EncodeSize() + _returns.EncodeSize(); + } + + public readonly int Encode(Span buffer) + { + int totalSize = EncodeSize(); + buffer[0] = 0x60; // function type indicator + + int paramSize = _params.Encode(buffer.Slice(1)); + int returnSize = _returns.Encode(buffer.Slice(1+paramSize)); + Debug.Assert(totalSize == 1 + paramSize + returnSize); + + return totalSize; + } + + public bool Equals(WasmFuncType other) + { + return _params.Equals(other._params) && _returns.Equals(other._returns); + } + + public override bool Equals(object? obj) + { + return obj is WasmFuncType other && Equals(other); + } + + public override int GetHashCode() + { + return HashCode.Combine(_params.GetHashCode(), _returns.GetHashCode()); + } + + public override string ToString() + { + string paramList = _params.ToTypeListString(); + string returnList = _returns.ToTypeListString(); + + if (string.IsNullOrEmpty(returnList)) + return $"(func (param {paramList}))"; + return $"(func (param {paramList}) (result {returnList}))"; + } + } + +} diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs new file mode 100644 index 00000000000000..d02cc5d4c30dca --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -0,0 +1,245 @@ +// 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.Buffers; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysis.ReadyToRun; +using ILCompiler.DependencyAnalysisFramework; +using Internal.Text; +using Internal.TypeSystem; + +using ObjectData = ILCompiler.DependencyAnalysis.ObjectNode.ObjectData; + +namespace ILCompiler.ObjectWriter +{ + /// + /// Wasm object file format writer. + /// + internal sealed class WasmObjectWriter : ObjectWriter + { + public WasmObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder) + : base(factory, options, outputInfoBuilder) + { + } + + private void EmitWasmHeader(Stream outputFileStream) + { + outputFileStream.Write("\0asm"u8); + outputFileStream.Write([0x1, 0x0, 0x0, 0x0]); + } + + // TODO: for now, we are fully overriding EmitObject. As we support more features, we should + // see if we can re-use the base, or refactor the base method to allow for code sharing. + public override void EmitObject(Stream outputFileStream, IReadOnlyCollection nodes, IObjectDumper dumper, Logger logger) + { + ArrayBuilder methodSignatures = new(); + ArrayBuilder methodNames = new(); + ArrayBuilder methodBodies = new(); + foreach (DependencyNode node in nodes) + { + if (node is not ObjectNode) + { + continue; + } + + if (node is IMethodBodyNode methodNode) + { + ObjectNode methodObject = (ObjectNode)node; + methodSignatures.Add(WasmAbiContext.GetSignature(methodNode.Method)); + methodNames.Add(methodNode.GetMangledName(_nodeFactory.NameMangler)); + methodBodies.Add(methodObject.GetData(_nodeFactory)); + // TODO: record relocations and attached data + } + } + + IEnumerable uniqueSignatures = methodSignatures.ToArray().Distinct(); + Dictionary signatureMap = uniqueSignatures.Select((sig, i) => (sig, i)).ToDictionary(); + + // TODO: The EmitSection calls here can be moved to this class' `EmitObjectFile` implementation + // when we can share more code with the base class. + EmitWasmHeader(outputFileStream); + EmitSection(() => WriteTypeSection(uniqueSignatures, logger), outputFileStream, logger); + EmitSection(() => WriteFunctionSection(methodSignatures, signatureMap, logger), outputFileStream, logger); + EmitSection(() => WriteExportSection(methodBodies, methodSignatures, methodNames, signatureMap, logger), outputFileStream, logger); + EmitSection(() => WriteCodeSection(methodBodies, logger), outputFileStream, logger); + } + + private WasmSection WriteExportSection(ArrayBuilder methodNodes, ArrayBuilder methodSignatures, + ArrayBuilder methodNames, Dictionary signatureMap, Logger logger) + { + WasmSection exportSection = new WasmSection(WasmSectionType.Export, new SectionData(), "export"); + SectionWriter writer = exportSection.Writer; + // Write the number of exports + writer.WriteULEB128((ulong)methodNodes.Count); + for (int i = 0; i < methodNodes.Count; i++) + { + ObjectData methodNode = methodNodes[i]; + WasmFuncType methodSignature = methodSignatures[i]; + string exportName = methodNames[i]; + + int length = Encoding.UTF8.GetByteCount(exportName); + writer.WriteULEB128((ulong)length); + writer.WriteUtf8StringNoNull(exportName); + writer.WriteByte(0x00); // export kind: function + writer.WriteULEB128((ulong)i); + if (logger.IsVerbose) + { + logger.LogMessage($"Emitting export: {exportName} for function index {i}"); + } + } + + return exportSection; + } + + private WasmSection WriteCodeSection(ArrayBuilder methodBodies, Logger logger) + { + WasmSection codeSection = new WasmSection(WasmSectionType.Code, new SectionData(), "code"); + SectionWriter writer = codeSection.Writer; + + // Write the number of functions + writer.WriteULEB128((ulong)methodBodies.Count); + for (int i = 0; i < methodBodies.Count; i++) + { + ObjectData methodBody = methodBodies[i]; + writer.WriteULEB128((ulong)methodBody.Data.Length); + writer.EmitData(methodBody.Data); + } + + return codeSection; + } + + private WasmSection WriteTypeSection(IEnumerable functionSignatures, Logger logger) + { + SectionData sectionData = new(); + WasmSection typeSection = new WasmSection(WasmSectionType.Type, sectionData, "type"); + SectionWriter writer = typeSection.Writer; + + // Write the number of types + writer.WriteULEB128((ulong)functionSignatures.Count()); + + IBufferWriter buffer = sectionData.BufferWriter; + foreach (WasmFuncType signature in functionSignatures) + { + if (logger.IsVerbose) + { + logger.LogMessage($"Emitting function signature: {signature}"); + } + + int signatureSize = signature.EncodeSize(); + signature.Encode(buffer.GetSpan(signatureSize)); + buffer.Advance(signatureSize); + } + + return typeSection; + } + + private WasmSection WriteFunctionSection(ArrayBuilder allFunctionSignatures, Dictionary signatureMap, Logger logger) + { + WasmSection functionSection = new WasmSection(WasmSectionType.Function, new SectionData(), "function"); + SectionWriter writer = functionSection.Writer; + // Write the number of functions + writer.WriteULEB128((ulong)allFunctionSignatures.Count); + for (int i = 0; i < allFunctionSignatures.Count; i++) + { + WasmFuncType signature = allFunctionSignatures[i]; + writer.WriteULEB128((ulong)signatureMap[signature]); + } + return functionSection; + } + + private void EmitSection(Func writeFunc, Stream outputFileStream, Logger logger) + { + WasmSection section = writeFunc(); + Span headerBuffer = stackalloc byte[section.HeaderSize]; + + section.EncodeHeader(headerBuffer); + outputFileStream.Write(headerBuffer); + if (logger.IsVerbose) + { + logger.LogMessage($"Wrote section header of size {headerBuffer.Length} bytes."); + } + + section.Data.GetReadStream().CopyTo(outputFileStream); + if (logger.IsVerbose) + { + logger.LogMessage($"Emitted section: {section.Name} of type `{section.Type}` with size {section.Data.Length} bytes."); + } + } + + protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) => throw new NotImplementedException(); + private WasmSection CreateSection(WasmSectionType sectionType, string symbolName, int sectionIndex) => throw new NotImplementedException(); + private protected override void CreateSection(ObjectNodeSection section, Utf8String comdatName, Utf8String symbolName, int sectionIndex, Stream sectionStream) => throw new NotImplementedException(); + private protected override void EmitObjectFile(Stream outputFileStream) => throw new NotImplementedException(); + private protected override void EmitRelocations(int sectionIndex, List relocationList) => throw new NotImplementedException(); + private protected override void EmitSymbolTable(IDictionary definedSymbols, SortedSet undefinedSymbols) => throw new NotImplementedException(); + } + + // TODO: This is a placeholder implementation. The real implementation will derive the Wasm function signature + // from the MethodDesc's signature and type system information. + public static class WasmAbiContext + { + public static WasmFuncType GetSignature(MethodDesc method) + { + return PlaceholderValues.CreateWasmFunc_i32_i32(); + } + } + + internal class WasmSection + { + public WasmSectionType Type { get; } + public string Name { get; } + public SectionData Data => _data; + private SectionWriter? _writer; + + private SectionData _data; + + public int HeaderSize + { + get + { + ulong sectionSize = (ulong)_data.Length; + uint sizeEncodeLength = DwarfHelper.SizeOfULEB128(sectionSize); + return 1 + (int)sizeEncodeLength; + } + } + + public int EncodeHeader(Span headerBuffer) + { + ulong sectionSize = (ulong)_data.Length; + uint encodeLength = DwarfHelper.SizeOfULEB128(sectionSize); + + // Section header consists of: + // 1 byte: section type + // ULEB128: size of section + headerBuffer[0] = (byte)Type; + DwarfHelper.WriteULEB128(headerBuffer.Slice(1), sectionSize); + + return 1 + (int)encodeLength; + } + + public SectionWriter Writer + { + get + { + if (_writer == null) + { + _writer = new SectionWriter(null, 0, _data); + } + return _writer.Value; + } + } + + public WasmSection(WasmSectionType type, SectionData data, string name) + { + Type = type; + Name = name; + _data = data; + } + } +} diff --git a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs index 636caacee76ca2..8ffe8f69227e5f 100644 --- a/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs +++ b/src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs @@ -317,6 +317,36 @@ IntPtr LocalObjectToHandle(object input) return null; } + private CorJitResult CompileWasmStub(out IntPtr exception, ref CORINFO_METHOD_INFO methodInfo, out uint codeSize) + { + byte[] stub = + [ + 0x00, // local variable count + 0x41, // i32.const + 0x0, // uleb128 0 + 0x0F, // return + 0x0B, // end + ]; + AllocMemArgs args = new AllocMemArgs + { + hotCodeSize = (uint)stub.Length, + coldCodeSize = (uint)0, + roDataSize = (uint)0, + xcptnsCount = _ehClauses != null ? (uint)_ehClauses.Length : 0, + flag = CorJitAllocMemFlag.CORJIT_ALLOCMEM_DEFAULT_CODE_ALIGN, + }; + allocMem(ref args); + + _code = stub; + + codeSize = (uint)stub.Length; + exception = IntPtr.Zero; + + _codeRelocs = new(); + + return CorJitResult.CORJIT_OK; + } + private CompilationResult CompileMethodInternal(IMethodNode methodCodeNodeNeedingCode, MethodIL methodIL) { // methodIL must not be null @@ -339,9 +369,17 @@ private CompilationResult CompileMethodInternal(IMethodNode methodCodeNodeNeedin IntPtr exception; IntPtr nativeEntry; uint codeSize; - var result = JitCompileMethod(out exception, + + TargetArchitecture architecture = _compilation.TypeSystemContext.Target.Architecture; + var result = architecture switch + { + // We currently do not have WASM codegen support, but for testing, we will return a stub + TargetArchitecture.Wasm32 => CompileWasmStub(out exception, ref methodInfo, out codeSize), + _ => JitCompileMethod(out exception, _jit, (IntPtr)(&_this), _unmanagedCallbacks, - ref methodInfo, (uint)CorJitFlag.CORJIT_FLAG_CALL_GETJITFLAGS, out nativeEntry, out codeSize); + ref methodInfo, (uint)CorJitFlag.CORJIT_FLAG_CALL_GETJITFLAGS, out nativeEntry, out codeSize) + }; + if (exception != IntPtr.Zero) { if (_lastException != null) diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs index eac1177708eb13..b08f12011a657f 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunContainerFormat.cs @@ -7,5 +7,6 @@ public enum ReadyToRunContainerFormat { PE, MachO, + Wasm, } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs index 45a398dc225a03..d0cd1542163e5c 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs @@ -183,6 +183,7 @@ public void EmitReadyToRunObjects(ReadyToRunContainerFormat format, Logger logge { ReadyToRunContainerFormat.PE => CreatePEObjectWriter(), ReadyToRunContainerFormat.MachO => CreateMachObjectWriter(), + ReadyToRunContainerFormat.Wasm => CreateWasmObjectWriter(), _ => throw new UnreachableException() }; @@ -297,6 +298,11 @@ private MachObjectWriter CreateMachObjectWriter() return new MachObjectWriter(_nodeFactory, ObjectWritingOptions.None, _outputInfoBuilder, baseSymbolName: "__mh_dylib_header"); } + private WasmObjectWriter CreateWasmObjectWriter() + { + return new WasmObjectWriter(_nodeFactory, ObjectWritingOptions.None, _outputInfoBuilder); + } + public static void EmitObject( string objectFilePath, EcmaModule componentModule, diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index 6a84242ae3c2d0..c73edf28b53b32 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -163,6 +163,8 @@ + + diff --git a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs index 3f9d6d2506765b..b63e33f17ac23b 100644 --- a/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs +++ b/src/coreclr/tools/aot/crossgen2/Crossgen2RootCommand.cs @@ -296,8 +296,8 @@ public static void PrintExtendedHelp(ParseResult _) Console.WriteLine(SR.DashDashHelp); Console.WriteLine(); - string[] ValidArchitectures = new string[] {"arm", "armel", "arm64", "x86", "x64", "riscv64", "loongarch64"}; - string[] ValidOS = new string[] {"windows", "linux", "osx", "ios", "iossimulator", "maccatalyst"}; + string[] ValidArchitectures = ["arm", "armel", "arm64", "x86", "x64", "riscv64", "loongarch64", "wasm"]; + string[] ValidOS = ["windows", "linux", "osx", "ios", "iossimulator", "maccatalyst", "browser"]; Console.WriteLine(String.Format(SR.SwitchWithDefaultHelp, "--targetos", String.Join("', '", ValidOS), Helpers.GetTargetOS(null).ToString().ToLowerInvariant())); Console.WriteLine(); @@ -416,6 +416,7 @@ private static ReadyToRunContainerFormat MakeOutputFormat(ArgumentResult result) { "pe" => ReadyToRunContainerFormat.PE, "macho" => ReadyToRunContainerFormat.MachO, + "wasm" => ReadyToRunContainerFormat.Wasm, _ => throw new CommandLineException(SR.InvalidOutputFormat) }; } diff --git a/src/coreclr/tools/aot/crossgen2/Program.cs b/src/coreclr/tools/aot/crossgen2/Program.cs index dfc8dd737d358c..6600e6a9dcf105 100644 --- a/src/coreclr/tools/aot/crossgen2/Program.cs +++ b/src/coreclr/tools/aot/crossgen2/Program.cs @@ -402,7 +402,7 @@ private void RunSingleCompilation(Dictionary inFilePaths, Instru } ReadyToRunContainerFormat format = Get(_command.OutputFormat); - if (!composite && format != ReadyToRunContainerFormat.PE) + if (!composite && format != ReadyToRunContainerFormat.PE && format != ReadyToRunContainerFormat.Wasm) { throw new Exception(string.Format(SR.ErrorContainerFormatRequiresComposite, format)); }