diff options
Diffstat (limited to 'src/jit/morph.cpp')
-rw-r--r-- | src/jit/morph.cpp | 232 |
1 files changed, 188 insertions, 44 deletions
diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 92d5e0967e..f63496b686 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -60,7 +60,8 @@ GenTreePtr Compiler::fgMorphCastIntoHelper(GenTreePtr tree, int helper, GenTreeP GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree, int helper, GenTreeArgList* args) { - tree->ChangeOper(GT_CALL); + // The helper call ought to be semantically equivalent to the original node, so preserve its VN. + tree->ChangeOper(GT_CALL, GenTree::PRESERVE_VN); tree->gtFlags |= GTF_CALL; if (args) @@ -3384,10 +3385,19 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) size = (unsigned)(roundUp(info.compCompHnd->getClassSize(argx->gtArgPlace.gtArgPlaceClsHnd), TARGET_POINTER_SIZE)) / TARGET_POINTER_SIZE; + if (isHfaArg) + { + hasMultiregStructArgs = true; + } + else if (size > 1 && size <= 4) + { + hasMultiregStructArgs = true; + } } else { // The typical case + // long/double type argument(s) will be changed to GT_FIELD_LIST in lower phase size = genTypeStSz(argx->gtType); } #elif defined(_TARGET_X86_) @@ -3399,7 +3409,8 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) #ifdef _TARGET_ARM_ else if (isHfaArg) { - size = GetHfaCount(argx); + size = GetHfaCount(argx); + hasMultiregStructArgs = true; } #endif // _TARGET_ARM_ else // struct type @@ -3759,14 +3770,25 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) } } -#ifndef _TARGET_X86_ - // TODO-Arm: Does this apply for _TARGET_ARM_, where structs passed by value can be split between - // registers and stack? +#ifdef _TARGET_64BIT_ if (size > 1) { hasMultiregStructArgs = true; } -#endif // !_TARGET_X86_ +#elif defined(_TARGET_ARM_) + // TODO-Arm: Need to handle the case + // where structs passed by value can be split between registers and stack. + if (size > 1 && size <= 4) + { + hasMultiregStructArgs = true; + } +#ifndef LEGACY_BACKEND + else if (size > 4 && passUsingIntRegs) + { + NYI_ARM("Struct can be split between registers and stack"); + } +#endif // !LEGACY_BACKEND +#endif // _TARGET_ARM_ } // The 'size' value has now must have been set. (the original value of zero is an invalid value) @@ -4058,6 +4080,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) #ifdef _TARGET_ARM_ if (fltArgRegNum > MAX_FLOAT_REG_ARG) { +#ifndef LEGACY_BACKEND + NYI_ARM("Struct split between float registers and stack"); +#endif // !LEGACY_BACKEND // This indicates a partial enregistration of a struct type assert(varTypeIsStruct(argx)); unsigned numRegsPartial = size - (fltArgRegNum - MAX_FLOAT_REG_ARG); @@ -4087,6 +4112,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) #ifdef _TARGET_ARM_ if (intArgRegNum > MAX_REG_ARG) { +#ifndef LEGACY_BACKEND + NYI_ARM("Struct split between integer registers and stack"); +#endif // !LEGACY_BACKEND // This indicates a partial enregistration of a struct type assert((isStructArg) || argx->OperIsCopyBlkOp() || (argx->gtOper == GT_COMMA && (args->gtFlags & GTF_ASG))); @@ -4145,7 +4173,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) // 'Lower' the MKREFANY tree and insert it. noway_assert(!reMorphing); -#ifdef _TARGET_X86_ +#ifndef _TARGET_64BIT_ // Build the mkrefany as a GT_FIELD_LIST GenTreeFieldList* fieldList = new (this, GT_FIELD_LIST) @@ -4156,7 +4184,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) fp->node = fieldList; args->gtOp.gtOp1 = fieldList; -#else // !_TARGET_X86_ +#else // _TARGET_64BIT_ // Get a new temp // Here we don't need unsafe value cls check since the addr of temp is used only in mkrefany @@ -4182,7 +4210,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) // EvalArgsToTemps will cause tmp to actually get loaded as the argument call->fgArgInfo->EvalToTmp(argIndex, tmp, asg); lvaSetVarAddrExposed(tmp); -#endif // !_TARGET_X86_ +#endif // _TARGET_64BIT_ } #endif // !LEGACY_BACKEND @@ -4221,7 +4249,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* call) } } } -#endif // defined (_TARGET_X86_) && !defined(LEGACY_BACKEND) +#endif // _TARGET_X86_ && !LEGACY_BACKEND #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING if (isStructArg && !isRegArg) @@ -4601,14 +4629,10 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) unsigned flagsSummary = 0; fgArgInfoPtr allArgInfo = call->fgArgInfo; - // Currently only ARM64 is using this method to morph the MultiReg struct args - // in the future AMD64_UNIX and for HFAs ARM32, will also use this method - // + // Currently ARM64/ARM is using this method to morph the MultiReg struct args + // in the future AMD64_UNIX will also use this method CLANG_FORMAT_COMMENT_ANCHOR; -#ifdef _TARGET_ARM_ - NYI_ARM("fgMorphMultiregStructArgs"); -#endif #ifdef _TARGET_X86_ assert(!"Logic error: no MultiregStructArgs for X86"); #endif @@ -4704,13 +4728,13 @@ void Compiler::fgMorphMultiregStructArgs(GenTreeCall* call) // this also forces the struct to be stack allocated into the local frame. // For the GT_OBJ case will clone the address expression and generate two (or more) // indirections. -// Currently the implementation only handles ARM64 and will NYI for other architectures. +// Currently the implementation handles ARM64/ARM and will NYI for other architectures. // GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr fgEntryPtr) { assert(arg->TypeGet() == TYP_STRUCT); -#ifndef _TARGET_ARM64_ +#ifndef _TARGET_ARMARCH_ NYI("fgMorphMultiregStructArg requires implementation for this target"); #endif @@ -4766,21 +4790,36 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f } else { +#ifdef _TARGET_ARM64_ assert(structSize <= 2 * TARGET_POINTER_SIZE); +#elif defined(_TARGET_ARM_) + assert(structSize <= 4 * TARGET_POINTER_SIZE); +#endif + +#ifdef _TARGET_ARM64_ BYTE gcPtrs[2] = {TYPE_GC_NONE, TYPE_GC_NONE}; info.compCompHnd->getClassGClayout(objClass, &gcPtrs[0]); elemCount = 2; type[0] = getJitGCType(gcPtrs[0]); type[1] = getJitGCType(gcPtrs[1]); +#elif defined(_TARGET_ARM_) + BYTE gcPtrs[4] = {TYPE_GC_NONE, TYPE_GC_NONE, TYPE_GC_NONE, TYPE_GC_NONE}; + elemCount = (unsigned)roundUp(structSize, TARGET_POINTER_SIZE) / TARGET_POINTER_SIZE; + info.compCompHnd->getClassGClayout(objClass, &gcPtrs[0]); + for (unsigned inx = 0; inx < elemCount; inx++) + { + type[inx] = getJitGCType(gcPtrs[inx]); + } +#endif // _TARGET_ARM_ if ((argValue->OperGet() == GT_LCL_FLD) || (argValue->OperGet() == GT_LCL_VAR)) { - // We can safely widen this to 16 bytes since we are loading from + 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. // - elemSize = TARGET_POINTER_SIZE; - structSize = 2 * TARGET_POINTER_SIZE; + structSize = elemCount * TARGET_POINTER_SIZE; } else // we must have a GT_OBJ { @@ -4788,21 +4827,25 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // We need to load the struct from an arbitrary address // and we can't read past the end of the structSize - // We adjust the second load type here + // We adjust the last load type here // - if (structSize < 2 * TARGET_POINTER_SIZE) + unsigned remainingBytes = structSize % TARGET_POINTER_SIZE; + unsigned lastElem = elemCount - 1; + if (remainingBytes != 0) { - switch (structSize - TARGET_POINTER_SIZE) + switch (remainingBytes) { case 1: - type[1] = TYP_BYTE; + type[lastElem] = TYP_BYTE; break; case 2: - type[1] = TYP_SHORT; + type[lastElem] = TYP_SHORT; break; +#ifdef _TARGET_ARM64_ case 4: - type[1] = TYP_INT; + type[lastElem] = TYP_INT; break; +#endif // _TARGET_ARM64_ default: noway_assert(!"NYI: odd sized struct in fgMorphMultiregStructArg"); break; @@ -4824,10 +4867,10 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f assert(varNum < lvaCount); LclVarDsc* varDsc = &lvaTable[varNum]; - // At this point any TYP_STRUCT LclVar must be a 16-byte struct + // 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() == 2 * TARGET_POINTER_SIZE) || varDsc->lvIsHfa()); + assert((varDsc->lvSize() == elemCount * TARGET_POINTER_SIZE) || varDsc->lvIsHfa()); varDsc->lvIsMultiRegArg = true; @@ -4855,8 +4898,12 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f } else { +#ifdef _TARGET_ARM64_ // We must have a 16-byte struct (non-HFA) noway_assert(elemCount == 2); +#elif defined(_TARGET_ARM_) + noway_assert(elemCount <= 4); +#endif for (unsigned inx = 0; inx < elemCount; inx++) { @@ -4878,6 +4925,7 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f } } +#ifdef _TARGET_ARM64_ // Is this LclVar a promoted struct with exactly 2 fields? // TODO-ARM64-CQ: Support struct promoted HFA types here if (varDsc->lvPromoted && (varDsc->lvFieldCnt == 2) && !varDsc->lvIsHfa()) @@ -4929,6 +4977,78 @@ GenTreePtr Compiler::fgMorphMultiregStructArg(GenTreePtr arg, fgArgTabEntryPtr f // lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DNER_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; + break; + } + } + + // Did we find the promoted fields at the necessary offsets? + if (!hasBadVarNum) + { + LclVarDsc* varDscs[4]; + var_types varType[4]; + bool varIsFloat = false; + + for (unsigned inx = 0; inx < elemCount; inx++) + { + varDscs[inx] = &lvaTable[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) + { + unsigned offset = 0; + GenTreeFieldList* listEntry = nullptr; + // We can use the struct promoted field as arguments + for (unsigned inx = 0; inx < elemCount; inx++) + { + GenTreePtr lclVar = gtNewLclvNode(varNums[inx], varType[inx], varNums[inx]); + // Create a new tree for 'arg' + // replace the existing LDOBJ(ADDR(LCLVAR)) + listEntry = new (this, GT_FIELD_LIST) GenTreeFieldList(lclVar, offset, varType[inx], listEntry); + if (newArg == nullptr) + { + newArg = listEntry; + } + offset += TARGET_POINTER_SIZE; + } + } + } + } + else + { + // + // We will create a list of GT_LCL_FLDs nodes to pass this struct + // + lvaSetVarDoNotEnregister(varNum DEBUG_ARG(DNER_LocalField)); + } +#endif // _TARGET_ARM_ } // If we didn't set newarg to a new List Node tree @@ -7862,7 +7982,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) // the call. GenTreeStmt* nextMorphStmt = fgMorphStmt->gtNextStmt; -#ifdef _TARGET_AMD64_ +#if !defined(FEATURE_CORECLR) && defined(_TARGET_AMD64_) // Legacy Jit64 Compat: // There could be any number of GT_NOPs between tail call and GT_RETURN. // That is tail call pattern could be one of the following: @@ -7929,7 +8049,7 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) fgRemoveStmt(compCurBB, morphStmtToRemove); } } -#endif // _TARGET_AMD64_ +#endif // !FEATURE_CORECLR && _TARGET_AMD64_ // Delete GT_RETURN if any if (nextMorphStmt != nullptr) @@ -11416,6 +11536,20 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) } } + // If gtOp1 is a GT_FIELD, we need to pass down the mac if + // its parent is GT_ADDR, since the address of the field + // is part of an ongoing address computation. Otherwise + // op1 represents the value of the field and so any address + // calculations it does are in a new context. + if ((op1->gtOper == GT_FIELD) && (tree->gtOper != GT_ADDR)) + { + subMac1 = nullptr; + + // The impact of this field's value to any ongoing + // address computation is handled below when looking + // at op2. + } + tree->gtOp.gtOp1 = op1 = fgMorphTree(op1, subMac1); #if LOCAL_ASSERTION_PROP @@ -11496,7 +11630,6 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) // (These are used to convey parent context about how addresses being calculated // will be used; see the specification comment for MorphAddrContext for full details.) // Assume it's an Ind context to start. - MorphAddrContext subIndMac2(MACK_Ind); switch (tree->gtOper) { case GT_ADD: @@ -11517,6 +11650,17 @@ GenTreePtr Compiler::fgMorphSmpOp(GenTreePtr tree, MorphAddrContext* mac) default: break; } + + // If gtOp2 is a GT_FIELD, we must be taking its value, + // so it should evaluate its address in a new context. + if (op2->gtOper == GT_FIELD) + { + // The impact of this field's value to any ongoing + // address computation is handled above when looking + // at op1. + mac = nullptr; + } + tree->gtOp.gtOp2 = op2 = fgMorphTree(op2, mac); /* Propagate the side effect flags from op2 */ @@ -13676,20 +13820,20 @@ GenTree* Compiler::fgMorphSmpOpOptional(GenTreeOp* tree) /* Make sure we have the operator range right */ - noway_assert(GT_SUB == GT_ADD + 1); - noway_assert(GT_MUL == GT_ADD + 2); - noway_assert(GT_DIV == GT_ADD + 3); - noway_assert(GT_MOD == GT_ADD + 4); - noway_assert(GT_UDIV == GT_ADD + 5); - noway_assert(GT_UMOD == GT_ADD + 6); + static_assert(GT_SUB == GT_ADD + 1, "bad oper value"); + static_assert(GT_MUL == GT_ADD + 2, "bad oper value"); + static_assert(GT_DIV == GT_ADD + 3, "bad oper value"); + static_assert(GT_MOD == GT_ADD + 4, "bad oper value"); + static_assert(GT_UDIV == GT_ADD + 5, "bad oper value"); + static_assert(GT_UMOD == GT_ADD + 6, "bad oper value"); - noway_assert(GT_OR == GT_ADD + 7); - noway_assert(GT_XOR == GT_ADD + 8); - noway_assert(GT_AND == GT_ADD + 9); + static_assert(GT_OR == GT_ADD + 7, "bad oper value"); + static_assert(GT_XOR == GT_ADD + 8, "bad oper value"); + static_assert(GT_AND == GT_ADD + 9, "bad oper value"); - noway_assert(GT_LSH == GT_ADD + 10); - noway_assert(GT_RSH == GT_ADD + 11); - noway_assert(GT_RSZ == GT_ADD + 12); + static_assert(GT_LSH == GT_ADD + 10, "bad oper value"); + static_assert(GT_RSH == GT_ADD + 11, "bad oper value"); + static_assert(GT_RSZ == GT_ADD + 12, "bad oper value"); /* Check for a suitable operator on the RHS */ |