From 7bc32bd35aa073f821f2e581e76bc85b15ae39da Mon Sep 17 00:00:00 2001 From: SingleAccretion <62474226+SingleAccretion@users.noreply.github.com> Date: Thu, 12 May 2022 22:51:38 +0300 Subject: [PATCH] Simplify `fgMorphMultiregStructArg` (#68853) * Remove invalid code from "fgMorphMultiregStructArg" In an "OBJ(ADDR(LCL_VAR))" construction, it is legal for the "OBJ" to not match "LCL_VAR", exactly, and the code in multi-reg argument morphing already handles this correctly. So, just remove the asserts and the second-guessing of the "type" array. --- src/coreclr/jit/morph.cpp | 502 +++++------------- .../Directed/StructABI/TypeMismatchedArgs.cs | 84 +++ .../StructABI/TypeMismatchedArgs.csproj | 13 + 3 files changed, 238 insertions(+), 361 deletions(-) create mode 100644 src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.cs create mode 100644 src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.csproj diff --git a/src/coreclr/jit/morph.cpp b/src/coreclr/jit/morph.cpp index 816c093659f2d..9ad2973964793 100644 --- a/src/coreclr/jit/morph.cpp +++ b/src/coreclr/jit/morph.cpp @@ -3734,14 +3734,12 @@ GenTree* Compiler::fgMorphMultiregStructArg(CallArg* arg) if (argNode->TypeGet() != TYP_STRUCT) { structSize = genTypeSize(argNode->TypeGet()); - assert(structSize == info.compCompHnd->getClassSize(objClass)); } else if (argNode->OperGet() == GT_OBJ) { GenTreeObj* argObj = argNode->AsObj(); const ClassLayout* objLayout = argObj->GetLayout(); structSize = objLayout->GetSize(); - assert(structSize == info.compCompHnd->getClassSize(objClass)); // If we have a GT_OBJ of a GT_ADDR then we set argValue to the child node of the GT_ADDR. GenTree* op1 = argObj->gtOp1; @@ -3764,29 +3762,32 @@ GenTree* Compiler::fgMorphMultiregStructArg(CallArg* arg) { LclVarDsc* varDsc = lvaGetDesc(argNode->AsLclVarCommon()); structSize = varDsc->lvExactSize; - assert(structSize == info.compCompHnd->getClassSize(objClass)); } else { structSize = info.compCompHnd->getClassSize(objClass); } - var_types hfaType = TYP_UNDEF; - var_types elemType = TYP_UNDEF; - unsigned elemCount = 0; - unsigned elemSize = 0; - var_types type[MAX_ARG_REG_COUNT] = {}; // TYP_UNDEF = 0 + assert(structSize == info.compCompHnd->getClassSize(objClass)); - hfaType = arg->AbiInfo.GetHfaType(); - if (varTypeIsValidHfaType(hfaType) && arg->AbiInfo.IsPassedInFloatRegisters()) + struct ArgElem { - elemType = hfaType; - elemSize = genTypeSize(elemType); - elemCount = structSize / elemSize; - assert(elemSize * elemCount == structSize); + var_types Type; // The type to load into the register (can be small). + unsigned Offset; // Offset of the element. + }; + + ArgElem elems[MAX_ARG_REG_COUNT] = {}; + const unsigned elemCount = arg->AbiInfo.NumRegs + arg->AbiInfo.GetStackSlotsNumber(); + ArgElem& lastElem = elems[elemCount - 1]; + assert((elemCount == arg->AbiInfo.NumRegs) || arg->AbiInfo.IsSplit()); + + if (arg->AbiInfo.IsHfaArg() && arg->AbiInfo.IsPassedInFloatRegisters()) + { + var_types hfaType = arg->AbiInfo.GetHfaType(); for (unsigned inx = 0; inx < elemCount; inx++) { - type[inx] = elemType; + elems[inx].Type = hfaType; + elems[inx].Offset = genTypeSize(hfaType) * inx; // HFAs are always tightly packed. } } else @@ -3794,78 +3795,58 @@ GenTree* Compiler::fgMorphMultiregStructArg(CallArg* arg) assert(structSize <= MAX_ARG_REG_COUNT * TARGET_POINTER_SIZE); BYTE gcPtrs[MAX_ARG_REG_COUNT]; info.compCompHnd->getClassGClayout(objClass, &gcPtrs[0]); - elemCount = roundUp(structSize, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; -#ifdef TARGET_LOONGARCH64 - // For LoongArch64's ABI, the struct which size is TARGET_POINTER_SIZE - // may be passed by two registers. - // e.g `struct {int a; float b;}` passed by an integer register and a float register. - if (arg->AbiInfo.NumRegs == 2) - { - elemCount = 2; - } -#endif - for (unsigned inx = 0; inx < elemCount; inx++) + // Here, we will set the sizes "rounded up" and then adjust the type of the last element below. + for (unsigned inx = 0, offset = 0; inx < elemCount; inx++) { + elems[inx].Offset = offset; + #if defined(UNIX_AMD64_ABI) if (gcPtrs[inx] == TYPE_GC_NONE) { - type[inx] = GetTypeFromClassificationAndSizes(arg->AbiInfo.StructDesc.eightByteClassifications[inx], - arg->AbiInfo.StructDesc.eightByteSizes[inx]); + elems[inx].Type = + GetTypeFromClassificationAndSizes(arg->AbiInfo.StructDesc.eightByteClassifications[inx], + arg->AbiInfo.StructDesc.eightByteSizes[inx]); + offset += 8; } else #elif defined(TARGET_LOONGARCH64) - if (varTypeIsFloating(arg->AbiInfo.StructFloatFieldType[inx]) || - (genTypeSize(arg->AbiInfo.StructFloatFieldType[inx]) == 4)) + if (arg->AbiInfo.StructFloatFieldType[inx] != TYP_UNDEF) { - type[inx] = arg->AbiInfo.StructFloatFieldType[inx]; + elems[inx].Type = arg->AbiInfo.StructFloatFieldType[inx]; + offset += (structSize > TARGET_POINTER_SIZE) ? 8 : 4; } else #endif // TARGET_LOONGARCH64 { - type[inx] = getJitGCType(gcPtrs[inx]); + elems[inx].Type = getJitGCType(gcPtrs[inx]); + offset += TARGET_POINTER_SIZE; } } -#ifndef UNIX_AMD64_ABI - if ((argValue->OperGet() == GT_LCL_FLD) || (argValue->OperGet() == GT_LCL_VAR)) - { - elemSize = TARGET_POINTER_SIZE; - // We can safely widen this to aligned bytes since we are loading from - // a GT_LCL_VAR or a GT_LCL_FLD which is properly padded and - // lives in the stack frame or will be a promoted field. - CLANG_FORMAT_COMMENT_ANCHOR; - -#ifndef TARGET_LOONGARCH64 - // For LoongArch64's ABI, the struct which size is TARGET_POINTER_SIZE - // may be passed by two registers. - // e.g `struct {int a; float b;}` passed by an integer register and a float register. - structSize = elemCount * TARGET_POINTER_SIZE; -#endif - } - else // we must have a GT_OBJ + // If our argument is local, we can safely load "too much" for the last element. + // However, if it comes from an unknown (arbitrary) address, we must fix up the + // last element's type. + // + if (!argValue->OperIs(GT_LCL_VAR, GT_LCL_FLD)) { - assert(argValue->OperGet() == GT_OBJ); + assert(argValue->OperIs(GT_OBJ)); - // We need to load the struct from an arbitrary address - // and we can't read past the end of the structSize - // We adjust the last load type here - // - unsigned remainingBytes = structSize % TARGET_POINTER_SIZE; - unsigned lastElem = elemCount - 1; - if (remainingBytes != 0) + unsigned lastElemExactSize = structSize - lastElem.Offset; + + if (genTypeSize(lastElem.Type) > lastElemExactSize) { - switch (remainingBytes) + switch (lastElemExactSize) { case 1: - type[lastElem] = TYP_BYTE; + lastElem.Type = TYP_BYTE; break; case 2: - type[lastElem] = TYP_SHORT; + lastElem.Type = TYP_SHORT; break; #if defined(TARGET_ARM64) || defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) case 4: - type[lastElem] = TYP_INT; + lastElem.Type = TYP_INT; break; #endif // (TARGET_ARM64) || (UNIX_AMD64_ABI) || (TARGET_LOONGARCH64) default: @@ -3874,376 +3855,175 @@ GenTree* Compiler::fgMorphMultiregStructArg(CallArg* arg) } } } -#endif // !UNIX_AMD64_ABI } +#ifdef DEBUG + // We have finished setting up the elements. Now verify what we have looks correct. + // + unsigned loadExtent = 0; + for (unsigned idx = 0; idx < elemCount; idx++) + { + assert(elems[idx].Type != TYP_UNDEF); + assert(varTypeUsesFloatReg(elems[idx].Type) == genIsValidFloatReg(arg->AbiInfo.GetRegNum(idx))); + assert(loadExtent <= elems[idx].Offset); + + loadExtent = elems[idx].Offset + genTypeSize(elems[idx].Type); + } + + if (argValue->IsLocal()) + { + assert((structSize <= loadExtent) && (loadExtent <= roundUp(structSize, TARGET_POINTER_SIZE))); + } + else + { + assert(loadExtent == structSize); + } +#endif // DEBUG + // We should still have a TYP_STRUCT - assert(varTypeIsStruct(argValue->TypeGet())); + assert(varTypeIsStruct(argValue)); GenTreeFieldList* newArg = nullptr; // Are we passing a struct LclVar? // - if (argValue->OperGet() == GT_LCL_VAR) + if (argValue->OperIs(GT_LCL_VAR)) { - GenTreeLclVarCommon* varNode = argValue->AsLclVarCommon(); - unsigned varNum = varNode->GetLclNum(); - LclVarDsc* varDsc = lvaGetDesc(varNum); - - // At this point any TYP_STRUCT LclVar must be an aligned struct - // or an HFA struct, both which are passed by value. - // - assert((varDsc->lvSize() == elemCount * TARGET_POINTER_SIZE) || varDsc->lvIsHfa()); + GenTreeLclVarCommon* lclNode = argValue->AsLclVarCommon(); + unsigned lclNum = lclNode->GetLclNum(); + LclVarDsc* varDsc = lvaGetDesc(lclNum); varDsc->lvIsMultiRegArg = true; -#ifdef DEBUG - if (verbose) - { - JITDUMP("Multireg struct argument V%02u : ", varNum); - arg->Dump(this); - } -#endif // DEBUG + JITDUMP("Multireg struct argument V%02u : ", lclNum); + JITDUMPEXEC(arg->Dump(this)); -#ifndef UNIX_AMD64_ABI - // This local variable must match the layout of the 'objClass' type exactly - if (varDsc->lvIsHfa() && arg->AbiInfo.IsPassedInFloatRegisters()) + // Try to see if we can use the promoted fields to pass this argument. + // + if (varDsc->lvPromoted && (varDsc->lvFieldCnt == elemCount)) { - // We have a HFA struct. - noway_assert(elemType == varDsc->GetHfaType()); - noway_assert(elemSize == genTypeSize(elemType)); - noway_assert(elemCount == (varDsc->lvExactSize / elemSize)); - noway_assert(elemSize * elemCount == varDsc->lvExactSize); - - for (unsigned inx = 0; (inx < elemCount); inx++) - { - noway_assert(type[inx] == elemType); - } - } - else - { -#if defined(TARGET_ARM64) - // We must have a 16-byte struct (non-HFA) - noway_assert(elemCount == 2); -#elif defined(TARGET_ARM) - noway_assert(elemCount <= 4); -#endif + bool fieldsMatch = true; for (unsigned inx = 0; inx < elemCount; inx++) { - var_types currentGcLayoutType = varDsc->GetLayout()->GetGCPtrType(inx); - - // We setup the type[inx] value above using the GC info from 'objClass' - // This GT_LCL_VAR must have the same GC layout info - // - if (varTypeIsGC(currentGcLayoutType)) - { - noway_assert(type[inx] == currentGcLayoutType); - } - else - { - // We may have use a small type when we setup the type[inx] values above - // We can safely widen this to TYP_I_IMPL - type[inx] = TYP_I_IMPL; - } - } - } - - if (varDsc->lvPromoted && varDsc->lvIsHfa() && arg->AbiInfo.IsPassedInFloatRegisters()) - { - bool canMorphToFieldList = true; - - for (unsigned fldOffset = 0; fldOffset < varDsc->lvExactSize; fldOffset += elemSize) - { - const unsigned fldVarNum = lvaGetFieldLocal(varDsc, fldOffset); - - if ((fldVarNum == BAD_VAR_NUM) || !varTypeUsesFloatReg(lvaGetDesc(fldVarNum))) + unsigned fieldLclNum = lvaGetFieldLocal(varDsc, elems[inx].Offset); + if (fieldLclNum == BAD_VAR_NUM) { - canMorphToFieldList = false; + fieldsMatch = false; break; } - } - - if (canMorphToFieldList) - { - newArg = fgMorphLclArgToFieldlist(varNode); - } - } - else -#endif // !UNIX_AMD64_ABI -#if defined(TARGET_ARM64) || defined(UNIX_AMD64_ABI) || defined(TARGET_LOONGARCH64) - // Is this LclVar a promoted struct with exactly 2 fields? - if (varDsc->lvPromoted && (varDsc->lvFieldCnt == 2) && !varDsc->lvIsHfa()) - { - // See if we have two promoted fields that start at offset 0 and 8? - unsigned loVarNum = lvaGetFieldLocal(varDsc, 0); - unsigned hiVarNum = lvaGetFieldLocal(varDsc, TARGET_POINTER_SIZE); - - // Did we find the promoted fields at the necessary offsets? - if ((loVarNum != BAD_VAR_NUM) && (hiVarNum != BAD_VAR_NUM)) - { - LclVarDsc* loVarDsc = lvaGetDesc(loVarNum); - LclVarDsc* hiVarDsc = lvaGetDesc(hiVarNum); - var_types loType = loVarDsc->lvType; - var_types hiType = hiVarDsc->lvType; - - if ((varTypeIsFloating(loType) != genIsValidFloatReg(arg->AbiInfo.GetRegNum(0))) || - (varTypeIsFloating(hiType) != genIsValidFloatReg(arg->AbiInfo.GetRegNum(1)))) - { - // TODO-LSRA - It currently doesn't support the passing of floating point LCL_VARS in the integer - // registers. So for now we will use GT_LCLFLD's to pass this struct (it won't be enregistered) - // - JITDUMP("Multireg struct V%02u will be passed using GT_LCLFLD because it has float fields.\n", - varNum); - // - // we call lvaSetVarDoNotEnregister and do the proper transformation below. - // - } - else + var_types fieldType = lvaGetDesc(fieldLclNum)->TypeGet(); + var_types regType = genActualType(elems[inx].Type); + if (varTypeUsesFloatReg(fieldType) != varTypeUsesFloatReg(regType)) { - // We can use the struct promoted field as the two arguments - - // Create a new tree for 'arg' - // replace the existing LDOBJ(ADDR(LCLVAR)) - // with a FIELD_LIST(LCLVAR-LO, FIELD_LIST(LCLVAR-HI, nullptr)) + // TODO-LSRA - It currently doesn't support the passing of floating point LCL_VARS in the + // integer registers. So for now we will use GT_LCLFLD's to pass this struct. // - - newArg = new (this, GT_FIELD_LIST) GenTreeFieldList(); - newArg->AddField(this, gtNewLclvNode(loVarNum, loType), 0, loType); - newArg->AddField(this, gtNewLclvNode(hiVarNum, hiType), TARGET_POINTER_SIZE, hiType); - } - } - } - else - { - // - // We will create a list of GT_LCL_FLDs nodes to pass this struct - // - lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DoNotEnregisterReason::LocalField)); - } -#elif defined(TARGET_ARM) - // Is this LclVar a promoted struct with exactly same size? - if (varDsc->lvPromoted && (varDsc->lvFieldCnt == elemCount) && !varDsc->lvIsHfa()) - { - // See if we have promoted fields? - unsigned varNums[4]; - bool hasBadVarNum = false; - for (unsigned inx = 0; inx < elemCount; inx++) - { - varNums[inx] = lvaGetFieldLocal(varDsc, TARGET_POINTER_SIZE * inx); - if (varNums[inx] == BAD_VAR_NUM) - { - hasBadVarNum = true; + JITDUMP("Multireg struct V%02u will be passed using GT_LCLFLD because of type mismatch: " + "register type is %s, field local V%02u's type is %s\n", + lclNum, varTypeName(regType), fieldLclNum, varTypeName(fieldType)); + fieldsMatch = false; break; } } - // Did we find the promoted fields at the necessary offsets? - if (!hasBadVarNum) + if (fieldsMatch) { - LclVarDsc* varDscs[4]; - var_types varType[4]; - bool varIsFloat = false; - - for (unsigned inx = 0; inx < elemCount; inx++) - { - varDscs[inx] = lvaGetDesc(varNums[inx]); - varType[inx] = varDscs[inx]->lvType; - if (varTypeIsFloating(varType[inx])) - { - // TODO-LSRA - It currently doesn't support the passing of floating point LCL_VARS in the - // integer - // registers. So for now we will use GT_LCLFLD's to pass this struct (it won't be enregistered) - // - JITDUMP("Multireg struct V%02u will be passed using GT_LCLFLD because it has float fields.\n", - varNum); - // - // we call lvaSetVarDoNotEnregister and do the proper transformation below. - // - varIsFloat = true; - break; - } - } - - if (!varIsFloat) - { - newArg = fgMorphLclArgToFieldlist(varNode); - } + newArg = fgMorphLclArgToFieldlist(lclNode); } } - else - { - // - // We will create a list of GT_LCL_FLDs nodes to pass this struct - // - lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DoNotEnregisterReason::LocalField)); - } -#endif // TARGET_ARM } - // If we didn't set newarg to a new List Node tree + // If we were not able to use the promoted fields... // if (newArg == nullptr) { if (arg->AbiInfo.GetRegNum() == REG_STK) { - // We leave this stack passed argument alone + // We leave this stack passed argument alone. return argNode; } - // Are we passing a GT_LCL_FLD (or a GT_LCL_VAR that was not struct promoted ) - // A GT_LCL_FLD could also contain a 16-byte struct or HFA struct inside it? - // - if ((argValue->OperGet() == GT_LCL_FLD) || (argValue->OperGet() == GT_LCL_VAR)) + if (argValue->OperIs(GT_LCL_VAR, GT_LCL_FLD)) { - GenTreeLclVarCommon* varNode = argValue->AsLclVarCommon(); - unsigned varNum = varNode->GetLclNum(); - LclVarDsc* varDsc = lvaGetDesc(varNum); - - unsigned baseOffset = varNode->GetLclOffs(); - unsigned lastOffset = baseOffset + structSize; - - // The allocated size of our LocalVar must be at least as big as lastOffset - assert(varDsc->lvSize() >= lastOffset); - - if (varDsc->HasGCPtr()) - { - // alignment of the baseOffset is required - noway_assert((baseOffset % TARGET_POINTER_SIZE) == 0); -#ifndef UNIX_AMD64_ABI - noway_assert(elemSize == TARGET_POINTER_SIZE); -#endif - unsigned baseIndex = baseOffset / TARGET_POINTER_SIZE; - ClassLayout* layout = varDsc->GetLayout(); - for (unsigned inx = 0; (inx < elemCount); inx++) - { - // The GC information must match what we setup using 'objClass' - if (layout->IsGCPtr(baseIndex + inx) || varTypeGCtype(type[inx])) - { - noway_assert(type[inx] == layout->GetGCPtrType(baseIndex + inx)); - } - } - } - else // this varDsc contains no GC pointers - { - for (unsigned inx = 0; inx < elemCount; inx++) - { - // The GC information must match what we setup using 'objClass' - noway_assert(!varTypeIsGC(type[inx])); - } - } + GenTreeLclVarCommon* lclNode = argValue->AsLclVarCommon(); + unsigned lclNum = lclNode->GetLclNum(); + LclVarDsc* varDsc = lvaGetDesc(lclNum); + unsigned lclOffset = lclNode->GetLclOffs(); - // - // We create a list of GT_LCL_FLDs nodes to pass this struct - // - lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DoNotEnregisterReason::LocalField)); - - // Create a new tree for 'arg' - // replace the existing LDOBJ(ADDR(LCLVAR)) - // with a FIELD_LIST(LCLFLD-LO, LCLFLD-HI) - // - unsigned offset = baseOffset; - newArg = new (this, GT_FIELD_LIST) GenTreeFieldList(); + newArg = new (this, GT_FIELD_LIST) GenTreeFieldList(); for (unsigned inx = 0; inx < elemCount; inx++) { - GenTree* nextLclFld = gtNewLclFldNode(varNum, type[inx], offset); - newArg->AddField(this, nextLclFld, offset, type[inx]); -#ifdef TARGET_LOONGARCH64 - if (structSize > TARGET_POINTER_SIZE) - { - // For LoongArch64's ABI, maybe there is a padding. - // e.g. `struct {float a; long b;}` - offset += TARGET_POINTER_SIZE; - } - else -#endif - { - offset += genTypeSize(type[inx]); - } + unsigned offset = lclOffset + elems[inx].Offset; +#ifdef DEBUG + // Make sure we've set up correct GC types above. + unsigned slotIdx = offset / TARGET_POINTER_SIZE; + var_types argGcType = varTypeIsGC(elems[inx].Type) ? elems[inx].Type : TYP_I_IMPL; + var_types lclGcType = varDsc->HasGCPtr() ? varDsc->GetLayout()->GetGCPtrType(slotIdx) : TYP_I_IMPL; + assert(argGcType == lclGcType); +#endif // DEBUG + + GenTree* lclFld = gtNewLclFldNode(lclNum, elems[inx].Type, offset); + newArg->AddField(this, lclFld, offset, lclFld->TypeGet()); } + + // Set DNER to block independent promotion. + lvaSetVarDoNotEnregister(lclNum DEBUGARG(DoNotEnregisterReason::LocalField)); } - // Are we passing a GT_OBJ struct? - // - else if (argValue->OperGet() == GT_OBJ) + else { + assert(argValue->OperIs(GT_OBJ)); + GenTreeObj* argObj = argValue->AsObj(); - GenTree* baseAddr = argObj->gtOp1; + GenTree* baseAddr = argObj->Addr(); var_types addrType = baseAddr->TypeGet(); - if (baseAddr->OperGet() == GT_ADDR) + // TODO-ADDR: make sure all such OBJs are transformed into TYP_STRUCT LCL_FLDs and delete this condition. + GenTreeLclVarCommon* lclSrcNode = baseAddr->IsLocalAddrExpr(); + if (lclSrcNode != nullptr) { - GenTree* addrTaken = baseAddr->AsOp()->gtOp1; - if (addrTaken->IsLocal()) - { - GenTreeLclVarCommon* varNode = addrTaken->AsLclVarCommon(); - unsigned varNum = varNode->GetLclNum(); - // We access non-struct type (for example, long) as a struct type. - // Make sure lclVar lives on stack to make sure its fields are accessible by address. - lvaSetVarDoNotEnregister(varNum DEBUGARG(DoNotEnregisterReason::LocalField)); - } + // Set DNER to block independent promotion. + lvaSetVarDoNotEnregister(lclSrcNode->GetLclNum() DEBUGARG(DoNotEnregisterReason::LocalField)); } - // Create a new tree for 'arg' - // replace the existing LDOBJ(EXPR) - // with a FIELD_LIST(IND(EXPR), FIELD_LIST(IND(EXPR+8), nullptr) ...) - // - - newArg = new (this, GT_FIELD_LIST) GenTreeFieldList(); - unsigned offset = 0; + newArg = new (this, GT_FIELD_LIST) GenTreeFieldList(); for (unsigned inx = 0; inx < elemCount; inx++) { - GenTree* curAddr = baseAddr; - if (offset != 0) + unsigned offset = elems[inx].Offset; + GenTree* curAddr = nullptr; + + if (offset == 0) { - GenTree* baseAddrDup = gtCloneExpr(baseAddr); - noway_assert(baseAddrDup != nullptr); - curAddr = gtNewOperNode(GT_ADD, addrType, baseAddrDup, gtNewIconNode(offset, TYP_I_IMPL)); + curAddr = baseAddr; } else { - curAddr = baseAddr; + assert((baseAddr->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) == 0); + + GenTree* baseAddrDup = gtCloneExpr(baseAddr); + GenTree* offsetNode = gtNewIconNode(offset, TYP_I_IMPL); + curAddr = gtNewOperNode(GT_ADD, addrType, baseAddrDup, offsetNode); } - GenTree* curItem = gtNewIndir(type[inx], curAddr); + + GenTree* argIndir = gtNewIndir(elems[inx].Type, curAddr); // For safety all GT_IND should have at least GT_GLOB_REF set. - curItem->gtFlags |= GTF_GLOB_REF; + argIndir->gtFlags |= GTF_GLOB_REF; - newArg->AddField(this, curItem, offset, type[inx]); -#ifdef TARGET_LOONGARCH64 - if (structSize > TARGET_POINTER_SIZE) - { - // For LoongArch64's ABI, maybe there is a padding. - // e.g. `struct {float a; long b;}` - offset += TARGET_POINTER_SIZE; - } - else -#endif - { - offset += genTypeSize(type[inx]); - } + newArg->AddField(this, argIndir, offset, argIndir->TypeGet()); } + + // Make sure we loaded exactly the required amount of bytes. + assert(structSize == (lastElem.Offset + genTypeSize(lastElem.Type))); } } -#ifdef DEBUG // If we reach here we should have set newArg to something - if (newArg == nullptr) - { - gtDispTree(argValue); - assert(!"Missing case in fgMorphMultiregStructArg"); - } -#endif - noway_assert(newArg != nullptr); -#ifdef DEBUG - if (verbose) - { - printf("fgMorphMultiregStructArg created tree:\n"); - gtDispTree(newArg); - } -#endif + JITDUMP("fgMorphMultiregStructArg created tree:\n"); + DISPTREE(newArg); argNode = newArg; // consider calling fgMorphTree(newArg); diff --git a/src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.cs b/src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.cs new file mode 100644 index 0000000000000..722e84acd5c24 --- /dev/null +++ b/src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.cs @@ -0,0 +1,84 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; + +public unsafe class TypeMismatchedArgs +{ + private static readonly HfaUnion s_hfaDblFlt = new HfaUnion { DblHfa = { FirstDblValue = 1.0, SecondDblValue = 2.0 } }; + private static readonly HfaDblLngUnion s_dblLngHfa = new HfaDblLngUnion { DblLng = { FirstLngValue = 10, SecondLngValue = 20 } }; + + public static int Main() + { + if (ProblemWithHfasMismatch(s_hfaDblFlt.FltHfa)) + { + return 101; + } + + if (ProblemWithHfaIntMismatch(s_dblLngHfa.DblLng)) + { + return 102; + } + + return 100; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool ProblemWithHfaIntMismatch(DblLngStruct dblLng) + { + var result = CallForHfaDblStruct(*(HfaDblStruct*)&dblLng); + + return result != s_dblLngHfa.DblHfa.FirstDblValue; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool ProblemWithHfasMismatch(HfaFltStruct fltHfa) + { + var result = CallForHfaDblStruct(*(HfaDblStruct*)&fltHfa); + + return result != s_hfaDblFlt.DblHfa.FirstDblValue; + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static double CallForHfaDblStruct(HfaDblStruct value) => value.FirstDblValue; +} + +[StructLayout(LayoutKind.Explicit)] +struct HfaDblLngUnion +{ + [FieldOffset(0)] + public HfaDblStruct DblHfa; + [FieldOffset(0)] + public DblLngStruct DblLng; +} + +[StructLayout(LayoutKind.Explicit)] +struct HfaUnion +{ + [FieldOffset(0)] + public HfaDblStruct DblHfa; + [FieldOffset(0)] + public HfaFltStruct FltHfa; +} + +struct DblLngStruct +{ + public long FirstLngValue; + public long SecondLngValue; +} + +struct HfaDblStruct +{ + public double FirstDblValue; + public double SecondDblValue; +} + +struct HfaFltStruct +{ + public float FirstFltValue; + public float SecondFltValue; + public float ThirdFltValue; + public float FourthFltValue; +} diff --git a/src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.csproj b/src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.csproj new file mode 100644 index 0000000000000..de62e29fa3e18 --- /dev/null +++ b/src/tests/JIT/Directed/StructABI/TypeMismatchedArgs.csproj @@ -0,0 +1,13 @@ + + + Exe + true + + + PdbOnly + True + + + + +