From: Bruce Forstall Date: Tue, 11 Apr 2017 23:36:18 +0000 (-0700) Subject: Merge similar arm32/arm64 codegen functions X-Git-Tag: submit/tizen/20210909.063632~11030^2~7292^2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=9857ae799ecdca9ac132b5ab27700247e96a5ced;p=platform%2Fupstream%2Fdotnet%2Fruntime.git Merge similar arm32/arm64 codegen functions Commit migrated from https://github.com/dotnet/coreclr/commit/8ce3737178f65ef5096ad9728f23cd451e89ac72 --- diff --git a/src/coreclr/src/jit/codegenarmarch.cpp b/src/coreclr/src/jit/codegenarmarch.cpp index 1b3a520..8c06e5e 100644 --- a/src/coreclr/src/jit/codegenarmarch.cpp +++ b/src/coreclr/src/jit/codegenarmarch.cpp @@ -24,8 +24,6 @@ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX #include "gcinfo.h" #include "emit.h" -#ifdef _TARGET_ARM_ - //------------------------------------------------------------------------ // genSetRegToIcon: Generate code that will set the given register to the integer constant. // @@ -42,32 +40,6 @@ void CodeGen::genSetRegToIcon(regNumber reg, ssize_t val, var_types type, insFla instGen_Set_Reg_To_Imm(emitActualTypeSize(type), reg, val, flags); } -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -/***************************************************************************** - * - * Generate code that will set the given register to the integer constant. - */ - -void CodeGen::genSetRegToIcon(regNumber reg, ssize_t val, var_types type, insFlags flags) -{ - // Reg cannot be a FP reg - assert(!genIsValidFloatReg(reg)); - - // The only TYP_REF constant that can come this path is a managed 'null' since it is not - // relocatable. Other ref type constants (e.g. string objects) go through a different - // code path. - noway_assert(type != TYP_REF || val == 0); - - instGen_Set_Reg_To_Imm(emitActualTypeSize(type), reg, val, flags); -} - -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - //--------------------------------------------------------------------- // genIntrinsic - generate code for a given intrinsic // @@ -90,63 +62,18 @@ void CodeGen::genIntrinsic(GenTreePtr treeNode) { case CORINFO_INTRINSIC_Abs: genConsumeOperands(treeNode->AsOp()); - getEmitter()->emitInsBinary(INS_vabs, emitTypeSize(treeNode), treeNode, srcNode); + getEmitter()->emitInsBinary(INS_ABS, emitTypeSize(treeNode), treeNode, srcNode); break; case CORINFO_INTRINSIC_Round: NYI_ARM("genIntrinsic for round - not implemented yet"); - break; - - case CORINFO_INTRINSIC_Sqrt: - genConsumeOperands(treeNode->AsOp()); - getEmitter()->emitInsBinary(INS_vsqrt, emitTypeSize(treeNode), treeNode, srcNode); - break; - - default: - assert(!"genIntrinsic: Unsupported intrinsic"); - unreached(); - } - - genProduceReg(treeNode); -} - -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -//--------------------------------------------------------------------- -// genIntrinsic - generate code for a given intrinsic -// -// Arguments -// treeNode - the GT_INTRINSIC node -// -// Return value: -// None -// -void CodeGen::genIntrinsic(GenTreePtr treeNode) -{ - // Both operand and its result must be of the same floating point type. - GenTreePtr srcNode = treeNode->gtOp.gtOp1; - assert(varTypeIsFloating(srcNode)); - assert(srcNode->TypeGet() == treeNode->TypeGet()); - - // Right now only Abs/Round/Sqrt are treated as math intrinsics. - // - switch (treeNode->gtIntrinsic.gtIntrinsicId) - { - case CORINFO_INTRINSIC_Abs: - genConsumeOperands(treeNode->AsOp()); - getEmitter()->emitInsBinary(INS_fabs, emitTypeSize(treeNode), treeNode, srcNode); - break; - - case CORINFO_INTRINSIC_Round: genConsumeOperands(treeNode->AsOp()); - getEmitter()->emitInsBinary(INS_frintn, emitTypeSize(treeNode), treeNode, srcNode); + getEmitter()->emitInsBinary(INS_ROUND, emitTypeSize(treeNode), treeNode, srcNode); break; case CORINFO_INTRINSIC_Sqrt: genConsumeOperands(treeNode->AsOp()); - getEmitter()->emitInsBinary(INS_fsqrt, emitTypeSize(treeNode), treeNode, srcNode); + getEmitter()->emitInsBinary(INS_SQRT, emitTypeSize(treeNode), treeNode, srcNode); break; default: @@ -157,106 +84,6 @@ void CodeGen::genIntrinsic(GenTreePtr treeNode) genProduceReg(treeNode); } -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - -//--------------------------------------------------------------------- -// genPutArgStk - generate code for a GT_PUTARG_STK node -// -// Arguments -// treeNode - the GT_PUTARG_STK node -// -// Return value: -// None -// -void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) -{ - assert(treeNode->OperGet() == GT_PUTARG_STK); - var_types targetType = treeNode->TypeGet(); - GenTreePtr source = treeNode->gtOp1; - emitter* emit = getEmitter(); - - // This is the varNum for our store operations, - // typically this is the varNum for the Outgoing arg space - // When we are generating a tail call it will be the varNum for arg0 - unsigned varNumOut; - unsigned argOffsetMax; // Records the maximum size of this area for assert checks - - // Get argument offset to use with 'varNumOut' - // Here we cross check that argument offset hasn't changed from lowering to codegen since - // we are storing arg slot number in GT_PUTARG_STK node in lowering phase. - unsigned argOffsetOut = treeNode->gtSlotNum * TARGET_POINTER_SIZE; - -#ifdef DEBUG - fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(treeNode->gtCall, treeNode); - assert(curArgTabEntry); - assert(argOffsetOut == (curArgTabEntry->slotNum * TARGET_POINTER_SIZE)); -#endif // DEBUG - - varNumOut = compiler->lvaOutgoingArgSpaceVar; - argOffsetMax = compiler->lvaOutgoingArgSpaceSize; - - bool isStruct = (targetType == TYP_STRUCT) || (source->OperGet() == GT_FIELD_LIST); - - if (!isStruct) // a normal non-Struct argument - { - instruction storeIns = ins_Store(targetType); - emitAttr storeAttr = emitTypeSize(targetType); - - // If it is contained then source must be the integer constant zero - if (source->isContained()) - { - assert(source->OperGet() == GT_CNS_INT); - assert(source->AsIntConCommon()->IconValue() == 0); - NYI("genPutArgStk: contained zero source"); - } - else - { - genConsumeReg(source); - emit->emitIns_S_R(storeIns, storeAttr, source->gtRegNum, varNumOut, argOffsetOut); - } - argOffsetOut += EA_SIZE_IN_BYTES(storeAttr); - assert(argOffsetOut <= argOffsetMax); // We can't write beyound the outgoing area area - } - else // We have some kind of a struct argument - { - assert(source->isContained()); // We expect that this node was marked as contained in LowerArm - - if (source->OperGet() == GT_FIELD_LIST) - { - // Deal with the multi register passed struct args. - GenTreeFieldList* fieldListPtr = source->AsFieldList(); - - // Evaluate each of the GT_FIELD_LIST items into their register - // and store their register into the outgoing argument area - for (; fieldListPtr != nullptr; fieldListPtr = fieldListPtr->Rest()) - { - GenTreePtr nextArgNode = fieldListPtr->gtOp.gtOp1; - genConsumeReg(nextArgNode); - - regNumber reg = nextArgNode->gtRegNum; - var_types type = nextArgNode->TypeGet(); - emitAttr attr = emitTypeSize(type); - - // Emit store instructions to store the registers produced by the GT_FIELD_LIST into the outgoing - // argument area - emit->emitIns_S_R(ins_Store(type), attr, reg, varNumOut, argOffsetOut); - argOffsetOut += EA_SIZE_IN_BYTES(attr); - assert(argOffsetOut <= argOffsetMax); // We can't write beyound the outgoing area area - } - } - else // We must have a GT_OBJ or a GT_LCL_VAR - { - NYI("genPutArgStk: GT_OBJ or GT_LCL_VAR source of struct type"); - } - } -} - -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - //--------------------------------------------------------------------- // genPutArgStk - generate code for a GT_PUTARG_STK node // @@ -279,10 +106,6 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) unsigned varNumOut; unsigned argOffsetMax; // Records the maximum size of this area for assert checks - // This is the varNum for our load operations, - // only used when we have a multireg struct with a LclVar source - unsigned varNumInp = BAD_VAR_NUM; - // Get argument offset to use with 'varNumOut' // Here we cross check that argument offset hasn't changed from lowering to codegen since // we are storing arg slot number in GT_PUTARG_STK node in lowering phase. @@ -299,6 +122,9 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) // All other calls - stk arg is setup in out-going arg area. if (treeNode->putInIncomingArgArea()) { + NYI_ARM("genPutArgStk: fast tail call"); + +#ifdef _TARGET_ARM64_ varNumOut = getFirstArgWithStackSlot(); argOffsetMax = compiler->compArgSize; #if FEATURE_FASTTAILCALL @@ -311,12 +137,14 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) LclVarDsc* varDsc = &(compiler->lvaTable[varNumOut]); assert(varDsc != nullptr); #endif // FEATURE_FASTTAILCALL +#endif // _TARGET_ARM64_ } else { varNumOut = compiler->lvaOutgoingArgSpaceVar; argOffsetMax = compiler->lvaOutgoingArgSpaceSize; } + bool isStruct = (targetType == TYP_STRUCT) || (source->OperGet() == GT_FIELD_LIST); if (!isStruct) // a normal non-Struct argument @@ -329,7 +157,11 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) { assert(source->OperGet() == GT_CNS_INT); assert(source->AsIntConCommon()->IconValue() == 0); + NYI_ARM("genPutArgStk: contained zero source"); + +#ifdef _TARGET_ARM64_ emit->emitIns_S_R(storeIns, storeAttr, REG_ZR, varNumOut, argOffsetOut); +#endif // _TARGET_ARM64_ } else { @@ -341,7 +173,7 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) } else // We have some kind of a struct argument { - assert(source->isContained()); // We expect that this node was marked as contained in LowerArm64 + assert(source->isContained()); // We expect that this node was marked as contained in Lower if (source->OperGet() == GT_FIELD_LIST) { @@ -370,6 +202,10 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) { noway_assert((source->OperGet() == GT_LCL_VAR) || (source->OperGet() == GT_OBJ)); + NYI_ARM("genPutArgStk: GT_OBJ or GT_LCL_VAR source of struct type"); + +#ifdef _TARGET_ARM64_ + var_types targetType = source->TypeGet(); noway_assert(varTypeIsStruct(targetType)); @@ -421,6 +257,10 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) int structSize; bool isHfa; + // This is the varNum for our load operations, + // only used when we have a multireg struct with a LclVar source + unsigned varNumInp = BAD_VAR_NUM; + // Setup the structSize, isHFa, and gcPtrCount if (varNode != nullptr) { @@ -683,81 +523,12 @@ void CodeGen::genPutArgStk(GenTreePutArgStk* treeNode) argOffsetOut += EA_SIZE_IN_BYTES(nextAttr); assert(argOffsetOut <= argOffsetMax); // We can't write beyound the outgoing area area } - } - } -} #endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - -//---------------------------------------------------------------------------------- -// genMultiRegCallStoreToLocal: store multi-reg return value of a call node to a local -// -// Arguments: -// treeNode - Gentree of GT_STORE_LCL_VAR -// -// Return Value: -// None -// -// Assumption: -// The child of store is a multi-reg call node. -// genProduceReg() on treeNode is made by caller of this routine. -// -void CodeGen::genMultiRegCallStoreToLocal(GenTreePtr treeNode) -{ - assert(treeNode->OperGet() == GT_STORE_LCL_VAR); - - // Longs are returned in two return registers on Arm32. - assert(varTypeIsLong(treeNode)); - - // Assumption: current Arm32 implementation requires that a multi-reg long - // var in 'var = call' is flagged as lvIsMultiRegRet to prevent it from - // being promoted. - unsigned lclNum = treeNode->AsLclVarCommon()->gtLclNum; - LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]); - noway_assert(varDsc->lvIsMultiRegRet); - - GenTree* op1 = treeNode->gtGetOp1(); - GenTree* actualOp1 = op1->gtSkipReloadOrCopy(); - GenTreeCall* call = actualOp1->AsCall(); - assert(call->HasMultiRegRetVal()); - - genConsumeRegs(op1); - - ReturnTypeDesc* retTypeDesc = call->GetReturnTypeDesc(); - unsigned regCount = retTypeDesc->GetReturnRegCount(); - assert(regCount <= MAX_RET_REG_COUNT); - - // Stack store - int offset = 0; - for (unsigned i = 0; i < regCount; ++i) - { - var_types type = retTypeDesc->GetReturnRegType(i); - regNumber reg = call->GetRegNumByIdx(i); - if (op1->IsCopyOrReload()) - { - // GT_COPY/GT_RELOAD will have valid reg for those positions - // that need to be copied or reloaded. - regNumber reloadReg = op1->AsCopyOrReload()->GetRegNumByIdx(i); - if (reloadReg != REG_NA) - { - reg = reloadReg; - } } - - assert(reg != REG_NA); - getEmitter()->emitIns_S_R(ins_Store(type), emitTypeSize(type), reg, lclNum, offset); - offset += genTypeSize(type); } - - varDsc->lvRegNum = REG_STK; } -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - //---------------------------------------------------------------------------------- // genMultiRegCallStoreToLocal: store multi-reg return value of a call node to a local // @@ -775,12 +546,17 @@ void CodeGen::genMultiRegCallStoreToLocal(GenTreePtr treeNode) { assert(treeNode->OperGet() == GT_STORE_LCL_VAR); +#if defined(_TARGET_ARM_) + // Longs are returned in two return registers on Arm32. + assert(varTypeIsLong(treeNode)); +#elif defined(_TARGET_ARM64_) // Structs of size >=9 and <=16 are returned in two return registers on ARM64 and HFAs. assert(varTypeIsStruct(treeNode)); +#endif // _TARGET_* - // Assumption: current ARM64 implementation requires that a multi-reg struct + // Assumption: current implementation requires that a multi-reg // var in 'var = call' is flagged as lvIsMultiRegRet to prevent it from - // being struct promoted. + // being promoted. unsigned lclNum = treeNode->AsLclVarCommon()->gtLclNum; LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]); noway_assert(varDsc->lvIsMultiRegRet); @@ -797,7 +573,7 @@ void CodeGen::genMultiRegCallStoreToLocal(GenTreePtr treeNode) if (treeNode->gtRegNum != REG_NA) { - // Right now the only enregistrable structs supported are SIMD types. + // Right now the only enregistrable multi-reg return types supported are SIMD types. assert(varTypeIsSIMD(treeNode)); NYI("GT_STORE_LCL_VAR of a SIMD enregisterable struct"); } @@ -829,109 +605,50 @@ void CodeGen::genMultiRegCallStoreToLocal(GenTreePtr treeNode) } } -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - //------------------------------------------------------------------------ // genRangeCheck: generate code for GT_ARR_BOUNDS_CHECK node. // void CodeGen::genRangeCheck(GenTreePtr oper) { +#ifdef FEATURE_SIMD + noway_assert(oper->OperGet() == GT_ARR_BOUNDS_CHECK || oper->OperGet() == GT_SIMD_CHK); +#else // !FEATURE_SIMD noway_assert(oper->OperGet() == GT_ARR_BOUNDS_CHECK); +#endif // !FEATURE_SIMD + GenTreeBoundsChk* bndsChk = oper->AsBoundsChk(); - GenTreePtr arrIdx = bndsChk->gtIndex->gtEffectiveVal(); - GenTreePtr arrLen = bndsChk->gtArrLen->gtEffectiveVal(); + GenTreePtr arrLen = bndsChk->gtArrLen; + GenTreePtr arrIndex = bndsChk->gtIndex; GenTreePtr arrRef = NULL; int lenOffset = 0; - genConsumeIfReg(arrIdx); - genConsumeIfReg(arrLen); - - GenTree * src1, *src2; + GenTree* src1; + GenTree* src2; emitJumpKind jmpKind; - if (arrIdx->isContainedIntOrIImmed()) + genConsumeRegs(arrIndex); + genConsumeRegs(arrLen); + + if (arrIndex->isContainedIntOrIImmed()) { // To encode using a cmp immediate, we place the // constant operand in the second position src1 = arrLen; - src2 = arrIdx; + src2 = arrIndex; jmpKind = genJumpKindForOper(GT_LE, CK_UNSIGNED); } else { - src1 = arrIdx; + src1 = arrIndex; src2 = arrLen; jmpKind = genJumpKindForOper(GT_GE, CK_UNSIGNED); } - getEmitter()->emitInsBinary(INS_cmp, emitAttr(TYP_INT), src1, src2); + getEmitter()->emitInsBinary(INS_cmp, EA_4BYTE, src1, src2); genJumpToThrowHlpBlk(jmpKind, SCK_RNGCHK_FAIL, bndsChk->gtIndRngFailBB); } -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -// generate code for BoundsCheck nodes -void CodeGen::genRangeCheck(GenTreePtr oper) -{ -#ifdef FEATURE_SIMD - noway_assert(oper->OperGet() == GT_ARR_BOUNDS_CHECK || oper->OperGet() == GT_SIMD_CHK); -#else // !FEATURE_SIMD - noway_assert(oper->OperGet() == GT_ARR_BOUNDS_CHECK); -#endif // !FEATURE_SIMD - - GenTreeBoundsChk* bndsChk = oper->AsBoundsChk(); - - GenTreePtr arrLen = bndsChk->gtArrLen; - GenTreePtr arrIndex = bndsChk->gtIndex; - GenTreePtr arrRef = NULL; - int lenOffset = 0; - - GenTree * src1, *src2; - emitJumpKind jmpKind; - - genConsumeRegs(arrIndex); - genConsumeRegs(arrLen); - - if (arrIndex->isContainedIntOrIImmed()) - { - // To encode using a cmp immediate, we place the - // constant operand in the second position - src1 = arrLen; - src2 = arrIndex; - jmpKind = genJumpKindForOper(GT_LE, CK_UNSIGNED); - } - else - { - src1 = arrIndex; - src2 = arrLen; - jmpKind = genJumpKindForOper(GT_GE, CK_UNSIGNED); - } - - GenTreeIntConCommon* intConst = nullptr; - if (src2->isContainedIntOrIImmed()) - { - intConst = src2->AsIntConCommon(); - } - - if (intConst != nullptr) - { - getEmitter()->emitIns_R_I(INS_cmp, EA_4BYTE, src1->gtRegNum, intConst->IconValue()); - } - else - { - getEmitter()->emitIns_R_R(INS_cmp, EA_4BYTE, src1->gtRegNum, src2->gtRegNum); - } - - genJumpToThrowHlpBlk(jmpKind, SCK_RNGCHK_FAIL, bndsChk->gtIndRngFailBB); -} - -#endif // _TARGET_ARM64_ - //------------------------------------------------------------------------ // genOffsetOfMDArrayLowerBound: Returns the offset from the Array object to the // lower bound for the given dimension. @@ -1022,8 +739,6 @@ void CodeGen::genCodeForArrIndex(GenTreeArrIndex* arrIndex) genProduceReg(arrIndex); } -#ifdef _TARGET_ARM_ - //------------------------------------------------------------------------ // genCodeForArrOffset: Generates code to compute the flattened array offset for // one dimension of an array reference: @@ -1052,89 +767,31 @@ void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset) { emitter* emit = getEmitter(); regNumber offsetReg = genConsumeReg(offsetNode); + regNumber indexReg = genConsumeReg(indexNode); + regNumber arrReg = genConsumeReg(arrOffset->gtArrObj); noway_assert(offsetReg != REG_NA); - regNumber indexReg = genConsumeReg(indexNode); noway_assert(indexReg != REG_NA); - GenTreePtr arrObj = arrOffset->gtArrObj; - regNumber arrReg = genConsumeReg(arrObj); noway_assert(arrReg != REG_NA); + regMaskTP tmpRegMask = arrOffset->gtRsvdRegs; regNumber tmpReg = genRegNumFromMask(tmpRegMask); noway_assert(tmpReg != REG_NA); + unsigned dim = arrOffset->gtCurrDim; unsigned rank = arrOffset->gtArrRank; var_types elemType = arrOffset->gtArrElemType; unsigned offset = genOffsetOfMDArrayDimensionSize(elemType, rank, dim); - // Load tmpReg with the dimension size +// Load tmpReg with the dimension size and evaluate +// tgtReg = offsetReg*dim_size + indexReg. +#if defined(_TARGET_ARM_) emit->emitIns_R_R_I(ins_Load(TYP_INT), EA_4BYTE, tmpReg, arrReg, offset); // a 4 BYTE sign extending load - - // Evaluate tgtReg = offsetReg*dim_size + indexReg. emit->emitIns_R_R_R(INS_MUL, EA_4BYTE, tgtReg, tmpReg, offsetReg); emit->emitIns_R_R_R(INS_add, EA_4BYTE, tgtReg, tgtReg, indexReg); - } - else - { - regNumber indexReg = genConsumeReg(indexNode); - if (indexReg != tgtReg) - { - inst_RV_RV(INS_mov, tgtReg, indexReg, TYP_INT); - } - } - genProduceReg(arrOffset); -} - -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -//------------------------------------------------------------------------ -// genCodeForArrOffset: Generates code to compute the flattened array offset for -// one dimension of an array reference: -// result = (prevDimOffset * dimSize) + effectiveIndex -// where dimSize is obtained from the arrObj operand -// -// Arguments: -// arrOffset - the node for which we're generating code -// -// Return Value: -// None. -// -// Notes: -// dimSize and effectiveIndex are always non-negative, the former by design, -// and the latter because it has been normalized to be zero-based. - -void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset) -{ - GenTreePtr offsetNode = arrOffset->gtOffset; - GenTreePtr indexNode = arrOffset->gtIndex; - regNumber tgtReg = arrOffset->gtRegNum; - - noway_assert(tgtReg != REG_NA); - - if (!offsetNode->IsIntegralConst(0)) - { - emitter* emit = getEmitter(); - regNumber offsetReg = genConsumeReg(offsetNode); - noway_assert(offsetReg != REG_NA); - regNumber indexReg = genConsumeReg(indexNode); - noway_assert(indexReg != REG_NA); - GenTreePtr arrObj = arrOffset->gtArrObj; - regNumber arrReg = genConsumeReg(arrObj); - noway_assert(arrReg != REG_NA); - regMaskTP tmpRegMask = arrOffset->gtRsvdRegs; - regNumber tmpReg = genRegNumFromMask(tmpRegMask); - noway_assert(tmpReg != REG_NA); - unsigned dim = arrOffset->gtCurrDim; - unsigned rank = arrOffset->gtArrRank; - var_types elemType = arrOffset->gtArrElemType; - unsigned offset = genOffsetOfMDArrayDimensionSize(elemType, rank, dim); - - // Load tmpReg with the dimension size +#elif defined(_TARGET_ARM64_) emit->emitIns_R_R_I(ins_Load(TYP_INT), EA_8BYTE, tmpReg, arrReg, offset); // a 4 BYTE sign extending load - - // Evaluate tgtReg = offsetReg*dim_size + indexReg. emit->emitIns_R_R_R_R(INS_madd, EA_4BYTE, tgtReg, tmpReg, offsetReg, indexReg); +#endif // _TARGET_* } else { @@ -1147,8 +804,6 @@ void CodeGen::genCodeForArrOffset(GenTreeArrOffs* arrOffset) genProduceReg(arrOffset); } -#endif // _TARGET_ARM64_ - //------------------------------------------------------------------------ // indirForm: Make a temporary indir we can feed to pattern matching routines // in cases where we don't want to instantiate all the indirs that happen. @@ -1177,8 +832,6 @@ GenTreeIntCon CodeGen::intForm(var_types type, ssize_t value) return i; } -#ifdef _TARGET_ARM_ - //------------------------------------------------------------------------ // genCodeForShift: Generates the code sequence for a GenTree node that // represents a bit shift or rotate operation (<<, >>, >>>, rol, ror). @@ -1208,49 +861,7 @@ void CodeGen::genCodeForShift(GenTreePtr tree) } else { - unsigned immWidth = size * BITS_PER_BYTE; - ssize_t shiftByImm = shiftBy->gtIntCon.gtIconVal & (immWidth - 1); - - getEmitter()->emitIns_R_R_I(ins, size, tree->gtRegNum, operand->gtRegNum, shiftByImm); - } - - genProduceReg(tree); -} - -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -//------------------------------------------------------------------------ -// genCodeForShift: Generates the code sequence for a GenTree node that -// represents a bit shift or rotate operation (<<, >>, >>>, rol, ror). -// -// Arguments: -// tree - the bit shift node (that specifies the type of bit shift to perform). -// -// Assumptions: -// a) All GenTrees are register allocated. -// -void CodeGen::genCodeForShift(GenTreePtr tree) -{ - var_types targetType = tree->TypeGet(); - genTreeOps oper = tree->OperGet(); - instruction ins = genGetInsForOper(oper, targetType); - emitAttr size = emitTypeSize(tree); - - assert(tree->gtRegNum != REG_NA); - - GenTreePtr operand = tree->gtGetOp1(); - genConsumeOperands(tree->AsOp()); - - GenTreePtr shiftBy = tree->gtGetOp2(); - if (!shiftBy->IsCnsIntOrI()) - { - getEmitter()->emitIns_R_R_R(ins, size, tree->gtRegNum, operand->gtRegNum, shiftBy->gtRegNum); - } - else - { - unsigned immWidth = emitter::getBitWidth(size); // immWidth will be set to 32 or 64 + unsigned immWidth = emitter::getBitWidth(size); // For ARM64, immWidth will be set to 32 or 64 ssize_t shiftByImm = shiftBy->gtIntCon.gtIconVal & (immWidth - 1); getEmitter()->emitIns_R_R_I(ins, size, tree->gtRegNum, operand->gtRegNum, shiftByImm); @@ -1259,10 +870,6 @@ void CodeGen::genCodeForShift(GenTreePtr tree) genProduceReg(tree); } -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - // Generate code for a CpBlk node by the means of the VM memcpy helper call // Preconditions: // a) The size argument of the CpBlk is not an integer constant @@ -1275,38 +882,17 @@ void CodeGen::genCodeForCpBlk(GenTreeBlk* cpBlkNode) assert(!dstAddr->isContained()); genConsumeBlockOp(cpBlkNode, REG_ARG_0, REG_ARG_1, REG_ARG_2); - genEmitHelperCall(CORINFO_HELP_MEMCPY, 0, EA_UNKNOWN); -} - -#endif // _TARGET_ARM_ #ifdef _TARGET_ARM64_ - -// Generate code for a CpBlk node by the means of the VM memcpy helper call -// Preconditions: -// a) The size argument of the CpBlk is not an integer constant -// b) The size argument is a constant but is larger than CPBLK_MOVS_LIMIT bytes. -void CodeGen::genCodeForCpBlk(GenTreeBlk* cpBlkNode) -{ - // Make sure we got the arguments of the cpblk operation in the right registers - unsigned blockSize = cpBlkNode->Size(); - GenTreePtr dstAddr = cpBlkNode->Addr(); - assert(!dstAddr->isContained()); - - genConsumeBlockOp(cpBlkNode, REG_ARG_0, REG_ARG_1, REG_ARG_2); - if (blockSize != 0) { assert(blockSize > CPBLK_UNROLL_LIMIT); } +#endif // _TARGET_ARM64_ genEmitHelperCall(CORINFO_HELP_MEMCPY, 0, EA_UNKNOWN); } -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - // Generates code for InitBlk by calling the VM memset helper function. // Preconditions: // a) The size argument of the InitBlk is not an integer constant. @@ -1333,54 +919,17 @@ void CodeGen::genCodeForInitBlk(GenTreeBlk* initBlkNode) assert(initBlkNode->gtRsvdRegs == RBM_ARG_2); } - genConsumeBlockOp(initBlkNode, REG_ARG_0, REG_ARG_1, REG_ARG_2); - genEmitHelperCall(CORINFO_HELP_MEMSET, 0, EA_UNKNOWN); -} - -#endif // _TARGET_ARM_ - #ifdef _TARGET_ARM64_ - -// Generates code for InitBlk by calling the VM memset helper function. -// Preconditions: -// a) The size argument of the InitBlk is not an integer constant. -// b) The size argument of the InitBlk is >= INITBLK_STOS_LIMIT bytes. -void CodeGen::genCodeForInitBlk(GenTreeBlk* initBlkNode) -{ - // Make sure we got the arguments of the initblk operation in the right registers - unsigned size = initBlkNode->Size(); - GenTreePtr dstAddr = initBlkNode->Addr(); - GenTreePtr initVal = initBlkNode->Data(); - if (initVal->OperIsInitVal()) - { - initVal = initVal->gtGetOp1(); - } - - assert(!dstAddr->isContained()); - assert(!initVal->isContained()); - if (initBlkNode->gtOper == GT_STORE_DYN_BLK) - { - assert(initBlkNode->AsDynBlk()->gtDynamicSize->gtRegNum == REG_ARG_2); - } - else - { - assert(initBlkNode->gtRsvdRegs == RBM_ARG_2); - } - if (size != 0) { assert(size > INITBLK_UNROLL_LIMIT); } +#endif // _TARGET_ARM64_ genConsumeBlockOp(initBlkNode, REG_ARG_0, REG_ARG_1, REG_ARG_2); - genEmitHelperCall(CORINFO_HELP_MEMSET, 0, EA_UNKNOWN); } -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - //------------------------------------------------------------------------ // genRegCopy: Generate a register copy. // @@ -1402,7 +951,10 @@ void CodeGen::genRegCopy(GenTree* treeNode) if (varTypeIsFloating(treeNode) != varTypeIsFloating(op1)) { - NYI("genRegCopy floating point"); + NYI_ARM("genRegCopy floating point"); +#ifdef _TARGET_ARM64_ + inst_RV_RV(INS_fmov, targetReg, genConsumeReg(op1), targetType); +#endif // _TARGET_ARM64_ } else { @@ -1447,92 +999,22 @@ void CodeGen::genRegCopy(GenTree* treeNode) genProduceReg(treeNode); } -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -void CodeGen::genRegCopy(GenTree* treeNode) +//------------------------------------------------------------------------ +// genCallInstruction: Produce code for a GT_CALL node +// +void CodeGen::genCallInstruction(GenTreeCall* call) { - assert(treeNode->OperGet() == GT_COPY); - - var_types targetType = treeNode->TypeGet(); - regNumber targetReg = treeNode->gtRegNum; - assert(targetReg != REG_NA); + gtCallTypes callType = (gtCallTypes)call->gtCallType; - GenTree* op1 = treeNode->gtOp.gtOp1; + IL_OFFSETX ilOffset = BAD_IL_OFFSET; - // Check whether this node and the node from which we're copying the value have the same - // register type. - // This can happen if (currently iff) we have a SIMD vector type that fits in an integer - // register, in which case it is passed as an argument, or returned from a call, - // in an integer register and must be copied if it's in an xmm register. + // all virtuals should have been expanded into a control expression + assert(!call->IsVirtual() || call->gtControlExpr || call->gtCallAddr); - if (varTypeIsFloating(treeNode) != varTypeIsFloating(op1)) - { - inst_RV_RV(INS_fmov, targetReg, genConsumeReg(op1), targetType); - } - else + // Consume all the arg regs + for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) { - inst_RV_RV(ins_Copy(targetType), targetReg, genConsumeReg(op1), targetType); - } - - if (op1->IsLocal()) - { - // The lclVar will never be a def. - // If it is a last use, the lclVar will be killed by genConsumeReg(), as usual, and genProduceReg will - // appropriately set the gcInfo for the copied value. - // If not, there are two cases we need to handle: - // - If this is a TEMPORARY copy (indicated by the GTF_VAR_DEATH flag) the variable - // will remain live in its original register. - // genProduceReg() will appropriately set the gcInfo for the copied value, - // and genConsumeReg will reset it. - // - Otherwise, we need to update register info for the lclVar. - - GenTreeLclVarCommon* lcl = op1->AsLclVarCommon(); - assert((lcl->gtFlags & GTF_VAR_DEF) == 0); - - if ((lcl->gtFlags & GTF_VAR_DEATH) == 0 && (treeNode->gtFlags & GTF_VAR_DEATH) == 0) - { - LclVarDsc* varDsc = &compiler->lvaTable[lcl->gtLclNum]; - - // If we didn't just spill it (in genConsumeReg, above), then update the register info - if (varDsc->lvRegNum != REG_STK) - { - // The old location is dying - genUpdateRegLife(varDsc, /*isBorn*/ false, /*isDying*/ true DEBUGARG(op1)); - - gcInfo.gcMarkRegSetNpt(genRegMask(op1->gtRegNum)); - - genUpdateVarReg(varDsc, treeNode); - - // The new location is going live - genUpdateRegLife(varDsc, /*isBorn*/ true, /*isDying*/ false DEBUGARG(treeNode)); - } - } - } - genProduceReg(treeNode); -} - -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - -//------------------------------------------------------------------------ -// genCallInstruction: Produce code for a GT_CALL node -// -void CodeGen::genCallInstruction(GenTreeCall* call) -{ - gtCallTypes callType = (gtCallTypes)call->gtCallType; - - IL_OFFSETX ilOffset = BAD_IL_OFFSET; - - // all virtuals should have been expanded into a control expression - assert(!call->IsVirtual() || call->gtControlExpr || call->gtCallAddr); - - // Consume all the arg regs - for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) - { - assert(list->OperIsList()); + assert(list->OperIsList()); GenTreePtr argNode = list->Current(); @@ -1581,13 +1063,16 @@ void CodeGen::genCallInstruction(GenTreeCall* call) if (call->IsVarargs() && varTypeIsFloating(argNode)) { NYI_ARM("CodeGen - IsVarargs"); + NYI_ARM64("CodeGen - IsVarargs"); } } // Insert a null check on "this" pointer if asked. if (call->NeedsNullCheck()) { - const regNumber regThis = genGetThisArgReg(call); + const regNumber regThis = genGetThisArgReg(call); + +#if defined(_TARGET_ARM_) regMaskTP tempMask = genFindLowestBit(call->gtRsvdRegs); const regNumber tmpReg = genRegNumFromMask(tempMask); if (genCountBits(call->gtRsvdRegs) > 1) @@ -1595,6 +1080,9 @@ void CodeGen::genCallInstruction(GenTreeCall* call) call->gtRsvdRegs &= ~tempMask; } getEmitter()->emitIns_R_R_I(INS_ldr, EA_4BYTE, tmpReg, regThis, 0); +#elif defined(_TARGET_ARM64_) + getEmitter()->emitIns_R_R_I(INS_ldr, EA_4BYTE, REG_ZR, regThis, 0); +#endif // _TARGET_* } // Either gtControlExpr != null or gtCallAddr != null or it is a direct non-virtual call to a user or helper method. @@ -1621,10 +1109,30 @@ void CodeGen::genCallInstruction(GenTreeCall* call) } #endif // DEBUG - // If fast tail call, then we are done. + // If fast tail call, then we are done. In this case we setup the args (both reg args + // and stack args in incoming arg area) and call target. Epilog sequence would + // generate "br ". if (call->IsFastTailCall()) { + // Don't support fast tail calling JIT helpers + assert(callType != CT_HELPER); + + // Fast tail calls materialize call target either in gtControlExpr or in gtCallAddr. + assert(target != nullptr); + + genConsumeReg(target); + NYI_ARM("fast tail call"); + +#ifdef _TARGET_ARM64_ + // Use IP0 as the call target register. + if (target->gtRegNum != REG_IP0) + { + inst_RV_RV(INS_mov, REG_IP0, target->gtRegNum); + } +#endif // _TARGET_ARM64_ + + return; } // For a pinvoke to unmanaged code we emit a label to clear @@ -1671,7 +1179,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) if (target != nullptr) { - // For ARM a call target can not be a contained indirection + // A call target can not be a contained indirection assert(!target->isContainedIndir()); genConsumeReg(target); @@ -1683,7 +1191,7 @@ void CodeGen::genCallInstruction(GenTreeCall* call) genEmitCall(emitter::EC_INDIR_R, methHnd, INDEBUG_LDISASM_COMMA(sigInfo) nullptr, // addr - retSize, ilOffset, target->gtRegNum); + retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset, target->gtRegNum); } else { @@ -1725,8 +1233,10 @@ void CodeGen::genCallInstruction(GenTreeCall* call) addr = addrInfo.addr; } - assert(addr); - // Non-virtual direct call to known addresses + assert(addr != nullptr); + +// Non-virtual direct call to known addresses +#ifdef _TARGET_ARM_ if (!arm_Valid_Imm_For_BL((ssize_t)addr)) { regNumber tmpReg = genRegNumFromMask(call->gtRsvdRegs); @@ -1734,303 +1244,13 @@ void CodeGen::genCallInstruction(GenTreeCall* call) genEmitCall(emitter::EC_INDIR_R, methHnd, INDEBUG_LDISASM_COMMA(sigInfo) NULL, retSize, ilOffset, tmpReg); } else - { - genEmitCall(emitter::EC_FUNC_TOKEN, methHnd, INDEBUG_LDISASM_COMMA(sigInfo) addr, retSize, ilOffset); - } - } - - // if it was a pinvoke we may have needed to get the address of a label - if (genPendingCallLabel) - { - assert(call->IsUnmanaged()); - genDefineTempLabel(genPendingCallLabel); - genPendingCallLabel = nullptr; - } - - // Update GC info: - // All Callee arg registers are trashed and no longer contain any GC pointers. - // TODO-ARM-Bug?: As a matter of fact shouldn't we be killing all of callee trashed regs here? - // For now we will assert that other than arg regs gc ref/byref set doesn't contain any other - // registers from RBM_CALLEE_TRASH - assert((gcInfo.gcRegGCrefSetCur & (RBM_CALLEE_TRASH & ~RBM_ARG_REGS)) == 0); - assert((gcInfo.gcRegByrefSetCur & (RBM_CALLEE_TRASH & ~RBM_ARG_REGS)) == 0); - gcInfo.gcRegGCrefSetCur &= ~RBM_ARG_REGS; - gcInfo.gcRegByrefSetCur &= ~RBM_ARG_REGS; - - var_types returnType = call->TypeGet(); - if (returnType != TYP_VOID) - { - regNumber returnReg; - - if (call->HasMultiRegRetVal()) - { - assert(pRetTypeDesc != nullptr); - unsigned regCount = pRetTypeDesc->GetReturnRegCount(); - - // If regs allocated to call node are different from ABI return - // regs in which the call has returned its result, move the result - // to regs allocated to call node. - for (unsigned i = 0; i < regCount; ++i) - { - var_types regType = pRetTypeDesc->GetReturnRegType(i); - returnReg = pRetTypeDesc->GetABIReturnReg(i); - regNumber allocatedReg = call->GetRegNumByIdx(i); - if (returnReg != allocatedReg) - { - inst_RV_RV(ins_Copy(regType), allocatedReg, returnReg, regType); - } - } - } - else - { - if (call->IsHelperCall(compiler, CORINFO_HELP_INIT_PINVOKE_FRAME)) - { - // The CORINFO_HELP_INIT_PINVOKE_FRAME helper uses a custom calling convention that returns with - // TCB in REG_PINVOKE_TCB. fgMorphCall() sets the correct argument registers. - returnReg = REG_PINVOKE_TCB; - } - else if (varTypeIsFloating(returnType)) - { - returnReg = REG_FLOATRET; - } - else - { - returnReg = REG_INTRET; - } - - if (call->gtRegNum != returnReg) - { - inst_RV_RV(ins_Copy(returnType), call->gtRegNum, returnReg, returnType); - } - } - - genProduceReg(call); - } - - // If there is nothing next, that means the result is thrown away, so this value is not live. - // However, for minopts or debuggable code, we keep it live to support managed return value debugging. - if ((call->gtNext == nullptr) && !compiler->opts.MinOpts() && !compiler->opts.compDbgCode) - { - gcInfo.gcMarkRegSetNpt(RBM_INTRET); - } -} - #endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -// Produce code for a GT_CALL node -void CodeGen::genCallInstruction(GenTreeCall* call) -{ - gtCallTypes callType = (gtCallTypes)call->gtCallType; - - IL_OFFSETX ilOffset = BAD_IL_OFFSET; - - // all virtuals should have been expanded into a control expression - assert(!call->IsVirtual() || call->gtControlExpr || call->gtCallAddr); - - // Consume all the arg regs - for (GenTreePtr list = call->gtCallLateArgs; list; list = list->MoveNext()) - { - assert(list->OperIsList()); - - GenTreePtr argNode = list->Current(); - - fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(call, argNode->gtSkipReloadOrCopy()); - assert(curArgTabEntry); - - if (curArgTabEntry->regNum == REG_STK) - continue; - - // Deal with multi register passed struct args. - if (argNode->OperGet() == GT_FIELD_LIST) - { - GenTreeArgList* argListPtr = argNode->AsArgList(); - unsigned iterationNum = 0; - regNumber argReg = curArgTabEntry->regNum; - for (; argListPtr != nullptr; argListPtr = argListPtr->Rest(), iterationNum++) - { - GenTreePtr putArgRegNode = argListPtr->gtOp.gtOp1; - assert(putArgRegNode->gtOper == GT_PUTARG_REG); - - genConsumeReg(putArgRegNode); - - if (putArgRegNode->gtRegNum != argReg) - { - inst_RV_RV(ins_Move_Extend(putArgRegNode->TypeGet(), putArgRegNode->InReg()), argReg, - putArgRegNode->gtRegNum); - } - - argReg = genRegArgNext(argReg); - } - } - else - { - regNumber argReg = curArgTabEntry->regNum; - genConsumeReg(argNode); - if (argNode->gtRegNum != argReg) - { - inst_RV_RV(ins_Move_Extend(argNode->TypeGet(), argNode->InReg()), argReg, argNode->gtRegNum); - } - } - - // In the case of a varargs call, - // the ABI dictates that if we have floating point args, - // we must pass the enregistered arguments in both the - // integer and floating point registers so, let's do that. - if (call->IsVarargs() && varTypeIsFloating(argNode)) - { - NYI_ARM64("CodeGen - IsVarargs"); - } - } - - // Insert a null check on "this" pointer if asked. - if (call->NeedsNullCheck()) - { - const regNumber regThis = genGetThisArgReg(call); - getEmitter()->emitIns_R_R_I(INS_ldr, EA_4BYTE, REG_ZR, regThis, 0); - } - - // Either gtControlExpr != null or gtCallAddr != null or it is a direct non-virtual call to a user or helper method. - CORINFO_METHOD_HANDLE methHnd; - GenTree* target = call->gtControlExpr; - if (callType == CT_INDIRECT) - { - assert(target == nullptr); - target = call->gtCallAddr; - methHnd = nullptr; - } - else - { - methHnd = call->gtCallMethHnd; - } - - CORINFO_SIG_INFO* sigInfo = nullptr; -#ifdef DEBUG - // Pass the call signature information down into the emitter so the emitter can associate - // native call sites with the signatures they were generated from. - if (callType != CT_HELPER) - { - sigInfo = call->callSig; - } -#endif // DEBUG - - // If fast tail call, then we are done. In this case we setup the args (both reg args - // and stack args in incoming arg area) and call target in IP0. Epilog sequence would - // generate "br IP0". - if (call->IsFastTailCall()) - { - // Don't support fast tail calling JIT helpers - assert(callType != CT_HELPER); - - // Fast tail calls materialize call target either in gtControlExpr or in gtCallAddr. - assert(target != nullptr); - - genConsumeReg(target); - - if (target->gtRegNum != REG_IP0) - { - inst_RV_RV(INS_mov, REG_IP0, target->gtRegNum); - } - return; - } - - // For a pinvoke to unmanged code we emit a label to clear - // the GC pointer state before the callsite. - // We can't utilize the typical lazy killing of GC pointers - // at (or inside) the callsite. - if (call->IsUnmanaged()) - { - genDefineTempLabel(genCreateTempLabel()); - } - - // Determine return value size(s). - ReturnTypeDesc* pRetTypeDesc = call->GetReturnTypeDesc(); - emitAttr retSize = EA_PTRSIZE; - emitAttr secondRetSize = EA_UNKNOWN; - - if (call->HasMultiRegRetVal()) - { - retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); - secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); - } - else - { - assert(!varTypeIsStruct(call)); - - if (call->gtType == TYP_REF || call->gtType == TYP_ARRAY) - { - retSize = EA_GCREF; - } - else if (call->gtType == TYP_BYREF) - { - retSize = EA_BYREF; - } - } - - // We need to propagate the IL offset information to the call instruction, so we can emit - // an IL to native mapping record for the call, to support managed return value debugging. - // We don't want tail call helper calls that were converted from normal calls to get a record, - // so we skip this hash table lookup logic in that case. - if (compiler->opts.compDbgInfo && compiler->genCallSite2ILOffsetMap != nullptr && !call->IsTailCall()) - { - (void)compiler->genCallSite2ILOffsetMap->Lookup(call, &ilOffset); - } - - if (target != nullptr) - { - // For Arm64 a call target can not be a contained indirection - assert(!target->isContainedIndir()); - - // We have already generated code for gtControlExpr evaluating it into a register. - // We just need to emit "call reg" in this case. - // - assert(genIsValidIntReg(target->gtRegNum)); - - genEmitCall(emitter::EC_INDIR_R, methHnd, - INDEBUG_LDISASM_COMMA(sigInfo) nullptr, // addr - retSize, secondRetSize, ilOffset, genConsumeReg(target)); - } - else - { - // Generate a direct call to a non-virtual user defined or helper method - assert(callType == CT_HELPER || callType == CT_USER_FUNC); - - void* addr = nullptr; - if (callType == CT_HELPER) { - // Direct call to a helper method. - CorInfoHelpFunc helperNum = compiler->eeGetHelperNum(methHnd); - noway_assert(helperNum != CORINFO_HELP_UNDEF); - - void* pAddr = nullptr; - addr = compiler->compGetHelperFtn(helperNum, (void**)&pAddr); - - if (addr == nullptr) - { - addr = pAddr; - } + genEmitCall(emitter::EC_FUNC_TOKEN, methHnd, INDEBUG_LDISASM_COMMA(sigInfo) addr, + retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), ilOffset); } - else - { - // Direct call to a non-virtual user function. - CORINFO_ACCESS_FLAGS aflags = CORINFO_ACCESS_ANY; - if (call->IsSameThis()) - { - aflags = (CORINFO_ACCESS_FLAGS)(aflags | CORINFO_ACCESS_THIS); - } - if ((call->NeedsNullCheck()) == 0) - { - aflags = (CORINFO_ACCESS_FLAGS)(aflags | CORINFO_ACCESS_NONNULL); - } - - CORINFO_CONST_LOOKUP addrInfo; - compiler->info.compCompHnd->getFunctionEntryPoint(methHnd, &addrInfo, aflags); - - addr = addrInfo.addr; - } -#if 0 +#if 0 && defined(_TARGET_ARM64_) // Use this path if you want to load an absolute call target using // a sequence of movs followed by an indirect call (blr instruction) @@ -2046,267 +1266,90 @@ void CodeGen::genCallInstruction(GenTreeCall* call) secondRetSize, ilOffset, REG_IP0); -#else - // Non-virtual direct call to known addresses - genEmitCall(emitter::EC_FUNC_TOKEN, methHnd, INDEBUG_LDISASM_COMMA(sigInfo) addr, retSize, secondRetSize, - ilOffset); #endif } // if it was a pinvoke we may have needed to get the address of a label - if (genPendingCallLabel) - { - assert(call->IsUnmanaged()); - genDefineTempLabel(genPendingCallLabel); - genPendingCallLabel = nullptr; - } - - // Update GC info: - // All Callee arg registers are trashed and no longer contain any GC pointers. - // TODO-ARM64-Bug?: As a matter of fact shouldn't we be killing all of callee trashed regs here? - // For now we will assert that other than arg regs gc ref/byref set doesn't contain any other - // registers from RBM_CALLEE_TRASH - assert((gcInfo.gcRegGCrefSetCur & (RBM_CALLEE_TRASH & ~RBM_ARG_REGS)) == 0); - assert((gcInfo.gcRegByrefSetCur & (RBM_CALLEE_TRASH & ~RBM_ARG_REGS)) == 0); - gcInfo.gcRegGCrefSetCur &= ~RBM_ARG_REGS; - gcInfo.gcRegByrefSetCur &= ~RBM_ARG_REGS; - - var_types returnType = call->TypeGet(); - if (returnType != TYP_VOID) - { - regNumber returnReg; - - if (call->HasMultiRegRetVal()) - { - assert(pRetTypeDesc != nullptr); - unsigned regCount = pRetTypeDesc->GetReturnRegCount(); - - // If regs allocated to call node are different from ABI return - // regs in which the call has returned its result, move the result - // to regs allocated to call node. - for (unsigned i = 0; i < regCount; ++i) - { - var_types regType = pRetTypeDesc->GetReturnRegType(i); - returnReg = pRetTypeDesc->GetABIReturnReg(i); - regNumber allocatedReg = call->GetRegNumByIdx(i); - if (returnReg != allocatedReg) - { - inst_RV_RV(ins_Copy(regType), allocatedReg, returnReg, regType); - } - } - } - else - { - if (varTypeIsFloating(returnType)) - { - returnReg = REG_FLOATRET; - } - else - { - returnReg = REG_INTRET; - } - - if (call->gtRegNum != returnReg) - { - inst_RV_RV(ins_Copy(returnType), call->gtRegNum, returnReg, returnType); - } - } - - genProduceReg(call); - } - - // If there is nothing next, that means the result is thrown away, so this value is not live. - // However, for minopts or debuggable code, we keep it live to support managed return value debugging. - if ((call->gtNext == nullptr) && !compiler->opts.MinOpts() && !compiler->opts.compDbgCode) - { - gcInfo.gcMarkRegSetNpt(RBM_INTRET); - } -} - -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - -//------------------------------------------------------------------------ -// genIntToIntCast: Generate code for an integer cast -// -// Arguments: -// treeNode - The GT_CAST node -// -// Return Value: -// None. -// -// Assumptions: -// The treeNode must have an assigned register. -// For a signed convert from byte, the source must be in a byte-addressable register. -// Neither the source nor target type can be a floating point type. -// -void CodeGen::genIntToIntCast(GenTreePtr treeNode) -{ - assert(treeNode->OperGet() == GT_CAST); - - GenTreePtr castOp = treeNode->gtCast.CastOp(); - emitter* emit = getEmitter(); - - var_types dstType = treeNode->CastToType(); - var_types srcType = genActualType(castOp->TypeGet()); - emitAttr movSize = emitActualTypeSize(dstType); - bool movRequired = false; - - if (varTypeIsLong(srcType)) - { - genLongToIntCast(treeNode); - return; - } - - regNumber targetReg = treeNode->gtRegNum; - regNumber sourceReg = castOp->gtRegNum; - - // For Long to Int conversion we will have a reserved integer register to hold the immediate mask - regNumber tmpReg = (treeNode->gtRsvdRegs == RBM_NONE) ? REG_NA : genRegNumFromMask(treeNode->gtRsvdRegs); - - assert(genIsValidIntReg(targetReg)); - assert(genIsValidIntReg(sourceReg)); - - instruction ins = INS_invalid; - - genConsumeReg(castOp); - Lowering::CastInfo castInfo; + if (genPendingCallLabel) + { + assert(call->IsUnmanaged()); + genDefineTempLabel(genPendingCallLabel); + genPendingCallLabel = nullptr; + } - // Get information about the cast. - Lowering::getCastDescription(treeNode, &castInfo); + // Update GC info: + // All Callee arg registers are trashed and no longer contain any GC pointers. + // TODO-Bug?: As a matter of fact shouldn't we be killing all of callee trashed regs here? + // For now we will assert that other than arg regs gc ref/byref set doesn't contain any other + // registers from RBM_CALLEE_TRASH + assert((gcInfo.gcRegGCrefSetCur & (RBM_CALLEE_TRASH & ~RBM_ARG_REGS)) == 0); + assert((gcInfo.gcRegByrefSetCur & (RBM_CALLEE_TRASH & ~RBM_ARG_REGS)) == 0); + gcInfo.gcRegGCrefSetCur &= ~RBM_ARG_REGS; + gcInfo.gcRegByrefSetCur &= ~RBM_ARG_REGS; - if (castInfo.requiresOverflowCheck) + var_types returnType = call->TypeGet(); + if (returnType != TYP_VOID) { - emitAttr cmpSize = EA_ATTR(genTypeSize(srcType)); + regNumber returnReg; - if (castInfo.signCheckOnly) + if (call->HasMultiRegRetVal()) { - // We only need to check for a negative value in sourceReg - emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, 0); - emitJumpKind jmpLT = genJumpKindForOper(GT_LT, CK_SIGNED); - genJumpToThrowHlpBlk(jmpLT, SCK_OVERFLOW); - noway_assert(genTypeSize(srcType) == 4 || genTypeSize(srcType) == 8); - // This is only interesting case to ensure zero-upper bits. - if ((srcType == TYP_INT) && (dstType == TYP_ULONG)) + assert(pRetTypeDesc != nullptr); + unsigned regCount = pRetTypeDesc->GetReturnRegCount(); + + // If regs allocated to call node are different from ABI return + // regs in which the call has returned its result, move the result + // to regs allocated to call node. + for (unsigned i = 0; i < regCount; ++i) { - // cast to TYP_ULONG: - // We use a mov with size=EA_4BYTE - // which will zero out the upper bits - movSize = EA_4BYTE; - movRequired = true; + var_types regType = pRetTypeDesc->GetReturnRegType(i); + returnReg = pRetTypeDesc->GetABIReturnReg(i); + regNumber allocatedReg = call->GetRegNumByIdx(i); + if (returnReg != allocatedReg) + { + inst_RV_RV(ins_Copy(regType), allocatedReg, returnReg, regType); + } } } - else if (castInfo.unsignedSource || castInfo.unsignedDest) - { - // When we are converting from/to unsigned, - // we only have to check for any bits set in 'typeMask' - - noway_assert(castInfo.typeMask != 0); - emit->emitIns_R_I(INS_tst, cmpSize, sourceReg, castInfo.typeMask); - emitJumpKind jmpNotEqual = genJumpKindForOper(GT_NE, CK_SIGNED); - genJumpToThrowHlpBlk(jmpNotEqual, SCK_OVERFLOW); - } else { - // For a narrowing signed cast - // - // We must check the value is in a signed range. - - // Compare with the MAX - - noway_assert((castInfo.typeMin != 0) && (castInfo.typeMax != 0)); - - if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMax, INS_FLAGS_DONT_CARE)) +#ifdef _TARGET_ARM_ + if (call->IsHelperCall(compiler, CORINFO_HELP_INIT_PINVOKE_FRAME)) { - emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, castInfo.typeMax); + // The CORINFO_HELP_INIT_PINVOKE_FRAME helper uses a custom calling convention that returns with + // TCB in REG_PINVOKE_TCB. fgMorphCall() sets the correct argument registers. + returnReg = REG_PINVOKE_TCB; } else +#endif // _TARGET_ARM_ + if (varTypeIsFloating(returnType)) { - noway_assert(tmpReg != REG_NA); - instGen_Set_Reg_To_Imm(cmpSize, tmpReg, castInfo.typeMax); - emit->emitIns_R_R(INS_cmp, cmpSize, sourceReg, tmpReg); - } - - emitJumpKind jmpGT = genJumpKindForOper(GT_GT, CK_SIGNED); - genJumpToThrowHlpBlk(jmpGT, SCK_OVERFLOW); - - // Compare with the MIN - - if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMin, INS_FLAGS_DONT_CARE)) - { - emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, castInfo.typeMin); + returnReg = REG_FLOATRET; } else { - noway_assert(tmpReg != REG_NA); - instGen_Set_Reg_To_Imm(cmpSize, tmpReg, castInfo.typeMin); - emit->emitIns_R_R(INS_cmp, cmpSize, sourceReg, tmpReg); + returnReg = REG_INTRET; } - emitJumpKind jmpLT = genJumpKindForOper(GT_LT, CK_SIGNED); - genJumpToThrowHlpBlk(jmpLT, SCK_OVERFLOW); - } - ins = INS_mov; - } - else // Non-overflow checking cast. - { - if (genTypeSize(srcType) == genTypeSize(dstType)) - { - ins = INS_mov; - } - else - { - var_types extendType = TYP_UNKNOWN; - - // If we need to treat a signed type as unsigned - if ((treeNode->gtFlags & GTF_UNSIGNED) != 0) - { - extendType = genUnsignedType(srcType); - movSize = emitTypeSize(extendType); - movRequired = true; - } - else + if (call->gtRegNum != returnReg) { - if (genTypeSize(srcType) < genTypeSize(dstType)) - { - extendType = srcType; - movSize = emitTypeSize(srcType); - if (srcType == TYP_UINT) - { - movRequired = true; - } - } - else // (genTypeSize(srcType) > genTypeSize(dstType)) - { - extendType = dstType; - movSize = emitTypeSize(dstType); - } + inst_RV_RV(ins_Copy(returnType), call->gtRegNum, returnReg, returnType); } - - ins = ins_Move_Extend(extendType, castOp->InReg()); } - } - // We should never be generating a load from memory instruction here! - assert(!emit->emitInsIsLoad(ins)); + genProduceReg(call); + } - if ((ins != INS_mov) || movRequired || (targetReg != sourceReg)) + // If there is nothing next, that means the result is thrown away, so this value is not live. + // However, for minopts or debuggable code, we keep it live to support managed return value debugging. + if ((call->gtNext == nullptr) && !compiler->opts.MinOpts() && !compiler->opts.compDbgCode) { - emit->emitIns_R_R(ins, movSize, targetReg, sourceReg); + gcInfo.gcMarkRegSetNpt(RBM_INTRET); } - - genProduceReg(treeNode); } -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - //------------------------------------------------------------------------ // genIntToIntCast: Generate code for an integer cast -// This method handles integer overflow checking casts -// as well as ordinary integer casts. // // Arguments: // treeNode - The GT_CAST node @@ -2315,7 +1358,7 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) // None. // // Assumptions: -// The treeNode is not a contained node and must have an assigned register. +// The treeNode must have an assigned register. // For a signed convert from byte, the source must be in a byte-addressable register. // Neither the source nor target type can be a floating point type. // @@ -2333,6 +1376,14 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) emitAttr movSize = emitActualTypeSize(dstType); bool movRequired = false; +#ifdef _TARGET_ARM_ + if (varTypeIsLong(srcType)) + { + genLongToIntCast(treeNode); + return; + } +#endif // _TARGET_ARM_ + regNumber targetReg = treeNode->gtRegNum; regNumber sourceReg = castOp->gtRegNum; @@ -2352,7 +1403,6 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) if (castInfo.requiresOverflowCheck) { - emitAttr cmpSize = EA_ATTR(genTypeSize(srcType)); if (castInfo.signCheckOnly) @@ -2392,7 +1442,11 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) noway_assert((castInfo.typeMin != 0) && (castInfo.typeMax != 0)); +#if defined(_TARGET_ARM_) + if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMax, INS_FLAGS_DONT_CARE)) +#elif defined(_TARGET_ARM64_) if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMax, cmpSize)) +#endif // _TARGET_* { emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, castInfo.typeMax); } @@ -2406,9 +1460,13 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) emitJumpKind jmpGT = genJumpKindForOper(GT_GT, CK_SIGNED); genJumpToThrowHlpBlk(jmpGT, SCK_OVERFLOW); - // Compare with the MIN +// Compare with the MIN +#if defined(_TARGET_ARM_) + if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMin, INS_FLAGS_DONT_CARE)) +#elif defined(_TARGET_ARM64_) if (emitter::emitIns_valid_imm_for_cmp(castInfo.typeMin, cmpSize)) +#endif // _TARGET_* { emit->emitIns_R_I(INS_cmp, cmpSize, sourceReg, castInfo.typeMin); } @@ -2446,24 +1504,33 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) if (genTypeSize(srcType) < genTypeSize(dstType)) { extendType = srcType; +#ifdef _TARGET_ARM_ + movSize = emitTypeSize(srcType); +#endif // _TARGET_ARM_ if (srcType == TYP_UINT) { +#ifdef _TARGET_ARM64_ // If we are casting from a smaller type to // a larger type, then we need to make sure the // higher 4 bytes are zero to gaurentee the correct value. // Therefore using a mov with EA_4BYTE in place of EA_8BYTE // will zero the upper bits - movSize = EA_4BYTE; + movSize = EA_4BYTE; +#endif // _TARGET_ARM64_ movRequired = true; } } else // (genTypeSize(srcType) > genTypeSize(dstType)) { extendType = dstType; +#if defined(_TARGET_ARM_) + movSize = emitTypeSize(dstType); +#elif defined(_TARGET_ARM64_) if (dstType == TYP_INT) { movSize = EA_8BYTE; // a sxtw instruction requires EA_8BYTE } +#endif // _TARGET_* } } @@ -2482,10 +1549,6 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) genProduceReg(treeNode); } -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - //------------------------------------------------------------------------ // genFloatToFloatCast: Generate code for a cast between float and double // @@ -2522,6 +1585,8 @@ void CodeGen::genFloatToFloatCast(GenTreePtr treeNode) // treeNode must be a reg assert(!treeNode->isContained()); +#if defined(_TARGET_ARM_) + if (srcType != dstType) { instruction insVcvt = (srcType == TYP_FLOAT) ? INS_vcvt_f2d // convert Float to Double @@ -2534,48 +1599,7 @@ void CodeGen::genFloatToFloatCast(GenTreePtr treeNode) getEmitter()->emitIns_R_R(INS_vmov, emitTypeSize(treeNode), treeNode->gtRegNum, op1->gtRegNum); } - genProduceReg(treeNode); -} - -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -//------------------------------------------------------------------------ -// genFloatToFloatCast: Generate code for a cast between float and double -// -// Arguments: -// treeNode - The GT_CAST node -// -// Return Value: -// None. -// -// Assumptions: -// Cast is a non-overflow conversion. -// The treeNode must have an assigned register. -// The cast is between float and double or vice versa. -// -void CodeGen::genFloatToFloatCast(GenTreePtr treeNode) -{ - // float <--> double conversions are always non-overflow ones - assert(treeNode->OperGet() == GT_CAST); - assert(!treeNode->gtOverflow()); - - regNumber targetReg = treeNode->gtRegNum; - assert(genIsValidFloatReg(targetReg)); - - GenTreePtr op1 = treeNode->gtOp.gtOp1; - assert(!op1->isContained()); // Cannot be contained - assert(genIsValidFloatReg(op1->gtRegNum)); // Must be a valid float reg. - - var_types dstType = treeNode->CastToType(); - var_types srcType = op1->TypeGet(); - assert(varTypeIsFloating(srcType) && varTypeIsFloating(dstType)); - - genConsumeOperands(treeNode->AsOp()); - - // treeNode must be a reg - assert(!treeNode->isContained()); +#elif defined(_TARGET_ARM64_) if (srcType != dstType) { @@ -2590,13 +1614,11 @@ void CodeGen::genFloatToFloatCast(GenTreePtr treeNode) getEmitter()->emitIns_R_R(INS_mov, emitTypeSize(treeNode), treeNode->gtRegNum, op1->gtRegNum); } +#endif // _TARGET_* + genProduceReg(treeNode); } -#endif // _TARGET_ARM64_ - -#ifdef _TARGET_ARM_ - //------------------------------------------------------------------------ // genCreateAndStoreGCInfo: Create and record GC Info for the function. // @@ -2607,48 +1629,6 @@ void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, IAllocator* allowZeroAlloc = new (compiler, CMK_GC) AllowZeroAllocator(compiler->getAllocatorGC()); GcInfoEncoder* gcInfoEncoder = new (compiler, CMK_GC) GcInfoEncoder(compiler->info.compCompHnd, compiler->info.compMethodInfo, allowZeroAlloc, NOMEM); - assert(gcInfoEncoder); - - // Follow the code pattern of the x86 gc info encoder (genCreateAndStoreGCInfoJIT32). - gcInfo.gcInfoBlockHdrSave(gcInfoEncoder, codeSize, prologSize); - - // We keep the call count for the second call to gcMakeRegPtrTable() below. - unsigned callCnt = 0; - // First we figure out the encoder ID's for the stack slots and registers. - gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_ASSIGN_SLOTS, &callCnt); - // Now we've requested all the slots we'll need; "finalize" these (make more compact data structures for them). - gcInfoEncoder->FinalizeSlotIds(); - // Now we can actually use those slot ID's to declare live ranges. - gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK, &callCnt); - - gcInfoEncoder->Build(); - - // GC Encoder automatically puts the GC info in the right spot using ICorJitInfo::allocGCInfo(size_t) - // let's save the values anyway for debugging purposes - compiler->compInfoBlkAddr = gcInfoEncoder->Emit(); - compiler->compInfoBlkSize = 0; // not exposed by the GCEncoder interface -} - -#endif // _TARGET_ARM_ - -#ifdef _TARGET_ARM64_ - -/***************************************************************************** - * - * Create and record GC Info for the function. - */ -void CodeGen::genCreateAndStoreGCInfo(unsigned codeSize, - unsigned prologSize, - unsigned epilogSize DEBUGARG(void* codePtr)) -{ - genCreateAndStoreGCInfoX64(codeSize, prologSize DEBUGARG(codePtr)); -} - -void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize DEBUGARG(void* codePtr)) -{ - IAllocator* allowZeroAlloc = new (compiler, CMK_GC) AllowZeroAllocator(compiler->getAllocatorGC()); - GcInfoEncoder* gcInfoEncoder = new (compiler, CMK_GC) - GcInfoEncoder(compiler->info.compCompHnd, compiler->info.compMethodInfo, allowZeroAlloc, NOMEM); assert(gcInfoEncoder != nullptr); // Follow the code pattern of the x86 gc info encoder (genCreateAndStoreGCInfoJIT32). @@ -2666,6 +1646,8 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize // Now we can actually use those slot ID's to declare live ranges. gcInfo.gcMakeRegPtrTable(gcInfoEncoder, codeSize, prologSize, GCInfo::MAKE_REG_PTR_MODE_DO_WORK, &callCnt); +#ifdef _TARGET_ARM64_ + if (compiler->opts.compDbgEnC) { // what we have to preserve is called the "frame header" (see comments in VM\eetwain.cpp) @@ -2690,6 +1672,8 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize gcInfoEncoder->SetSizeOfEditAndContinuePreservedArea(preservedAreaSize); } +#endif // _TARGET_ARM64_ + gcInfoEncoder->Build(); // GC Encoder automatically puts the GC info in the right spot using ICorJitInfo::allocGCInfo(size_t) @@ -2698,8 +1682,6 @@ void CodeGen::genCreateAndStoreGCInfoX64(unsigned codeSize, unsigned prologSize compiler->compInfoBlkSize = 0; // not exposed by the GCEncoder interface } -#endif // _TARGET_ARM64_ - #endif // _TARGET_ARMARCH_ #endif // !LEGACY_BACKEND diff --git a/src/coreclr/src/jit/compiler.h b/src/coreclr/src/jit/compiler.h index 832ed03..1071f4d 100644 --- a/src/coreclr/src/jit/compiler.h +++ b/src/coreclr/src/jit/compiler.h @@ -9482,6 +9482,10 @@ const instruction INS_ADDC = INS_adc; const instruction INS_SUBC = INS_sbc; const instruction INS_NOT = INS_mvn; +const instruction INS_ABS = INS_vabs; +const instruction INS_ROUND = INS_invalid; +const instruction INS_SQRT = INS_vsqrt; + #endif #ifdef _TARGET_ARM64_ @@ -9503,6 +9507,10 @@ const instruction INS_ADDC = INS_adc; const instruction INS_SUBC = INS_sbc; const instruction INS_NOT = INS_mvn; +const instruction INS_ABS = INS_fabs; +const instruction INS_ROUND = INS_frintn; +const instruction INS_SQRT = INS_fsqrt; + #endif /*****************************************************************************/ diff --git a/src/coreclr/src/jit/emitarm.h b/src/coreclr/src/jit/emitarm.h index 4ec1893..ec42667 100644 --- a/src/coreclr/src/jit/emitarm.h +++ b/src/coreclr/src/jit/emitarm.h @@ -232,6 +232,13 @@ inline static bool insOptsROR(insOpts opt) return (opt == INS_OPTS_ROR); } +// Returns the number of bits used by the given 'size'. +inline static unsigned getBitWidth(emitAttr size) +{ + assert(size <= EA_8BYTE); + return (unsigned)size * BITS_PER_BYTE; +} + /************************************************************************/ /* The public entry points to output instructions */ /************************************************************************/