diff options
author | Mikhail Skvortcov <m.skvortcov@partner.samsung.com> | 2017-03-03 15:20:04 +0300 |
---|---|---|
committer | Mikhail Skvortcov <m.skvortcov@partner.samsung.com> | 2017-03-03 15:23:09 +0300 |
commit | 0d02a5a1e7c44db3d06c159a641a597b13b23178 (patch) | |
tree | 0e6abd46127420a90816dfd365f629660699bd7e /src | |
parent | 92c7f802c9d2ec5ad2d15a6bdef448883e872816 (diff) | |
download | coreclr-0d02a5a1e7c44db3d06c159a641a597b13b23178.tar.gz coreclr-0d02a5a1e7c44db3d06c159a641a597b13b23178.tar.bz2 coreclr-0d02a5a1e7c44db3d06c159a641a597b13b23178.zip |
RyuJIT/ARM32: enable codegen for long nodes.
Diffstat (limited to 'src')
-rw-r--r-- | src/jit/codegenarm.cpp | 581 | ||||
-rw-r--r-- | src/jit/codegenlinear.h | 4 | ||||
-rw-r--r-- | src/jit/gentree.cpp | 12 | ||||
-rw-r--r-- | src/jit/gentree.h | 2 | ||||
-rw-r--r-- | src/jit/lclvars.cpp | 4 | ||||
-rw-r--r-- | src/jit/lower.cpp | 52 | ||||
-rw-r--r-- | src/jit/lsra.cpp | 2 | ||||
-rw-r--r-- | src/jit/lsraarm.cpp | 148 | ||||
-rw-r--r-- | src/jit/morph.cpp | 4 |
9 files changed, 692 insertions, 117 deletions
diff --git a/src/jit/codegenarm.cpp b/src/jit/codegenarm.cpp index c0243d46f3..7b12bdd9e7 100644 --- a/src/jit/codegenarm.cpp +++ b/src/jit/codegenarm.cpp @@ -133,6 +133,98 @@ void CodeGen::genIntrinsic(GenTreePtr treeNode) genProduceReg(treeNode); } +//--------------------------------------------------------------------- +// 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->gtOp.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->AsPutArgStk()->gtSlotNum * TARGET_POINTER_SIZE; + +#ifdef DEBUG + fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(treeNode->AsPutArgStk()->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"); + } + } +} + //------------------------------------------------------------------------ // instGen_Set_Reg_To_Imm: Move an immediate value into an integer register. // @@ -271,6 +363,77 @@ void CodeGen::genSetRegToConst(regNumber targetReg, var_types targetType, GenTre } //------------------------------------------------------------------------ +// genReturn: Generates code for return statement. +// In case of struct return, delegates to the genStructReturn method. +// +// Arguments: +// treeNode - The GT_RETURN or GT_RETFILT tree node. +// +// Return Value: +// None +// +void CodeGen::genReturn(GenTreePtr treeNode) +{ + assert(treeNode->OperGet() == GT_RETURN || treeNode->OperGet() == GT_RETFILT); + GenTreePtr op1 = treeNode->gtGetOp1(); + var_types targetType = treeNode->TypeGet(); + +#ifdef DEBUG + if (targetType == TYP_VOID) + { + assert(op1 == nullptr); + } +#endif + + if (treeNode->TypeGet() == TYP_LONG) + { + assert(op1 != nullptr); + noway_assert(op1->OperGet() == GT_LONG); + GenTree* loRetVal = op1->gtGetOp1(); + GenTree* hiRetVal = op1->gtGetOp2(); + noway_assert((loRetVal->gtRegNum != REG_NA) && (hiRetVal->gtRegNum != REG_NA)); + + genConsumeReg(loRetVal); + genConsumeReg(hiRetVal); + if (loRetVal->gtRegNum != REG_LNGRET_LO) + { + inst_RV_RV(ins_Copy(targetType), REG_LNGRET_LO, loRetVal->gtRegNum, TYP_INT); + } + if (hiRetVal->gtRegNum != REG_LNGRET_HI) + { + inst_RV_RV(ins_Copy(targetType), REG_LNGRET_HI, hiRetVal->gtRegNum, TYP_INT); + } + } + else + { + if (varTypeIsStruct(treeNode)) + { + NYI_ARM("struct return"); + } + else if (targetType != TYP_VOID) + { + assert(op1 != nullptr); + noway_assert(op1->gtRegNum != REG_NA); + + // !! NOTE !! genConsumeReg will clear op1 as GC ref after it has + // consumed a reg for the operand. This is because the variable + // is dead after return. But we are issuing more instructions + // like "profiler leave callback" after this consumption. So + // if you are issuing more instructions after this point, + // remember to keep the variable live up until the new method + // exit point where it is actually dead. + genConsumeReg(op1); + + regNumber retReg = varTypeIsFloating(treeNode) ? REG_FLOATRET : REG_INTRET; + if (op1->gtRegNum != retReg) + { + inst_RV_RV(ins_Move_Extend(targetType, true), retReg, op1->gtRegNum, targetType); + } + } + } +} + +//------------------------------------------------------------------------ // genCodeForTreeNode Generate code for a single node in the tree. // // Preconditions: @@ -518,10 +681,17 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) // case is handled separately. if (data->gtSkipReloadOrCopy()->IsMultiRegCall()) { - NYI_ARM("st.lclVar multi-reg value"); + genMultiRegCallStoreToLocal(treeNode); + break; } else { + if (treeNode->TypeGet() == TYP_LONG) + { + genStoreLongLclVar(treeNode); + break; + } + genConsumeRegs(data); regNumber dataReg = REG_NA; @@ -576,26 +746,8 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) __fallthrough; case GT_RETURN: - { - GenTreePtr op1 = treeNode->gtOp.gtOp1; - if (targetType == TYP_VOID) - { - assert(op1 == nullptr); - break; - } - assert(op1 != nullptr); - op1 = op1->gtEffectiveVal(); - - NYI_IF(op1->gtRegNum == REG_NA, "GT_RETURN: return of a value not in register"); - genConsumeReg(op1); - - regNumber retReg = varTypeIsFloating(op1) ? REG_FLOATRET : REG_INTRET; - if (op1->gtRegNum != retReg) - { - inst_RV_RV(ins_Move_Extend(targetType, true), retReg, op1->gtRegNum, targetType); - } - } - break; + genReturn(treeNode); + break; case GT_LEA: { @@ -693,6 +845,22 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) // vmrs with register 0xf has special meaning of transferring flags emit->emitIns_R(INS_vmrs, EA_4BYTE, REG_R15); } + else if (varTypeIsLong(op1)) + { +#ifdef DEBUG + // The result of an unlowered long compare on a 32-bit target must either be + // a) materialized into a register, or + // b) unused. + // + // A long compare that has a result that is used but not materialized into a register should + // have been handled by Lowering::LowerCompare. + + LIR::Use use; + assert((treeNode->gtRegNum != REG_NA) || !LIR::AsRange(compiler->compCurBB).TryGetUse(treeNode, &use)); +#endif + genCompareLong(treeNode); + break; + } else { var_types op1Type = op1->TypeGet(); @@ -743,6 +911,19 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) } break; + case GT_JCC: + { + GenTreeJumpCC* jcc = treeNode->AsJumpCC(); + + assert(compiler->compCurBB->bbJumpKind == BBJ_COND); + + CompareKind compareKind = ((jcc->gtFlags & GTF_UNSIGNED) != 0) ? CK_UNSIGNED : CK_SIGNED; + emitJumpKind jumpKind = genJumpKindForOper(jcc->gtCondition, compareKind); + + inst_JMP(jumpKind, compiler->compCurBB->bbJumpDest); + } + break; + case GT_RETURNTRAP: { // this is nothing but a conditional call to CORINFO_HELP_STOP_FOR_GC @@ -860,33 +1041,8 @@ void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) break; case GT_PUTARG_STK: - { - NYI_IF(targetType == TYP_STRUCT, "GT_PUTARG_STK: struct support not implemented"); - - // Get argument offset on stack. - // 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. - int argOffset = treeNode->AsPutArgStk()->gtSlotNum * TARGET_POINTER_SIZE; -#ifdef DEBUG - fgArgTabEntryPtr curArgTabEntry = compiler->gtArgEntryByNode(treeNode->AsPutArgStk()->gtCall, treeNode); - assert(curArgTabEntry); - assert(argOffset == (int)curArgTabEntry->slotNum * TARGET_POINTER_SIZE); -#endif - - GenTreePtr data = treeNode->gtOp.gtOp1->gtEffectiveVal(); - if (data->isContained()) - { - emit->emitIns_S_I(ins_Store(targetType), emitTypeSize(targetType), compiler->lvaOutgoingArgSpaceVar, - argOffset, (int)data->AsIntConCommon()->IconValue()); - } - else - { - genConsumeReg(data); - emit->emitIns_S_R(ins_Store(targetType), emitTypeSize(targetType), data->gtRegNum, - compiler->lvaOutgoingArgSpaceVar, argOffset); - } - } - break; + genPutArgStk(treeNode->AsPutArgStk()); + break; case GT_PUTARG_REG: { @@ -1032,6 +1188,69 @@ void CodeGen::genLockedInstructions(GenTreeOp* treeNode) NYI("genLockedInstructions"); } +//---------------------------------------------------------------------------------- +// 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; +} + //------------------------------------------------------------------------ // genTableBasedSwitch: generate code for a switch statement based on a table of ip-relative offsets // @@ -1041,8 +1260,6 @@ void CodeGen::genTableBasedSwitch(GenTree* treeNode) regNumber idxReg = treeNode->gtOp.gtOp1->gtRegNum; regNumber baseReg = treeNode->gtOp.gtOp2->gtRegNum; - regNumber tmpReg = genRegNumFromMask(treeNode->gtRsvdRegs); - getEmitter()->emitIns_R_ARX(INS_ldr, EA_4BYTE, REG_PC, baseReg, idxReg, TARGET_POINTER_SIZE, 0); } @@ -1369,12 +1586,14 @@ void CodeGen::genCallInstruction(GenTreePtr node) } // Determine return value size(s). - ReturnTypeDesc* pRetTypeDesc = call->GetReturnTypeDesc(); - emitAttr retSize = EA_PTRSIZE; + ReturnTypeDesc* pRetTypeDesc = call->GetReturnTypeDesc(); + emitAttr retSize = EA_PTRSIZE; + emitAttr secondRetSize = EA_UNKNOWN; if (call->HasMultiRegRetVal()) { - NYI_ARM("has multi reg ret val"); + retSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(0)); + secondRetSize = emitTypeSize(pRetTypeDesc->GetReturnRegType(1)); } else { @@ -1565,6 +1784,158 @@ void CodeGen::genLeaInstruction(GenTreeAddrMode* lea) } //------------------------------------------------------------------------ +// genCompareLong: Generate code for comparing two longs when the result of the compare +// is manifested in a register. +// +// Arguments: +// treeNode - the compare tree +// +// Return Value: +// None. +// +// Comments: +// For long compares, we need to compare the high parts of operands first, then the low parts. +// If the high compare is false, we do not need to compare the low parts. For less than and +// greater than, if the high compare is true, we can assume the entire compare is true. +// +void CodeGen::genCompareLong(GenTreePtr treeNode) +{ + assert(treeNode->OperIsCompare()); + + GenTreeOp* tree = treeNode->AsOp(); + GenTreePtr op1 = tree->gtOp1; + GenTreePtr op2 = tree->gtOp2; + + assert(varTypeIsLong(op1->TypeGet())); + assert(varTypeIsLong(op2->TypeGet())); + + regNumber targetReg = treeNode->gtRegNum; + + genConsumeOperands(tree); + + GenTreePtr loOp1 = op1->gtGetOp1(); + GenTreePtr hiOp1 = op1->gtGetOp2(); + GenTreePtr loOp2 = op2->gtGetOp1(); + GenTreePtr hiOp2 = op2->gtGetOp2(); + + // Create compare for the high parts + instruction ins = INS_cmp; + var_types cmpType = TYP_INT; + emitAttr cmpAttr = emitTypeSize(cmpType); + + // Emit the compare instruction + getEmitter()->emitInsBinary(ins, cmpAttr, hiOp1, hiOp2); + + // If the result is not being materialized in a register, we're done. + if (targetReg == REG_NA) + { + return; + } + + BasicBlock* labelTrue = genCreateTempLabel(); + BasicBlock* labelFalse = genCreateTempLabel(); + BasicBlock* labelNext = genCreateTempLabel(); + + genJccLongHi(tree->gtOper, labelTrue, labelFalse, tree->IsUnsigned()); + getEmitter()->emitInsBinary(ins, cmpAttr, loOp1, loOp2); + genJccLongLo(tree->gtOper, labelTrue, labelFalse); + + genDefineTempLabel(labelFalse); + getEmitter()->emitIns_R_I(INS_mov, emitActualTypeSize(tree->gtType), tree->gtRegNum, 0); + getEmitter()->emitIns_J(INS_b, labelNext); + + genDefineTempLabel(labelTrue); + getEmitter()->emitIns_R_I(INS_mov, emitActualTypeSize(tree->gtType), tree->gtRegNum, 1); + + genDefineTempLabel(labelNext); + + genProduceReg(tree); +} + +void CodeGen::genJccLongHi(genTreeOps cmp, BasicBlock* jumpTrue, BasicBlock* jumpFalse, bool isUnsigned) +{ + if (cmp != GT_NE) + { + jumpFalse->bbFlags |= BBF_JMP_TARGET | BBF_HAS_LABEL; + } + + switch (cmp) + { + case GT_EQ: + inst_JMP(EJ_ne, jumpFalse); + break; + + case GT_NE: + inst_JMP(EJ_ne, jumpTrue); + break; + + case GT_LT: + case GT_LE: + if (isUnsigned) + { + inst_JMP(EJ_hi, jumpFalse); + inst_JMP(EJ_lo, jumpTrue); + } + else + { + inst_JMP(EJ_gt, jumpFalse); + inst_JMP(EJ_lt, jumpTrue); + } + break; + + case GT_GE: + case GT_GT: + if (isUnsigned) + { + inst_JMP(EJ_lo, jumpFalse); + inst_JMP(EJ_hi, jumpTrue); + } + else + { + inst_JMP(EJ_lt, jumpFalse); + inst_JMP(EJ_gt, jumpTrue); + } + break; + + default: + noway_assert(!"expected a comparison operator"); + } +} + +void CodeGen::genJccLongLo(genTreeOps cmp, BasicBlock* jumpTrue, BasicBlock* jumpFalse) +{ + switch (cmp) + { + case GT_EQ: + inst_JMP(EJ_eq, jumpTrue); + break; + + case GT_NE: + inst_JMP(EJ_ne, jumpTrue); + break; + + case GT_LT: + inst_JMP(EJ_lo, jumpTrue); + break; + + case GT_LE: + inst_JMP(EJ_ls, jumpTrue); + break; + + case GT_GE: + inst_JMP(EJ_hs, jumpTrue); + break; + + case GT_GT: + inst_JMP(EJ_hi, jumpTrue); + break; + + default: + noway_assert(!"expected comparison"); + } +} + +//------------------------------------------------------------------------ // genSetRegToCond: Generate code to materialize a condition into a register. // // Arguments: @@ -1605,6 +1976,52 @@ void CodeGen::genSetRegToCond(regNumber dstReg, GenTreePtr tree) } //------------------------------------------------------------------------ +// genLongToIntCast: Generate code for long to int casts. +// +// Arguments: +// cast - The GT_CAST node +// +// Return Value: +// None. +// +// Assumptions: +// The cast node and its sources (via GT_LONG) must have been assigned registers. +// The destination cannot be a floating point type or a small integer type. +// +void CodeGen::genLongToIntCast(GenTree* cast) +{ + assert(cast->OperGet() == GT_CAST); + + GenTree* src = cast->gtGetOp1(); + noway_assert(src->OperGet() == GT_LONG); + + genConsumeRegs(src); + + var_types srcType = ((cast->gtFlags & GTF_UNSIGNED) != 0) ? TYP_ULONG : TYP_LONG; + var_types dstType = cast->CastToType(); + regNumber loSrcReg = src->gtGetOp1()->gtRegNum; + regNumber hiSrcReg = src->gtGetOp2()->gtRegNum; + regNumber dstReg = cast->gtRegNum; + + assert((dstType == TYP_INT) || (dstType == TYP_UINT)); + assert(genIsValidIntReg(loSrcReg)); + assert(genIsValidIntReg(hiSrcReg)); + assert(genIsValidIntReg(dstReg)); + + if (cast->gtOverflow()) + { + NYI("genLongToIntCast: overflow check"); + } + + if (dstReg != loSrcReg) + { + inst_RV_RV(INS_mov, dstReg, loSrcReg, TYP_INT, EA_4BYTE); + } + + genProduceReg(cast); +} + +//------------------------------------------------------------------------ // genIntToIntCast: Generate code for an integer cast // // Arguments: @@ -1630,6 +2047,12 @@ void CodeGen::genIntToIntCast(GenTreePtr treeNode) emitAttr movSize = emitActualTypeSize(dstType); bool movRequired = false; + if (varTypeIsLong(srcType)) + { + genLongToIntCast(treeNode); + return; + } + regNumber targetReg = treeNode->gtRegNum; regNumber sourceReg = castOp->gtRegNum; @@ -2010,6 +2433,58 @@ void CodeGen::genEmitHelperCall(unsigned helper, int argSize, emitAttr retSize, regTracker.rsTrashRegsForGCInterruptability(); } +//------------------------------------------------------------------------ +// genStoreLongLclVar: Generate code to store a non-enregistered long lclVar +// +// Arguments: +// treeNode - A TYP_LONG lclVar node. +// +// Return Value: +// None. +// +// Assumptions: +// 'treeNode' must be a TYP_LONG lclVar node for a lclVar that has NOT been promoted. +// Its operand must be a GT_LONG node. +// +void CodeGen::genStoreLongLclVar(GenTree* treeNode) +{ + emitter* emit = getEmitter(); + + GenTreeLclVarCommon* lclNode = treeNode->AsLclVarCommon(); + unsigned lclNum = lclNode->gtLclNum; + LclVarDsc* varDsc = &(compiler->lvaTable[lclNum]); + assert(varDsc->TypeGet() == TYP_LONG); + assert(!varDsc->lvPromoted); + GenTreePtr op1 = treeNode->gtOp.gtOp1; + noway_assert(op1->OperGet() == GT_LONG || op1->OperGet() == GT_MUL_LONG); + genConsumeRegs(op1); + + if (op1->OperGet() == GT_LONG) + { + // Definitions of register candidates will have been lowered to 2 int lclVars. + assert(!treeNode->InReg()); + + GenTreePtr loVal = op1->gtGetOp1(); + GenTreePtr hiVal = op1->gtGetOp2(); + + // NYI: Contained immediates. + NYI_IF((loVal->gtRegNum == REG_NA) || (hiVal->gtRegNum == REG_NA), + "Store of long lclVar with contained immediate"); + + emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, loVal->gtRegNum, lclNum, 0); + emit->emitIns_S_R(ins_Store(TYP_INT), EA_4BYTE, hiVal->gtRegNum, lclNum, genTypeSize(TYP_INT)); + } + else if (op1->OperGet() == GT_MUL_LONG) + { + assert((op1->gtFlags & GTF_MUL_64RSLT) != 0); + + // Stack store + getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_LO, lclNum, 0); + getEmitter()->emitIns_S_R(ins_Store(TYP_INT), emitTypeSize(TYP_INT), REG_LNGRET_HI, lclNum, + genTypeSize(TYP_INT)); + } +} + #endif // _TARGET_ARM_ #endif // !LEGACY_BACKEND diff --git a/src/jit/codegenlinear.h b/src/jit/codegenlinear.h index c8a5af657a..af545fe782 100644 --- a/src/jit/codegenlinear.h +++ b/src/jit/codegenlinear.h @@ -57,6 +57,10 @@ void genCompareInt(GenTreePtr treeNode); #if !defined(_TARGET_64BIT_) void genCompareLong(GenTreePtr treeNode); +#if defined(_TARGET_ARM_) +void genJccLongHi(genTreeOps cmp, BasicBlock* jumpTrue, BasicBlock* jumpFalse, bool isUnsigned = false); +void genJccLongLo(genTreeOps cmp, BasicBlock* jumpTrue, BasicBlock* jumpFalse); +#endif // defined(_TARGET_ARM_) #endif #ifdef FEATURE_SIMD diff --git a/src/jit/gentree.cpp b/src/jit/gentree.cpp index 1dc83cede2..947314f4c7 100644 --- a/src/jit/gentree.cpp +++ b/src/jit/gentree.cpp @@ -6927,7 +6927,7 @@ GenTreeCall* Compiler::gtNewCallNode( // Initialize spill flags of gtOtherRegs node->ClearOtherRegFlags(); -#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) +#if (defined(_TARGET_X86_) || defined(_TARGET_ARM_)) && !defined(LEGACY_BACKEND) // Initialize the multi-reg long return info if necessary if (varTypeIsLong(node)) { @@ -6941,7 +6941,7 @@ GenTreeCall* Compiler::gtNewCallNode( // must be a long returned in two registers assert(retTypeDesc->GetReturnRegCount() == 2); } -#endif // defined(_TARGET_X86_) && !defined(_LEGACY_BACKEND_) +#endif // (defined(_TARGET_X86_) || defined(_TARGET_ARM_)) && !defined(_LEGACY_BACKEND_) return node; } @@ -17023,7 +17023,7 @@ void ReturnTypeDesc::InitializeStructReturnType(Compiler* comp, CORINFO_CLASS_HA // void ReturnTypeDesc::InitializeLongReturnType(Compiler* comp) { -#if defined(_TARGET_X86_) +#if defined(_TARGET_X86_) || defined(_TARGET_ARM_) // Setups up a ReturnTypeDesc for returning a long using two registers // @@ -17031,11 +17031,11 @@ void ReturnTypeDesc::InitializeLongReturnType(Compiler* comp) m_regType[0] = TYP_INT; m_regType[1] = TYP_INT; -#else // not _TARGET_X86_ +#else // not (_TARGET_X86_ or _TARGET_ARM_) m_regType[0] = TYP_LONG; -#endif // _TARGET_X86_ +#endif // _TARGET_X86_ or _TARGET_ARM_ #ifdef DEBUG m_inited = true; @@ -17111,7 +17111,7 @@ regNumber ReturnTypeDesc::GetABIReturnReg(unsigned idx) } } -#elif defined(_TARGET_X86_) +#elif defined(_TARGET_X86_) || defined(_TARGET_ARM_) if (idx == 0) { diff --git a/src/jit/gentree.h b/src/jit/gentree.h index cd1034de21..a85ca20eba 100644 --- a/src/jit/gentree.h +++ b/src/jit/gentree.h @@ -3398,7 +3398,7 @@ struct GenTreeCall final : public GenTree // bool HasMultiRegRetVal() const { -#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) +#if (defined(_TARGET_X86_) || defined(_TARGET_ARM_)) && !defined(LEGACY_BACKEND) // LEGACY_BACKEND does not use multi reg returns for calls with long return types return varTypeIsLong(gtType); #elif FEATURE_MULTIREG_RET diff --git a/src/jit/lclvars.cpp b/src/jit/lclvars.cpp index 6e4465b2f9..3a5cfdf88b 100644 --- a/src/jit/lclvars.cpp +++ b/src/jit/lclvars.cpp @@ -5998,11 +5998,15 @@ void Compiler::lvaAssignFrameOffsetsToPromotedStructs() // if (varDsc->lvIsStructField #ifndef UNIX_AMD64_ABI +#if !defined(_TARGET_ARM_) || defined(LEGACY_BACKEND) + // Non-legacy ARM: lo/hi parts of a promoted long arg need to be updated. + // For System V platforms there is no outgoing args space. // A register passed struct arg is homed on the stack in a separate local var. // The offset of these structs is already calculated in lvaAssignVirtualFrameOffsetToArg methos. // Make sure the code below is not executed for these structs and the offset is not changed. && !varDsc->lvIsParam +#endif // !defined(_TARGET_ARM_) || defined(LEGACY_BACKEND) #endif // UNIX_AMD64_ABI ) { diff --git a/src/jit/lower.cpp b/src/jit/lower.cpp index 8869895561..2a65a949fd 100644 --- a/src/jit/lower.cpp +++ b/src/jit/lower.cpp @@ -1124,25 +1124,41 @@ void Lowering::LowerArg(GenTreeCall* call, GenTreePtr* ppArg) { if (isReg) { - NYI("Lowering of long register argument"); - } + noway_assert(arg->OperGet() == GT_LONG); + assert(info->numRegs == 2); + + GenTreePtr argLo = arg->gtGetOp1(); + GenTreePtr argHi = arg->gtGetOp2(); + + GenTreeFieldList* fieldList = new (comp, GT_FIELD_LIST) GenTreeFieldList(argLo, 0, TYP_INT, nullptr); + (void)new (comp, GT_FIELD_LIST) GenTreeFieldList(argHi, 4, TYP_INT, fieldList); + + putArg = NewPutArg(call, fieldList, info, TYP_VOID); - // For longs, we will replace the GT_LONG with a GT_FIELD_LIST, and put that under a PUTARG_STK. - // Although the hi argument needs to be pushed first, that will be handled by the general case, - // in which the fields will be reversed. - noway_assert(arg->OperGet() == GT_LONG); - assert(info->numSlots == 2); - GenTreePtr argLo = arg->gtGetOp1(); - GenTreePtr argHi = arg->gtGetOp2(); - GenTreeFieldList* fieldList = new (comp, GT_FIELD_LIST) GenTreeFieldList(argLo, 0, TYP_INT, nullptr); - // Only the first fieldList node (GTF_FIELD_LIST_HEAD) is in the instruction sequence. - (void)new (comp, GT_FIELD_LIST) GenTreeFieldList(argHi, 4, TYP_INT, fieldList); - putArg = NewPutArg(call, fieldList, info, TYP_VOID); - - // We can't call ReplaceArgWithPutArgOrCopy here because it presumes that we are keeping the original arg. - BlockRange().InsertBefore(arg, fieldList, putArg); - BlockRange().Remove(arg); - *ppArg = putArg; + BlockRange().InsertBefore(arg, putArg); + BlockRange().Remove(arg); + *ppArg = fieldList; + info->node = fieldList; + } + else + { + // For longs, we will replace the GT_LONG with a GT_FIELD_LIST, and put that under a PUTARG_STK. + // Although the hi argument needs to be pushed first, that will be handled by the general case, + // in which the fields will be reversed. + noway_assert(arg->OperGet() == GT_LONG); + assert(info->numSlots == 2); + GenTreePtr argLo = arg->gtGetOp1(); + GenTreePtr argHi = arg->gtGetOp2(); + GenTreeFieldList* fieldList = new (comp, GT_FIELD_LIST) GenTreeFieldList(argLo, 0, TYP_INT, nullptr); + // Only the first fieldList node (GTF_FIELD_LIST_HEAD) is in the instruction sequence. + (void)new (comp, GT_FIELD_LIST) GenTreeFieldList(argHi, 4, TYP_INT, fieldList); + putArg = NewPutArg(call, fieldList, info, TYP_VOID); + + // We can't call ReplaceArgWithPutArgOrCopy here because it presumes that we are keeping the original arg. + BlockRange().InsertBefore(arg, fieldList, putArg); + BlockRange().Remove(arg); + *ppArg = putArg; + } } else #endif // !defined(_TARGET_64BIT_) diff --git a/src/jit/lsra.cpp b/src/jit/lsra.cpp index ab622b6e0e..45af8326d4 100644 --- a/src/jit/lsra.cpp +++ b/src/jit/lsra.cpp @@ -4005,8 +4005,6 @@ void LinearScan::buildRefPositionsForNode(GenTree* tree, #if defined(_TARGET_AMD64_) // Multi-reg call node is the only node that could produce multi-reg value assert(produce <= 1 || (tree->IsMultiRegCall() && produce == MAX_RET_REG_COUNT)); -#elif defined(_TARGET_ARM_) - assert(!varTypeIsMultiReg(tree->TypeGet())); #endif // _TARGET_xxx_ // Add kill positions before adding def positions diff --git a/src/jit/lsraarm.cpp b/src/jit/lsraarm.cpp index 7817f4f5de..22164762d9 100644 --- a/src/jit/lsraarm.cpp +++ b/src/jit/lsraarm.cpp @@ -81,6 +81,24 @@ void Lowering::TreeNodeInfoInitCmp(GenTreePtr tree) info->srcCount = 2; info->dstCount = 1; + + GenTreePtr op1 = tree->gtOp.gtOp1; + GenTreePtr op2 = tree->gtOp.gtOp2; + var_types op1Type = op1->TypeGet(); + var_types op2Type = op2->TypeGet(); + + // Long compares will consume GT_LONG nodes, each of which produces two results. + // Thus for each long operand there will be an additional source. + // TODO-ARM-CQ: Mark hiOp2 and loOp2 as contained if it is a constant. + if (varTypeIsLong(op1Type)) + { + info->srcCount++; + } + if (varTypeIsLong(op2Type)) + { + info->srcCount++; + } + CheckImmedAndMakeContained(tree, tree->gtOp.gtOp2); } @@ -290,42 +308,76 @@ void Lowering::TreeNodeInfoInitReturn(GenTree* tree) LinearScan* l = m_lsra; Compiler* compiler = comp; - GenTree* op1 = tree->gtGetOp1(); - regMaskTP useCandidates = RBM_NONE; - - info->srcCount = (tree->TypeGet() == TYP_VOID) ? 0 : 1; - info->dstCount = 0; - - if (varTypeIsStruct(tree)) + if (tree->TypeGet() == TYP_LONG) { - NYI_ARM("struct return"); + GenTree* op1 = tree->gtGetOp1(); + noway_assert(op1->OperGet() == GT_LONG); + GenTree* loVal = op1->gtGetOp1(); + GenTree* hiVal = op1->gtGetOp2(); + info->srcCount = 2; + loVal->gtLsraInfo.setSrcCandidates(l, RBM_LNGRET_LO); + hiVal->gtLsraInfo.setSrcCandidates(l, RBM_LNGRET_HI); + info->dstCount = 0; } else { - // Non-struct type return - determine useCandidates - switch (tree->TypeGet()) + GenTree* op1 = tree->gtGetOp1(); + regMaskTP useCandidates = RBM_NONE; + + info->srcCount = (tree->TypeGet() == TYP_VOID) ? 0 : 1; + info->dstCount = 0; + + if (varTypeIsStruct(tree)) { - case TYP_VOID: - useCandidates = RBM_NONE; - break; - case TYP_FLOAT: - useCandidates = RBM_FLOATRET; - break; - case TYP_DOUBLE: - useCandidates = RBM_DOUBLERET; - break; - case TYP_LONG: - useCandidates = RBM_LNGRET; - break; - default: - useCandidates = RBM_INTRET; - break; + // op1 has to be either an lclvar or a multi-reg returning call + if (op1->OperGet() == GT_LCL_VAR) + { + GenTreeLclVarCommon* lclVarCommon = op1->AsLclVarCommon(); + LclVarDsc* varDsc = &(compiler->lvaTable[lclVarCommon->gtLclNum]); + assert(varDsc->lvIsMultiRegRet); + + // Mark var as contained if not enregistrable. + if (!varTypeIsEnregisterableStruct(op1)) + { + MakeSrcContained(tree, op1); + } + } + else + { + noway_assert(op1->IsMultiRegCall()); + + ReturnTypeDesc* retTypeDesc = op1->AsCall()->GetReturnTypeDesc(); + info->srcCount = retTypeDesc->GetReturnRegCount(); + useCandidates = retTypeDesc->GetABIReturnRegs(); + } + } + else + { + // Non-struct type return - determine useCandidates + switch (tree->TypeGet()) + { + case TYP_VOID: + useCandidates = RBM_NONE; + break; + case TYP_FLOAT: + useCandidates = RBM_FLOATRET; + break; + case TYP_DOUBLE: + useCandidates = RBM_DOUBLERET; + break; + case TYP_LONG: + useCandidates = RBM_LNGRET; + break; + default: + useCandidates = RBM_INTRET; + break; + } } - } - if (useCandidates != RBM_NONE) - { - tree->gtOp.gtOp1->gtLsraInfo.setSrcCandidates(l, useCandidates); + if (useCandidates != RBM_NONE) + { + tree->gtOp.gtOp1->gtLsraInfo.setSrcCandidates(l, useCandidates); + } } } @@ -656,7 +708,6 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) DISPNODE(tree); NYI_IF(tree->TypeGet() == TYP_STRUCT, "lowering struct"); - NYI_IF(tree->TypeGet() == TYP_LONG, "lowering long"); NYI_IF(tree->TypeGet() == TYP_DOUBLE, "lowering double"); switch (tree->OperGet()) @@ -666,7 +717,14 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) case GT_STORE_LCL_FLD: case GT_STORE_LCL_VAR: - info->srcCount = 1; + if (tree->gtGetOp1()->OperGet() == GT_LONG) + { + info->srcCount = 2; + } + else + { + info->srcCount = 1; + } info->dstCount = 0; LowerStoreLoc(tree->AsLclVarCommon()); TreeNodeInfoInitStoreLoc(tree->AsLclVarCommon()); @@ -737,6 +795,12 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) } #endif // DEBUG + if (varTypeIsLong(castOpType)) + { + noway_assert(castOp->OperGet() == GT_LONG); + info->srcCount = 2; + } + if (tree->gtOverflow()) { NYI_ARM("overflow checks"); @@ -769,9 +833,8 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) break; case GT_SWITCH_TABLE: - info->srcCount = 2; - info->internalIntCount = 1; - info->dstCount = 0; + info->srcCount = 2; + info->dstCount = 0; break; case GT_ASG: @@ -844,6 +907,21 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) info->dstCount = 0; break; + case GT_LONG: + if ((tree->gtLIRFlags & LIR::Flags::IsUnusedValue) != 0) + { + // An unused GT_LONG node needs to consume its sources. + info->srcCount = 2; + } + else + { + // Passthrough + info->srcCount = 0; + } + + info->dstCount = 0; + break; + case GT_CNS_DBL: info->srcCount = 0; info->dstCount = 1; @@ -1032,7 +1110,6 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) unsigned varNum = tree->gtLclVarCommon.gtLclNum; LclVarDsc* varDsc = comp->lvaTable + varNum; NYI_IF(varTypeIsStruct(varDsc), "lowering struct var"); - NYI_IF(varTypeIsLong(varDsc), "lowering long var"); } case GT_PHYSREG: case GT_CLS_VAR_ADDR: @@ -1042,6 +1119,7 @@ void Lowering::TreeNodeInfoInit(GenTree* tree) case GT_PUTARG_STK: case GT_LABEL: case GT_PINVOKE_PROLOG: + case GT_JCC: info->dstCount = tree->IsValue() ? 1 : 0; if (kind & (GTK_CONST | GTK_LEAF)) { diff --git a/src/jit/morph.cpp b/src/jit/morph.cpp index 29d93253b3..3eb565a192 100644 --- a/src/jit/morph.cpp +++ b/src/jit/morph.cpp @@ -92,7 +92,7 @@ GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree, int helper, GenTreeA tree->gtCall.gtEntryPoint.addr = nullptr; #endif -#if defined(_TARGET_X86_) && !defined(LEGACY_BACKEND) +#if (defined(_TARGET_X86_) || defined(_TARGET_ARM_)) && !defined(LEGACY_BACKEND) if (varTypeIsLong(tree)) { GenTreeCall* callNode = tree->AsCall(); @@ -101,7 +101,7 @@ GenTreePtr Compiler::fgMorphIntoHelperCall(GenTreePtr tree, int helper, GenTreeA retTypeDesc->InitializeLongReturnType(this); callNode->ClearOtherRegs(); } -#endif +#endif // _TARGET_XXX_ /* Perform the morphing */ |