diff --git a/docs/design/datacontracts/GCInfo.md b/docs/design/datacontracts/GCInfo.md index ec8e820e248f84..a114899650afda 100644 --- a/docs/design/datacontracts/GCInfo.md +++ b/docs/design/datacontracts/GCInfo.md @@ -220,7 +220,7 @@ The fat header is used for methods that cannot be encoded using the compact slim | ReversePInvokeFrameSlot | `REVERSE_PINVOKE_FRAME_ENCBASE` | Normalized reverse P/Invoke frame slot | If GC_INFO_REVERSE_PINVOKE_FRAME | | SizeOfStackOutgoingAndScratchArea | `SIZE_OF_STACK_AREA_ENCBASE` | Size of stack parameter area | Platform dependent | | NumSafePoints | `NUM_SAFE_POINTS_ENCBASE` | Number of safe points/callsites | #ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED | -| NumInterruptibleRanges | `NUM_INTERRUPTIBLE_RANGES_ENCBASE` | Number of interruptible ranges | | +| NumInterruptibleRanges | `NUM_INTERRUPTIBLE_RANGES_ENCBASE` | Number of interruptible ranges | If `HAS_INTERRUPTIBLE_RANGES` (omitted for Wasm R2R) | ##### Header Flags @@ -517,7 +517,10 @@ if HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA: // platform-dependent fixedStackParameterScratchArea = DenormalizeSizeOfStackArea(DecodeVarLengthUnsigned(...)) numSafePoints = DecodeVarLengthUnsigned(NUM_SAFE_POINTS_ENCBASE) -numInterruptibleRanges = DecodeVarLengthUnsigned(NUM_INTERRUPTIBLE_RANGES_ENCBASE) +if HAS_INTERRUPTIBLE_RANGES: // false for Wasm R2R, which never has interruptible ranges + numInterruptibleRanges = DecodeVarLengthUnsigned(NUM_INTERRUPTIBLE_RANGES_ENCBASE) +else: + numInterruptibleRanges = 0 ``` #### Body Decoding @@ -647,6 +650,8 @@ For each chunk, the encoding stores: To determine liveness at the target offset: start from the chunk's final state, then apply any transitions that occur *after* the target offset (toggling the state backwards). A slot is live if its final state (after toggle adjustment) is 1. +For encodings without interruptible ranges (`HAS_INTERRUPTIBLE_RANGES` is false, as for Wasm R2R), this step is replaced by reporting only untracked slots, including for `ExecutionAborted` frames. An aborted funclet shares those slots with parent frames that are skipped. + **Step 4 — Report untracked slots**: Untracked slots are always live (they represent stack locations the JIT doesn't track at each safe point). They are reported unconditionally unless `ParentOfFuncletStackFrame` or `NoReportUntracked` flags are set. Untracked slots are reported with `reportScratchSlots=true` since the JIT may produce untracked scratch register slots for interior pointers. **Slot filtering**: Before reporting any slot, the algorithm checks: diff --git a/src/coreclr/gcinfo/gcinfoencoder.cpp b/src/coreclr/gcinfo/gcinfoencoder.cpp index 34ddc23e9c42b6..f69eb1154a8780 100644 --- a/src/coreclr/gcinfo/gcinfoencoder.cpp +++ b/src/coreclr/gcinfo/gcinfoencoder.cpp @@ -612,6 +612,8 @@ template void TGcInfoEncoder::WriteSlo template void TGcInfoEncoder::DefineInterruptibleRange( UINT32 startInstructionOffset, UINT32 length ) { + _ASSERTE(GcInfoEncoding::HAS_INTERRUPTIBLE_RANGES); + UINT32 stopInstructionOffset = startInstructionOffset + length; UINT32 normStartOffset = GcInfoEncoding::NORMALIZE_CODE_OFFSET(startInstructionOffset); @@ -1108,7 +1110,7 @@ template void TGcInfoEncoder::Build() m_NumCallSites = numCallSites; #endif // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED - if (slimHeader) + if (slimHeader || !GcInfoEncoding::HAS_INTERRUPTIBLE_RANGES) { _ASSERTE(numInterruptibleRanges == 0); } diff --git a/src/coreclr/inc/gcinfoencoder.h b/src/coreclr/inc/gcinfoencoder.h index 81bc90ef10465b..7e705bb8c516a7 100644 --- a/src/coreclr/inc/gcinfoencoder.h +++ b/src/coreclr/inc/gcinfoencoder.h @@ -43,7 +43,7 @@ - ReversePInvokeFrameSlot (if any) - SizeOfStackOutgoingAndScratchArea (#ifdef FIXED_STACK_PARAMETER_SCRATCH_AREA) - NumCallSites (#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) - - NumInterruptibleRanges + - NumInterruptibleRanges (fat header only; omitted if !HAS_INTERRUPTIBLE_RANGES) 2. Call sites offsets (#ifdef PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED) 3. Fully-interruptible ranges diff --git a/src/coreclr/inc/gcinfotypes.h b/src/coreclr/inc/gcinfotypes.h index 682c294014aa63..2ef36ca12051ef 100644 --- a/src/coreclr/inc/gcinfotypes.h +++ b/src/coreclr/inc/gcinfotypes.h @@ -618,6 +618,7 @@ struct AMD64GcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = true; + static const bool HAS_INTERRUPTIBLE_RANGES = true; }; #elif defined(TARGET_ARM) @@ -676,6 +677,7 @@ struct ARM32GcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = true; + static const bool HAS_INTERRUPTIBLE_RANGES = true; }; #elif defined(TARGET_ARM64) @@ -737,6 +739,7 @@ struct ARM64GcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = true; + static const bool HAS_INTERRUPTIBLE_RANGES = true; }; #elif defined(TARGET_LOONGARCH64) @@ -796,6 +799,7 @@ struct LoongArch64GcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = true; + static const bool HAS_INTERRUPTIBLE_RANGES = true; }; #elif defined(TARGET_RISCV64) @@ -856,6 +860,7 @@ struct RISCV64GcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = true; + static const bool HAS_INTERRUPTIBLE_RANGES = true; }; #elif defined(TARGET_X86) @@ -911,6 +916,7 @@ struct X86GcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = true; + static const bool HAS_INTERRUPTIBLE_RANGES = true; }; #elif defined(TARGET_WASM) && !defined(TARGET_64BIT) @@ -968,6 +974,7 @@ struct Wasm32GcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = false; + static const bool HAS_INTERRUPTIBLE_RANGES = false; }; #else // No target defined @@ -1031,6 +1038,7 @@ struct InterpreterGcInfoEncoding { static const int LIVESTATE_RLE_RUN_ENCBASE = 2; static const int LIVESTATE_RLE_SKIP_ENCBASE = 4; static const bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = false; + static const bool HAS_INTERRUPTIBLE_RANGES = true; }; #endif // FEATURE_INTERPRETER diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs index 111129841a5ddf..b38be1644ff478 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/Amd64/GcInfo.cs @@ -174,7 +174,7 @@ public GcInfo(NativeReader imageReader, int offset, Machine machine, int version // PARTIALLY_INTERRUPTIBLE_GC_SUPPORTED (this macro is always defined in _gcInfoTypes.h) NumSafePoints = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.NUM_SAFE_POINTS_ENCBASE, ref bitOffset); - if (!_slimHeader) + if (!_slimHeader && _gcInfoTypes.HAS_INTERRUPTIBLE_RANGES) { NumInterruptibleRanges = imageReader.DecodeVarLengthUnsigned(_gcInfoTypes.NUM_INTERRUPTIBLE_RANGES_ENCBASE, ref bitOffset); } diff --git a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs index ad59bf379bf006..6dd5387285826f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs +++ b/src/coreclr/tools/aot/ILCompiler.Reflection.ReadyToRun/GCInfoTypes.cs @@ -120,6 +120,7 @@ public class GcInfoTypes internal int LIVESTATE_RLE_SKIP_ENCBASE { get; } = 4; internal int NUM_NORM_CODE_OFFSETS_PER_CHUNK_LOG2 { get; } = 6; internal bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA { get; } = true; + internal bool HAS_INTERRUPTIBLE_RANGES { get; } = true; internal GcInfoTypes(Machine machine, bool denormalizeCodeOffsets) { @@ -180,6 +181,7 @@ internal GcInfoTypes(Machine machine, bool denormalizeCodeOffsets) LIVESTATE_RLE_RUN_ENCBASE = 2; LIVESTATE_RLE_SKIP_ENCBASE = 4; HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA = false; + HAS_INTERRUPTIBLE_RANGES = false; break; case Machine.I386: diff --git a/src/coreclr/vm/gcinfodecoder.cpp b/src/coreclr/vm/gcinfodecoder.cpp index 6460eace52685a..4777ef53ebe5c2 100644 --- a/src/coreclr/vm/gcinfodecoder.cpp +++ b/src/coreclr/vm/gcinfodecoder.cpp @@ -374,7 +374,7 @@ TGcInfoDecoder::TGcInfoDecoder( m_SafePointIndex = m_NumSafePoints; #endif - if (slimHeader) + if (slimHeader || !GcInfoEncoding::HAS_INTERRUPTIBLE_RANGES) { m_NumInterruptibleRanges = 0; } @@ -736,6 +736,7 @@ template bool TGcInfoDecoder::Enumerat { unsigned executionAborted = (inputFlags & ExecutionAborted); + bool reportUntrackedOnly = false; // In order to make ARM more x86-like we only ever report the leaf frame // of any given function. We accomplish this by having the stackwalker @@ -777,6 +778,12 @@ template bool TGcInfoDecoder::Enumerat m_Reader.DecodeVarLengthUnsigned( GcInfoEncoding::INTERRUPTIBLE_RANGE_DELTA2_ENCBASE ); } } + else if constexpr (!GcInfoEncoding::HAS_INTERRUPTIBLE_RANGES) + { + // Outside of safe points only untracked slots can be reported. Report them for aborted + // frames too: an aborted funclet shares them with parent frames that are skipped. + reportUntrackedOnly = true; + } else { // @@ -829,6 +836,9 @@ template bool TGcInfoDecoder::Enumerat slotDecoder.DecodeSlotTable(m_Reader); + if (reportUntrackedOnly) + goto ReportUntracked; + { UINT32 numSlots = slotDecoder.GetNumTracked(); diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs index 26beed0ee971be..b5ac9c24c5fa9b 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/GCInfoDecoder.cs @@ -493,7 +493,9 @@ private IEnumerable DecodeFatHeader() _numSafePoints = _reader.DecodeVarLengthUnsigned(TTraits.NUM_SAFE_POINTS_ENCBASE, ref _bitOffset); } - _numInterruptibleRanges = _reader.DecodeVarLengthUnsigned(TTraits.NUM_INTERRUPTIBLE_RANGES_ENCBASE, ref _bitOffset); + _numInterruptibleRanges = TTraits.HAS_INTERRUPTIBLE_RANGES + ? _reader.DecodeVarLengthUnsigned(TTraits.NUM_INTERRUPTIBLE_RANGES_ENCBASE, ref _bitOffset) + : 0; } private void EnsureDecodedTo(DecodePoints point) @@ -976,6 +978,12 @@ private bool EnumerateLiveSlots( { // We have a safe point match — skip interruptible range computation } + else if (!TTraits.HAS_INTERRUPTIBLE_RANGES) + { + // Outside of safe points only untracked slots can be reported. Report them for aborted + // frames too: an aborted funclet shares them with parent frames that are skipped. + return ReportUntrackedAndSucceed(); + } else { // Compute pseudoBreakOffset from interruptible ranges diff --git a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/IGCInfoTraits.cs b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/IGCInfoTraits.cs index a91e495fd8d0c9..4c6b0f75a8544a 100644 --- a/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/IGCInfoTraits.cs +++ b/src/native/managed/cdac/Microsoft.Diagnostics.DataContractReader.Contracts/Contracts/GCInfo/PlatformTraits/IGCInfoTraits.cs @@ -50,6 +50,9 @@ internal interface IGCInfoTraits static abstract bool HAS_FIXED_STACK_PARAMETER_SCRATCH_AREA { get; } + // False for encodings whose code is never interruptible (Wasm R2R). + static virtual bool HAS_INTERRUPTIBLE_RANGES => true; + /// /// Returns true if the given register is a scratch (volatile) register. /// Scratch register slots should only be reported for the active (leaf) stack frame.