From 88a2ab1809849fbaf1c34a1d741b9cee978705f8 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Thu, 28 May 2026 19:02:53 +0300 Subject: [PATCH 1/2] Print liveness ranges in hex offsets This matches the way the interp IR is being logged. --- src/coreclr/interpreter/compiler.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index a1c4debad37ca7..aa76a1714f5d24 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -1399,16 +1399,16 @@ class InterpGcSlotAllocator if (existingRange.OverlapsOrAdjacentTo(start, end)) { ConservativeRange updatedRange = existingRange; - INTERP_DUMP("Merging with existing range [%u - %u]\n", existingRange.startOffset, existingRange.endOffset); + INTERP_DUMP("Merging with existing range [%04x - %04x]\n", existingRange.startOffset, existingRange.endOffset); updatedRange.MergeWith(start, end); while ((i + 1 < m_liveRanges.GetSize()) && m_liveRanges.Get(i + 1).OverlapsOrAdjacentTo(start, end)) { ConservativeRange otherExistingRange = m_liveRanges.Get(i + 1); - INTERP_DUMP("Merging with existing range [%u - %u]\n", otherExistingRange.startOffset, otherExistingRange.endOffset); + INTERP_DUMP("Merging with existing range [%04x - %04x]\n", otherExistingRange.startOffset, otherExistingRange.endOffset); updatedRange.MergeWith(otherExistingRange.startOffset, otherExistingRange.endOffset); m_liveRanges.RemoveAt(i + 1); } - INTERP_DUMP("Final merged range [%u - %u]\n", updatedRange.startOffset, updatedRange.endOffset); + INTERP_DUMP("Final merged range [%04x - %04x]\n", updatedRange.startOffset, updatedRange.endOffset); m_liveRanges.Set(i, updatedRange); return; } @@ -1497,7 +1497,7 @@ class InterpGcSlotAllocator uint32_t startOffset = ConvertOffset(m_compiler->GetLiveStartOffset(varIndex)), endOffset = ConvertOffset(m_compiler->GetLiveEndOffset(varIndex)); INTERP_DUMP( - "Slot %u (%s var #%d offset %u) live [IR_%04x - IR_%04x] [%u - %u]\n", + "Slot %u (%s var #%d offset %u) live [IR_%04x - IR_%04x] [%04x - %04x]\n", slot, pVar->global ? "global" : "local", varIndex, pVar->offset, m_compiler->GetLiveStartOffset(varIndex), m_compiler->GetLiveEndOffset(varIndex), @@ -1532,7 +1532,7 @@ class InterpGcSlotAllocator ConservativeRange range = ranges->m_liveRanges.Get(iRange); INTERP_DUMP( - "Conservative range for slot %u at %u [%u - %u]\n", + "Conservative range for slot %u at %u [%04x - %04x]\n", *pSlot, (unsigned)(iSlot * sizeof(void *)), range.startOffset, From 4bdc50104fa3ef6b982306ee854d39c7d0086c83 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Tue, 26 May 2026 15:23:49 +0300 Subject: [PATCH 2/2] [clr-interp] Fix GC liveness reporting stale data before instruction executes Liveness for a var starts at the instruction that writes it. Consider this scenario: ``` call GC.Collect() ldc var <- null ``` When the GC runs, the ip in this frame points to the address of the `ldc` instruction. The liveness of var starts at this instruction as well, which means the GC will see it as alive, even though it hasn't been set yet, reporting stale data as a root. As a simple fix, we consider the liveness start for a var as being the last ip inside the instruction that sets it. The var is not logically alive until the instruction that sets it actually finishes executing. --- src/coreclr/interpreter/compiler.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index aa76a1714f5d24..7a1a9d7d84d1e1 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -883,7 +883,12 @@ int32_t InterpCompiler::GetLiveStartOffset(int32_t var) else { assert(m_pVars[var].liveStart != NULL); - return m_pVars[var].liveStart->nativeOffset; + // The value of the var is not valid at the start of the instruction that sets it. + // We don't set it as the start of the next instruction because a live range needs + // to have start != end, which wouldn't be the case if the var is always dead. Ignoring + // the live range for such a var could be problematic if this var is the return of a call, + // with its address being passed directly to the a jit-ed method. + return m_pVars[var].liveStart->nativeOffset + GetInsLength(m_pVars[var].liveStart) - 1; } }