diff --git a/src/coreclr/inc/clrnt.h b/src/coreclr/inc/clrnt.h index a8f093dd188bc4..d95d0e0ec07142 100644 --- a/src/coreclr/inc/clrnt.h +++ b/src/coreclr/inc/clrnt.h @@ -506,7 +506,7 @@ RtlVirtualUnwind( #define UNW_FLAG_EHANDLER 0x1 /* filter handler */ #define UNW_FLAG_UHANDLER 0x2 /* unwind handler */ -inline PEXCEPTION_ROUTINE +PEXCEPTION_ROUTINE RtlVirtualUnwind ( _In_ DWORD HandlerType, _In_ DWORD ImageBase, @@ -516,25 +516,19 @@ RtlVirtualUnwind ( _Out_ PVOID *HandlerData, _Out_ PDWORD EstablisherFrame, __inout_opt PT_KNONVOLATILE_CONTEXT_POINTERS ContextPointers - ) -{ - PORTABILITY_ASSERT("The function RtlVirtualUnwind is not implemented on wasm"); - return nullptr; -} + ); + +UINT32 DecodeULEB128AsU32(PTR_BYTE* ppData); -FORCEINLINE ULONG RtlpGetFunctionEndAddress ( _In_ PT_RUNTIME_FUNCTION FunctionEntry, _In_ TADDR ImageBase - ) -{ - PORTABILITY_ASSERT("The function RtlpGetFunctionEndAddress is not implemented on wasm"); - return 0; -} + ); -#define RUNTIME_FUNCTION__BeginAddress(FunctionEntry) ((FunctionEntry)->BeginAddress) -#define RUNTIME_FUNCTION__SetBeginAddress(FunctionEntry,address) ((FunctionEntry)->BeginAddress = (address)) +#define RUNTIME_FUNCTION__IsFunclet(FunctionEntry) ((FunctionEntry)->BeginAddress & 0x80000000) +#define RUNTIME_FUNCTION__BeginAddress(FunctionEntry) ((FunctionEntry)->BeginAddress & 0x7FFFFFFF) +#define RUNTIME_FUNCTION__SetBeginAddress(FunctionEntry,address) ((FunctionEntry)->BeginAddress = address) #define RUNTIME_FUNCTION__EndAddress(FunctionEntry, ImageBase) (RtlpGetFunctionEndAddress(FunctionEntry, (ULONG64)(ImageBase))) diff --git a/src/coreclr/jit/compiler.h b/src/coreclr/jit/compiler.h index 0435afc4f8f524..8dd4e11fc418c9 100644 --- a/src/coreclr/jit/compiler.h +++ b/src/coreclr/jit/compiler.h @@ -1775,6 +1775,7 @@ struct FuncInfoDsc jitstd::vector* funWasmLocalDecls; unsigned funWasmFrameSize; + unsigned funWasmVirtualIPCount; bool needsUnwindableFrame; emitLocation* startLoc; emitLocation* endLoc; diff --git a/src/coreclr/jit/flowgraph.cpp b/src/coreclr/jit/flowgraph.cpp index 7fa0f4a3f7bf02..e4fcadc2c3be79 100644 --- a/src/coreclr/jit/flowgraph.cpp +++ b/src/coreclr/jit/flowgraph.cpp @@ -3154,6 +3154,7 @@ PhaseStatus Compiler::fgCreateFunclets() #endif #ifdef TARGET_WASM funcInfo[i].funWasmLocalDecls = nullptr; + funcInfo[i].funWasmVirtualIPCount = 10; #endif } #endif diff --git a/src/coreclr/jit/unwindwasm.cpp b/src/coreclr/jit/unwindwasm.cpp index 0d6b73e8c46657..63ba0079f13c0b 100644 --- a/src/coreclr/jit/unwindwasm.cpp +++ b/src/coreclr/jit/unwindwasm.cpp @@ -60,7 +60,8 @@ void Compiler::unwindReserveFunc(FuncInfoDsc* func) { bool isFunclet = func->IsFunclet(); bool isColdCode = false; - ULONG encodedSize = emitter::SizeOfULEB128(func->funWasmFrameSize); + ULONG encodedSize = emitter::SizeOfULEB128(func->funWasmFrameSize) + + emitter::SizeOfULEB128(func->funWasmVirtualIPCount); eeReserveUnwindInfo(isFunclet, isColdCode, encodedSize); } @@ -104,11 +105,13 @@ void Compiler::unwindEmitFunc(FuncInfoDsc* func, void* pHotCode, void* pColdCode // pColdCode = nullptr; - // Unwind info is just the frame size. - // Record frame size with ULEB128 compression. + // Unwind info is the frame size followed by the virtual IP count, + // both encoded as ULEB128. // - uint8_t buffer[5]; + uint8_t buffer[10]; ULONG encodedSize = (ULONG)GetEmitter()->emitOutputULEB128(buffer, func->funWasmFrameSize); + encodedSize += (ULONG)GetEmitter()->emitOutputULEB128(buffer + encodedSize, func->funWasmVirtualIPCount); + // WASM-TODO, sum total of virtual IP size should be recorded into the GC info for the method as well. assert(encodedSize <= sizeof(buffer)); eeAllocUnwindInfo((BYTE*)pHotCode, (BYTE*)pColdCode, startOffset, endOffset, encodedSize, (BYTE*)&buffer, diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 5eb2018c7cf947..0a94d7a27b1f83 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -210,7 +210,8 @@ or IMAGE_REL_BASED_THUMB_BRANCH24 or IMAGE_REL_BASED_THUMB_MOV32_PCREL && { fixed (byte* pData = data) { - Relocation.WriteValue(relocType, (void*)pData, definedSymbol.Size); + long adjustedAddend = addend + Relocation.ReadValue(relocType, (void*)pData); + Relocation.WriteValue(relocType, (void*)pData, definedSymbol.Size + adjustedAddend); } } else diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs index 74f24a11758d1e..d4875e7e1812ec 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs @@ -895,31 +895,27 @@ private unsafe void ResolveRelocations(int sectionIndex, MemoryStream sectionStr break; } - // TODO-Wasm: None of the IMAGE_REL type relocs should occur in Wasm - // code, and we should add asserts for this once we've updated the necessary - // dependency nodes to emit the proper reloc type on Wasm. case RelocType.IMAGE_REL_BASED_ABSOLUTE: // No action required break; case RelocType.IMAGE_REL_BASED_DIR64: case RelocType.IMAGE_REL_BASED_HIGHLOW: - // Debug.Assert(betweenWebcilSections); // This is an ImageBase-relative value in PE, but our image base // for Webcil is virtual address 0 + Debug.Assert(symbolWebcilSection != null); Relocation.WriteValue(reloc.Type, pData, virtualSymbolImageOffset + 0 + addend); break; case RelocType.IMAGE_REL_BASED_ADDR32NB: - // Debug.Assert(betweenWebcilSections); + Debug.Assert(symbolWebcilSection != null); Relocation.WriteValue(reloc.Type, pData, virtualSymbolImageOffset + addend); break; case RelocType.IMAGE_REL_BASED_REL32: case RelocType.IMAGE_REL_BASED_RELPTR32: - // Debug.Assert(betweenWebcilSections); + Debug.Assert(symbolWebcilSection != null); Relocation.WriteValue(reloc.Type, pData, virtualSymbolImageOffset - (virtualRelocOffset + relocLength) + addend); break; case RelocType.IMAGE_REL_FILE_ABSOLUTE: - // Debug.Assert(betweenWebcilSections && symbolWebcilSection != null); Debug.Assert(symbolWebcilSection != null); long fileOffset = symbolWebcilSection.Header.PointerToRawData + definedSymbol.Value; Relocation.WriteValue(reloc.Type, pData, fileOffset + addend); diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs index f9fe3657851a16..82c9ff952d77a8 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ExceptionInfoLookupTableNode.cs @@ -118,10 +118,20 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) // Add the symbol representing this object node exceptionInfoLookupBuilder.AddSymbol(this); + bool isWasm = factory.Target.Architecture == TargetArchitecture.Wasm32; + // First, emit the actual EH records in sequence and store map from methods to the EH record symbols for (int index = 0; index < _methodNodes.Count; index++) { - exceptionInfoLookupBuilder.EmitReloc(_methodNodes[index], RelocType.IMAGE_REL_BASED_ADDR32NB); + if (isWasm) + { + // For WASM, we use a virtual IP in the table here instead of a method pointer, so that the runtime can perform a binary search based on the virtual IP. The virtual IP is emitted as a plain u32 (not a reloc). + exceptionInfoLookupBuilder.EmitUInt(factory.RuntimeFunctionsTable.GetWasmVirtualIP(_methodNodes[index], 0)); + } + else + { + exceptionInfoLookupBuilder.EmitReloc(_methodNodes[index], RelocType.IMAGE_REL_BASED_ADDR32NB); + } exceptionInfoLookupBuilder.EmitReloc(_ehInfoNode, RelocType.IMAGE_REL_BASED_ADDR32NB, _ehInfoOffsets[index]); } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHeaderNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHeaderNode.cs index 3f556dda8aedc9..03d7df49578a9d 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHeaderNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ReadyToRunHeaderNode.cs @@ -189,8 +189,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) builder.EmitReloc(item.StartSymbol, RelocType.IMAGE_REL_BASED_ADDR32NB); // The header entry for the runtime functions table should not include the 4 byte 0xffffffff sentinel - // value in the covered range. - int delta = item.Id == ReadyToRunSectionType.RuntimeFunctions ? RuntimeFunctionsTableNode.SentinelSizeAdjustment : 0; + // value in the covered range. For WASM, also excludes the trailing min table index. + int delta = item.Id == ReadyToRunSectionType.RuntimeFunctions ? factory.RuntimeFunctionsTable.SentinelSizeAdjustment : 0; builder.EmitReloc(item.StartSymbol, RelocType.IMAGE_REL_SYMBOL_SIZE, delta); count++; diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs index 063caae405075c..645d76169fff91 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/RuntimeFunctionsTableNode.cs @@ -15,6 +15,7 @@ public class RuntimeFunctionsTableNode : HeaderTableNode { private List _methodNodes; private Dictionary _insertedMethodNodes; + private Dictionary, uint> _methodToVirtualIP; private readonly NodeFactory _nodeFactory; private int _tableSize = -1; @@ -61,6 +62,80 @@ private void LayoutRuntimeFunctions() } } + public uint GetWasmVirtualIP(MethodWithGCInfo lookupMethod, int lookupFrameIndex) + { + bool isWasm = _nodeFactory.Target.Architecture == TargetArchitecture.Wasm32; + + if (!isWasm) + { + // For non-WASM targets, virtual IPs are not used, so throw + throw new InvalidOperationException("Virtual IPs are not used on non-WASM targets."); + } + + if (_methodToVirtualIP != null) + { + return _methodToVirtualIP[(lookupMethod, lookupFrameIndex)]; + } + + if (_methodNodes == null) + { + LayoutRuntimeFunctions(); + } + + Dictionary, uint> methodToVirtualIP = new (); + + // For WASM, track virtual IP index for each RUNTIME_FUNCTION entry + uint currentVirtualIP = 0; + + foreach (MethodWithGCInfo method in _methodNodes) + { + int[] funcletOffsets = method.GCInfoNode.CalculateFuncletOffsets(_nodeFactory); + + for (int frameIndex = 0; frameIndex < method.FrameInfos.Length; frameIndex++) + { + FrameInfo frameInfo = method.FrameInfos[frameIndex]; + methodToVirtualIP.Add((method, frameIndex), currentVirtualIP); + + // Advance the virtual IP by the number of virtual IPs for this frame + uint virtualIPCount = GetVirtualIPCountFromUnwindBlob(frameInfo.BlobData); + currentVirtualIP += virtualIPCount; + } + } + + _methodToVirtualIP = methodToVirtualIP; + return _methodToVirtualIP[(lookupMethod, lookupFrameIndex)]; + } + + /// + /// Decode a ULEB128 value from a byte array starting at the given offset. + /// Returns the decoded value and advances the offset past the encoded bytes. + /// + private static uint DecodeULEB128(byte[] data, ref int offset) + { + uint result = 0; + int shift = 0; + while (offset < data.Length) + { + byte b = data[offset++]; + result |= (uint)(b & 0x7F) << shift; + if ((b & 0x80) == 0) + break; + shift += 7; + } + return result; + } + + /// + /// Read the virtual IP count from a WASM unwind blob. + /// The blob format is: ULEB128(frameSize) followed by ULEB128(virtualIPCount). + /// + private static uint GetVirtualIPCountFromUnwindBlob(byte[] blobData) + { + int offset = 0; + DecodeULEB128(blobData, ref offset); // skip frame size + return DecodeULEB128(blobData, ref offset) * 2; // Multiply by 2 to force all virtual IPs to be an even number. + } + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) { if (relocsOnly) @@ -78,6 +153,8 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) uint runtimeFunctionIndex = 0; List mapping = new List(); + bool isWasm = _nodeFactory.Target.Architecture == TargetArchitecture.Wasm32; + for (int cold = 0; cold < 2; cold++) { foreach (MethodWithGCInfo method in _methodNodes) @@ -124,9 +201,11 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) symbol = method; } - if (_nodeFactory.Target.Architecture == TargetArchitecture.Wasm32) + if (isWasm) { - runtimeFunctionsBuilder.EmitReloc(symbol, RelocType.WASM_TABLE_INDEX_I32, frameIndex); + // Emit the virtual IP index as a plain u32 (not a reloc) + // Set high bit to indicate that the RUNTIME_FUNCTION is a funclet + runtimeFunctionsBuilder.EmitUInt(GetWasmVirtualIP(method, frameIndex) | (frameIndex != 0 ? 0x80000000 : 0)); } else { @@ -157,6 +236,20 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) // Emit sentinel entry runtimeFunctionsBuilder.EmitUInt(~0u); + if (isWasm) + { + // After sentinel, emit a WASM_TABLE_INDEX_I32 reloc pointing to the first + // compiled method. This records the min function table index in the file. + if (_methodNodes.Count > 0) + { + runtimeFunctionsBuilder.EmitReloc(_methodNodes[0], RelocType.WASM_TABLE_INDEX_I32, delta: 0); + } + else + { + runtimeFunctionsBuilder.EmitUInt(0); + } + } + _tableSize = runtimeFunctionsBuilder.CountBytes; return runtimeFunctionsBuilder.ToObjectData(); } @@ -164,7 +257,7 @@ public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) /// /// Returns the runtime functions table size and excludes the 4 byte sentinel entry at the end (used by /// the runtime in NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod) so that it's not treated as - /// part of the table itself. + /// part of the table itself. For WASM, also excludes the trailing 4-byte min table index. /// public int TableSizeExcludingSentinel { @@ -179,6 +272,7 @@ public int TableSizeExcludingSentinel public override int ClassCode => (int)ObjectNodeOrder.RuntimeFunctionsTableNode; - internal const int SentinelSizeAdjustment = -4; + internal int SentinelSizeAdjustment => + _nodeFactory.Target.Architecture == TargetArchitecture.Wasm32 ? -8 : -4; } } diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs index 41324dbd4898dc..b34bd15a6fa5c4 100644 --- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs @@ -870,9 +870,12 @@ public void AttachToDependencyGraph(DependencyAnalyzerBase graph, I RuntimeFunctionsGCInfo = new RuntimeFunctionsGCInfoNode(); graph.AddRoot(RuntimeFunctionsGCInfo, "GC info is always generated"); - DelayLoadMethodCallThunks = new SymbolNodeRange("DelayLoadMethodCallThunkNodeRange"); - graph.AddRoot(DelayLoadMethodCallThunks, "DelayLoadMethodCallThunks header entry is always generated"); - Header.Add(Internal.Runtime.ReadyToRunSectionType.DelayLoadMethodCallThunks, DelayLoadMethodCallThunks); + if (!Target.IsWasm) + { + DelayLoadMethodCallThunks = new SymbolNodeRange("DelayLoadMethodCallThunkNodeRange"); + graph.AddRoot(DelayLoadMethodCallThunks, "DelayLoadMethodCallThunks header entry is always generated"); + Header.Add(Internal.Runtime.ReadyToRunSectionType.DelayLoadMethodCallThunks, DelayLoadMethodCallThunks); + } ExceptionInfoLookupTableNode exceptionInfoLookupTableNode = new ExceptionInfoLookupTableNode(this); Header.Add(Internal.Runtime.ReadyToRunSectionType.ExceptionInfo, exceptionInfoLookupTableNode); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs index bd633fbbc9e406..e5ac86ad77eeb9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcSlotTable.cs @@ -74,6 +74,8 @@ private static string GetRegisterName(int registerNumber, Machine machine) case Machine.RiscV64: return ((RiscV64.Registers)registerNumber).ToString(); + case WasmMachine.Wasm32: + throw new NotImplementedException("No implementation for machine type Wasm32."); default: throw new NotImplementedException(machine.ToString()); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs index 749f7e48f22e2f..69d12790f9d951 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcTransition.cs @@ -75,6 +75,9 @@ public string GetSlotState(GcSlotTable slotTable, Machine machine) regType = typeof(RiscV64.Registers); break; + case WasmMachine.Wasm32: + throw new NotImplementedException($"No implementation for machine type Wasm32."); + default: throw new NotImplementedException(); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs index d2c0e7af16732f..2e448b1e66d60b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/DebugInfo.cs @@ -86,6 +86,8 @@ public static string GetPlatformSpecificRegister(Machine machine, int regnum) return ((LoongArch64.Registers)regnum).ToString(); case Machine.RiscV64: return ((RiscV64.Registers)regnum).ToString(); + case WasmMachine.Wasm32: + return "NYI"; default: throw new NotImplementedException($"No implementation for machine type {machine}."); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs index 40bb78977e71df..b30acd5f0a3b7a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs @@ -149,6 +149,38 @@ internal GcInfoTypes(Machine machine, bool denormalizeCodeOffsets) STACK_BASE_REGISTER_ENCBASE = 2; NUM_REGISTERS_ENCBASE = 3; break; + case WasmMachine.Wasm32: + PSP_SYM_STACK_SLOT_ENCBASE = 6; + GENERICS_INST_CONTEXT_STACK_SLOT_ENCBASE = 6; + SECURITY_OBJECT_STACK_SLOT_ENCBASE = 6; + GS_COOKIE_STACK_SLOT_ENCBASE = 6; + CODE_LENGTH_ENCBASE = 6; +// SIZE_OF_RETURN_KIND_IN_SLIM_HEADER = 2; +// SIZE_OF_RETURN_KIND_IN_FAT_HEADER = 2; + STACK_BASE_REGISTER_ENCBASE = 3; + SIZE_OF_STACK_AREA_ENCBASE = 6; + SIZE_OF_EDIT_AND_CONTINUE_PRESERVED_AREA_ENCBASE = 3; + REVERSE_PINVOKE_FRAME_ENCBASE = 6; + NUM_REGISTERS_ENCBASE = 3; + NUM_STACK_SLOTS_ENCBASE = 5; + NUM_UNTRACKED_SLOTS_ENCBASE = 5; + NORM_PROLOG_SIZE_ENCBASE = 4; + NORM_EPILOG_SIZE_ENCBASE = 3; +// NORM_CODE_OFFSET_DELTA_ENCBASE = 3; + INTERRUPTIBLE_RANGE_DELTA1_ENCBASE = 5; + INTERRUPTIBLE_RANGE_DELTA2_ENCBASE = 5; + REGISTER_ENCBASE = 3; + REGISTER_DELTA_ENCBASE = REGISTER_ENCBASE; + STACK_SLOT_ENCBASE = 6; + STACK_SLOT_DELTA_ENCBASE = 4; + NUM_SAFE_POINTS_ENCBASE = 4; + NUM_INTERRUPTIBLE_RANGES_ENCBASE = 1; +// NUM_EH_CLAUSES_ENCBASE = 2; + POINTER_SIZE_ENCBASE = 3; + LIVESTATE_RLE_RUN_ENCBASE = 2; + LIVESTATE_RLE_SKIP_ENCBASE = 4; + break; + case Machine.I386: CODE_LENGTH_ENCBASE = 6; NORM_PROLOG_SIZE_ENCBASE = 4; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs index b9466fe9ca4b2c..6a11ed9f8ab304 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunMethod.cs @@ -644,6 +644,10 @@ private void ParseRuntimeFunctions(bool partial) { unwindInfo = new RiscV64.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); } + else if (_readyToRunReader.Machine == WasmMachine.Wasm32) + { + unwindInfo = new Wasm32.UnwindInfo(_readyToRunReader.ImageReader, unwindOffset); + } if (i == 0 && unwindInfo != null) { diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs index 08b2faee48f947..ce57353f32ef76 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/ReadyToRunReader.cs @@ -33,6 +33,11 @@ public enum OperatingSystem Unknown = -1 } + public static class WasmMachine + { + public const Machine Wasm32 = (Machine)0xFFFE; + } + public struct InstanceMethod { public byte Bucket; @@ -683,6 +688,7 @@ private unsafe void EnsureHeader() case Machine.Arm: case Machine.Thumb: case Machine.ArmThumb2: + case WasmMachine.Wasm32: _pointerSize = 4; break; @@ -1529,6 +1535,7 @@ private void EnsureImportSectionsImpl() { case Machine.I386: case Machine.ArmThumb2: + case WasmMachine.Wasm32: entrySize = 4; break; diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs index 85d6bda61dbbf2..0ac97342559900 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/TransitionBlock.cs @@ -37,6 +37,9 @@ public static TransitionBlock FromReader(ReadyToRunReader reader) case Machine.RiscV64: return RiscV64TransitionBlock.Instance; + case WasmMachine.Wasm32: + return Wasm32TransitionBlock.Instance; + default: throw new NotImplementedException(); } @@ -100,6 +103,24 @@ public override int OffsetFromGCRefMapPos(int pos) } } + private sealed class Wasm32TransitionBlock : TransitionBlock + { + public static readonly TransitionBlock Instance = new Wasm32TransitionBlock(); + + public override int PointerSize => 4; + public override int NumArgumentRegisters => 0; + public override int NumCalleeSavedRegisters => 0; + // Argument registers, callee-save registers, return address + public override int SizeOfTransitionBlock => 8; + public override int OffsetOfArgumentRegisters => 0; + + public override int OffsetFromGCRefMapPos(int pos) + { + // This might be right? + return SizeOfTransitionBlock + pos * PointerSize; + } + } + private sealed class X64WindowsTransitionBlock : TransitionBlock { public static readonly TransitionBlock Instance = new X64WindowsTransitionBlock(); diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Wasm32/UnwindInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Wasm32/UnwindInfo.cs new file mode 100644 index 00000000000000..eff38c55b072de --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Wasm32/UnwindInfo.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Text; + +namespace ILCompiler.Reflection.ReadyToRun.Wasm32 +{ + /// + /// based on src/jit/unwindriscv64.cpp DumpUnwindInfo + /// + public class UnwindInfo : BaseUnwindInfo + { + public uint BytesUnwind { get; set; } + public uint VirtualIPCount { get; set; } + + public UnwindInfo() { } + + + private static uint ReadLebU32(NativeReader imageReader, ref int offset) + { + uint result = 0; + int shift = 0; + byte b; + do + { + b = imageReader.ReadByte(ref offset); + result |= (uint)(b & 0x7F) << shift; + shift += 7; + } while ((b & 0x80) != 0); + return result; + } + + public UnwindInfo(NativeReader imageReader, int offset) + { + uint startOffset = (uint)offset; + BytesUnwind = ReadLebU32(imageReader, ref offset); + VirtualIPCount = ReadLebU32(imageReader, ref offset); + Size = offset - (int)startOffset; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendLine($" BytesUnwind: {BytesUnwind}"); + sb.AppendLine($" VirtualIPCount: {VirtualIPCount}"); + return sb.ToString(); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs index 7ced312c9239a5..6c4aea1392d52d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/WebcilImageReader.cs @@ -28,7 +28,7 @@ public class WebcilImageReader : IBinaryImageReader private readonly CorFlags _corFlags; private readonly DirectoryEntry _managedNativeHeaderDirectory; - public Machine Machine => Machine.I386; // Webcil doesn't encode machine type; wasm targets use a placeholder + public Machine Machine => WasmMachine.Wasm32; // Webcil doesn't encode machine type; wasm targets use a placeholder public OperatingSystem OperatingSystem => OperatingSystem.Unknown; public ulong ImageBase => 0; diff --git a/src/coreclr/tools/r2rdump/CoreDisTools.cs b/src/coreclr/tools/r2rdump/CoreDisTools.cs index 5f3e1ed5780b7c..8bbf7ee666cb13 100644 --- a/src/coreclr/tools/r2rdump/CoreDisTools.cs +++ b/src/coreclr/tools/r2rdump/CoreDisTools.cs @@ -187,6 +187,7 @@ private void SetIndentations() // to 7 * 3 characters; see https://github.com/dotnet/llilc/blob/master/lib/CoreDisTools/coredistools.cpp. Machine.I386 => 7 * 3, Machine.Amd64 => 7 * 3, + WasmMachine.Wasm32 => 7 * 3, // Instructions are either 2 or 4 bytes long Machine.ArmThumb2 => 4 * 3, diff --git a/src/coreclr/tools/r2rdump/Program.cs b/src/coreclr/tools/r2rdump/Program.cs index 78467ba5bd81fb..d2ed2cdb89aca4 100644 --- a/src/coreclr/tools/r2rdump/Program.cs +++ b/src/coreclr/tools/r2rdump/Program.cs @@ -212,6 +212,7 @@ public void Dump(ReadyToRunReader r2r) Machine.Arm64 => TargetArchitecture.ARM64, Machine.LoongArch64 => TargetArchitecture.LoongArch64, Machine.RiscV64 => TargetArchitecture.RiscV64, + WasmMachine.Wasm32 => TargetArchitecture.Wasm32, _ => throw new NotImplementedException(r2r.Machine.ToString()), }; TargetOS os = r2r.OperatingSystem switch diff --git a/src/coreclr/vm/ceeload.cpp b/src/coreclr/vm/ceeload.cpp index a729f1b240846e..620ccc1844f1aa 100644 --- a/src/coreclr/vm/ceeload.cpp +++ b/src/coreclr/vm/ceeload.cpp @@ -3591,6 +3591,10 @@ void Module::RunEagerFixupsUnlocked() } } +#ifdef TARGET_WASM + // For WASM, register virtual IP ranges instead of real code address ranges. + GetReadyToRunInfo()->RegisterVirtualIPRange(this); +#else TADDR base = dac_cast(pNativeImage->GetBase()); ExecutionManager::AddCodeRange( @@ -3598,6 +3602,7 @@ void Module::RunEagerFixupsUnlocked() ExecutionManager::GetReadyToRunJitManager(), RangeSection::RANGE_SECTION_NONE, this /* pHeapListOrZapModule */); +#endif // TARGET_WASM } #endif // !DACCESS_COMPILE diff --git a/src/coreclr/vm/ceeload.h b/src/coreclr/vm/ceeload.h index 24737cb71c8576..3133af858bdea2 100644 --- a/src/coreclr/vm/ceeload.h +++ b/src/coreclr/vm/ceeload.h @@ -881,6 +881,7 @@ class Module : public ModuleBase #endif PTR_PEAssembly GetPEAssembly() const { LIMITED_METHOD_DAC_CONTRACT; return m_pPEAssembly; } + PTR_VOID GetModuleBaseAddress() { return m_baseAddress; } void ApplyMetaData(); @@ -906,6 +907,8 @@ class Module : public ModuleBase PTR_Assembly GetAssembly() const; + + MethodTable *GetGlobalMethodTable(); bool NeedsGlobalMethodTable(); diff --git a/src/coreclr/vm/codeman.cpp b/src/coreclr/vm/codeman.cpp index 033a2d62645e3e..4c5b034b18895c 100644 --- a/src/coreclr/vm/codeman.cpp +++ b/src/coreclr/vm/codeman.cpp @@ -64,6 +64,12 @@ SVAL_IMPL(RangeSectionMapData, ExecutionManager, g_codeRangeMap); VOLATILE_SVAL_IMPL_INIT(LONG, ExecutionManager, m_dwReaderCount, 0); VOLATILE_SVAL_IMPL_INIT(LONG, ExecutionManager, m_dwWriterLock, 0); +#ifdef TARGET_WASM +VirtualIPRangeSection* ExecutionManager::s_pVirtualIPRangeList = nullptr; +FunctionTableIndexRangeSection* ExecutionManager::s_pFunctionTableIndexRangeList = nullptr; +static TADDR s_nextVirtualIP = 0x80000001; +#endif // TARGET_WASM + #ifndef DACCESS_COMPILE CrstStatic ExecutionManager::m_JumpStubCrst; @@ -1132,6 +1138,13 @@ PTR_VOID GetUnwindDataBlob(TADDR moduleBase, PTR_RUNTIME_FUNCTION pRuntimeFuncti *pSize = size; return xdata; +#elif defined(TARGET_WASM) + PTR_BYTE pUnwindInfo(dac_cast(moduleBase + pRuntimeFunction->UnwindData)); + PTR_BYTE pUnwindData = pUnwindInfo; + DecodeULEB128AsU32(&pUnwindData); // Skip the count of bytes to unwind + DecodeULEB128AsU32(&pUnwindData); // Read the function length in virtual IP units + *pSize = static_cast(pUnwindData - pUnwindInfo); + return pUnwindInfo; #else PORTABILITY_ASSERT("GetUnwindDataBlob"); return NULL; @@ -1147,7 +1160,7 @@ TADDR IJitManager::GetFuncletStartAddress(EECodeInfo * pCodeInfo) _ASSERTE((pFunctionEntry->UnwindData & RUNTIME_FUNCTION_INDIRECT) == 0); #endif - TADDR baseAddress = pCodeInfo->GetModuleBase(); + TADDR baseAddress = pCodeInfo->GetModuleFunctionsBase(); TADDR funcletStartAddress = baseAddress + RUNTIME_FUNCTION__BeginAddress(pFunctionEntry); #if defined(EXCEPTION_DATA_SUPPORTS_FUNCTION_FRAGMENTS) @@ -5006,7 +5019,7 @@ DWORD EEJitManager::GetFuncletStartOffsets(const METHODTOKEN& MethodToken, DWORD CONTRACTL_END; CodeHeader * pCH = dac_cast(GetCodeHeader(MethodToken)); - TADDR moduleBase = JitTokenToModuleBase(MethodToken); + TADDR moduleBase = JitTokenToModuleFunctionsBase(MethodToken); _ASSERTE(pCH->GetNumberOfUnwindInfos() >= 1); @@ -5258,6 +5271,16 @@ ExecutionManager::FindCodeRange(PCODE currentPC, ScanFlag scanFlag) if (currentPC == (PCODE)NULL) return NULL; +#ifdef TARGET_WASM + if (IsVirtualIP(currentPC)) + { + VirtualIPRangeSection* pVIPRange = FindVirtualIPRangeSection(currentPC); + if (pVIPRange != nullptr) + return &pVIPRange->rangeSection; + return NULL; + } +#endif // TARGET_WASM + if (scanFlag == ScanReaderLock) return FindCodeRangeWithLock(currentPC); @@ -5703,6 +5726,143 @@ void ExecutionManager::AddCodeRange(TADDR pStartRange, ThrowOutOfMemory(); } +#ifdef TARGET_WASM +TADDR ExecutionManager::AddVirtualIPRange(UINT32 numVirtualIPs, + IJitManager* pJit, + PTR_Module pModule) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + PRECONDITION(numVirtualIPs > 0); + PRECONDITION(CheckPointer(pJit)); + PRECONDITION(CheckPointer(pModule)); + } CONTRACTL_END; + + // Check for odd number of virtual IPs. We require an even number of virtual IPs to ensure that the encoded virtual IP + // Will always have the low bit set to 1. + if ((numVirtualIPs & 1) == 1) + { + COMPlusThrow(kOverflowException, W("Virtual IP: cannot have odd number of virtual IPs")); + } + + TADDR endVIP; + if (sizeof(TADDR) == 4) + { + endVIP = (TADDR)InterlockedAdd((LONG*)&s_nextVirtualIP, numVirtualIPs); + } + else + { + endVIP = (TADDR)InterlockedAdd64((LONGLONG*)&s_nextVirtualIP, numVirtualIPs); + } + + TADDR startVIP = endVIP - numVirtualIPs; + + // Check for overflow + if (endVIP < startVIP) + { + EEPOLICY_HANDLE_FATAL_ERROR(COR_E_EXECUTIONENGINE); + } + + // Create the VirtualIPRangeSection + VirtualIPRangeSection* pNewRange = new VirtualIPRangeSection( + Range(startVIP, endVIP), + pJit, + RangeSection::RANGE_SECTION_VIRTUALIP, + pModule); + + VirtualIPRangeSection* pOldRangeSection = nullptr; + do + { + VirtualIPRangeSection* pOldRangeSection = VolatileLoadWithoutBarrier(&s_pVirtualIPRangeList); + pNewRange->pNext = pOldRangeSection; + } while (pOldRangeSection != InterlockedCompareExchangeT(&s_pVirtualIPRangeList, pNewRange, pOldRangeSection)); + + return startVIP; +} + +VirtualIPRangeSection* ExecutionManager::FindVirtualIPRangeSection(UINT32 virtualIP) +{ + LIMITED_METHOD_CONTRACT; + + VirtualIPRangeSection* pCurrent = s_pVirtualIPRangeList; + while (pCurrent != nullptr) + { + if (pCurrent->rangeSection._range.IsInRange(virtualIP)) + { + return pCurrent; + } + pCurrent = pCurrent->pNext; + } + return nullptr; +} + +void ExecutionManager::AddFunctionTableIndexRange(DWORD minFunctionTableIndex, + DWORD numRuntimeFunctions, + PTR_Module pModule) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + PRECONDITION(numRuntimeFunctions > 0); + PRECONDITION(CheckPointer(pModule)); + } CONTRACTL_END; + + FunctionTableIndexRangeSection* pNewRange = new FunctionTableIndexRangeSection( + minFunctionTableIndex, numRuntimeFunctions, pModule); + + FunctionTableIndexRangeSection* pOldRangeSection = nullptr; + do + { + pOldRangeSection = VolatileLoadWithoutBarrier(&s_pFunctionTableIndexRangeList); + pNewRange->pNext = pOldRangeSection; + } while (pOldRangeSection != InterlockedCompareExchangeT(&s_pFunctionTableIndexRangeList, pNewRange, pOldRangeSection)); +} + +FunctionTableIndexRangeSection* ExecutionManager::FindFunctionTableIndexRangeSection(DWORD functionIndex) +{ + LIMITED_METHOD_CONTRACT; + + FunctionTableIndexRangeSection* pCurrent = s_pFunctionTableIndexRangeList; + while (pCurrent != nullptr) + { + if (functionIndex >= pCurrent->minFunctionTableIndex && + functionIndex < pCurrent->minFunctionTableIndex + pCurrent->numRuntimeFunctions) + { + return pCurrent; + } + pCurrent = pCurrent->pNext; + } + return nullptr; +} + +TADDR ExecutionManager::GetWasmVirtualIPFromFunctionTableIndex(DWORD functionIndex) +{ + LIMITED_METHOD_CONTRACT; + + FunctionTableIndexRangeSection* pSection = FindFunctionTableIndexRangeSection(functionIndex); + if (pSection == nullptr) + { + return 0; + } + + Module* pModule = pSection->pR2RModule; + ReadyToRunInfo* pR2RInfo = pModule->GetReadyToRunInfo(); + + DWORD localIndex = functionIndex - pSection->minFunctionTableIndex; + do + { + PTR_RUNTIME_FUNCTION pRuntimeFunction = pR2RInfo->GetRuntimeFunctions() + localIndex; + if (RUNTIME_FUNCTION__IsFunclet(pRuntimeFunction)) + { + localIndex--; + continue; + } + return pR2RInfo->GetMinVirtualIP() + RUNTIME_FUNCTION__BeginAddress(pRuntimeFunction); + } while (true); +} +#endif // TARGET_WASM + // Deletes a single range starting at pStartRange void ExecutionManager::DeleteRange(TADDR pStartRange) { @@ -6295,6 +6455,9 @@ int NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod(DWORD RelativePc, #ifdef TARGET_ARM RelativePc |= THUMB_CODE; +#define RUNTIME_FUNCTION__BeginAddress_NoRemoveThumbBit(pFunctionEntry) ((pFunctionEntry)->BeginAddress) +#else +#define RUNTIME_FUNCTION__BeginAddress_NoRemoveThumbBit(pFunctionEntry) RUNTIME_FUNCTION__BeginAddress(pFunctionEntry) #endif // Entries are sorted and terminated by sentinel value (DWORD)-1 @@ -6307,7 +6470,7 @@ int NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod(DWORD RelativePc, int Middle = Low + (High - Low) / 2; PTR_RUNTIME_FUNCTION pFunctionEntry = pRuntimeFunctionTable + Middle; - if (RelativePc < pFunctionEntry->BeginAddress) + if (RelativePc < RUNTIME_FUNCTION__BeginAddress_NoRemoveThumbBit(pFunctionEntry)) { High = Middle - 1; } @@ -6322,10 +6485,10 @@ int NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod(DWORD RelativePc, // This is safe because of entries are terminated by sentinel value (DWORD)-1 PTR_RUNTIME_FUNCTION pNextFunctionEntry = pRuntimeFunctionTable + (i + 1); - if (RelativePc < pNextFunctionEntry->BeginAddress) + if (RelativePc < RUNTIME_FUNCTION__BeginAddress_NoRemoveThumbBit(pNextFunctionEntry)) { PTR_RUNTIME_FUNCTION pFunctionEntry = pRuntimeFunctionTable + i; - if (RelativePc >= pFunctionEntry->BeginAddress) + if (RelativePc >= RUNTIME_FUNCTION__BeginAddress_NoRemoveThumbBit(pFunctionEntry)) { return i; } @@ -6336,6 +6499,7 @@ int NativeUnwindInfoLookupTable::LookupUnwindInfoForMethod(DWORD RelativePc, return -1; } +#ifdef FEATURE_COLD_R2R_CODE int HotColdMappingLookupTable::LookupMappingForMethod(ReadyToRunInfo* pInfo, ULONG MethodIndex) { CONTRACTL { @@ -6415,7 +6579,7 @@ int HotColdMappingLookupTable::LookupMappingForMethod(ReadyToRunInfo* pInfo, ULO return -1; } - +#endif // FEATURE_COLD_R2R_CODE //*************************************************************************************** //*************************************************************************************** @@ -6472,7 +6636,7 @@ TADDR ReadyToRunJitManager::JitTokenToStartAddress(const METHODTOKEN& MethodToke SUPPORTS_DAC; } CONTRACTL_END; - return JitTokenToModuleBase(MethodToken) + + return JitTokenToModuleFunctionsBase(MethodToken) + RUNTIME_FUNCTION__BeginAddress(dac_cast(MethodToken.m_pCodeHeader)); } @@ -6485,7 +6649,7 @@ GCInfoToken ReadyToRunJitManager::GetGCInfoToken(const METHODTOKEN& MethodToken) } CONTRACTL_END; PTR_RUNTIME_FUNCTION pRuntimeFunction = JitTokenToRuntimeFunction(MethodToken); - TADDR baseAddress = JitTokenToModuleBase(MethodToken); + TADDR baseAddress = JitTokenToModuleRVABase(MethodToken); SIZE_T nUnwindDataSize; PTR_VOID pUnwindData = GetUnwindDataBlob(baseAddress, pRuntimeFunction, &nUnwindDataSize); @@ -6518,7 +6682,7 @@ unsigned ReadyToRunJitManager::InitializeEHEnumeration(const METHODTOKEN& Method // at least 2 entries (1 valid entry + 1 sentinel entry) _ASSERTE(numLookupTableEntries >= 2); - DWORD methodStartRVA = (DWORD)(JitTokenToStartAddress(MethodToken) - JitTokenToModuleBase(MethodToken)); + DWORD methodStartRVA = (DWORD)(JitTokenToStartAddress(MethodToken) - JitTokenToModuleFunctionsBase(MethodToken)); COUNT_T ehInfoSize = 0; DWORD exceptionInfoRVA = NativeExceptionInfoLookupTable::LookupExceptionInfoRVAForMethod(pExceptionLookupTable, @@ -6529,7 +6693,7 @@ unsigned ReadyToRunJitManager::InitializeEHEnumeration(const METHODTOKEN& Method return 0; pEnumState->iCurrentPos = 0; - pEnumState->pExceptionClauseArray = JitTokenToModuleBase(MethodToken) + exceptionInfoRVA; + pEnumState->pExceptionClauseArray = JitTokenToModuleRVABase(MethodToken) + exceptionInfoRVA; return ehInfoSize / sizeof(CORCOMPILE_EXCEPTION_CLAUSE); } @@ -6561,6 +6725,9 @@ PTR_EXCEPTION_CLAUSE_TOKEN ReadyToRunJitManager::GetNextEHClause(EH_CLAUSE_ENUME StubCodeBlockKind ReadyToRunJitManager::GetStubCodeBlockKind(RangeSection * pRangeSection, PCODE currentPC) { +#ifdef TARGET_WASM + return STUB_CODE_BLOCK_UNKNOWN; +#else CONTRACTL { NOTHROW; @@ -6583,6 +6750,7 @@ StubCodeBlockKind ReadyToRunJitManager::GetStubCodeBlockKind(RangeSection * pRan } return STUB_CODE_BLOCK_UNKNOWN; +#endif // !TARGET_WASM } #ifndef DACCESS_COMPILE @@ -6845,12 +7013,14 @@ BOOL ReadyToRunJitManager::JitCodeToMethodInfo(RangeSection * pRangeSection, // Save the raw entry PTR_RUNTIME_FUNCTION RawFunctionEntry = pRuntimeFunctions + MethodIndex; +#ifdef FEATURE_COLD_R2R_CODE const int lookupIndex = HotColdMappingLookupTable::LookupMappingForMethod(pInfo, (ULONG)MethodIndex); if ((lookupIndex != -1) && ((lookupIndex & 1) == 1)) { // If the MethodIndex happens to be the cold code block, turn it into the associated hot code block MethodIndex = pInfo->m_pHotColdMap[lookupIndex]; } +#endif // FEATURE_COLD_R2R_CODE MethodDesc *pMethodDesc; while ((pMethodDesc = pInfo->GetMethodDescForEntryPoint(ImageBase + RUNTIME_FUNCTION__BeginAddress(pRuntimeFunctions + MethodIndex))) == NULL) @@ -6917,7 +7087,7 @@ DWORD ReadyToRunJitManager::GetFuncletStartOffsets(const METHODTOKEN& MethodToke { PTR_RUNTIME_FUNCTION pFirstFuncletFunctionEntry = dac_cast(MethodToken.m_pCodeHeader) + 1; - TADDR moduleBase = JitTokenToModuleBase(MethodToken); + TADDR moduleBase = JitTokenToModuleFunctionsBase(MethodToken); DWORD nFunclets = 0; MethodRegionInfo regionInfo; JitTokenToMethodRegionInfo(MethodToken, ®ionInfo); @@ -6970,6 +7140,7 @@ BOOL ReadyToRunJitManager::LazyIsFunclet(EECodeInfo* pCodeInfo) ULONG methodIndex = (ULONG)(pCodeInfo->GetFunctionEntry() - pRuntimeFunctions); +#ifdef FEATURE_COLD_R2R_CODE const int lookupIndex = HotColdMappingLookupTable::LookupMappingForMethod(pInfo, methodIndex); if ((lookupIndex != -1) && ((lookupIndex & 1) == 1)) @@ -6988,6 +7159,7 @@ BOOL ReadyToRunJitManager::LazyIsFunclet(EECodeInfo* pCodeInfo) return false; #endif } +#endif // FEATURE_COLD_R2R_CODE // Fall back to existing logic if it is not cold @@ -7056,6 +7228,7 @@ void ReadyToRunJitManager::JitTokenToMethodRegionInfo(const METHODTOKEN& MethodT methodRegionInfo->coldStartAddress = 0; methodRegionInfo->coldSize = 0; +#ifdef FEATURE_COLD_R2R_CODE ReadyToRunInfo * pInfo = JitTokenToReadyToRunInfo(MethodToken); COUNT_T nRuntimeFunctions = pInfo->m_nRuntimeFunctions; PTR_RUNTIME_FUNCTION pRuntimeFunctions = pInfo->m_pRuntimeFunctions; @@ -7077,7 +7250,7 @@ void ReadyToRunJitManager::JitTokenToMethodRegionInfo(const METHODTOKEN& MethodT _ASSERTE((lookupIndex & 1) == 0); ULONG coldMethodIndex = pInfo->m_pHotColdMap[lookupIndex]; PTR_RUNTIME_FUNCTION pColdRuntimeFunction = pRuntimeFunctions + coldMethodIndex; - methodRegionInfo->coldStartAddress = JitTokenToModuleBase(MethodToken) + methodRegionInfo->coldStartAddress = JitTokenToModuleFunctionsBase(MethodToken) + RUNTIME_FUNCTION__BeginAddress(pColdRuntimeFunction); ULONG coldMethodIndexNext; @@ -7095,6 +7268,7 @@ void ReadyToRunJitManager::JitTokenToMethodRegionInfo(const METHODTOKEN& MethodT - RUNTIME_FUNCTION__BeginAddress(pColdRuntimeFunction); methodRegionInfo->hotSize -= methodRegionInfo->coldSize; #endif //TARGET_X86 +#endif // FEATURE_COLD_R2R_CODE } #ifdef DACCESS_COMPILE diff --git a/src/coreclr/vm/codeman.h b/src/coreclr/vm/codeman.h index 7ac1c976fac92b..36fa8bf03492b7 100644 --- a/src/coreclr/vm/codeman.h +++ b/src/coreclr/vm/codeman.h @@ -713,6 +713,7 @@ struct RangeSection RANGE_SECTION_CODEHEAP = 0x2, RANGE_SECTION_RANGELIST = 0x4, RANGE_SECTION_INTERPRETER = 0x8, + RANGE_SECTION_VIRTUALIP = 0x10, // This range section contains virtual IPs (e.g. for ReadyToRun code) instead of actual code addresses in linear memory }; #ifdef FEATURE_READYTORUN @@ -1768,7 +1769,8 @@ class IJitManager return GetGCInfoToken(MethodToken).Info; } - TADDR JitTokenToModuleBase(const METHODTOKEN& MethodToken); + TADDR JitTokenToModuleFunctionsBase(const METHODTOKEN& MethodToken); + TADDR JitTokenToModuleRVABase(const METHODTOKEN& MethodToken); virtual PTR_RUNTIME_FUNCTION LazyGetFunctionEntry(EECodeInfo * pCodeInfo) = 0; @@ -2306,6 +2308,45 @@ struct cdac_data }; +//***************************************************************************** +// +// VirtualIPRangeSection: a linked list node tracking a range of virtual IPs +// registered from a WASM R2R module. +// +//***************************************************************************** + +#ifdef TARGET_WASM +struct VirtualIPRangeSection +{ + VirtualIPRangeSection(Range range, IJitManager* pJit, RangeSection::RangeSectionFlags flags, PTR_Module pR2RModule) : rangeSection(range, pJit, flags, pR2RModule), pNext(nullptr) + { + } + + RangeSection rangeSection; // Synthetic RangeSection for compatibility with existing APIs + VirtualIPRangeSection* pNext; // Next entry in the linked list +}; + +//***************************************************************************** +// +// FunctionTableIndexRangeSection: a linked list node tracking a range of +// function table indices registered from a WASM R2R module. +// +//***************************************************************************** + +struct FunctionTableIndexRangeSection +{ + FunctionTableIndexRangeSection(DWORD minIndex, DWORD count, PTR_Module pModule) + : minFunctionTableIndex(minIndex), numRuntimeFunctions(count), pR2RModule(pModule), pNext(nullptr) + { + } + + DWORD minFunctionTableIndex; // Start of the function table index range + DWORD numRuntimeFunctions; // Number of RUNTIME_FUNCTION entries + PTR_Module pR2RModule; // Module owning this range + FunctionTableIndexRangeSection* pNext; // Next entry in the linked list +}; +#endif // TARGET_WASM + //***************************************************************************** // // This class manages IJitManagers and ICorJitCompilers. It has only static @@ -2444,6 +2485,50 @@ class ExecutionManager RangeSection::RangeSectionFlags flags, PTR_Module pModule); +#ifdef TARGET_WASM + // Register a virtual IP range for a WASM R2R module. + // Returns the start virtual IP assigned to this module. + static TADDR AddVirtualIPRange(UINT32 numVirtualIPs, + IJitManager* pJit, + PTR_Module pModule); + + // Find the VirtualIPRangeSection for a given virtual IP. + static VirtualIPRangeSection* FindVirtualIPRangeSection(UINT32 virtualIP); + + // Returns true if the given PCODE is a virtual IP encoding (both low and high bits set). + static bool IsVirtualIP(PCODE pc) + { + return (pc & 0x80000001) == 0x80000001; + } + + // Encode a virtual IP value into a PCODE. + static PCODE EncodeVirtualIP(UINT32 virtualIP) + { + _ASSERTE(virtualIP < (1u << 30)); + return (PCODE)((virtualIP << 1) | 0x80000001); + } + + // Decode a virtual IP value from a PCODE. + static UINT32 DecodeVirtualIP(PCODE pc) + { + _ASSERTE(IsVirtualIP(pc)); + return (UINT32)((pc >> 1) & 0x3FFFFFFF); + } + + // Register a function table index range for a WASM R2R module. + static void AddFunctionTableIndexRange(DWORD minFunctionTableIndex, + DWORD numRuntimeFunctions, + PTR_Module pModule); + + // Find the FunctionTableIndexRangeSection for a given function table index. + static FunctionTableIndexRangeSection* FindFunctionTableIndexRangeSection(DWORD functionIndex); + + // Given a function table index, look up the corresponding Module and RUNTIME_FUNCTION, + // then compute and return the virtual IP for that the entrypoint for that function + // (which may require a walk back to find the main function if functionIndex represents a funclet) + static TADDR GetWasmVirtualIPFromFunctionTableIndex(DWORD functionIndex); +#endif // TARGET_WASM + static void DeleteRange(TADDR StartRange); static void CleanupCodeHeaps(); @@ -2491,6 +2576,11 @@ class ExecutionManager SPTR_DECL(InterpreterCodeManager, m_pInterpreterCodeMan); #endif +#ifdef TARGET_WASM + static VirtualIPRangeSection* s_pVirtualIPRangeList; + static FunctionTableIndexRangeSection* s_pFunctionTableIndexRangeList; +#endif // TARGET_WASM + static CrstStatic m_JumpStubCrst; static CrstStatic m_RangeCrst; // Acquire before writing into m_CodeRangeList and m_DataRangeList @@ -2687,6 +2777,7 @@ class NativeUnwindInfoLookupTable int EndIndex); }; +#ifdef FEATURE_COLD_R2R_CODE class HotColdMappingLookupTable { public: @@ -2699,6 +2790,7 @@ class HotColdMappingLookupTable // static int LookupMappingForMethod(ReadyToRunInfo* pInfo, ULONG MethodIndex); }; +#endif // FEATURE_COLD_R2R_CODE #endif // FEATURE_READYTORUN @@ -3059,7 +3151,13 @@ class EECodeInfo TADDR GetModuleBase() { WRAPPER_NO_CONTRACT; - return GetJitManager()->JitTokenToModuleBase(GetMethodToken()); + return GetJitManager()->JitTokenToModuleRVABase(GetMethodToken()); + } + + TADDR GetModuleFunctionsBase() + { + WRAPPER_NO_CONTRACT; + return GetJitManager()->JitTokenToModuleFunctionsBase(GetMethodToken()); } PTR_RUNTIME_FUNCTION GetFunctionEntry(); diff --git a/src/coreclr/vm/codeman.inl b/src/coreclr/vm/codeman.inl index 8af0fc0e48bfbd..099ff1cc13128d 100644 --- a/src/coreclr/vm/codeman.inl +++ b/src/coreclr/vm/codeman.inl @@ -9,7 +9,17 @@ inline BOOL ExecutionManager::IsCollectibleMethod(const METHODTOKEN& MethodToken return MethodToken.m_pRangeSection->_flags & RangeSection::RANGE_SECTION_COLLECTIBLE; } -inline TADDR IJitManager::JitTokenToModuleBase(const METHODTOKEN& MethodToken) +inline TADDR IJitManager::JitTokenToModuleRVABase(const METHODTOKEN& MethodToken) +{ +#ifdef TARGET_WASM + if (MethodToken.m_pRangeSection->_flags & RangeSection::RANGE_SECTION_VIRTUALIP) + return (TADDR)MethodToken.m_pRangeSection->_pR2RModule->GetModuleBaseAddress(); +#endif + // For non-wasm, the rva base is always the same as the range base. + return MethodToken.m_pRangeSection->_range.RangeStart(); +} + +inline TADDR IJitManager::JitTokenToModuleFunctionsBase(const METHODTOKEN& MethodToken) { return MethodToken.m_pRangeSection->_range.RangeStart(); } diff --git a/src/coreclr/vm/datadescriptor/datadescriptor.inc b/src/coreclr/vm/datadescriptor/datadescriptor.inc index fb94ca4218e96f..8249ec1258f30a 100644 --- a/src/coreclr/vm/datadescriptor/datadescriptor.inc +++ b/src/coreclr/vm/datadescriptor/datadescriptor.inc @@ -740,9 +740,13 @@ CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, ReadyToRunHeader, cdac_data::CompositeInfo) CDAC_TYPE_FIELD(ReadyToRunInfo, T_UINT32, NumRuntimeFunctions, cdac_data::NumRuntimeFunctions) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, RuntimeFunctions, cdac_data::RuntimeFunctions) +#ifdef FEATURE_COLD_R2R_CODE CDAC_TYPE_FIELD(ReadyToRunInfo, T_UINT32, NumHotColdMap, cdac_data::NumHotColdMap) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, HotColdMap, cdac_data::HotColdMap) +#endif // FEATURE_COLD_R2R_CODE +#ifndef TARGET_WASM CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, DelayLoadMethodCallThunks, cdac_data::DelayLoadMethodCallThunks) +#endif // !TARGET_WASM CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, DebugInfoSection, cdac_data::DebugInfoSection) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, ExceptionInfoSection, cdac_data::ExceptionInfoSection) CDAC_TYPE_FIELD(ReadyToRunInfo, T_POINTER, ImportSections, cdac_data::ImportSections) diff --git a/src/coreclr/vm/eetwain.cpp b/src/coreclr/vm/eetwain.cpp index 12261ea5d4d492..1bcdbdb128c512 100644 --- a/src/coreclr/vm/eetwain.cpp +++ b/src/coreclr/vm/eetwain.cpp @@ -1625,6 +1625,9 @@ size_t EECodeManager::GetFunctionSize(GCInfoToken gcInfoToken) ); UINT32 codeLength = gcInfoDecoder.GetCodeLength(); +#ifdef TARGET_WASM + codeLength = 1; // WASM-TODO, something isn't working in the GCEncoder/Decoder for WASM, and we're getting back 0 for code length. We'll need to fix this at some point. +#endif // TARGET_WASM _ASSERTE( codeLength > 0 ); return codeLength; diff --git a/src/coreclr/vm/excep.cpp b/src/coreclr/vm/excep.cpp index 5e87f606da0be9..5c9fc47aba241d 100644 --- a/src/coreclr/vm/excep.cpp +++ b/src/coreclr/vm/excep.cpp @@ -10765,7 +10765,8 @@ void SoftwareExceptionFrame::UpdateContextFromTransitionBlock(TransitionBlock *p { m_Context.InterpreterSP = pTransitionBlock->m_StackPointer; m_Context.InterpreterFP = 0; - m_Context.InterpreterIP = 0; + m_Context.InterpreterIP = GetWasmVirtualIPFromStackPointer(pTransitionBlock->m_StackPointer); + m_ReturnAddress = m_Context.InterpreterIP; m_Context.InterpreterWalkFramePointer = 0; } } diff --git a/src/coreclr/vm/fcall.h b/src/coreclr/vm/fcall.h index 79b3cb9155992f..36930c0e826fcf 100644 --- a/src/coreclr/vm/fcall.h +++ b/src/coreclr/vm/fcall.h @@ -489,15 +489,15 @@ struct FCSigCheck { #endif // !SWIZZLE_REGARG_ORDER #elif defined(TARGET_WASM) -#define HCIMPL0(rettype, funcname) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) -#define HCIMPL1(rettype, funcname, a1) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) -#define HCIMPL1_RAW(rettype, funcname, a1) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext) { -#define HCIMPL1_V(rettype, funcname, a1) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) -#define HCIMPL2(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) -#define HCIMPL2_RAW(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) { -#define HCIMPL2_VV(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) -#define HCIMPL3(rettype, funcname, a1, a2, a3) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, a3, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) -#define HCIMPL3_RAW(rettype, funcname, a1, a2, a3) rettype F_CALL_CONV funcname(uintptr_t callersStackPointer, a1, a2, a3, PCODE portableEntryPointContext) { +#define HCIMPL0(rettype, funcname) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_1(rettype, funcname, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) +#define HCIMPL1(rettype, funcname, a1) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_2(rettype, funcname, a1, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) +#define HCIMPL1_RAW(rettype, funcname, a1) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_2(rettype, funcname, a1, PCODE portableEntryPointContext) { +#define HCIMPL1_V(rettype, funcname, a1) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_2(rettype, funcname, a1, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) +#define HCIMPL2(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_3(rettype, funcname, a1, a2, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) +#define HCIMPL2_RAW(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_3(rettype, funcname, a1, a2, PCODE portableEntryPointContext) { +#define HCIMPL2_VV(rettype, funcname, a1, a2) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_3(rettype, funcname, a1, a2, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) +#define HCIMPL3(rettype, funcname, a1, a2, a3) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, a3, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_4(rettype, funcname, a1, a2, a3, PCODE portableEntryPointContext) { HCIMPL_PROLOG(funcname) +#define HCIMPL3_RAW(rettype, funcname, a1, a2, a3) rettype F_CALL_CONV funcname ## _IMPL(uintptr_t callersStackPointer, a1, a2, a3, PCODE portableEntryPointContext); WASM_CALLABLE_FUNC_4(rettype, funcname, a1, a2, a3, PCODE portableEntryPointContext) { #else // SWIZZLE_STKARG_ORDER diff --git a/src/coreclr/vm/frames.h b/src/coreclr/vm/frames.h index d2da804694077c..90017601be95be 100644 --- a/src/coreclr/vm/frames.h +++ b/src/coreclr/vm/frames.h @@ -766,6 +766,10 @@ inline CONTEXT * GETREDIRECTEDCONTEXT(Thread * thread) { LIMITED_METHOD_CONTRACT typedef DPTR(class TransitionFrame) PTR_TransitionFrame; +#ifdef TARGET_WASM +TADDR GetWasmVirtualIPFromStackPointer(TADDR sp); +#endif + class TransitionFrame : public Frame { #ifndef DACCESS_COMPILE @@ -848,7 +852,19 @@ class TransitionFrame : public Frame TADDR GetSP() { LIMITED_METHOD_DAC_CONTRACT; +#ifdef TARGET_WASM + TransitionBlock* pTransitionBlock = (TransitionBlock*)GetTransitionBlock(); + if (pTransitionBlock != NULL) + { + if (pTransitionBlock->m_ReturnAddress != 0) + { + return pTransitionBlock->m_StackPointer; + } + } + return 0; +#else return GetTransitionBlock() + sizeof(TransitionBlock); +#endif } BOOL NeedsUpdateRegDisplay_Impl() @@ -1169,6 +1185,17 @@ class FramedMethodFrame : public TransitionFrame TADDR GetTransitionBlock_Impl() { LIMITED_METHOD_DAC_CONTRACT; +#ifdef TARGET_WASM + TransitionBlock* pTransitionBlock = (TransitionBlock*)m_pTransitionBlock; + if (pTransitionBlock != NULL) + { + if (pTransitionBlock->m_ReturnAddress == 0) + { + // Lazy compute the virtual IP for the frame + pTransitionBlock->m_ReturnAddress = GetWasmVirtualIPFromStackPointer(pTransitionBlock->m_StackPointer); + } + } +#endif return m_pTransitionBlock; } diff --git a/src/coreclr/vm/prestub.cpp b/src/coreclr/vm/prestub.cpp index 67836befe68743..e915b93909a94c 100644 --- a/src/coreclr/vm/prestub.cpp +++ b/src/coreclr/vm/prestub.cpp @@ -2137,8 +2137,7 @@ void ExecuteInterpretedMethodWithArgs_PortableEntryPoint_Complex(PCODE portableE EX_CATCH { OBJECTHANDLE ohThrowable = CURRENT_THREAD->LastThrownObjectHandle(); - _ASSERTE(ohThrowable); - if (finishedPrestubPortion) + if (ohThrowable != NULL && finishedPrestubPortion) { StackTraceInfo::AppendElement(ObjectFromHandle(ohThrowable), 0, (UINT_PTR)block, pMethod, NULL); } diff --git a/src/coreclr/vm/readytoruninfo.cpp b/src/coreclr/vm/readytoruninfo.cpp index 555aca66b28f68..fb95631e75f9f8 100644 --- a/src/coreclr/vm/readytoruninfo.cpp +++ b/src/coreclr/vm/readytoruninfo.cpp @@ -375,7 +375,7 @@ PTR_MethodDesc ReadyToRunInfo::GetMethodDescForEntryPointInNativeImage(PCODE ent return NULL; #endif - TADDR val = (TADDR)m_entryPointToMethodDescMap.LookupValue(PCODEToPINSTR(entryPoint), (LPVOID)PCODEToPINSTR(entryPoint)); + TADDR val = (TADDR)m_entryPointToMethodDescMap.LookupValue(PCODEToPINSTR(entryPoint), 0); if (val == (TADDR)INVALIDENTRY) return NULL; @@ -394,7 +394,7 @@ bool ReadyToRunInfo::SetMethodDescForEntryPointInNativeImage(PCODE entryPoint, M CONTRACTL_END; CrstHolder ch(&m_Crst); - if ((TADDR)m_entryPointToMethodDescMap.LookupValue(PCODEToPINSTR(entryPoint), (LPVOID)PCODEToPINSTR(entryPoint)) == (TADDR)INVALIDENTRY) + if ((TADDR)m_entryPointToMethodDescMap.LookupValue(PCODEToPINSTR(entryPoint), 0) == (TADDR)INVALIDENTRY) { m_entryPointToMethodDescMap.InsertValue(PCODEToPINSTR(entryPoint), methodDesc); return true; @@ -882,6 +882,22 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocat m_nRuntimeFunctions = 0; } +#ifdef TARGET_WASM + // For WASM, the min function table index is stored as a u32 immediately after the + // sentinel entry (0xFFFFFFFF) at the end of the RUNTIME_FUNCTION table. + if (m_nRuntimeFunctions > 0) + { + DWORD* pSentinel = (DWORD*)&m_pRuntimeFunctions[m_nRuntimeFunctions]; + _ASSERTE(*pSentinel == 0xFFFFFFFF); + m_minFunctionTableIndex = *(pSentinel + 1); + } + else + { + m_minFunctionTableIndex = 0; + } +#endif // TARGET_WASM + +#ifdef FEATURE_COLD_R2R_CODE IMAGE_DATA_DIRECTORY * pHotColdMapDir = m_pComposite->FindSection(ReadyToRunSectionType::HotColdMap); if (pHotColdMapDir != NULL) { @@ -892,6 +908,7 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocat { m_nHotColdMap = 0; } +#endif // FEATURE_COLD_R2R_CODE IMAGE_DATA_DIRECTORY * pImportSectionsDir = m_pComposite->FindSection(ReadyToRunSectionType::ImportSections); if (pImportSectionsDir != NULL) @@ -912,7 +929,10 @@ ReadyToRunInfo::ReadyToRunInfo(Module * pModule, LoaderAllocator* pLoaderAllocat m_methodDefEntryPoints = NativeArray(&m_nativeReader, pEntryPointsDir->VirtualAddress); } +#ifndef TARGET_WASM m_pSectionDelayLoadMethodCallThunks = m_pComposite->FindSection(ReadyToRunSectionType::DelayLoadMethodCallThunks); +#endif + m_pSectionDebugInfo = m_pComposite->FindSection(ReadyToRunSectionType::DebugInfo); m_pSectionExceptionInfo = m_pComposite->FindSection(ReadyToRunSectionType::ExceptionInfo); @@ -1376,18 +1396,21 @@ PCODE ReadyToRunInfo::GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig _ASSERTE(id < m_nRuntimeFunctions); #ifndef FEATURE_PORTABLE_ENTRYPOINTS - pEntryPoint = dac_cast(GetImage()->GetBase()) + m_pRuntimeFunctions[id].BeginAddress; + pEntryPoint = dac_cast(GetImage()->GetBase()) + RUNTIME_FUNCTION__BeginAddress(&m_pRuntimeFunctions[id]); + m_pCompositeInfo->SetMethodDescForEntryPointInNativeImage(pEntryPoint, pMD); #else // When we have portable entrypoints enabled, the R2R image contains actual entrypoints. #ifdef FEATURE_TIERED_COMPILATION #error "Portable entry points are not currently supported with tiered compilation, as the interaction between the two is not yet fully worked out." #endif PCODE actualEntryPoint; - actualEntryPoint = m_pRuntimeFunctions[id].BeginAddress; + actualEntryPoint = GetMinFunctionTableIndex() + id; + PCODE virtualEntrypointIP; + virtualEntrypointIP = GetMinVirtualIP() + RUNTIME_FUNCTION__BeginAddress(&m_pRuntimeFunctions[id]); pEntryPoint = pMD->GetTemporaryEntryPoint(); PortableEntryPoint::SetActualCode(pEntryPoint, actualEntryPoint); + m_pCompositeInfo->SetMethodDescForEntryPointInNativeImage(virtualEntrypointIP, pMD); #endif - m_pCompositeInfo->SetMethodDescForEntryPointInNativeImage(pEntryPoint, pMD); #ifdef PROFILING_SUPPORTED { @@ -1585,7 +1608,11 @@ MethodDesc * ReadyToRunInfo::MethodIterator::GetMethodDesc_NoRestore() } _ASSERTE(id < m_pInfo->m_nRuntimeFunctions); - PCODE pEntryPoint = dac_cast(m_pInfo->GetImage()->GetBase()) + m_pInfo->m_pRuntimeFunctions[id].BeginAddress; +#ifdef TARGET_WASM + PCODE pEntryPoint = ExecutionManager::EncodeVirtualIP(m_pInfo->GetMinVirtualIP() + RUNTIME_FUNCTION__BeginAddress(&m_pInfo->m_pRuntimeFunctions[id])); +#else + PCODE pEntryPoint = dac_cast(m_pInfo->GetImage()->GetBase()) + RUNTIME_FUNCTION__BeginAddress(&m_pInfo->m_pRuntimeFunctions[id]); +#endif return m_pInfo->GetMethodDescForEntryPoint(pEntryPoint); } @@ -2811,4 +2838,61 @@ PCODE DynamicHelpers::CreateDictionaryLookupHelper(LoaderAllocator * pAllocator, } } #endif // FEATURE_STUBPRECODE_DYNAMIC_HELPERS + +#ifdef TARGET_WASM +// Decode a ULEB128-encoded value that must fit in a UINT32. +// Advances *ppData past the encoded bytes. Asserts if the value overflows 32 bits. +UINT32 DecodeULEB128AsU32(PTR_BYTE* ppData) +{ + UINT32 result = 0; + int shift = 0; + BYTE b; + do + { + b = *(*ppData)++; + _ASSERTE(shift < 35); // A valid u32 ULEB128 is at most 5 bytes + result |= (UINT32)(b & 0x7F) << shift; + shift += 7; + } while (b & 0x80); + return result; +} + +void ReadyToRunInfo::RegisterVirtualIPRange(Module* pModule) +{ + CONTRACTL { + THROWS; + GC_NOTRIGGER; + PRECONDITION(CheckPointer(pModule)); + } CONTRACTL_END; + + if (m_nRuntimeFunctions == 0) + return; + + TADDR imageBase = dac_cast(m_pComposite->GetLayout()->GetBase()); + + // The last RUNTIME_FUNCTION entry's BeginAddress is the virtual IP index of that entry. + // Total virtual IPs = lastEntry.BeginAddress + virtualIPCount(lastEntry) + T_RUNTIME_FUNCTION* pLastEntry = &m_pRuntimeFunctions[m_nRuntimeFunctions - 1]; + UINT32 lastEntryVirtualIPIndex = RUNTIME_FUNCTION__BeginAddress(pLastEntry); + + // Decode the virtual IP count from the last entry's unwind data. + // Unwind format: ULEB128(frameSize) ULEB128(virtualIPCount) + PTR_BYTE pUnwindData = dac_cast(imageBase + pLastEntry->UnwindData); + DecodeULEB128AsU32(&pUnwindData); // skip frame size + UINT32 lastEntryVIPCount = DecodeULEB128AsU32(&pUnwindData); + + UINT32 totalVirtualIPs = lastEntryVirtualIPIndex + lastEntryVIPCount; + + m_minVirtualIP = ExecutionManager::AddVirtualIPRange( + totalVirtualIPs, + ExecutionManager::GetReadyToRunJitManager(), + pModule); + + ExecutionManager::AddFunctionTableIndexRange( + m_minFunctionTableIndex, + m_nRuntimeFunctions, + pModule); +} +#endif // TARGET_WASM + #endif // DACCESS_COMPILE diff --git a/src/coreclr/vm/readytoruninfo.h b/src/coreclr/vm/readytoruninfo.h index 64c3324d9b2acc..42ad2c4dbed2a5 100644 --- a/src/coreclr/vm/readytoruninfo.h +++ b/src/coreclr/vm/readytoruninfo.h @@ -16,6 +16,10 @@ #include "wellknownattributes.h" #include "nativeimage.h" +#ifndef TARGET_WASM +#define FEATURE_COLD_R2R_CODE +#endif // !TARGET_WASM + typedef DPTR(struct READYTORUN_SECTION) PTR_READYTORUN_SECTION; class NativeImage; @@ -146,10 +150,19 @@ class ReadyToRunInfo PTR_RUNTIME_FUNCTION m_pRuntimeFunctions; DWORD m_nRuntimeFunctions; +#ifdef TARGET_WASM + DWORD m_minFunctionTableIndex; + TADDR m_minVirtualIP; +#endif // TARGET_WASM + +#ifdef FEATURE_COLD_R2R_CODE PTR_ULONG m_pHotColdMap; DWORD m_nHotColdMap; +#endif // FEATURE_COLD_R2R_CODE +#ifndef TARGET_WASM PTR_IMAGE_DATA_DIRECTORY m_pSectionDelayLoadMethodCallThunks; +#endif // !TARGET_WASM PTR_IMAGE_DATA_DIRECTORY m_pSectionDebugInfo; PTR_IMAGE_DATA_DIRECTORY m_pSectionExceptionInfo; @@ -198,7 +211,10 @@ class ReadyToRunInfo PTR_READYTORUN_HEADER GetReadyToRunHeader() const { return m_pHeader; } +#ifndef TARGET_WASM PTR_IMAGE_DATA_DIRECTORY GetDelayMethodCallThunksSection() const { return m_pSectionDelayLoadMethodCallThunks; } +#endif // !TARGET_WASM + PTR_IMAGE_DATA_DIRECTORY GetExceptionInfoSection() const { return m_pSectionExceptionInfo; } PTR_NativeImage GetNativeImage() const { return m_pNativeImage; } @@ -206,6 +222,15 @@ class ReadyToRunInfo PTR_ReadyToRunLoadedImage GetImage() const { return m_pComposite->GetImage(); } IMAGE_DATA_DIRECTORY * FindSection(ReadyToRunSectionType type) const { return m_pComposite->FindSection(type); } + PTR_RUNTIME_FUNCTION GetRuntimeFunctions() const { return m_pRuntimeFunctions; } + DWORD GetRuntimeFunctionCount() const { return m_nRuntimeFunctions; } + +#ifdef TARGET_WASM + DWORD GetMinFunctionTableIndex() const { return m_minFunctionTableIndex; } + TADDR GetMinVirtualIP() const { return m_minVirtualIP; } + void RegisterVirtualIPRange(Module* pModule); +#endif // TARGET_WASM + void RegisterResumptionStub(PCODE stubEntryPoint); PCODE GetEntryPoint(MethodDesc * pMD, PrepareCodeConfig* pConfig, BOOL fFixups); @@ -401,9 +426,13 @@ struct cdac_data static constexpr size_t CompositeInfo = offsetof(ReadyToRunInfo, m_pCompositeInfo); static constexpr size_t NumRuntimeFunctions = offsetof(ReadyToRunInfo, m_nRuntimeFunctions); static constexpr size_t RuntimeFunctions = offsetof(ReadyToRunInfo, m_pRuntimeFunctions); +#ifdef FEATURE_COLD_R2R_CODE static constexpr size_t NumHotColdMap = offsetof(ReadyToRunInfo, m_nHotColdMap); static constexpr size_t HotColdMap = offsetof(ReadyToRunInfo, m_pHotColdMap); +#endif // FEATURE_COLD_R2R_CODE +#ifndef TARGET_WASM static constexpr size_t DelayLoadMethodCallThunks = offsetof(ReadyToRunInfo, m_pSectionDelayLoadMethodCallThunks); +#endif // !TARGET_WASM static constexpr size_t DebugInfoSection = offsetof(ReadyToRunInfo, m_pSectionDebugInfo); static constexpr size_t ExceptionInfoSection = offsetof(ReadyToRunInfo, m_pSectionExceptionInfo); static constexpr size_t ImportSections = offsetof(ReadyToRunInfo, m_pImportSections); diff --git a/src/coreclr/vm/stackwalk.cpp b/src/coreclr/vm/stackwalk.cpp index f8e344552c303e..131221da1b21ed 100644 --- a/src/coreclr/vm/stackwalk.cpp +++ b/src/coreclr/vm/stackwalk.cpp @@ -411,10 +411,6 @@ PCODE Thread::VirtualUnwindCallFrame(T_CONTEXT* pContext, T_KNONVOLATILE_CONTEXT_POINTERS* pContextPointers /*= NULL*/, EECodeInfo * pCodeInfo /*= NULL*/) { -#ifdef TARGET_WASM - _ASSERTE("VirtualUnwindCallFrame is not supported on WebAssembly"); - return 0; -#else CONTRACTL { NOTHROW; @@ -550,7 +546,6 @@ PCODE Thread::VirtualUnwindCallFrame(T_CONTEXT* pContext, #endif // !DACCESS_COMPILE return uControlPc; -#endif // TARGET_WASM } #ifndef DACCESS_COMPILE diff --git a/src/coreclr/vm/wasm/helpers.cpp b/src/coreclr/vm/wasm/helpers.cpp index f82a4826b37dec..683fea7e94b421 100644 --- a/src/coreclr/vm/wasm/helpers.cpp +++ b/src/coreclr/vm/wasm/helpers.cpp @@ -480,6 +480,9 @@ void FaultingExceptionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool u void TransitionFrame::UpdateRegDisplay_Impl(const PREGDISPLAY pRD, bool updateFloats) { + pRD->IsCallerContextValid = FALSE; + pRD->IsCallerSPValid = FALSE; // Don't add usage of this field. This is only temporary. + pRD->pCurrentContext->InterpreterIP = GetReturnAddress(); pRD->pCurrentContext->InterpreterSP = GetSP(); @@ -538,7 +541,6 @@ EXTERN_C void JIT_StackProbe_End() EXTERN_C VOID STDCALL ResetCurrentContext() { - PORTABILITY_ASSERT("ResetCurrentContext is not implemented on wasm"); } extern "C" void STDCALL GenericPInvokeCalliHelper(void) @@ -718,6 +720,17 @@ void FlushWriteBarrierInstructionCache() // Nothing to do - wasm has static write barriers } +ULONG +RtlpGetFunctionEndAddress ( + _In_ PT_RUNTIME_FUNCTION FunctionEntry, + _In_ TADDR ImageBase + ) +{ + PTR_BYTE pUnwindData = dac_cast(FunctionEntry->UnwindData + ImageBase); + DecodeULEB128AsU32(&pUnwindData); // Skip the count of bytes to unwind + return DecodeULEB128AsU32(&pUnwindData); // Read the function length in virtual IP units, then multiply by 2 to report them as even numbers. +} + EXTERN_C Thread * JIT_InitPInvokeFrame(InlinedCallFrame *pFrame) { PORTABILITY_ASSERT("JIT_InitPInvokeFrame is not implemented on wasm"); @@ -1313,3 +1326,82 @@ void InvokeUnmanagedMethod(MethodDesc *targetMethod, int8_t *pArgs, int8_t *pRet { PORTABILITY_ASSERT("Attempted to execute unmanaged code from interpreter on wasm, this is not yet implemented"); } + +TADDR GetWasmFramePointerFromStackPointer(TADDR sp) +{ + if (sp <= 0x1000) + return 0; + else + { + if (*(int*)sp == 0) + { + sp = *(TADDR*)(sp + sizeof(TADDR)); + } + if (*(int*)sp == TERMINATE_R2R_STACK_WALK) + { + return 0; + } + else + { + return sp; + } + } +} + +TADDR GetWasmVirtualIPFromStackPointer(TADDR sp) +{ + TADDR fp = GetWasmFramePointerFromStackPointer(sp); + + if (fp == 0) + { + return 0; + } + else + { + uint32_t r2rFunctionTableEntryNumber = ((uint32_t*)fp)[0]; + uint32_t logicalVirtualIP = ((uint32_t*)fp)[1] * 2; // Multiple by 2 as virtual IPs are encoded in units of 2 to leave the low bit in the VirtualIP mapping available to distinguish between virtual IPs and interpreter addresses/PortableEntryPoints. + TADDR baseVirtualIP = ExecutionManager::GetWasmVirtualIPFromFunctionTableIndex(r2rFunctionTableEntryNumber); + return baseVirtualIP + logicalVirtualIP; + } +} + +PEXCEPTION_ROUTINE +RtlVirtualUnwind ( + _In_ DWORD HandlerType, + _In_ DWORD ImageBase, + _In_ DWORD ControlPc, + _In_ PRUNTIME_FUNCTION FunctionEntry, + __inout PT_CONTEXT ContextRecord, + _Out_ PVOID *HandlerData, + _Out_ PDWORD EstablisherFrame, + __inout_opt PT_KNONVOLATILE_CONTEXT_POINTERS ContextPointers + ) +{ + _ASSERTE(FunctionEntry != NULL); + _ASSERTE(HandlerType == 0); + _ASSERTE(ExecutionManager::IsVirtualIP(ControlPc)); + _ASSERTE(ImageBase != 0); + + // I don't believe any users of this api actually use HandlerData or EstablisherFrame, so we will just set them to 0 rather than trying to unwind them properly. + // If we do need to support them in the future we can add support for setting them properly. + *HandlerData = 0; + *EstablisherFrame = 0; + + TADDR sp = ContextRecord->InterpreterSP; + TADDR fp = GetWasmFramePointerFromStackPointer(sp); + if (fp != 0) + { + PTR_BYTE pUnwindData = dac_cast(FunctionEntry->UnwindData + ImageBase); + ContextRecord->InterpreterSP = fp + DecodeULEB128AsU32(&pUnwindData); // Unwind the frame pointer to the callers stack pointer + ContextRecord->InterpreterIP = GetWasmVirtualIPFromStackPointer(ContextRecord->InterpreterSP); + } + else + { + // Unwind failed! + _ASSERTE(FALSE); + ContextRecord->InterpreterIP = 0; + ContextRecord->InterpreterSP = 0; + } + + return nullptr; +}