From 62148cc8a5c60a447b0c7e56384a6a2f0d527106 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Mon, 29 Jun 2026 15:38:17 +0300 Subject: [PATCH 1/2] [clr-interp] Fix peepCallConfigureAwait* peeps The parsing validation was not matching the actual opcode peeps. Added comments and reordered the parsing in opcode order to make it easier to follow. All these peeps failed on ConfigureAwait patterns, as tested on local sample. Validated both from C# as well as IL, to also hit the large local index path. Fixes System.Threading.Tasks.Tests.AsyncProfilerTests.RuntimeAsync_ConfigureAwaitFalse --- src/coreclr/interpreter/compiler.cpp | 122 ++++++++++++++++----------- 1 file changed, 72 insertions(+), 50 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 28ef2438eb1d4b..a602c46fe6e2d0 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -7425,7 +7425,13 @@ bool InterpCompiler::IsRuntimeAsyncCall(const uint8_t* ip, OpcodePeepElement* pa bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) { - switch (*(ip + pattern[2].offsetIntoPeep - 1)) + // IL pattern: + // # CALL | CALLVIRT at offset 0 + // # LDC_I4_0 | LDC_I4_1 + // CALLVIRT + // CALL + + switch (*(ip + pattern[0].offsetIntoPeep - 1)) { case CEE_LDC_I4_0: m_currentContinuationContextHandling = ContinuationContextHandling::ContinueOnThreadPool; @@ -7437,38 +7443,8 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, Opc return false; } - uint32_t stLocVar = 0; - uint32_t ldlocaVar = 0; - - switch (*(ip + pattern[0].offsetIntoPeep)) - { - case CEE_STLOC_S: - stLocVar = (ip + pattern[0].offsetIntoPeep)[1]; - break; - default: - // Must be STLOC - stLocVar = getU2LittleEndian(ip + pattern[0].offsetIntoPeep + 2); - break; - } - - switch (*(ip + pattern[1].offsetIntoPeep)) - { - case CEE_LDLOCA_S: - ldlocaVar = (ip + pattern[1].offsetIntoPeep)[1]; - break; - default: - // Must be LDLOCA - ldlocaVar = getU2LittleEndian(ip + pattern[1].offsetIntoPeep + 2); - break; - } - - if (ldlocaVar != stLocVar) - { - return false; - } - CORINFO_RESOLVED_TOKEN configureAwaitResolvedToken; - ResolveToken(getU4LittleEndian(ip + pattern[2].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &configureAwaitResolvedToken); + ResolveToken(getU4LittleEndian(ip + pattern[0].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &configureAwaitResolvedToken); if (!m_compHnd->isIntrinsic(configureAwaitResolvedToken.hMethod) || GetNamedIntrinsic(m_compHnd, m_methodHnd, configureAwaitResolvedToken.hMethod) != NI_System_Threading_Tasks_Task_ConfigureAwait) { @@ -7476,7 +7452,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, Opc } CORINFO_RESOLVED_TOKEN awaitResolvedToken; - ResolveToken(getU4LittleEndian(ip + pattern[3].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &awaitResolvedToken); + ResolveToken(getU4LittleEndian(ip + pattern[1].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &awaitResolvedToken); if (!m_compHnd->isIntrinsic(awaitResolvedToken.hMethod) || GetNamedIntrinsic(m_compHnd, m_methodHnd, awaitResolvedToken.hMethod) != NI_System_Runtime_CompilerServices_AsyncHelpers_Await) { @@ -7488,17 +7464,13 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitTask(const uint8_t* ip, Opc bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) { - switch (*(ip + pattern[1].offsetIntoPeep - 1)) - { - case CEE_LDC_I4_0: - m_currentContinuationContextHandling = ContinuationContextHandling::ContinueOnThreadPool; - break; - case CEE_LDC_I4_1: - m_currentContinuationContextHandling = ContinuationContextHandling::ContinueOnCapturedContext; - break; - default: - return false; - } + // IL pattern: + // # CALL | CALLVIRT at offset 0 + // # STLOC_0 | STLOC_1 | STLOC_2 | STLOC_3 + // LDLOCA | LDLOCA_S + // # LDC_I4_0 | LDC_I4_1 + // CALL + // CALL uint32_t stLocVar = 0; uint32_t ldlocaVar = 0; @@ -7521,14 +7493,14 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const u return false; } - switch (*(ip + pattern[1].offsetIntoPeep)) + switch (*(ip + pattern[0].offsetIntoPeep)) { case CEE_LDLOCA_S: - ldlocaVar = (ip + pattern[1].offsetIntoPeep)[1]; + ldlocaVar = (ip + pattern[0].offsetIntoPeep)[1]; break; default: // Must be LDLOCA - ldlocaVar = getU2LittleEndian(ip + pattern[1].offsetIntoPeep + 2); + ldlocaVar = getU2LittleEndian(ip + pattern[0].offsetIntoPeep + 2); break; } @@ -7537,6 +7509,18 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const u return false; } + switch (*(ip + pattern[1].offsetIntoPeep - 1)) + { + case CEE_LDC_I4_0: + m_currentContinuationContextHandling = ContinuationContextHandling::ContinueOnThreadPool; + break; + case CEE_LDC_I4_1: + m_currentContinuationContextHandling = ContinuationContextHandling::ContinueOnCapturedContext; + break; + default: + return false; + } + CORINFO_RESOLVED_TOKEN configureAwaitResolvedToken; ResolveToken(getU4LittleEndian(ip + pattern[1].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &configureAwaitResolvedToken); if (!m_compHnd->isIntrinsic(configureAwaitResolvedToken.hMethod) || @@ -7558,7 +7542,45 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTaskExactStLoc(const u bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip, OpcodePeepElement* pattern, void** ppComputedInfo) { - switch (*(ip + pattern[0].offsetIntoPeep - 1)) + // IL pattern: + // # CALL | CALLVIRT at offset 0 + // STLOC | STLOC_S + // LDLOCA | LDLOCA_S + // # LDC_I4_0 | LDC_I4_1 + // CALL + // CALL + + uint32_t stLocVar = 0; + uint32_t ldlocaVar = 0; + + switch (*(ip + pattern[0].offsetIntoPeep)) + { + case CEE_STLOC_S: + stLocVar = (ip + pattern[0].offsetIntoPeep)[1]; + break; + default: + // Must be STLOC + stLocVar = getU2LittleEndian(ip + pattern[0].offsetIntoPeep + 2); + break; + } + + switch (*(ip + pattern[1].offsetIntoPeep)) + { + case CEE_LDLOCA_S: + ldlocaVar = (ip + pattern[1].offsetIntoPeep)[1]; + break; + default: + // Must be LDLOCA + ldlocaVar = getU2LittleEndian(ip + pattern[1].offsetIntoPeep + 2); + break; + } + + if (ldlocaVar != stLocVar) + { + return false; + } + + switch (*(ip + pattern[2].offsetIntoPeep - 1)) { case CEE_LDC_I4_0: m_currentContinuationContextHandling = ContinuationContextHandling::ContinueOnThreadPool; @@ -7571,7 +7593,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip } CORINFO_RESOLVED_TOKEN configureAwaitResolvedToken; - ResolveToken(getU4LittleEndian(ip + pattern[0].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &configureAwaitResolvedToken); + ResolveToken(getU4LittleEndian(ip + pattern[2].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &configureAwaitResolvedToken); if (!m_compHnd->isIntrinsic(configureAwaitResolvedToken.hMethod) || GetNamedIntrinsic(m_compHnd, m_methodHnd, configureAwaitResolvedToken.hMethod) != NI_System_Threading_Tasks_Task_ConfigureAwait) { @@ -7579,7 +7601,7 @@ bool InterpCompiler::IsRuntimeAsyncCallConfigureAwaitValueTask(const uint8_t* ip } CORINFO_RESOLVED_TOKEN awaitResolvedToken; - ResolveToken(getU4LittleEndian(ip + pattern[1].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &awaitResolvedToken); + ResolveToken(getU4LittleEndian(ip + pattern[3].offsetIntoPeep + 1), CORINFO_TOKENKIND_Method, &awaitResolvedToken); if (!m_compHnd->isIntrinsic(awaitResolvedToken.hMethod) || GetNamedIntrinsic(m_compHnd, m_methodHnd, awaitResolvedToken.hMethod) != NI_System_Runtime_CompilerServices_AsyncHelpers_Await) { From 3f91e778804ff1cf601c62d84d9ee83b0ceb0c73 Mon Sep 17 00:00:00 2001 From: Vlad Brezae Date: Tue, 30 Jun 2026 12:52:19 +0300 Subject: [PATCH 2/2] [clr-interp] Fix peep new bblock validation Peeps don't contain all matching opcodes. For example: ``` static OpcodePeepElement peepRuntimeAsyncCallConfigureAwaitValueTask_L_L[] = { // Call or CallVirt at the start (at offset 0) { 5, CEE_STLOC}, { 9, CEE_LDLOCA }, // LDC_I4_0 or LDC_I4_1 goes here (at offset 13 { 14, CEE_CALL}, { 19, CEE_CALL }, { 24, CEE_ILLEGAL } // End marker ``` m_pCBB is the current basic block. Aka the bblock that the call at offset 0 belongs to. All future opcodes in the peep have not yet been reached by the interpreter compiler so the offset to bb isn't initialized for them (only the first instruction in the bblock is mapped). This means that, in the above example, we could have code where LDC_I4 is the first instruction in the bblock and previous code was failing to check it. The fix avoids storing extra information in the peeps for this minor check, instead validating each il offset in the peep range. --- src/coreclr/interpreter/compiler.cpp | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index a602c46fe6e2d0..0d614a135a6aa9 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -7671,10 +7671,10 @@ bool InterpCompiler::FindAndApplyPeep(OpcodePeep* Peeps[]) // Check to see if peep applies to the current block just looking at the IL streams // starting at the current ip. - for (int iPeepOpCode = 0; peep->pattern[iPeepOpCode].opcode != CEE_ILLEGAL; iPeepOpCode++) + int iPeepOpCode = 0; + for (; peep->pattern[iPeepOpCode].opcode != CEE_ILLEGAL; iPeepOpCode++) { const uint8_t *ipForOpcode = ip + peep->pattern[iPeepOpCode].offsetIntoPeep; - int32_t insOffset = (int32_t)(ipForOpcode - m_pILCode); if (ipForOpcode >= m_pILCode + m_ILCodeSize) { @@ -7682,17 +7682,29 @@ bool InterpCompiler::FindAndApplyPeep(OpcodePeep* Peeps[]) skipToNextPeep = true; break; } - InterpBasicBlock *pNewBB = m_ppOffsetToBB[insOffset]; - if (pNewBB != NULL && m_pCBB != pNewBB) + + if (peep->pattern[iPeepOpCode].opcode != CEEDecodeOpcode(&ipForOpcode)) { - // Ran into a different basic block + // Opcode does not match skipToNextPeep = true; break; } + } + if (skipToNextPeep) + continue; - if (peep->pattern[iPeepOpCode].opcode != CEEDecodeOpcode(&ipForOpcode)) + // Peeps don't necessarily contain all the opcodes that should match. The opcodes that we + // are matching in the peep have not been reached yet by the compiler, so we don't have + // the `m_ppOffsetToBB` offset initialized for them, since this is initialized only for + // the first instruction in the bblock. We therefore need to scan all offsets looking for + // a new bblock. + int32_t startOffset = (int32_t)(ip - m_pILCode); + int32_t endOffset = startOffset + peep->pattern[iPeepOpCode].offsetIntoPeep; + for (int32_t ilOffset = startOffset + 1; ilOffset < endOffset; ilOffset++) + { + InterpBasicBlock *pNewBB = m_ppOffsetToBB[ilOffset]; + if (pNewBB != NULL && m_pCBB != pNewBB) { - // Opcode does not match skipToNextPeep = true; break; }