From 4fafa26ba903bc132770755cb36271f65ae48d23 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 4 Dec 2025 00:13:42 +0100 Subject: [PATCH 1/3] Fix interpreter delegate args alignment The recent fix to handle alignment of the first argument of a delegate call when we were removing the delegate obj from the stack was not complete. It only handled the case when the first argument required 16 byte alignment. But the same issue exists if any of the args requires this alignment. This change fixes it by finding the first argument that requires 16 byte alignment and then moving the args before that as usual and moving that arg and all following ones to 16 byte aligned location. This preserves the alignment for the rest of the stuff too. --- src/coreclr/interpreter/compiler.cpp | 19 ++++++++++++------- src/coreclr/vm/interpexec.cpp | 23 +++++++++++++++++++---- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/src/coreclr/interpreter/compiler.cpp b/src/coreclr/interpreter/compiler.cpp index 262cb8673422e8..c5ddc3c9942602 100644 --- a/src/coreclr/interpreter/compiler.cpp +++ b/src/coreclr/interpreter/compiler.cpp @@ -4528,15 +4528,20 @@ void InterpCompiler::EmitCall(CORINFO_RESOLVED_TOKEN* pConstrainedToken, bool re } else if (opcode == INTOP_CALLDELEGATE) { - int32_t firstTargetArgOffset = INTERP_STACK_SLOT_SIZE; - if (numArgs > 1) + int32_t sizeOfArgsUpto16ByteAlignment = 0; + for (int argIndex = 1; argIndex < numArgs; argIndex++) { - // The first argument is the delegate obj, the second is the first target arg - // The offset of the first target arg relative to the start of delegate call args is equal to the alignment of the - // first target arg. - GetInterpTypeStackSize(m_pVars[callArgs[1]].clsHnd, m_pVars[callArgs[1]].interpType, &firstTargetArgOffset); + int32_t argAlignment = INTERP_STACK_SLOT_SIZE; + int32_t size = GetInterpTypeStackSize(m_pVars[callArgs[argIndex]].clsHnd, m_pVars[callArgs[argIndex]].interpType, &argAlignment); + size = ALIGN_UP_TO(size, INTERP_STACK_SLOT_SIZE); + if (argAlignment == INTERP_STACK_ALIGNMENT) + { + break; + } + sizeOfArgsUpto16ByteAlignment += size; } - m_pLastNewIns->data[1] = firstTargetArgOffset; + + m_pLastNewIns->data[1] = sizeOfArgsUpto16ByteAlignment; } } break; diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index ed673cf571728f..b34106e4abb065 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2564,10 +2564,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int8_t* returnValueAddress = LOCAL_VAR_ADDR(returnOffset, int8_t); // Used only for INTOP_CALLDELEGATE to allow removal of the delegate object from the argument list - size_t firstTargetArgOffset = 0; + size_t sizeOfArgsUpto16ByteAlignment = 0; if (*ip == INTOP_CALLDELEGATE) { - firstTargetArgOffset = (size_t)ip[4]; + sizeOfArgsUpto16ByteAlignment = (size_t)ip[4]; ip += 5; } else @@ -2633,8 +2633,23 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr } else { - // Shift args down by one slot to remove the delegate obj pointer - memmove(LOCAL_VAR_ADDR(callArgsOffset, int8_t), LOCAL_VAR_ADDR(callArgsOffset + firstTargetArgOffset, int8_t), pTargetMethod->argsSize); + // Shift args down by one slot to remove the delegate obj pointer. + // We need to preserve alignment of arguments that require 16-byte alignment. + // The sizeOfArgsUpto16ByteAlignment is the size of all the target method args starting at the first argument upto the last + // argument that doesn't require 16-byte alignment. + if (sizeOfArgsUpto16ByteAlignment != 0) + { + memmove(LOCAL_VAR_ADDR(callArgsOffset, int8_t), LOCAL_VAR_ADDR(callArgsOffset + INTERP_STACK_SLOT_SIZE, int8_t), sizeOfArgsUpto16ByteAlignment); + } + + if (sizeOfArgsUpto16ByteAlignment != pTargetMethod->argsSize) + { + // There are arguments that require 16-byte alignment + size_t firstAlignedTargetArgDstOffset = ALIGN_UP(sizeOfArgsUpto16ByteAlignment, INTERP_STACK_ALIGNMENT); + size_t firstAlignedTargetArgSrcOffset = ALIGN_UP(INTERP_STACK_SLOT_SIZE + sizeOfArgsUpto16ByteAlignment, INTERP_STACK_ALIGNMENT); + memmove(LOCAL_VAR_ADDR(callArgsOffset + firstAlignedTargetArgDstOffset, int8_t), LOCAL_VAR_ADDR(callArgsOffset + firstAlignedTargetArgSrcOffset, int8_t), pTargetMethod->argsSize - sizeOfArgsUpto16ByteAlignment); + } + // Allocate child frame. InterpMethodContextFrame *pChildFrame = pFrame->pNext; if (!pChildFrame) From 3501d6ccee996e4e26944ea570cac9296bd22231 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 4 Dec 2025 01:17:30 +0100 Subject: [PATCH 2/3] Update src/coreclr/vm/interpexec.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/coreclr/vm/interpexec.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index b34106e4abb065..4e7bf7d5f65003 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2635,8 +2635,7 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr { // Shift args down by one slot to remove the delegate obj pointer. // We need to preserve alignment of arguments that require 16-byte alignment. - // The sizeOfArgsUpto16ByteAlignment is the size of all the target method args starting at the first argument upto the last - // argument that doesn't require 16-byte alignment. + // The sizeOfArgsUpto16ByteAlignment is the size of all the target method args starting at the first argument up to (but not including) the first argument that requires 16-byte alignment. if (sizeOfArgsUpto16ByteAlignment != 0) { memmove(LOCAL_VAR_ADDR(callArgsOffset, int8_t), LOCAL_VAR_ADDR(callArgsOffset + INTERP_STACK_SLOT_SIZE, int8_t), sizeOfArgsUpto16ByteAlignment); From 092e716e32667269ab2d615481e1d716d3b058b7 Mon Sep 17 00:00:00 2001 From: Jan Vorlicek Date: Thu, 4 Dec 2025 13:28:07 +0100 Subject: [PATCH 3/3] Fix gcc build break --- src/coreclr/vm/interpexec.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/interpexec.cpp b/src/coreclr/vm/interpexec.cpp index 4e7bf7d5f65003..db0994390e29f8 100644 --- a/src/coreclr/vm/interpexec.cpp +++ b/src/coreclr/vm/interpexec.cpp @@ -2564,10 +2564,10 @@ void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFr int8_t* returnValueAddress = LOCAL_VAR_ADDR(returnOffset, int8_t); // Used only for INTOP_CALLDELEGATE to allow removal of the delegate object from the argument list - size_t sizeOfArgsUpto16ByteAlignment = 0; + int32_t sizeOfArgsUpto16ByteAlignment = 0; if (*ip == INTOP_CALLDELEGATE) { - sizeOfArgsUpto16ByteAlignment = (size_t)ip[4]; + sizeOfArgsUpto16ByteAlignment = ip[4]; ip += 5; } else