diff options
Diffstat (limited to 'src/jit/morph.cpp')
-rwxr-xr-x | src/jit/morph.cpp | 217 |
1 files changed, 159 insertions, 58 deletions
diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index f5c8d083ab..c8835ead82 100755 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -89,7 +89,7 @@ GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree, GenTreeCall* callNode = tree->AsCall(); ReturnTypeDesc* retTypeDesc = callNode->GetReturnTypeDesc(); retTypeDesc->Reset(); - retTypeDesc->InitializeReturnType(this, callNode->gtRetClsHnd); + retTypeDesc->InitializeLongReturnType(this); callNode->ClearOtherRegs(); NYI("Helper with TYP_LONG return type"); @@ -1508,11 +1508,11 @@ void fgArgInfo::ArgsComplete() // so we skip this for ARM32 until it is ported to use RyuJIT backend // #if FEATURE_MULTIREG_ARGS - if ((argx->TypeGet() == TYP_STRUCT) && - (curArgTabEntry->numRegs > 1) && - (curArgTabEntry->needTmp == false)) + bool isMultiRegArg = (curArgTabEntry->numRegs > 1); + + if ((argx->TypeGet() == TYP_STRUCT) && (curArgTabEntry->needTmp == false)) { - if ((argx->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0) + if (isMultiRegArg && ((argx->gtFlags & GTF_PERSISTENT_SIDE_EFFECTS) != 0)) { // Spill multireg struct arguments that have Assignments or Calls embedded in them curArgTabEntry->needTmp = true; @@ -1522,7 +1522,7 @@ void fgArgInfo::ArgsComplete() // We call gtPrepareCost to measure the cost of evaluating this tree compiler->gtPrepareCost(argx); - if (argx->gtCostEx > (6 * IND_COST_EX)) + if (isMultiRegArg && (argx->gtCostEx > (6 * IND_COST_EX))) { // Spill multireg struct arguments that are expensive to evaluate twice curArgTabEntry->needTmp = true; @@ -1534,6 +1534,21 @@ void fgArgInfo::ArgsComplete() unsigned structSize = compiler->info.compCompHnd->getClassSize(objClass); switch (structSize) { + case 3: + case 5: + case 6: + case 7: + // If we have a stack based LclVar we can perform a wider read of 4 or 8 bytes + // + if (argObj->gtObj.gtOp1->IsVarAddr() == false) // Is the source not a LclVar? + { + // If we don't have a LclVar we need to read exactly 3,5,6 or 7 bytes + // For now we use a a GT_CPBLK to copy the exact size into a GT_LCL_VAR temp. + // + curArgTabEntry->needTmp = true; + } + break; + case 11: case 13: case 14: @@ -2032,7 +2047,7 @@ GenTreePtr Compiler::fgMakeTmpArgNode(unsigned tmpVarNum #if FEATURE_MULTIREG_ARGS #ifdef _TARGET_ARM64_ assert(varTypeIsStruct(type)); - if (varDsc->lvIsMultiregStruct()) + if (lvaIsMultiregStruct(varDsc)) { // ToDo-ARM64: Consider using: arg->ChangeOper(GT_LCL_FLD); // as that is how FEATURE_UNIX_AMD64_STRUCT_PASSING works. @@ -2191,22 +2206,45 @@ void fgArgInfo::EvalArgsToTemps() { setupArg = compiler->gtNewTempAssign(tmpVarNum, argx); + LclVarDsc* varDsc = compiler->lvaTable + tmpVarNum; + #ifndef LEGACY_BACKEND if (compiler->fgOrder == Compiler::FGOrderLinear) { // We'll reference this temporary variable just once // when we perform the function call after // setting up this argument. - LclVarDsc* varDsc = compiler->lvaTable + tmpVarNum; varDsc->lvRefCnt = 1; } #endif // !LEGACY_BACKEND + var_types lclVarType = genActualType(argx->gtType); + var_types scalarType = TYP_UNKNOWN; + if (setupArg->OperIsCopyBlkOp()) + { setupArg = compiler->fgMorphCopyBlock(setupArg); +#ifdef _TARGET_ARM64_ + // This scalar LclVar widening step is only performed for ARM64 + // + CORINFO_CLASS_HANDLE clsHnd = compiler->lvaGetStruct(tmpVarNum); + unsigned structSize = varDsc->lvExactSize; - /* Create a copy of the temp to go to the late argument list */ - defArg = compiler->gtNewLclvNode(tmpVarNum, genActualType(argx->gtType)); + scalarType = compiler->getPrimitiveTypeForStruct(structSize, clsHnd); +#endif // _TARGET_ARM64_ + } + + // scalarType can be set to a wider type for ARM64: (3 => 4) or (5,6,7 => 8) + if ((scalarType != TYP_UNKNOWN) && (scalarType != lclVarType)) + { + // Create a GT_LCL_FLD using the wider type to go to the late argument list + defArg = compiler->gtNewLclFldNode(tmpVarNum, scalarType, 0); + } + else + { + // Create a copy of the temp to go to the late argument list + defArg = compiler->gtNewLclvNode(tmpVarNum, lclVarType); + } curArgTabEntry->isTmp = true; curArgTabEntry->tmpNum = tmpVarNum; @@ -2303,7 +2341,7 @@ void fgArgInfo::EvalArgsToTemps() { BADCODE("Unhandled struct argument tree in fgMorphArgs"); } - } + } #endif // !(defined(_TARGET_AMD64_) && !defined(FEATURE_UNIX_AMD64_STRUCT_PASSING)) @@ -2564,6 +2602,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) regMaskTP argSkippedRegMask = RBM_NONE; #if defined(_TARGET_ARM_) || defined(_TARGET_AMD64_) + // Note this in ONLY used by _TARGET_ARM_ regMaskTP fltArgSkippedRegMask = RBM_NONE; #endif @@ -2861,7 +2900,10 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) call->fgArgInfo = new (this, CMK_Unknown) fgArgInfo(this, call, numArgs); } - fgFixupStructReturn(call); + if (varTypeIsStruct(call)) + { + fgFixupStructReturn(call); + } /* First we morph the argument subtrees ('this' pointer, arguments, etc.). * During the first call to fgMorphArgs we also record the @@ -3323,7 +3365,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) } else // We must have a GT_OBJ with a struct type, but the GT_OBJ may be be a child of a GT_COMMA { - GenTreePtr argObj = argx; + GenTreePtr argObj = argx; GenTreePtr* parentOfArgObj = parentArgx; assert(args->IsList()); @@ -3333,7 +3375,7 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) while (argObj->gtOper == GT_COMMA) { parentOfArgObj = &argObj->gtOp.gtOp2; - argObj = argObj->gtOp.gtOp2; + argObj = argObj->gtOp.gtOp2; } if (argObj->gtOper != GT_OBJ) @@ -3346,10 +3388,26 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) unsigned originalSize = info.compCompHnd->getClassSize(objClass); originalSize = (originalSize == 0 ? TARGET_POINTER_SIZE : originalSize); - unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE); + unsigned roundupSize = (unsigned)roundUp(originalSize, TARGET_POINTER_SIZE); structSize = originalSize; + structPassingKind howToPassStruct; + structBaseType = getArgTypeForStruct(objClass, &howToPassStruct, originalSize); + +#ifdef _TARGET_ARM64_ + if ((howToPassStruct == SPK_PrimitiveType) && // Passed in a single register + !isPow2(originalSize)) // size is 3,5,6 or 7 bytes + { + if (argObj->gtObj.gtOp1->IsVarAddr()) // Is the source a LclVar? + { + // For ARM64 we pass structs that are 3,5,6,7 bytes in size + // we can read 4 or 8 bytes from the LclVar to pass this arg + originalSize = genTypeSize(structBaseType); + } + } +#endif // _TARGET_ARM64_ + #ifdef FEATURE_UNIX_AMD64_STRUCT_PASSING // On System V OS-es a struct is never passed by reference. // It is either passed by value on the stack or in registers. @@ -3371,7 +3429,12 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) #ifndef FEATURE_UNIX_AMD64_STRUCT_PASSING // Check for struct argument with size 1, 2, 4 or 8 bytes // As we can optimize these by turning them into a GT_IND of the correct type - if ((originalSize > TARGET_POINTER_SIZE) || ((originalSize & (originalSize - 1)) != 0) || (isHfaArg && (hfaSlots != 1))) + // + // Check for cases that we cannot optimize: + // + if ((originalSize > TARGET_POINTER_SIZE) || // it is struct that is larger than a pointer + !isPow2(originalSize) || // it is not a power of two (1, 2, 4 or 8) + (isHfaArg && (hfaSlots != 1))) // it is a one element HFA struct #endif // FEATURE_UNIX_AMD64_STRUCT_PASSING { // Normalize 'size' to the number of pointer sized items @@ -3479,8 +3542,6 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // change our GT_OBJ into a GT_IND of the correct type. // We've already ensured above that size is a power of 2, and less than or equal to pointer size. - structPassingKind howToPassStruct; - structBaseType = getArgTypeForStruct(objClass, &howToPassStruct, originalSize); assert(howToPassStruct == SPK_PrimitiveType); // ToDo: remove this block as getArgTypeForStruct properly handles turning one element HFAs into primitives @@ -3615,19 +3676,9 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // There are a few special cases where we can omit using a CopyBlk // where we normally would need to use one. - GenTreePtr objAddr = argObj->gtObj.gtOp1; - if (objAddr->gtOper == GT_ADDR) + if (argObj->gtObj.gtOp1->IsVarAddr()) // Is the source a LclVar? { - // exception : no need to use CopyBlk if the valuetype is on the stack - if (objAddr->gtFlags & GTF_ADDR_ONSTACK) - { - copyBlkClass = NO_CLASS_HANDLE; - } - // exception : no need to use CopyBlk if the valuetype is already a struct local - else if (objAddr->gtOp.gtOp1->gtOper == GT_LCL_VAR) - { - copyBlkClass = NO_CLASS_HANDLE; - } + copyBlkClass = NO_CLASS_HANDLE; } } @@ -3701,12 +3752,30 @@ GenTreeCall* Compiler::fgMorphArgs(GenTreeCall* callNode) // unsigned roundupSize = (unsigned)roundUp(structSize, TARGET_POINTER_SIZE); size = roundupSize / TARGET_POINTER_SIZE; + + // We also must update fltArgRegNum so that we no longer try to + // allocate any new floating point registers for args + // This prevents us from backfilling a subsequent arg into d7 + // + fltArgRegNum = MAX_FLOAT_REG_ARG; } } else { // Check if the last register needed is still in the int argument register range. isRegArg = (intArgRegNum + (size - 1)) < maxRegArgs; + + // Did we run out of registers when we had a 16-byte struct (size===2) ? + // (i.e we only have one register remaining but we needed two registers to pass this arg) + // This prevents us from backfilling a subsequent arg into x7 + // + if (!isRegArg && (size > 1)) + { + // We also must update intArgRegNum so that we no longer try to + // allocate any new general purpose registers for args + // + intArgRegNum = maxRegArgs; + } } #else // not _TARGET_ARM_ or _TARGET_ARM64_ @@ -5043,38 +5112,68 @@ void Compiler::fgAddSkippedRegsInPromotedStructArg(LclVarDsc* var #endif // _TARGET_ARM_ -/***************************************************************************** - * - * The companion to impFixupCallStructReturn. Now that the importer is done - * and we no longer care as much about the declared return type, change to - * precomputed native return type (at least for architectures that don't - * always use return buffers for structs). - * - */ +//**************************************************************************** +// fgFixupStructReturn: +// The companion to impFixupCallStructReturn. Now that the importer is done +// change the gtType to the precomputed native return type +// requires that callNode currently has a struct type +// void Compiler::fgFixupStructReturn(GenTreePtr callNode) { + assert(varTypeIsStruct(callNode)); + GenTreeCall* call = callNode->AsCall(); bool callHasRetBuffArg = call->HasRetBufArg(); + bool isHelperCall = call->IsHelperCall(); + + // Decide on the proper return type for this call that currently returns a struct + // + CORINFO_CLASS_HANDLE retClsHnd = call->gtRetClsHnd; + Compiler::structPassingKind howToReturnStruct; + var_types returnType; - if (!callHasRetBuffArg && varTypeIsStruct(call)) + // There are a couple of Helper Calls that say they return a TYP_STRUCT but they + // expect this method to re-type this to a TYP_REF (what is in call->gtReturnType) + // + // CORINFO_HELP_METHODDESC_TO_STUBRUNTIMEMETHOD + // CORINFO_HELP_FIELDDESC_TO_STUBRUNTIMEFIELD + // CORINFO_HELP_TYPEHANDLE_TO_RUNTIMETYPE_MAYBENULL + // + if (isHelperCall) { -#if FEATURE_MULTIREG_RET - if (call->gtCall.IsVarargs() || !IsHfa(call)) -#endif // FEATURE_MULTIREG_RET + assert(!callHasRetBuffArg); + assert(retClsHnd == NO_CLASS_HANDLE); + + // Now that we are past the importer, re-type this node + howToReturnStruct = SPK_PrimitiveType; + returnType = (var_types)call->gtReturnType; + } + else + { + returnType = getReturnTypeForStruct(retClsHnd, &howToReturnStruct); + } + + if (howToReturnStruct == SPK_ByReference) + { + assert(returnType == TYP_UNKNOWN); + assert(callHasRetBuffArg); + } + else + { + assert(returnType != TYP_UNKNOWN); + + if (returnType != TYP_STRUCT) { - // Now that we are past the importer, re-type this node so the register predictor does - // the right thing - call->gtType = genActualType((var_types)call->gtCall.gtReturnType); + // Widen the primitive type if necessary + returnType = genActualType(returnType); } + call->gtType = returnType; } -#ifdef FEATURE_HFA - // Either we don't have a struct now or if struct, then it is HFA returned in regs. - assert(!varTypeIsStruct(call) || (IsHfa(call) && !callHasRetBuffArg)); -#elif defined(FEATURE_UNIX_AMD64_STRUCT_PASSING) +#if FEATURE_MULTIREG_RET // Either we don't have a struct now or if struct, then it is a struct returned in regs or in return buffer. assert(!varTypeIsStruct(call) || call->HasMultiRegRetVal() || callHasRetBuffArg); -#else +#else // !FEATURE_MULTIREG_RET // No more struct returns assert(call->TypeGet() != TYP_STRUCT); #endif @@ -7309,7 +7408,10 @@ GenTreePtr Compiler::fgMorphCall(GenTreeCall* call) } #endif // FEATURE_TAILCALL_OPT - fgFixupStructReturn(call); + if (varTypeIsStruct(call)) + { + fgFixupStructReturn(call); + } var_types callType = call->TypeGet(); @@ -16002,11 +16104,11 @@ void Compiler::fgPromoteStructs() #if !FEATURE_MULTIREG_STRUCT_PROMOTE #if defined(_TARGET_ARM64_) // - // For now we currently don't promote structs that could be passed in registers + // For now we currently don't promote structs that are passed in registers // - else if (varDsc->lvIsMultiregStruct()) + else if (lvaIsMultiregStruct(varDsc)) { - JITDUMP("Not promoting promotable struct local V%02u (size==%d): ", + JITDUMP("Not promoting promotable multireg struct local V%02u (size==%d): ", lclNum, lvaLclExactSize(lclNum)); shouldPromote = false; } @@ -16015,8 +16117,8 @@ void Compiler::fgPromoteStructs() else if (varDsc->lvIsParam) { #if FEATURE_MULTIREG_STRUCT_PROMOTE - if (varDsc->lvIsMultiregStruct() && // Is this a variable holding a value that is passed in multiple registers? - (structPromotionInfo.fieldCnt != 2)) // Does it have exactly two fields + if (lvaIsMultiregStruct(varDsc) && // Is this a variable holding a value that is passed in multiple registers? + (structPromotionInfo.fieldCnt != 2)) // Does it have exactly two fields { JITDUMP("Not promoting multireg struct local V%02u, because lvIsParam is true and #fields != 2\n", lclNum); @@ -16316,8 +16418,7 @@ void Compiler::fgMarkImplicitByRefArgs() #if defined(_TARGET_AMD64_) if (size > REGSIZE_BYTES || (size & (size - 1)) != 0) #elif defined(_TARGET_ARM64_) - if ((size > TARGET_POINTER_SIZE) && !varDsc->lvIsMultiregStruct()) - + if ((size > TARGET_POINTER_SIZE) && !lvaIsMultiregStruct(varDsc)) #endif { // Previously nobody was ever setting lvIsParam and lvIsTemp on the same local |