diff --git a/eng/Subsets.props b/eng/Subsets.props index 337bca6fa63caf..8201b66e9fe187 100644 --- a/eng/Subsets.props +++ b/eng/Subsets.props @@ -338,6 +338,10 @@ $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true + + $(ClrRuntimeBuildSubsets);ClrWasmJitSubset=true + + $(ClrRuntimeBuildSubsets);ClrPalTestsSubset=true @@ -394,6 +398,7 @@ <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and $(_subset.Contains('+clr.toolstests+'))" Include="ClrAllJitsSubset=true" /> + <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and ('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'arm64') and $(_subset.Contains('+clr.toolstests+')) and ('$(BuildArchitecture)' == 'x64' or '$(BuildArchitecture)' == 'arm64')" Include="ClrWasmJitSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' != 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrJitSubset=true" /> <_CrossToolSubset Condition="'$(_BuildCrossComponents)' == 'true' and '$(TargetArchitecture)' == 'wasm' and ($(_subset.Contains('+clr.tools+')) or $(_subset.Contains('+clr.nativecorelib+')) or $(_subset.Contains('+clr.crossarchtools+')))" Include="ClrWasmJitSubset=true" /> diff --git a/src/coreclr/jit/codegenlinear.cpp b/src/coreclr/jit/codegenlinear.cpp index c9dc081a0d4b23..b9a50148748e8a 100644 --- a/src/coreclr/jit/codegenlinear.cpp +++ b/src/coreclr/jit/codegenlinear.cpp @@ -872,6 +872,12 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) break; case BBJ_SWITCH: +#if defined(TARGET_WASM) + if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) + { + genEmitFunctionEnd(); + } +#endif break; case BBJ_ALWAYS: @@ -922,7 +928,6 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) genEmitFunctionEnd(); } #endif // defined(TARGET_WASM) - break; case BBJ_COND: @@ -933,6 +938,12 @@ void CodeGen::genEmitEndBlock(BasicBlock* block) SetLoopAlignBackEdge(block, block->GetFalseTarget()); #endif // FEATURE_LOOP_ALIGN +#if defined(TARGET_WASM) + if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) + { + genEmitFunctionEnd(); + } +#endif break; default: diff --git a/src/coreclr/jit/codegenwasm.cpp b/src/coreclr/jit/codegenwasm.cpp index 96cf252708742b..1ce06482125015 100644 --- a/src/coreclr/jit/codegenwasm.cpp +++ b/src/coreclr/jit/codegenwasm.cpp @@ -416,17 +416,17 @@ void CodeGen::genFnEpilog(BasicBlock* block) { if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) { - instGen(INS_end); + genEmitFunctionEnd(/* emitTerminalUnreachable */ false); } return; } // TODO-WASM: shadow stack maintenance - // TODO-WASM: we need to handle the end-of-function case if we reach the end of a codegen for a function - // and do NOT have an epilog. In those cases we currently will not emit an end instruction. + // Close the root function before the first funclet starts. Other returns + // within the root function leave the remaining root blocks reachable. if (block->IsLast() || m_compiler->bbIsFuncletBeg(block->Next())) { - instGen(INS_end); + genEmitFunctionEnd(/* emitTerminalUnreachable */ false); } else { @@ -3184,7 +3184,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) if (target != nullptr) { // Codegen should have already evaluated our target node (last) and pushed it onto the stack, - // ready for call_indirect. Consume it. + // ready for call_indirect. Consume it. genConsumeReg(target); params.callType = EC_INDIR_R; @@ -3195,16 +3195,22 @@ void CodeGen::genCallInstruction(GenTreeCall* call) // Generate a direct call to a non-virtual user defined or helper method assert(call->IsHelperCall() || (call->gtCallType == CT_USER_FUNC)); - assert(call->gtEntryPoint.addr == NULL); - if (call->IsHelperCall()) { assert(!call->IsFastTailCall()); - CorInfoHelpFunc helperNum = m_compiler->eeGetHelperNum(params.methHnd); - noway_assert(helperNum != CORINFO_HELP_UNDEF); - CORINFO_CONST_LOOKUP helperLookup = m_compiler->compGetHelperFtn(helperNum); - assert(helperLookup.accessType == IAT_VALUE); - params.addr = helperLookup.addr; + + if (call->gtDirectCallAddress != nullptr) + { + params.addr = call->gtDirectCallAddress; + } + else + { + CorInfoHelpFunc helperNum = m_compiler->eeGetHelperNum(params.methHnd); + noway_assert(helperNum != CORINFO_HELP_UNDEF); + CORINFO_CONST_LOOKUP helperLookup = m_compiler->compGetHelperFtn(helperNum); + assert(helperLookup.accessType == IAT_VALUE); + params.addr = helperLookup.addr; + } } else { @@ -3247,9 +3253,8 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, } else { - params.addr = nullptr; assert(helperFunction.accessType == IAT_PVALUE); - + params.addr = nullptr; params.callType = EC_INDIR_R; } @@ -3309,12 +3314,14 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, if (helperIsManaged) { - // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter. - // The helper function address is the address of an indirection cell, so we load from the cell to get the PEP - // address to push. - assert(helperFunction.accessType == IAT_PVALUE); GetEmitter()->emitAddressConstant(helperFunction.addr); - GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0); + if (helperFunction.accessType != IAT_VALUE) + { + // Push PEP onto the stack because we are calling a managed helper that expects it as the last parameter. + // The helper function address is the address of an indirection cell, so load the PEP from it. + assert(helperFunction.accessType == IAT_PVALUE); + GetEmitter()->emitIns_I(INS_I_load, EA_PTRSIZE, 0); + } } if (params.callType == EC_INDIR_R) diff --git a/src/coreclr/jit/emitwasm.cpp b/src/coreclr/jit/emitwasm.cpp index cced3becef1a9b..bf7eb773a948ee 100644 --- a/src/coreclr/jit/emitwasm.cpp +++ b/src/coreclr/jit/emitwasm.cpp @@ -320,7 +320,7 @@ void emitter::emitIns_Call(const EmitCallParams& params) { case EC_FUNC_TOKEN: ins = params.isJump ? INS_return_call : INS_call; - id = emitNewInstrSC(EA_HANDLE_CNS_RELOC, 0 /* FIXME-WASM: function index reloc */); + id = emitNewInstrSC(EA_HANDLE_CNS_RELOC, (cnsval_ssize_t)params.addr); id->idIns(ins); id->idInsFmt(IF_FUNCIDX); break; diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 85bd63121d5559..3c4068e88c85d3 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -866,7 +866,10 @@ GenTreeCall* Compiler::fgGetSharedCCtor(CORINFO_CLASS_HANDLE cls) { #if defined(TARGET_WASM) // Wasm does not support dynamically created helpers - return fgGetStaticsCCtorHelper(cls, CORINFO_HELP_INITCLASS); + if (!IsNativeAot()) + { + return fgGetStaticsCCtorHelper(cls, CORINFO_HELP_INITCLASS); + } #endif #ifdef FEATURE_READYTORUN diff --git a/src/coreclr/jit/lower.cpp b/src/coreclr/jit/lower.cpp index 099e0400e1794c..2174e99f68d12a 100644 --- a/src/coreclr/jit/lower.cpp +++ b/src/coreclr/jit/lower.cpp @@ -7384,21 +7384,27 @@ GenTree* Lowering::LowerVirtualVtableCall(GenTreeCall* call) { noway_assert(call->gtCallType == CT_USER_FUNC); - GenTree* thisArgNode; + CallArg* thisArg; if (call->IsTailCallViaJitHelper()) { assert(call->gtArgs.CountArgs() > 0); - thisArgNode = call->gtArgs.GetArgByIndex(0)->GetNode(); + thisArg = call->gtArgs.GetArgByIndex(0); } else { assert(call->gtArgs.HasThisPointer()); - thisArgNode = call->gtArgs.GetThisArg()->GetNode(); + thisArg = call->gtArgs.GetThisArg(); } + GenTree* thisArgNode = thisArg->GetNode(); // get a reference to the thisPtr being passed +#if HAS_FIXED_REGISTER_SET assert(thisArgNode->OperIs(GT_PUTARG_REG)); GenTree* thisPtr = thisArgNode->AsUnOp()->gtGetOp1(); +#else + // On platforms without fixed registers (e.g., WASM), PUTARG nodes are not inserted. + GenTree* thisPtr = thisArgNode; +#endif // If what we are passing as the thisptr is not already a local, make a new local to place it in // because we will be creating expressions based on it. @@ -7415,7 +7421,11 @@ GenTree* Lowering::LowerVirtualVtableCall(GenTreeCall* call) vtableCallTemp = m_compiler->lvaGrabTemp(true DEBUGARG("virtual vtable call")); } +#if HAS_FIXED_REGISTER_SET LIR::Use thisPtrUse(BlockRange(), &thisArgNode->AsUnOp()->gtOp1, thisArgNode); +#else + LIR::Use thisPtrUse(BlockRange(), &thisArg->NodeRef(), call); +#endif ReplaceWithLclVar(thisPtrUse, vtableCallTemp); lclNum = vtableCallTemp; diff --git a/src/coreclr/jit/lowerwasm.cpp b/src/coreclr/jit/lowerwasm.cpp index 5f73d554c8bf99..44d45ea6fa1aa1 100644 --- a/src/coreclr/jit/lowerwasm.cpp +++ b/src/coreclr/jit/lowerwasm.cpp @@ -66,22 +66,28 @@ void Lowering::LowerPEPCall(GenTreeCall* call) JITDUMP("Begin lowering PEP call\n"); DISPTREERANGE(BlockRange(), call); - // PEP call must always have a control expression - assert(call->gtControlExpr != nullptr); - LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); + GenTree* callTargetForArg; + if (call->gtControlExpr != nullptr) + { + LIR::Use callTargetUse(BlockRange(), &call->gtControlExpr, call); - JITDUMP("Creating new local variable for PEP"); - unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); - GenTreeLclVar* callTargetLclForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); + JITDUMP("Creating new local variable for PEP"); + unsigned int callTargetLclNum = callTargetUse.ReplaceWithLclVar(m_compiler); + callTargetForArg = m_compiler->gtNewLclvNode(callTargetLclNum, TYP_I_IMPL); + } + else + { + assert(call->gtDirectCallAddress != nullptr); + callTargetForArg = AddrGen(call->gtDirectCallAddress); + } DISPTREE(call); JITDUMP("Add new arg to call arg list corresponding to PEP target"); - NewCallArg pepTargetArg = - NewCallArg::Primitive(callTargetLclForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); - CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); + NewCallArg pepTargetArg = NewCallArg::Primitive(callTargetForArg).WellKnown(WellKnownArg::WasmPortableEntryPoint); + CallArg* pepArg = call->gtArgs.PushBack(m_compiler, pepTargetArg); pepArg->SetEarlyNode(nullptr); - pepArg->SetLateNode(callTargetLclForArg); + pepArg->SetLateNode(callTargetForArg); call->gtArgs.PushLateBack(pepArg); // Set up ABI information for this arg; PEP's should be passed as the last param to a wasm function @@ -90,12 +96,18 @@ void Lowering::LowerPEPCall(GenTreeCall* call) pepArg->AbiInfo = ABIPassingInformation::FromSegmentByValue(m_compiler, ABIPassingSegment::InRegister(pepReg, 0, TARGET_POINTER_SIZE)); - BlockRange().InsertBefore(call, callTargetLclForArg); + BlockRange().InsertBefore(call, callTargetForArg); // Lower the new PEP arg now that the call abi info is updated and lcl var is inserted LowerArg(call, pepArg); DISPTREE(call); + if (call->gtControlExpr == nullptr) + { + JITDUMP("Finished lowering direct PEP call\n"); + return; + } + JITDUMP("Rewrite PEP call's control expression to indirect through the new local variable\n"); // Rewrite the call's control expression to have an additional load from the PEP local diff --git a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs index 900b7a089812e4..50ee58b73eb434 100644 --- a/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs +++ b/src/coreclr/tools/Common/Compiler/DependencyAnalysis/Target_Wasm/WasmEmitter.cs @@ -13,14 +13,26 @@ namespace ILCompiler.DependencyAnalysis.Wasm { - public struct WasmEmitter(NodeFactory factory, bool relocsOnly) + public struct WasmEmitter { #if READYTORUN public WasmFunctionBody FunctionBody = null; #endif - public bool Is64Bit => factory.Target.PointerSize == 8; - public bool RelocsOnly => relocsOnly; + private readonly NodeFactory _factory; + private readonly bool _relocsOnly; + + public WasmEmitter(NodeFactory factory, bool relocsOnly) + { + _factory = factory; + _relocsOnly = relocsOnly; +#if READYTORUN + FunctionBody = null; +#endif + } + + public bool Is64Bit => _factory.Target.PointerSize == 8; + public bool RelocsOnly => _relocsOnly; public ObjectNode.ObjectData Encode(ISymbolDefinitionNode symbolDefinitionNode) { @@ -33,7 +45,7 @@ public ObjectNode.ObjectData Encode(ISymbolDefinitionNode symbolDefinitionNode) return new ObjectNode.ObjectData(encodedThunk, relocs, 1, new ISymbolDefinitionNode[] { symbolDefinitionNode }); #else - return default(ObjectNode.ObjectData); + throw new PlatformNotSupportedException("NativeAOT WebAssembly assembly stubs are not supported."); #endif } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index c2cf6b7fdf4505..a6b608fe79d602 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -713,6 +713,9 @@ public static void EmitObject(string objectFilePath, IReadOnlyCollection buffer) public abstract class WasmExpr : IWasmEncodable { - WasmExprKind _kind; + private WasmExprKind _kind; public WasmExpr(WasmExprKind kind) { _kind = kind; @@ -303,7 +303,7 @@ public static void OffsetRelocationsByOffset(Span buffer, int offset } } - readonly struct WasmEncodableULong : IWasmEncodable + internal readonly struct WasmEncodableULong : IWasmEncodable { private readonly ulong _value; public WasmEncodableULong(ulong value) @@ -322,7 +322,7 @@ public int Encode(Span buffer) public int EncodeRelocations(Span buffer) => 0; } - readonly struct WasmEncodableSymbol : IWasmEncodable + internal readonly struct WasmEncodableSymbol : IWasmEncodable { private readonly ISymbolNode _symbol; private readonly RelocType _relocType; @@ -374,10 +374,10 @@ public int EncodeRelocations(Span buffer) } } - class WasmMemoryArgInstruction : WasmExpr where TOffset : IWasmEncodable + internal sealed class WasmMemoryArgInstruction : WasmExpr where TOffset : IWasmEncodable { - readonly uint _align; - readonly TOffset _offset; + private readonly uint _align; + private readonly TOffset _offset; public WasmMemoryArgInstruction(WasmExprKind kind, uint align, TOffset offset) : base(kind) { @@ -418,9 +418,9 @@ public override int EncodeRelocations(Span buffer) } // Represents a constant expression (e.g., (i32.const )) - class WasmConstExpr : WasmExpr + internal sealed class WasmConstExpr : WasmExpr { - readonly long ConstValue; + private readonly long ConstValue; public WasmConstExpr(WasmExprKind kind, long value) : base(kind) { @@ -448,10 +448,10 @@ public override int Encode(Span buffer) } } - sealed class WasmIndirectCallInstruction : WasmExpr + internal sealed class WasmIndirectCallInstruction : WasmExpr { - ISymbolNode _type; - uint _tableIndex; + private ISymbolNode _type; + private uint _tableIndex; public WasmIndirectCallInstruction(WasmExprKind kind, ISymbolNode type, uint tableIndex) : base(kind) { @@ -484,9 +484,9 @@ public override int EncodeRelocations(Span buffer) } } - sealed class WasmLEBConstantReloc : WasmExpr + internal sealed class WasmLEBConstantReloc : WasmExpr { - readonly WasmEncodableSymbol _symbol; + private readonly WasmEncodableSymbol _symbol; public WasmLEBConstantReloc(WasmExprKind kind, ISymbolNode symbol, RelocType relocType) : base(kind) { @@ -511,7 +511,7 @@ public override int EncodeRelocations(Span buffer) } // Represents a local variable expression (e.g., (local.get )) - class WasmLocalVarExpr : WasmExpr + internal sealed class WasmLocalVarExpr : WasmExpr { public readonly int LocalIndex; public WasmLocalVarExpr(WasmExprKind kind, int localIndex) : base(kind) @@ -536,7 +536,7 @@ public override int EncodeSize() } // Represents a global variable expression (e.g., (global.get () - class WasmMemoryCopyExpr : WasmExpr + internal sealed class WasmMemoryCopyExpr : WasmExpr { public readonly int DstMemoryIndex; public readonly int SrcMemoryIndex; @@ -614,7 +614,7 @@ public override int EncodeSize() // Represents a memory.fill expression. // Binary encoding: 0xFC prefix + u32(11) sub-opcode + u32(memoryIndex) // Stack operands: (dst: i32, val: i32, len: i32) -> () - class WasmMemoryFillExpr : WasmExpr + internal sealed class WasmMemoryFillExpr : WasmExpr { public readonly int MemoryIndex; @@ -640,7 +640,7 @@ public override int EncodeSize() } // Represents a memory.init expression. - class WasmMemoryInitExpr : WasmExpr + internal sealed class WasmMemoryInitExpr : WasmExpr { public readonly int DataSegmentIndex; public readonly int MemoryIndex; @@ -672,7 +672,7 @@ public override int EncodeSize() // Represents a table.init expression. // Binary encoding: 0xFC prefix + u32(12) sub-opcode + u32(elemidx) + u32(tableidx) - class WasmTableInitExpr : WasmExpr + internal sealed class WasmTableInitExpr : WasmExpr { public readonly int ElemIndex; public readonly int TableIndex; @@ -702,7 +702,7 @@ public override int EncodeSize() } } - class WasmTableGrowExpr : WasmExpr + internal sealed class WasmTableGrowExpr : WasmExpr { public readonly uint TableIndex; @@ -723,14 +723,14 @@ public override int EncodeSize() } } - enum WasmAbsHeapType : byte + internal enum WasmAbsHeapType : byte { Func = 0x70, } - class WasmRefNullExpr : WasmExpr + internal sealed class WasmRefNullExpr : WasmExpr { - WasmAbsHeapType absheaptype; + private WasmAbsHeapType absheaptype; public WasmRefNullExpr(WasmAbsHeapType heapType) : base(WasmExprKind.RefNull) { @@ -749,7 +749,7 @@ public override int EncodeSize() } } - enum WasmBlockType : byte + internal enum WasmBlockType : byte { Empty = 0x40, I32 = 0x7F, @@ -758,9 +758,9 @@ enum WasmBlockType : byte F64 = 0x7C, V128 = 0x7B, } - class WasmBlockStartExpr : WasmExpr + internal sealed class WasmBlockStartExpr : WasmExpr { - WasmBlockType BlockType; + private WasmBlockType BlockType; public WasmBlockStartExpr(WasmExprKind kind, WasmBlockType blockType) : base(kind) { BlockType = blockType; @@ -780,7 +780,7 @@ public override int EncodeSize() // ************************************************ // Simple DSL wrapper for creating Wasm expressions // ************************************************ - static class Local + internal static class Local { public static WasmExpr Get(int index) { @@ -796,7 +796,7 @@ public static WasmExpr Tee(int index) } } - static class Global + internal static class Global { public static WasmExpr Get(int index) { @@ -808,7 +808,7 @@ public static WasmExpr Set(int index) } } - static class I32 + internal static class I32 { public static WasmExpr Const(long value) { @@ -827,7 +827,7 @@ public static WasmExpr ConstRVA(ISymbolNode symbolNode) public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.I32Store, 4, new WasmEncodableULong(offset)); } - static class I64 + internal static class I64 { public static WasmExpr Const(long value) { @@ -837,25 +837,25 @@ public static WasmExpr Const(long value) public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.I64Store, 8, new WasmEncodableULong(offset)); } - static class F32 + internal static class F32 { public static WasmExpr Load(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F32Load, 4, new WasmEncodableULong(offset)); public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F32Store, 4, new WasmEncodableULong(offset)); } - static class F64 + internal static class F64 { public static WasmExpr Load(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F64Load, 8, new WasmEncodableULong(offset)); public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.F64Store, 8, new WasmEncodableULong(offset)); } - static class V128 + internal static class V128 { public static WasmExpr Load(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.V128Load, 16, new WasmEncodableULong(offset)); public static WasmExpr Store(ulong offset) => new WasmMemoryArgInstruction(WasmExprKind.V128Store, 16, new WasmEncodableULong(offset)); } - static class Memory + internal static class Memory { public static WasmExpr Copy(int dstMemoryIndex = 0, int srcMemoryIndex = 0) { @@ -872,20 +872,20 @@ public static WasmExpr Init(int dataSegmentIndex, int memoryIndex = 0) return new WasmMemoryInitExpr(dataSegmentIndex, memoryIndex); } } - static class ControlFlow + internal static class ControlFlow { public static WasmExpr CallIndirect(ISymbolNode funcType, uint tableIndex) => new WasmIndirectCallInstruction(WasmExprKind.CallIndirect, funcType, tableIndex); } - static class Table + internal static class Table { public static WasmExpr Grow(uint tableIndex) => new WasmTableGrowExpr(tableIndex); public static WasmExpr Init(int elemSegmentIndex, int tableIndex = 0) => new WasmTableInitExpr(elemSegmentIndex, tableIndex); } - static class Ref + internal static class Ref { public static WasmExpr NullFuncRef => new WasmRefNullExpr(WasmAbsHeapType.Func); } - static class Block + internal static class Block { public static WasmExpr If(WasmBlockType blockType) => new WasmBlockStartExpr(WasmExprKind.If, blockType); public static WasmExpr End => new WasmUnaryExpr(WasmExprKind.End); diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs index 6441eef518f560..9bc77de3a9f201 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmNative.cs @@ -85,8 +85,8 @@ internal enum WasmExportKind : byte public class WasmGlobalImportType : WasmImportType { - WasmValueType _valueType; - WasmMutabilityType _mutability; + private readonly WasmValueType _valueType; + private readonly WasmMutabilityType _mutability; public WasmGlobalImportType(WasmValueType valueType, WasmMutabilityType mutability) : base (WasmExternalKind.Global) { @@ -115,7 +115,7 @@ public WasmTableImportType() : base (WasmExternalKind.Table) public override int Encode(Span buffer) { int pos = 0; - buffer[pos++] = (byte)0x70; // element type: funcref + buffer[pos++] = (byte)0x70; // element type: funcref buffer[pos++] = (byte)0; // table limits: flags (0 = min-only, 1 = min+max) pos += DwarfHelper.WriteULEB128(buffer.Slice(pos), 1); // Requires 1 table entry return pos; @@ -131,12 +131,12 @@ public enum WasmLimitType : byte HasMin = 0x00, HasMinAndMax = 0x01 } - + public class WasmMemoryImportType : WasmImportType { - WasmLimitType _limitType; - uint _min; - uint? _max; + private readonly WasmLimitType _limitType; + private readonly uint _min; + private readonly uint? _max; public WasmMemoryImportType(WasmLimitType limitType, uint min, uint? max = null) : base(WasmExternalKind.Memory) { diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 29f33411a947f4..58f386655f7e40 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -21,11 +21,6 @@ namespace ILCompiler.ObjectWriter /// internal abstract partial class WasmObjectWriter : ObjectWriter { - public const int StackPointerGlobalIndex = WasmGlobalImports.StackPointerGlobalIndex; - public const int ImageBaseGlobalIndex = WasmGlobalImports.ImageBaseGlobalIndex; - public const int TableBaseGlobalIndex = WasmGlobalImports.TableBaseGlobalIndex; - public const int AsyncContinuationGlobalIndex = WasmGlobalImports.AsyncContinuationGlobalIndex; - private readonly Dictionary _sectionToType = new() { { WasmObjectNodeSection.MemorySection, WasmSectionType.Memory }, @@ -74,13 +69,10 @@ private protected int[] SectionEmitOrder { get { - if (_sectionEmitOrder is null) - { - _sectionEmitOrder = _sectionOrder - .Where(name => _sections.Contains(name)) - .Select(name => _sections.GetSectionIndex(name)) - .ToArray(); - } + _sectionEmitOrder ??= _sectionOrder + .Where(_sections.Contains) + .Select(_sections.GetSectionIndex) + .ToArray(); return _sectionEmitOrder; } @@ -160,7 +152,15 @@ private protected override void RecordMethodDeclaration(INodeWithTypeSignature n flags |= WasmLowering.LoweringFlags.IsUnmanagedCallersOnly; } WriteSignatureIndexForFunction(node.Signature, flags, node); - RegisterFunctionSymbol(new Utf8String(node.GetMangledName(_nodeFactory.NameMangler))); + Utf8String functionName = GetMangledName(node); + RegisterFunctionSymbol(functionName); + + Utf8String alternateName = _nodeFactory.GetSymbolAlternateName(node, out _); + if (!alternateName.IsNull) + { + _wasmSymbolManager.AddAlias(ExternCName(alternateName), functionName); + } + if (node is INodeWithFunclets nodeWithFunclets) { RecordFunclets(nodeWithFunclets); @@ -400,9 +400,9 @@ private protected void FinalizeSectionEntryCounts() _sections.GetSection(ObjectNodeSection.WasmCodeSection.Name) .SetEntryCount(MethodCount); - Debug.Assert(_sections.GetSection(WasmObjectNodeSection.FunctionSection.Name).EntryCount == MethodCount); - Debug.Assert(_sections.GetSection(WasmObjectNodeSection.ImportSection.Name).EntryCount == _wasmSymbolManager.GetImportCount()); - Debug.Assert(_sections.GetSection(WasmObjectNodeSection.GlobalSection.Name).EntryCount == _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Global)); + Debug.Assert(GetOrCreateSection(WasmObjectNodeSection.ImportSection, out _).EntryCount == _wasmSymbolManager.GetImportCount()); + Debug.Assert(GetOrCreateSection(WasmObjectNodeSection.FunctionSection, out _).EntryCount == MethodCount); + Debug.Assert(GetOrCreateSection(WasmObjectNodeSection.GlobalSection, out _).EntryCount == _wasmSymbolManager.GetDefinitionCount(WasmIndexSpace.Global)); } } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs new file mode 100644 index 00000000000000..bc267f37baed92 --- /dev/null +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmRelocatableObjectWriter.cs @@ -0,0 +1,214 @@ +// 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.Collections.Generic; +using System.IO; +using ILCompiler.DependencyAnalysis; +using Internal.JitInterface; +using Internal.Text; +using Internal.TypeSystem.TypesDebugInfo; +using ILCompiler.DependencyAnalysis.Wasm; + +namespace ILCompiler.ObjectWriter +{ + internal sealed partial class WasmRelocatableObjectWriter : WasmObjectWriter + { + public WasmRelocatableObjectWriter(NodeFactory factory, ObjectWritingOptions options, OutputInfoBuilder outputInfoBuilder = null) : base(factory, options, outputInfoBuilder) + { + } + + private protected override void EmitObjectFile(Stream outputFileStream) + { + Debug.Assert(outputFileStream.CanSeek, $"EmitObjectFile requires seekable output stream"); + + FinalizeSectionEntryCounts(); + + EmitWasmHeader(outputFileStream); + + foreach (int index in SectionEmitOrder) + { + SectionDataEmitter section = _sections[index]; + if (_resolvableRelocations.TryGetValue(index, out List relocations) && + section is WasmSection) + { + using (Stream originalStream = section.ContentReadStream) + { + MemoryStream stream = new MemoryStream((int)originalStream.Length); + originalStream.Position = 0; + originalStream.CopyTo(stream); + ResolveRelocations(index, stream, relocations, sectionStart: 0); + section.ContentReadStream = stream; + // originalStream may be disposed, section.Stream now points to resolved stream + } + } + + section.EmitToStream(outputFileStream); + } + } + + private Dictionary> _resolvableRelocations = new(); + private protected override void EmitRelocations(int sectionIndex, List relocationList) + { + foreach (var reloc in relocationList) + { + if (!_resolvableRelocations.TryGetValue(sectionIndex, out List resolvable)) + { + _resolvableRelocations[sectionIndex] = resolvable = new List(); + } + // Unconditionally add the reloc to our resolvable list; we do some amount of relocation resolution + // for all relocation types. + resolvable.Add(reloc); + } + } + + private unsafe void ResolveRelocations(int sectionIndex, MemoryStream sectionStream, List relocs, long sectionStart = 0) + { + byte[] relocScratchBuffer = new byte[Relocation.MaxSize]; + + foreach (SymbolicRelocation reloc in relocs) + { + int size = Relocation.GetSize(reloc.Type); + if (size > relocScratchBuffer.Length) + { + throw new InvalidOperationException($"Unsupported relocation size for relocation: {reloc.Type}"); + } + + SymbolDefinition definedSymbol = _definedSymbols[reloc.SymbolName]; + + // We need a pinned raw pointer here for manipulation with Relocation.WriteValue + fixed (byte* pData = ReadRelocToDataSpan(reloc, relocScratchBuffer, sectionStart)) + { + long addend = Relocation.ReadValue(reloc.Type, pData); + int relocLength = Relocation.GetSize(reloc.Type); + + switch (reloc.Type) + { + case RelocType.WASM_TYPE_INDEX_LEB: + case RelocType.WASM_GLOBAL_INDEX_LEB: + case RelocType.WASM_TABLE_INDEX_I32: + case RelocType.WASM_TABLE_INDEX_I64: + case RelocType.WASM_TABLE_INDEX_SLEB: + case RelocType.WASM_TABLE_INDEX_REL_I32: + case RelocType.WASM_FUNCTION_INDEX_LEB: + case RelocType.WASM_MEMORY_ADDR_REL_SLEB when _sections.GetSection(definedSymbol.SectionIndex).Type == WasmSectionType.Code: + { + // These relocations reference a wasm structural index (function, type, + // table entry, or well-known global). For R2R we self-resolve them here to + // the index assigned when the symbol was registered into its index space. + if (!_wasmSymbolManager.TryGetSymbol(reloc.SymbolName, out WasmSymbol symbol)) + { + throw new InvalidOperationException($"Symbol '{reloc.SymbolName}' was not registered. Relocation type {reloc.Type}."); + } + Relocation.WriteValue(reloc.Type, pData, symbol.Index + addend); + break; + } + + default: + // TODO-WASM: add other cases as needed; + // ignoring other reloc types for now + throw new NotSupportedException($"Relocation type {reloc.Type} not yet implemented"); + } + + WriteRelocFromDataSpan(reloc, pData, sectionStart); + } + } + + Span ReadRelocToDataSpan(SymbolicRelocation reloc, byte[] buffer, long sectionStart) + { + Span relocContents = buffer.AsSpan(0, Relocation.GetSize(reloc.Type)); + sectionStream.Position = reloc.Offset + sectionStart; + sectionStream.ReadExactly(relocContents); + return relocContents; + } + + void WriteRelocFromDataSpan(SymbolicRelocation reloc, byte* pData, long sectionStart) + { + sectionStream.Position = reloc.Offset + sectionStart; + sectionStream.Write(new Span(pData, Relocation.GetSize(reloc.Type))); + } + } + + private protected override SectionDataEmitter CreateDataSection( + ObjectNodeSection section, + int sectionIndex, + Stream sectionStream) + { + return new WasmSection(WasmSectionType.Data, sectionStream, new Utf8String("data"), sectionIndex); + } + + protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) + { + } + private protected override void WriteGlobalSection() + { + } + + private const int RtlRestoreContextTagIndex = 0; + private static readonly WasmFuncType RtlRestoreContextTagSignature = new( + new([]), + new([])); + private const int StackPointerGlobalIndex = 0; + private const int ImageBaseGlobalIndex = 1; + private const int TableBaseGlobalIndex = 2; + private const int AsyncContinuationGlobalIndex = 3; + private static readonly Utf8String RtlRestoreContextTagName = new Utf8String("RtlRestoreContextTag"); + private WasmImport[] CreateDefaultGlobalImports() + { + int rtlRestoreContextTagTypeIndex = RegisterSignature(RtlRestoreContextTagSignature); + + return + [ + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.StackPointerName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: StackPointerGlobalIndex), + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.ImageBaseName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: ImageBaseGlobalIndex), + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.TableBaseName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Const), index: TableBaseGlobalIndex), + new WasmImport("webcil", WasmWellKnownGlobalSymbolNode.AsyncContinuationName, import: new WasmGlobalImportType(WasmValueType.I32, WasmMutabilityType.Mut), index: AsyncContinuationGlobalIndex), + new WasmImport("webcil", "table", import: new WasmTableImportType(), index: 0), + new WasmImport("webcil", RtlRestoreContextTagName.ToString(), import: new WasmTagImportType(rtlRestoreContextTagTypeIndex), index: RtlRestoreContextTagIndex), + new WasmImport("webcil", "memory", import: new WasmMemoryImportType(WasmLimitType.HasMin, /* TODO: This is an arbitrary number */ 32)) + ]; + } + + private protected override void WriteImports() + { + foreach (WasmImport import in CreateDefaultGlobalImports()) + { + WriteImport(import); + } + } + + private protected override void WriteExports() + { + } + + private protected override void WriteElements() + { + } + } + + // AOT + internal sealed partial class WasmRelocatableObjectWriter : WasmObjectWriter + { + private protected override void EmitUnwindInfo(SectionWriter sectionWriter, INodeWithCodeInfo nodeWithCodeInfo, Utf8String currentSymbolName) + { + } + + private protected override ITypesDebugInfoWriter CreateDebugInfoBuilder() + { + return null; + } + + private protected override void EmitDebugFunctionInfo(uint methodTypeIndex, Utf8String methodName, SymbolDefinition methodSymbol, INodeWithDebugInfo debugNode, bool hasSequencePoints) + { + } + + private protected override void EmitDebugSections(IDictionary definedSymbols) + { + } + + private protected override void CreateEhSections() + { + } + } +} diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs index 1fe51a1d5eb0f7..b53d4ad06dd570 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmSymbolManager.cs @@ -56,6 +56,7 @@ public T this[WasmIndexSpace indexSpace] } private readonly Dictionary _entries = new(); + private readonly Dictionary _aliases = new(); private IndexSpaceArray _importCounts = new IndexSpaceArray(); private IndexSpaceArray _definitionCounts = new IndexSpaceArray(); private IndexSpaceArray _importsFrozen = new IndexSpaceArray(); @@ -76,14 +77,21 @@ public void AddDefinition(Utf8String name, WasmIndexSpace indexSpace) _definitionCounts[indexSpace]++; } + public void AddAlias(Utf8String alias, Utf8String target) + { + Entry entry = _entries[target]; + _aliases.Add(alias, entry with { Name = alias }); + } + public WasmSymbol GetSymbol(Utf8String name) { - return ResolveAndFreeze(_entries[name]); + return ResolveAndFreeze(GetEntry(name)); } public bool TryGetSymbol(Utf8String name, out WasmSymbol symbol) { - if (!_entries.TryGetValue(name, out Entry entry)) + if (!_entries.TryGetValue(name, out Entry entry) && + !_aliases.TryGetValue(name, out entry)) { symbol = default; return false; @@ -93,6 +101,9 @@ public bool TryGetSymbol(Utf8String name, out WasmSymbol symbol) return true; } + private Entry GetEntry(Utf8String name) => + _entries.TryGetValue(name, out Entry entry) ? entry : _aliases[name]; + public int GetImportCount() => _importCounts.Values.Sum(); public int GetDefinitionCount(WasmIndexSpace indexSpace) => diff --git a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs index 592ec82427ad16..1d7bdd03e6d639 100644 --- a/src/coreclr/tools/Common/JitInterface/WasmLowering.cs +++ b/src/coreclr/tools/Common/JitInterface/WasmLowering.cs @@ -394,7 +394,7 @@ public static WasmSignature GetSignature(MethodSignature signature, LoweringFlag { if (!flags.HasFlag(LoweringFlags.IsUnmanagedCallersOnly) && signature.Flags.HasFlag(MethodSignatureFlags.UnmanagedCallingConvention)) { - flags = flags | LoweringFlags.IsUnmanagedCallersOnly; + flags |= LoweringFlags.IsUnmanagedCallersOnly; } TypeDesc returnType = signature.ReturnType; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj index 2e0b217160f958..2bd02d648b64e7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/ILCompiler.Compiler.Tests.Assets.csproj @@ -15,4 +15,8 @@ + + + + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs new file mode 100644 index 00000000000000..4d4f9ef801ffa6 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.Assets/XunitStubs.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xunit +{ + internal sealed class FactAttribute : System.Attribute + { + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj index 69e9d87637f92d..ef24d8566e4d3b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj @@ -26,6 +26,9 @@ + + Configuration=$(CoreCLRConfiguration) + false @@ -43,5 +46,22 @@ + + + + + <_NativeAotWasmTestSupported Condition="('$(BuildArchitecture)' == 'x64' or '$(BuildArchitecture)' == 'arm64') and ('$(TargetArchitecture)' == 'x64' or '$(TargetArchitecture)' == 'arm64')">true + + + + + $(_NativeAotWasmTestSupported) + + + $(BuildArchitecture) + + + $(CoreCLRArtifactsPath) + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs new file mode 100644 index 00000000000000..0eb70da110a9e8 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/WasmSingleMethodTests.cs @@ -0,0 +1,195 @@ +// 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.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +using Microsoft.DotNet.XUnitExtensions; + +using Xunit; + +namespace ILCompiler.Compiler.Tests +{ + public class WasmSingleMethodTests + { + private const string ExportName = "ILCompiler_Compiler_Tests_Assets_SwitchTest__TestEntryPoint"; + private static readonly byte[] WasmHeader = [0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00]; + + public static bool IsWasmCompilationSupported => + string.Equals( + AppContext.GetData("NativeAotWasmTest.IsSupported") as string, + "true", + StringComparison.OrdinalIgnoreCase); + + public static bool IsWasmExecutionSupported => + IsWasmCompilationSupported && + RunProcess( + "node", + ["-e", "process.exit(typeof WebAssembly.Tag === 'function' ? 0 : 1)"], + throwOnError: false).ExitCode == 0; + + [ConditionalFact(nameof(IsWasmCompilationSupported))] + public void NativeAotWasmSingleMethodCompiles() + { + string outputPath = CompileSwitchTest(); + try + { + byte[] output = File.ReadAllBytes(outputPath); + Assert.True(output.Length >= WasmHeader.Length); + Assert.Equal(WasmHeader, output.AsSpan(0, WasmHeader.Length).ToArray()); + } + finally + { + File.Delete(outputPath); + } + } + + [ConditionalFact(nameof(IsWasmExecutionSupported))] + public void NativeAotWasmSingleMethodExecutes() + { + string outputPath = CompileSwitchTest(); + string scriptPath = Path.ChangeExtension(outputPath, ".js"); + try + { + File.WriteAllText(scriptPath, + $$""" + const fs = require("fs"); + const bytes = fs.readFileSync({{ToJavaScriptString(outputPath)}}); + if (!WebAssembly.validate(bytes)) { + throw new Error("NativeAOT produced an invalid WebAssembly module."); + } + const webcil = { + stackPointer: new WebAssembly.Global({ value: "i32", mutable: true }, 65000), + imageBase: new WebAssembly.Global({ value: "i32", mutable: false }, 0), + tableBase: new WebAssembly.Global({ value: "i32", mutable: false }, 0), + asyncContinuation: new WebAssembly.Global({ value: "i32", mutable: true }, 0), + table: new WebAssembly.Table({ initial: 4096, element: "anyfunc" }), + rtlRestoreContextTag: new WebAssembly.Tag({ parameters: [] }), + memory: new WebAssembly.Memory({ initial: 16 }), + }; + WebAssembly.instantiate(bytes, { webcil }).then(({ instance }) => { + const result = instance.exports.{{ExportName}}(65000, 0); + if (result !== 100) { + throw new Error(`Expected 100, got ${result}.`); + } + }); + """); + + ProcessResult result = RunProcess("node", [scriptPath], throwOnError: false); + Assert.True(result.ExitCode == 0, result.Output); + } + finally + { + File.Delete(scriptPath); + File.Delete(outputPath); + } + } + + private static string CompileSwitchTest() + { + string coreClrArtifactsDir = Assert.IsType(AppContext.GetData("NativeAotWasmTest.CoreCLRArtifactsDir")); + string buildArchitecture = Assert.IsType(AppContext.GetData("NativeAotWasmTest.BuildArchitecture")); + string ilcPath = Path.Combine( + coreClrArtifactsDir, + buildArchitecture, + "ilc", + OperatingSystem.IsWindows() ? "ilc.exe" : "ilc"); + string jitFileName = OperatingSystem.IsWindows() + ? $"clrjit_universal_wasm_{buildArchitecture}.dll" + : OperatingSystem.IsMacOS() + ? $"libclrjit_universal_wasm_{buildArchitecture}.dylib" + : $"libclrjit_universal_wasm_{buildArchitecture}.so"; + string jitPath = Path.Combine(coreClrArtifactsDir, jitFileName); + if (!File.Exists(jitPath)) + { + jitPath = Path.Combine(coreClrArtifactsDir, buildArchitecture, jitFileName); + } + + Assert.True(File.Exists(jitPath), $"WASM JIT not found at '{jitPath}'."); + + string outputPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid():N}.wasm"); + try + { + RunProcess( + ilcPath, + [ + "--singlemethodtypename", "SwitchTest, ILCompiler.Compiler.Tests.Assets", + "--singlemethodname", "TestEntryPoint", + Path.Combine(AppContext.BaseDirectory, "ILCompiler.Compiler.Tests.Assets.dll"), + $"-r:{Path.Combine(AppContext.BaseDirectory, "Test.CoreLib.dll")}", + "--systemmodule:Test.CoreLib", + $"-o:{outputPath}", + "--targetarch:wasm", + "--targetos:browser", + $"--jitpath:{jitPath}", + "--stacktracedata:none", + "--reflectiondata:none", + ], + throwOnError: true); + + return outputPath; + } + catch + { + File.Delete(outputPath); + throw; + } + } + + private static ProcessResult RunProcess(string fileName, IEnumerable arguments, bool throwOnError) + { + var startInfo = new ProcessStartInfo(fileName) + { + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + }; + foreach (string argument in arguments) + { + startInfo.ArgumentList.Add(argument); + } + + try + { + using Process process = Process.Start(startInfo) ?? + throw new InvalidOperationException($"Failed to start '{fileName}'."); + Task standardOutput = process.StandardOutput.ReadToEndAsync(); + Task standardError = process.StandardError.ReadToEndAsync(); + process.WaitForExit(); + + var result = new ProcessResult( + process.ExitCode, + standardOutput.GetAwaiter().GetResult() + standardError.GetAwaiter().GetResult()); + if (throwOnError && result.ExitCode != 0) + { + throw new InvalidOperationException(result.Output); + } + + return result; + } + catch (Exception ex) when (!throwOnError && ex is Win32Exception or InvalidOperationException) + { + return new ProcessResult(-1, ex.ToString()); + } + } + + private static string ToJavaScriptString(string value) => + '"' + value.Replace("\\", "\\\\", StringComparison.Ordinal).Replace("\"", "\\\"", StringComparison.Ordinal) + '"'; + + private readonly struct ProcessResult + { + public ProcessResult(int exitCode, string output) + { + ExitCode = exitCode; + Output = output; + } + + public int ExitCode { get; } + public string Output { get; } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs index 636bf28595a23f..035042b3d012ff 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -11,6 +11,7 @@ using ILCompiler.DependencyAnalysisFramework; using Internal.IL; +using Internal.JitInterface; using Internal.NativeFormat; using Internal.Runtime; using Internal.Text; @@ -1618,9 +1619,14 @@ public AnalysisCharacteristicNode AnalysisCharacteristic(string ch) // memory efficiency on lookup public WasmTypeNode WasmTypeNode(MethodDesc desc) { - // TODO-Wasm: Construct proper function type based on the passed in MethodDesc - // once we have defined lowering rules for signatures in NativeAOT. - throw new NotImplementedException("NAOT wasm type signature lowering not yet implemented"); + WasmFuncType funcType = WasmLowering.GetSignature(desc).FuncType; + return _wasmTypeNodes.GetOrAdd(funcType); + } + + public WasmTypeNode WasmTypeNode(CorInfoWasmType[] types) + { + WasmFuncType funcType = WasmFuncType.FromCorInfoSignature(types); + return _wasmTypeNodes.GetOrAdd(funcType); } /// diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs index ec6fa5d049cd3f..a8e07d0f528fca 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ReadyToRunGenericHelperNode.cs @@ -13,6 +13,10 @@ namespace ILCompiler.DependencyAnalysis { + /// + /// Represents a NativeAOT runtime generic dictionary lookup helper. + /// "ReadyToRun" refers to the JIT helper ABI used to request the lookup, not to the ReadyToRun compiler. + /// public abstract partial class ReadyToRunGenericHelperNode : AssemblyStubNode, INodeWithRuntimeDeterminedDependencies { private readonly ReadyToRunHelperId _id; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs index 0adaef32572541..eca26b3792cc9a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmJumpStubNode.cs @@ -11,7 +11,7 @@ public partial class JumpStubNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly jump stubs are not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs index c5c13718eb873c..dfed6e0e5c3eb9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunGenericHelperNode.cs @@ -11,12 +11,14 @@ public partial class ReadyToRunGenericHelperNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException( + "NativeAOT WebAssembly does not support runtime generic dictionary lookup helpers."); } protected virtual void EmitLoadGenericContext(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException( + "NativeAOT WebAssembly runtime generic dictionary context loading is not supported."); } } @@ -24,7 +26,8 @@ public partial class ReadyToRunGenericLookupFromTypeNode { protected override void EmitLoadGenericContext(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException( + "NativeAOT WebAssembly runtime generic dictionary context loading from a type is not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs index 310f7e22c3153d..09702fc27ce327 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmReadyToRunHelperNode.cs @@ -11,7 +11,7 @@ public partial class ReadyToRunHelperNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly ReadyToRun helpers are not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs index 7596bdeec2a9dd..f20476c7d37649 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmTentativeMethodNode.cs @@ -11,7 +11,7 @@ public partial class TentativeMethodNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly tentative method stubs are not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs index 0ed23ac753b3c3..253053cd051ea0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/Target_Wasm/WasmUnboxingStubNode.cs @@ -11,7 +11,7 @@ public partial class UnboxingStubNode { protected override void EmitCode(NodeFactory factory, ref WasmEmitter encoder, bool relocsOnly) { - throw new NotImplementedException(); + throw new PlatformNotSupportedException("NativeAOT WebAssembly unboxing stubs are not supported."); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index e26214457ac8c3..014d87408377bc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -377,6 +377,14 @@ + + + + + + + + diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj index e96e97d7c8f03e..2fad7885d05cc8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj @@ -201,7 +201,6 @@ - diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs index f8418a8d67befc..1d5c713c5596d8 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/MethodCodeNode.cs @@ -50,7 +50,10 @@ public void SetCode(ObjectData data) public override ObjectNodeSection GetSection(NodeFactory factory) { return factory.Target.IsWindows ? - ObjectNodeSection.ManagedCodeWindowsContentSection : ObjectNodeSection.ManagedCodeUnixContentSection; + ObjectNodeSection.ManagedCodeWindowsContentSection : + factory.Target.IsWasm ? + ObjectNodeSection.WasmCodeSection : + ObjectNodeSection.ManagedCodeUnixContentSection; } public override bool StaticDependenciesAreComputed => _methodCode != null; @@ -119,7 +122,7 @@ public ISymbolNode GetUnboxingThunkTarget(NodeFactory factory) public MethodExceptionHandlingInfoNode EHInfo => _ehInfo; // TODO-WASM: Appropriately extract funclet kinds from eh clause info - public FuncletKind[] GetFuncletKinds() => throw new NotImplementedException(); + public FuncletKind[] GetFuncletKinds() => []; public ISymbolNode GetAssociatedDataNode(NodeFactory factory) { diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs index 5c635bf69e3b4c..a1b006eba54336 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilation.cs @@ -110,8 +110,11 @@ protected override void CompileInternal(string outputFile, ObjectDumper dumper) if ((_compilationOptions & RyuJitCompilationOptions.UseDwarf5) != 0) options |= ObjectWritingOptions.UseDwarf5; - if (_debugInformationProvider is not NullDebugInformationProvider) + if (_debugInformationProvider is not NullDebugInformationProvider && + NodeFactory.Target.Architecture != TargetArchitecture.Wasm32) + { options |= ObjectWritingOptions.GenerateDebugInfo; + } if ((_compilationOptions & RyuJitCompilationOptions.ControlFlowGuardAnnotations) != 0) options |= ObjectWritingOptions.ControlFlowGuard; diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj b/src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj index 12164a8e937f9f..c80cac8a2dfc77 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/ILCompiler.RyuJit.csproj @@ -50,12 +50,6 @@ Compiler\JitHelper.cs - - ObjectWriter\WasmNative.cs - - - ObjectWriter\WasmInstructions.cs - IL\HelperExtensions.cs @@ -95,9 +89,7 @@ JitInterface\SwiftPhysicalLowering.cs - - JitInterface\WasmLowering.cs - + Pgo\TypeSystemEntityOrUnknown.cs diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs index 14f0f9b6c1e33a..4a74c65d1c0073 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs @@ -15,6 +15,7 @@ using ILCompiler; using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysis.Wasm; using System.Runtime.CompilerServices; #if SUPPORT_JIT @@ -2518,7 +2519,10 @@ private void getThreadLocalStaticInfo_NativeAOT(CORINFO_THREAD_STATIC_INFO_NATIV private CORINFO_WASM_TYPE_SYMBOL_STRUCT_* getWasmTypeSymbol(CorInfoWasmType* types, nuint typesSize) { - throw new NotImplementedException(); + CorInfoWasmType[] typeArray = new ReadOnlySpan(types, (int)typesSize).ToArray(); + + WasmTypeNode typeNode = _compilation.NodeFactory.WasmTypeNode(typeArray); + return (CORINFO_WASM_TYPE_SYMBOL_STRUCT_*)ObjectToHandle(typeNode); } #pragma warning disable CA1822 // Mark members as static diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompiler.props b/src/coreclr/tools/aot/ILCompiler/ILCompiler.props index e901d56df167fb..84855fc2f189ad 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompiler.props +++ b/src/coreclr/tools/aot/ILCompiler/ILCompiler.props @@ -52,5 +52,6 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler/reproZero/Program.cs b/src/coreclr/tools/aot/ILCompiler/reproZero/Program.cs new file mode 100644 index 00000000000000..a9f7df1193567b --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler/reproZero/Program.cs @@ -0,0 +1,129 @@ +// 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; +using System.Runtime.InteropServices; + +#region A couple very basic things +namespace System +{ + public class Object + { +#pragma warning disable 169 + // The layout of object is a contract with the compiler. + private IntPtr m_pMethodTable; +#pragma warning restore 169 + } + public struct Void { } + + // The layout of primitive types is special cased because it would be recursive. + // These really don't need any fields to work. + public struct Boolean { } + public struct Char { } + public struct SByte { } + public struct Byte { } + public struct Int16 { } + public struct UInt16 { } + public struct Int32 { } + public struct UInt32 { } + public struct Int64 { } + public struct UInt64 { } + public struct IntPtr { } + public struct UIntPtr { } + public struct Single { } + public struct Double { } + + public abstract class ValueType { } + public abstract class Enum : ValueType { } + + public struct Nullable where T : struct { } + + public sealed class String { public readonly int Length; } + public abstract class Array { } + public abstract class Delegate { } + public abstract class MulticastDelegate : Delegate { } + + public struct RuntimeTypeHandle { } + public struct RuntimeMethodHandle { } + public struct RuntimeFieldHandle { } + + public class Attribute { } + + public enum AttributeTargets { } + + public sealed class AttributeUsageAttribute : Attribute + { + public AttributeUsageAttribute(AttributeTargets validOn) { } + public bool AllowMultiple { get; set; } + public bool Inherited { get; set; } + } + + public class AppContext + { + public static void SetData(string s, object o) { } + } + + namespace Runtime.CompilerServices + { + public class RuntimeHelpers + { + public static unsafe int OffsetToStringData => sizeof(IntPtr) + sizeof(int); + } + } +} +namespace System.Runtime.InteropServices +{ + public sealed class DllImportAttribute : Attribute + { + public DllImportAttribute(string dllName) { } + } +} +#endregion + +#region Things needed by ILC +namespace System +{ + namespace Runtime + { + internal sealed class RuntimeExportAttribute : Attribute + { + public RuntimeExportAttribute(string entry) { } + } + } + + class Array : Array { } +} + +namespace Internal.Runtime.CompilerHelpers +{ + // A class that the compiler looks for that has helpers to initialize the + // process. The compiler can gracefully handle the helpers not being present, + // but the class itself being absent is unhandled. Let's add an empty class. + class StartupCodeHelpers + { + // A couple symbols the generated code will need we park them in this class + // for no particular reason. These aid in transitioning to/from managed code. + // Since we don't have a GC, the transition is a no-op. + [RuntimeExport("RhpReversePInvoke")] + static void RhpReversePInvoke(IntPtr frame) { } + [RuntimeExport("RhpReversePInvokeReturn")] + static void RhpReversePInvokeReturn(IntPtr frame) { } + [RuntimeExport("RhpPInvoke")] + static void RhpPInvoke(IntPtr frame) { } + [RuntimeExport("RhpPInvokeReturn")] + static void RhpPInvokeReturn(IntPtr frame) { } + + [RuntimeExport("RhpFallbackFailFast")] + static void RhpFallbackFailFast() { while (true) ; } + } +} +#endregion + +unsafe class Program +{ + static int Main() + { + return 42; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler/reproZero/reproZero.csproj b/src/coreclr/tools/aot/ILCompiler/reproZero/reproZero.csproj new file mode 100644 index 00000000000000..9a501e553cfbcd --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler/reproZero/reproZero.csproj @@ -0,0 +1,64 @@ + + + + $(NetCoreAppToolCurrent) + Exe + x64;x86;wasm + AnyCPU + false + false + Debug;Release;Checked + true + false + true + true + false + false + false + v4.0.30319 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +