From d2e6d957208bb2f44cee16cfb893bbf9cd79065d Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 25 Nov 2025 00:11:39 +0100 Subject: [PATCH 1/2] JIT: Skip GC info for trivial async calls for x86 For x86 we only report GC pointers live in callee save registers, and we also avoid reporting any trivial call. There is compensating code upstream of the GC reporting that returns for trivial calls, but this code did not account for the GC ref returned for async continuations. Thus it was possible that we made it into the GC encoder with a trivial call and then hit an assert. --- src/coreclr/jit/emit.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index 631332e98f0174..85b78286c0a346 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -10373,8 +10373,9 @@ void emitter::emitStackPopLargeStk(BYTE* addr, bool isCall, unsigned char callIn #ifdef JIT32_GCENCODER // x86 does not report GC refs/byrefs in return registers at call sites - gcrefRegs &= ~(1u << (REG_INTRET - REG_INT_FIRST)); - byrefRegs &= ~(1u << (REG_INTRET - REG_INT_FIRST)); + unsigned returnRegs = (RBM_INTRET | RBM_LNGRET | RBM_ASYNC_CONTINUATION_RET).GetIntRegSet() >> REG_INT_FIRST; + gcrefRegs &= ~returnRegs; + byrefRegs &= ~returnRegs; // For the general encoder, we always have to record calls, so we don't take this early return. /* Are there any // args to pop at this call site? From c1dd57bd9c061b16a744c3461bf05d17bdf003fc Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Tue, 25 Nov 2025 00:17:58 +0100 Subject: [PATCH 2/2] Negate the mask --- src/coreclr/jit/emit.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreclr/jit/emit.cpp b/src/coreclr/jit/emit.cpp index 85b78286c0a346..38935948add61d 100644 --- a/src/coreclr/jit/emit.cpp +++ b/src/coreclr/jit/emit.cpp @@ -10372,10 +10372,10 @@ void emitter::emitStackPopLargeStk(BYTE* addr, bool isCall, unsigned char callIn assert(regMaskTP::FromIntRegSet(SingleTypeRegSet(byrefRegs << REG_INT_FIRST)) == emitThisByrefRegs); #ifdef JIT32_GCENCODER - // x86 does not report GC refs/byrefs in return registers at call sites - unsigned returnRegs = (RBM_INTRET | RBM_LNGRET | RBM_ASYNC_CONTINUATION_RET).GetIntRegSet() >> REG_INT_FIRST; - gcrefRegs &= ~returnRegs; - byrefRegs &= ~returnRegs; + // x86 only reports GC refs/byrefs in callee saves at call sites -- no return registers. + unsigned reportedRegs = (RBM_INT_CALLEE_SAVED | RBM_EBP).GetIntRegSet() >> REG_INT_FIRST; + gcrefRegs &= reportedRegs; + byrefRegs &= reportedRegs; // For the general encoder, we always have to record calls, so we don't take this early return. /* Are there any // args to pop at this call site?