diff options
Diffstat (limited to 'src/jit/codegenarm.cpp')
-rw-r--r-- | src/jit/codegenarm.cpp | 1313 |
1 files changed, 549 insertions, 764 deletions
diff --git a/src/jit/codegenarm.cpp b/src/jit/codegenarm.cpp index c28b27bf9b..40371e358c 100644 --- a/src/jit/codegenarm.cpp +++ b/src/jit/codegenarm.cpp @@ -259,6 +259,11 @@ void CodeGen::genReturn(GenTreePtr treeNode) GenTreePtr op1 = treeNode->gtGetOp1(); var_types targetType = treeNode->TypeGet(); + // A void GT_RETFILT is the end of a finally. For non-void filter returns we need to load the result in the return + // register, if it's not already there. The processing is the same as GT_RETURN. For filters, the IL spec says the + // result is type int32. Further, the only legal values are 0 or 1; the use of other values is "undefined". + assert(!treeNode->OperIs(GT_RETFILT) || (targetType == TYP_VOID) || (targetType == TYP_INT)); + #ifdef DEBUG if (targetType == TYP_VOID) { @@ -315,741 +320,6 @@ void CodeGen::genReturn(GenTreePtr treeNode) } //------------------------------------------------------------------------ -// genCodeForTreeNode Generate code for a single node in the tree. -// -// Preconditions: -// All operands have been evaluated. -// -void CodeGen::genCodeForTreeNode(GenTreePtr treeNode) -{ - regNumber targetReg = treeNode->gtRegNum; - var_types targetType = treeNode->TypeGet(); - emitter* emit = getEmitter(); - -#ifdef DEBUG - lastConsumedNode = nullptr; - if (compiler->verbose) - { - unsigned seqNum = treeNode->gtSeqNum; // Useful for setting a conditional break in Visual Studio - compiler->gtDispLIRNode(treeNode, "Generating: "); - } -#endif - - // contained nodes are part of their parents for codegen purposes - // ex : immediates, most LEAs - if (treeNode->isContained()) - { - return; - } - - switch (treeNode->gtOper) - { - case GT_LCLHEAP: - genLclHeap(treeNode); - break; - - case GT_CNS_INT: - case GT_CNS_DBL: - genSetRegToConst(targetReg, targetType, treeNode); - genProduceReg(treeNode); - break; - - case GT_NOT: - assert(!varTypeIsFloating(targetType)); - - __fallthrough; - - case GT_NEG: - { - instruction ins = genGetInsForOper(treeNode->OperGet(), targetType); - - // The arithmetic node must be sitting in a register (since it's not contained) - assert(!treeNode->isContained()); - // The dst can only be a register. - assert(targetReg != REG_NA); - - GenTreePtr operand = treeNode->gtGetOp1(); - assert(!operand->isContained()); - // The src must be a register. - regNumber operandReg = genConsumeReg(operand); - - if (ins == INS_vneg) - { - getEmitter()->emitIns_R_R(ins, emitTypeSize(treeNode), targetReg, operandReg); - } - else - { - getEmitter()->emitIns_R_R_I(ins, emitTypeSize(treeNode), targetReg, operandReg, 0); - } - } - genProduceReg(treeNode); - break; - - case GT_OR: - case GT_XOR: - case GT_AND: - assert(varTypeIsIntegralOrI(treeNode)); - __fallthrough; - - case GT_ADD_LO: - case GT_ADD_HI: - case GT_SUB_LO: - case GT_SUB_HI: - case GT_ADD: - case GT_SUB: - case GT_MUL: - genConsumeOperands(treeNode->AsOp()); - genCodeForBinary(treeNode); - break; - - case GT_LSH: - case GT_RSH: - case GT_RSZ: - case GT_ROR: - genCodeForShift(treeNode); - break; - - case GT_LSH_HI: - case GT_RSH_LO: - genCodeForShiftLong(treeNode); - break; - - case GT_CAST: - // Cast is never contained (?) - noway_assert(targetReg != REG_NA); - - if (varTypeIsFloating(targetType) && varTypeIsFloating(treeNode->gtOp.gtOp1)) - { - // Casts float/double <--> double/float - genFloatToFloatCast(treeNode); - } - else if (varTypeIsFloating(treeNode->gtOp.gtOp1)) - { - // Casts float/double --> int32/int64 - genFloatToIntCast(treeNode); - } - else if (varTypeIsFloating(targetType)) - { - // Casts int32/uint32/int64/uint64 --> float/double - genIntToFloatCast(treeNode); - } - else - { - // Casts int <--> int - genIntToIntCast(treeNode); - } - // The per-case functions call genProduceReg() - break; - - case GT_LCL_VAR: - { - GenTreeLclVarCommon* lcl = treeNode->AsLclVarCommon(); - // lcl_vars are not defs - assert((treeNode->gtFlags & GTF_VAR_DEF) == 0); - - bool isRegCandidate = compiler->lvaTable[lcl->gtLclNum].lvIsRegCandidate(); - - if (isRegCandidate && !(treeNode->gtFlags & GTF_VAR_DEATH)) - { - assert((treeNode->InReg()) || (treeNode->gtFlags & GTF_SPILLED)); - } - - // If this is a register candidate that has been spilled, genConsumeReg() will - // reload it at the point of use. Otherwise, if it's not in a register, we load it here. - - if (!treeNode->InReg() && !(treeNode->gtFlags & GTF_SPILLED)) - { - assert(!isRegCandidate); - emit->emitIns_R_S(ins_Load(treeNode->TypeGet()), emitTypeSize(treeNode), treeNode->gtRegNum, - lcl->gtLclNum, 0); - genProduceReg(treeNode); - } - } - break; - - case GT_LCL_FLD_ADDR: - case GT_LCL_VAR_ADDR: - { - // Address of a local var. This by itself should never be allocated a register. - // If it is worth storing the address in a register then it should be cse'ed into - // a temp and that would be allocated a register. - noway_assert(targetType == TYP_BYREF); - noway_assert(!treeNode->InReg()); - - inst_RV_TT(INS_lea, targetReg, treeNode, 0, EA_BYREF); - } - genProduceReg(treeNode); - break; - - case GT_LCL_FLD: - { - NYI_IF(targetType == TYP_STRUCT, "GT_LCL_FLD: struct load local field not supported"); - NYI_IF(treeNode->gtRegNum == REG_NA, "GT_LCL_FLD: load local field not into a register is not supported"); - - emitAttr size = emitTypeSize(targetType); - unsigned offs = treeNode->gtLclFld.gtLclOffs; - unsigned varNum = treeNode->gtLclVarCommon.gtLclNum; - assert(varNum < compiler->lvaCount); - - if (varTypeIsFloating(targetType)) - { - if (treeNode->InReg()) - { - NYI("GT_LCL_FLD with reg-to-reg floating point move"); - } - else - { - emit->emitIns_R_S(ins_Load(targetType), size, targetReg, varNum, offs); - } - } - else - { - emit->emitIns_R_S(ins_Move_Extend(targetType, treeNode->InReg()), size, targetReg, varNum, offs); - } - } - genProduceReg(treeNode); - break; - - case GT_STORE_LCL_FLD: - { - noway_assert(targetType != TYP_STRUCT); - - // record the offset - unsigned offset = treeNode->gtLclFld.gtLclOffs; - - // We must have a stack store with GT_STORE_LCL_FLD - noway_assert(!treeNode->InReg()); - noway_assert(targetReg == REG_NA); - - GenTreeLclVarCommon* varNode = treeNode->AsLclVarCommon(); - unsigned varNum = varNode->gtLclNum; - assert(varNum < compiler->lvaCount); - LclVarDsc* varDsc = &(compiler->lvaTable[varNum]); - - // Ensure that lclVar nodes are typed correctly. - assert(!varDsc->lvNormalizeOnStore() || targetType == genActualType(varDsc->TypeGet())); - - GenTreePtr data = treeNode->gtOp.gtOp1->gtEffectiveVal(); - instruction ins = ins_Store(targetType); - emitAttr attr = emitTypeSize(targetType); - if (data->isContainedIntOrIImmed()) - { - assert(data->IsIntegralConst(0)); - NYI_ARM("st.lclFld contained operand"); - } - else - { - assert(!data->isContained()); - genConsumeReg(data); - emit->emitIns_S_R(ins, attr, data->gtRegNum, varNum, offset); - } - - genUpdateLife(varNode); - varDsc->lvRegNum = REG_STK; - } - break; - - case GT_STORE_LCL_VAR: - { - GenTreeLclVarCommon* varNode = treeNode->AsLclVarCommon(); - - unsigned varNum = varNode->gtLclNum; - assert(varNum < compiler->lvaCount); - LclVarDsc* varDsc = &(compiler->lvaTable[varNum]); - unsigned offset = 0; - - // Ensure that lclVar nodes are typed correctly. - assert(!varDsc->lvNormalizeOnStore() || targetType == genActualType(varDsc->TypeGet())); - - GenTreePtr data = treeNode->gtOp.gtOp1->gtEffectiveVal(); - - // var = call, where call returns a multi-reg return value - // case is handled separately. - if (data->gtSkipReloadOrCopy()->IsMultiRegCall()) - { - genMultiRegCallStoreToLocal(treeNode); - break; - } - else - { - if (treeNode->TypeGet() == TYP_LONG) - { - genStoreLongLclVar(treeNode); - break; - } - - genConsumeRegs(data); - - regNumber dataReg = REG_NA; - if (data->isContainedIntOrIImmed()) - { - assert(data->IsIntegralConst(0)); - NYI_ARM("st.lclVar contained operand"); - } - else - { - assert(!data->isContained()); - dataReg = data->gtRegNum; - } - assert(dataReg != REG_NA); - - if (targetReg == REG_NA) // store into stack based LclVar - { - inst_set_SV_var(varNode); - - instruction ins = ins_Store(targetType); - emitAttr attr = emitTypeSize(targetType); - - emit->emitIns_S_R(ins, attr, dataReg, varNum, offset); - - genUpdateLife(varNode); - - varDsc->lvRegNum = REG_STK; - } - else // store into register (i.e move into register) - { - if (dataReg != targetReg) - { - // Assign into targetReg when dataReg (from op1) is not the same register - inst_RV_RV(ins_Copy(targetType), targetReg, dataReg, targetType); - } - genProduceReg(treeNode); - } - } - } - break; - - case GT_RETFILT: - // A void GT_RETFILT is the end of a finally. For non-void filter returns we need to load the result in - // the return register, if it's not already there. The processing is the same as GT_RETURN. - if (targetType != TYP_VOID) - { - // For filters, the IL spec says the result is type int32. Further, the only specified legal values - // are 0 or 1, with the use of other values "undefined". - assert(targetType == TYP_INT); - } - - __fallthrough; - - case GT_RETURN: - genReturn(treeNode); - break; - - case GT_LEA: - { - // if we are here, it is the case where there is an LEA that cannot - // be folded into a parent instruction - GenTreeAddrMode* lea = treeNode->AsAddrMode(); - genLeaInstruction(lea); - } - // genLeaInstruction calls genProduceReg() - break; - - case GT_IND: - genConsumeAddress(treeNode->AsIndir()->Addr()); - emit->emitInsLoadStoreOp(ins_Load(targetType), emitTypeSize(treeNode), targetReg, treeNode->AsIndir()); - genProduceReg(treeNode); - break; - - case GT_MOD: - case GT_UDIV: - case GT_UMOD: - // We shouldn't be seeing GT_MOD on float/double args as it should get morphed into a - // helper call by front-end. Similarly we shouldn't be seeing GT_UDIV and GT_UMOD - // on float/double args. - noway_assert(!varTypeIsFloating(treeNode)); - __fallthrough; - - case GT_DIV: - { - genConsumeOperands(treeNode->AsOp()); - - noway_assert(targetReg != REG_NA); - - GenTreePtr dst = treeNode; - GenTreePtr src1 = treeNode->gtGetOp1(); - GenTreePtr src2 = treeNode->gtGetOp2(); - instruction ins = genGetInsForOper(treeNode->OperGet(), targetType); - emitAttr attr = emitTypeSize(treeNode); - regNumber result = REG_NA; - - // dst can only be a reg - assert(!dst->isContained()); - - // src can be only reg - assert(!src1->isContained() || !src2->isContained()); - - if (varTypeIsFloating(targetType)) - { - // Floating point divide never raises an exception - - emit->emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); - } - else // an signed integer divide operation - { - // TODO-ARM-Bug: handle zero division exception. - - emit->emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); - } - - genProduceReg(treeNode); - } - break; - - case GT_INTRINSIC: - { - genIntrinsic(treeNode); - } - break; - - case GT_EQ: - case GT_NE: - case GT_LT: - case GT_LE: - case GT_GE: - case GT_GT: - { - // TODO-ARM-CQ: Check if we can use the currently set flags. - // TODO-ARM-CQ: Check for the case where we can simply transfer the carry bit to a register - // (signed < or >= where targetReg != REG_NA) - - GenTreeOp* tree = treeNode->AsOp(); - GenTreePtr op1 = tree->gtOp1->gtEffectiveVal(); - GenTreePtr op2 = tree->gtOp2->gtEffectiveVal(); - - genConsumeIfReg(op1); - genConsumeIfReg(op2); - - instruction ins = INS_cmp; - emitAttr cmpAttr; - if (varTypeIsFloating(op1)) - { - assert(op1->TypeGet() == op2->TypeGet()); - ins = INS_vcmp; - cmpAttr = emitTypeSize(op1->TypeGet()); - emit->emitInsBinary(ins, cmpAttr, op1, op2); - // 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(); - var_types op2Type = op2->TypeGet(); - assert(!varTypeIsFloating(op2Type)); - ins = INS_cmp; - if (op1Type == op2Type) - { - cmpAttr = emitTypeSize(op1Type); - } - else - { - var_types cmpType = TYP_INT; - bool op1Is64Bit = (varTypeIsLong(op1Type) || op1Type == TYP_REF); - bool op2Is64Bit = (varTypeIsLong(op2Type) || op2Type == TYP_REF); - NYI_IF(op1Is64Bit || op2Is64Bit, "Long compare"); - assert(!op1->isUsedFromMemory() || op1Type == op2Type); - assert(!op2->isUsedFromMemory() || op1Type == op2Type); - cmpAttr = emitTypeSize(cmpType); - } - emit->emitInsBinary(ins, cmpAttr, op1, op2); - } - - // Are we evaluating this into a register? - if (targetReg != REG_NA) - { - genSetRegToCond(targetReg, tree); - genProduceReg(tree); - } - } - break; - - case GT_JTRUE: - genCodeForJumpTrue(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 - // based on the contents of 'data' - - GenTree* data = treeNode->gtOp.gtOp1->gtEffectiveVal(); - genConsumeIfReg(data); - GenTreeIntCon cns = intForm(TYP_INT, 0); - emit->emitInsBinary(INS_cmp, emitTypeSize(TYP_INT), data, &cns); - - BasicBlock* skipLabel = genCreateTempLabel(); - - emitJumpKind jmpEqual = genJumpKindForOper(GT_EQ, CK_SIGNED); - inst_JMP(jmpEqual, skipLabel); - // emit the call to the EE-helper that stops for GC (or other reasons) - - genEmitHelperCall(CORINFO_HELP_STOP_FOR_GC, 0, EA_UNKNOWN); - genDefineTempLabel(skipLabel); - } - break; - - case GT_STOREIND: - { - GenTreeStoreInd* storeInd = treeNode->AsStoreInd(); - GenTree* data = storeInd->Data(); - GenTree* addr = storeInd->Addr(); - var_types targetType = storeInd->TypeGet(); - - assert(!varTypeIsFloating(targetType) || (targetType == data->TypeGet())); - - GCInfo::WriteBarrierForm writeBarrierForm = gcInfo.gcIsWriteBarrierCandidate(treeNode, data); - if (writeBarrierForm != GCInfo::WBF_NoBarrier) - { - // data and addr must be in registers. - // Consume both registers so that any copies of interfering - // registers are taken care of. - genConsumeOperands(storeInd->AsOp()); - -#if NOGC_WRITE_BARRIERS - NYI_ARM("NOGC_WRITE_BARRIERS"); -#else - // At this point, we should not have any interference. - // That is, 'data' must not be in REG_ARG_0, - // as that is where 'addr' must go. - noway_assert(data->gtRegNum != REG_ARG_0); - - // addr goes in REG_ARG_0 - if (addr->gtRegNum != REG_ARG_0) - { - inst_RV_RV(INS_mov, REG_ARG_0, addr->gtRegNum, addr->TypeGet()); - } - - // data goes in REG_ARG_1 - if (data->gtRegNum != REG_ARG_1) - { - inst_RV_RV(INS_mov, REG_ARG_1, data->gtRegNum, data->TypeGet()); - } -#endif // NOGC_WRITE_BARRIERS - - genGCWriteBarrier(storeInd, writeBarrierForm); - } - else // A normal store, not a WriteBarrier store - { - bool reverseOps = ((storeInd->gtFlags & GTF_REVERSE_OPS) != 0); - bool dataIsUnary = false; - - // We must consume the operands in the proper execution order, - // so that liveness is updated appropriately. - if (!reverseOps) - { - genConsumeAddress(addr); - } - - if (!data->isContained()) - { - genConsumeRegs(data); - } - - if (reverseOps) - { - genConsumeAddress(addr); - } - - emit->emitInsLoadStoreOp(ins_Store(targetType), emitTypeSize(storeInd), data->gtRegNum, - treeNode->AsIndir()); - } - } - break; - - case GT_COPY: - // This is handled at the time we call genConsumeReg() on the GT_COPY - break; - - case GT_LIST: - case GT_FIELD_LIST: - case GT_ARGPLACE: - // Nothing to do - break; - - case GT_PUTARG_STK: - genPutArgStk(treeNode->AsPutArgStk()); - break; - - case GT_PUTARG_REG: - { - NYI_IF(targetType == TYP_STRUCT, "GT_PUTARG_REG: struct support not implemented"); - - // commas show up here commonly, as part of a nullchk operation - GenTree* op1 = treeNode->gtOp.gtOp1->gtEffectiveVal(); - // If child node is not already in the register we need, move it - genConsumeReg(op1); - if (treeNode->gtRegNum != op1->gtRegNum) - { - inst_RV_RV(ins_Move_Extend(targetType, true), treeNode->gtRegNum, op1->gtRegNum, targetType); - } - } - genProduceReg(treeNode); - break; - - case GT_CALL: - genCallInstruction(treeNode->AsCall()); - break; - - case GT_LOCKADD: - case GT_XCHG: - case GT_XADD: - genLockedInstructions(treeNode->AsOp()); - break; - - case GT_MEMORYBARRIER: - instGen_MemoryBarrier(); - break; - - case GT_CMPXCHG: - { - NYI("GT_CMPXCHG"); - } - genProduceReg(treeNode); - break; - - case GT_RELOAD: - // do nothing - reload is just a marker. - // The parent node will call genConsumeReg on this which will trigger the unspill of this node's child - // into the register specified in this node. - break; - - case GT_NOP: - break; - - case GT_NO_OP: - if (treeNode->gtFlags & GTF_NO_OP_NO) - { - noway_assert(!"GTF_NO_OP_NO should not be set"); - } - else - { - instGen(INS_nop); - } - break; - - case GT_ARR_BOUNDS_CHECK: - genRangeCheck(treeNode); - break; - - case GT_PHYSREG: - if (treeNode->gtRegNum != treeNode->AsPhysReg()->gtSrcReg) - { - inst_RV_RV(INS_mov, treeNode->gtRegNum, treeNode->AsPhysReg()->gtSrcReg, targetType); - - genTransferRegGCState(treeNode->gtRegNum, treeNode->AsPhysReg()->gtSrcReg); - } - break; - - case GT_PHYSREGDST: - break; - - case GT_NULLCHECK: - { - assert(!treeNode->gtOp.gtOp1->isContained()); - regNumber addrReg = genConsumeReg(treeNode->gtOp.gtOp1); - emit->emitIns_R_R_I(INS_ldr, EA_4BYTE, targetReg, addrReg, 0); - } - break; - - case GT_CATCH_ARG: - - noway_assert(handlerGetsXcptnObj(compiler->compCurBB->bbCatchTyp)); - - /* Catch arguments get passed in a register. genCodeForBBlist() - would have marked it as holding a GC object, but not used. */ - - noway_assert(gcInfo.gcRegGCrefSetCur & RBM_EXCEPTION_OBJECT); - genConsumeReg(treeNode); - break; - - case GT_PINVOKE_PROLOG: - noway_assert(((gcInfo.gcRegGCrefSetCur | gcInfo.gcRegByrefSetCur) & ~fullIntArgRegMask()) == 0); - - // the runtime side requires the codegen here to be consistent - emit->emitDisableRandomNops(); - break; - - case GT_LABEL: - genPendingCallLabel = genCreateTempLabel(); - treeNode->gtLabel.gtLabBB = genPendingCallLabel; - emit->emitIns_J_R(INS_adr, EA_PTRSIZE, genPendingCallLabel, treeNode->gtRegNum); - break; - - case GT_CLS_VAR_ADDR: - emit->emitIns_R_C(INS_lea, EA_PTRSIZE, targetReg, treeNode->gtClsVar.gtClsVarHnd, 0); - genProduceReg(treeNode); - break; - - case GT_STORE_DYN_BLK: - case GT_STORE_BLK: - genCodeForStoreBlk(treeNode->AsBlk()); - break; - - case GT_JMPTABLE: - genJumpTable(treeNode); - break; - - case GT_SWITCH_TABLE: - genTableBasedSwitch(treeNode); - break; - - case GT_ARR_INDEX: - genCodeForArrIndex(treeNode->AsArrIndex()); - break; - - case GT_ARR_OFFSET: - genCodeForArrOffset(treeNode->AsArrOffs()); - break; - - case GT_IL_OFFSET: - // Do nothing; these nodes are simply markers for debug info. - break; - - default: - { -#ifdef DEBUG - char message[256]; - _snprintf_s(message, _countof(message), _TRUNCATE, "NYI: Unimplemented node type %s", - GenTree::NodeName(treeNode->OperGet())); - NYIRAW(message); -#else - NYI("unimplemented node"); -#endif - } - break; - } -} - -//------------------------------------------------------------------------ // genLockedInstructions: Generate code for the locked operations. // // Notes: @@ -1511,43 +781,161 @@ void CodeGen::genCodeForInitBlkUnroll(GenTreeBlk* initBlkNode) NYI_ARM("genCodeForInitBlkUnroll"); } -void CodeGen::genCodeForStoreBlk(GenTreeBlk* blkOp) +//------------------------------------------------------------------------ +// genCodeForNegNot: Produce code for a GT_NEG/GT_NOT node. +// +// Arguments: +// tree - the node +// +void CodeGen::genCodeForNegNot(GenTree* tree) { - if (blkOp->gtBlkOpGcUnsafe) + assert(tree->OperIs(GT_NEG, GT_NOT)); + + var_types targetType = tree->TypeGet(); + + assert(!tree->OperIs(GT_NOT) || !varTypeIsFloating(targetType)); + + regNumber targetReg = tree->gtRegNum; + instruction ins = genGetInsForOper(tree->OperGet(), targetType); + + // The arithmetic node must be sitting in a register (since it's not contained) + assert(!tree->isContained()); + // The dst can only be a register. + assert(targetReg != REG_NA); + + GenTreePtr operand = tree->gtGetOp1(); + assert(!operand->isContained()); + // The src must be a register. + regNumber operandReg = genConsumeReg(operand); + + if (ins == INS_vneg) { - getEmitter()->emitDisableGC(); + getEmitter()->emitIns_R_R(ins, emitTypeSize(tree), targetReg, operandReg); } - bool isCopyBlk = blkOp->OperIsCopyBlkOp(); + else + { + getEmitter()->emitIns_R_R_I(ins, emitTypeSize(tree), targetReg, operandReg, 0); + } + + genProduceReg(tree); +} - switch (blkOp->gtBlkOpKind) +// Generate code for CpObj nodes wich copy structs that have interleaved +// GC pointers. +// For this case we'll generate a sequence of loads/stores in the case of struct +// slots that don't contain GC pointers. The generated code will look like: +// ldr tempReg, [R13, #8] +// str tempReg, [R14, #8] +// +// In the case of a GC-Pointer we'll call the ByRef write barrier helper +// who happens to use the same registers as the previous call to maintain +// the same register requirements and register killsets: +// bl CORINFO_HELP_ASSIGN_BYREF +// +// So finally an example would look like this: +// ldr tempReg, [R13, #8] +// str tempReg, [R14, #8] +// bl CORINFO_HELP_ASSIGN_BYREF +// ldr tempReg, [R13, #8] +// str tempReg, [R14, #8] +// bl CORINFO_HELP_ASSIGN_BYREF +// ldr tempReg, [R13, #8] +// str tempReg, [R14, #8] +void CodeGen::genCodeForCpObj(GenTreeObj* cpObjNode) +{ + GenTreePtr dstAddr = cpObjNode->Addr(); + GenTreePtr source = cpObjNode->Data(); + var_types srcAddrType = TYP_BYREF; + bool sourceIsLocal = false; + regNumber dstReg = REG_NA; + regNumber srcReg = REG_NA; + + assert(source->isContained()); + if (source->gtOper == GT_IND) { - case GenTreeBlk::BlkOpKindHelper: - if (isCopyBlk) - { - genCodeForCpBlk(blkOp); - } - else - { - genCodeForInitBlk(blkOp); - } - break; - case GenTreeBlk::BlkOpKindUnroll: - if (isCopyBlk) - { - genCodeForCpBlkUnroll(blkOp); - } - else - { - genCodeForInitBlkUnroll(blkOp); - } - break; - default: - unreached(); + GenTree* srcAddr = source->gtGetOp1(); + assert(!srcAddr->isContained()); + srcAddrType = srcAddr->TypeGet(); + } + else + { + noway_assert(source->IsLocal()); + sourceIsLocal = true; + } + + bool dstOnStack = dstAddr->OperIsLocalAddr(); + +#ifdef DEBUG + assert(!dstAddr->isContained()); + + // This GenTree node has data about GC pointers, this means we're dealing + // with CpObj. + assert(cpObjNode->gtGcPtrCount > 0); +#endif // DEBUG + + // Consume the operands and get them into the right registers. + // They may now contain gc pointers (depending on their type; gcMarkRegPtrVal will "do the right thing"). + genConsumeBlockOp(cpObjNode, REG_WRITE_BARRIER_DST_BYREF, REG_WRITE_BARRIER_SRC_BYREF, REG_NA); + gcInfo.gcMarkRegPtrVal(REG_WRITE_BARRIER_SRC_BYREF, srcAddrType); + gcInfo.gcMarkRegPtrVal(REG_WRITE_BARRIER_DST_BYREF, dstAddr->TypeGet()); + + // Temp register used to perform the sequence of loads and stores. + regNumber tmpReg = cpObjNode->ExtractTempReg(); + assert(genIsValidIntReg(tmpReg)); + + unsigned slots = cpObjNode->gtSlots; + emitter* emit = getEmitter(); + + BYTE* gcPtrs = cpObjNode->gtGcPtrs; + + // If we can prove it's on the stack we don't need to use the write barrier. + emitAttr attr = EA_PTRSIZE; + if (dstOnStack) + { + for (unsigned i = 0; i < slots; ++i) + { + if (gcPtrs[i] == GCT_GCREF) + attr = EA_GCREF; + else if (gcPtrs[i] == GCT_BYREF) + attr = EA_BYREF; + emit->emitIns_R_R_I(INS_ldr, attr, tmpReg, REG_WRITE_BARRIER_SRC_BYREF, TARGET_POINTER_SIZE, + INS_FLAGS_DONT_CARE, INS_OPTS_LDST_POST_INC); + emit->emitIns_R_R_I(INS_str, attr, tmpReg, REG_WRITE_BARRIER_DST_BYREF, TARGET_POINTER_SIZE, + INS_FLAGS_DONT_CARE, INS_OPTS_LDST_POST_INC); + } } - if (blkOp->gtBlkOpGcUnsafe) + else { - getEmitter()->emitEnableGC(); + unsigned gcPtrCount = cpObjNode->gtGcPtrCount; + + unsigned i = 0; + while (i < slots) + { + switch (gcPtrs[i]) + { + case TYPE_GC_NONE: + emit->emitIns_R_R_I(INS_ldr, attr, tmpReg, REG_WRITE_BARRIER_SRC_BYREF, TARGET_POINTER_SIZE, + INS_FLAGS_DONT_CARE, INS_OPTS_LDST_POST_INC); + emit->emitIns_R_R_I(INS_str, attr, tmpReg, REG_WRITE_BARRIER_DST_BYREF, TARGET_POINTER_SIZE, + INS_FLAGS_DONT_CARE, INS_OPTS_LDST_POST_INC); + break; + + default: + // In the case of a GC-Pointer we'll call the ByRef write barrier helper + genEmitHelperCall(CORINFO_HELP_ASSIGN_BYREF, 0, EA_PTRSIZE); + + gcPtrCount--; + break; + } + ++i; + } + assert(gcPtrCount == 0); } + + // Clear the gcInfo for registers of source and dest. + // While we normally update GC info prior to the last instruction that uses them, + // these actually live into the helper call. + gcInfo.gcMarkRegSetNpt(RBM_WRITE_BARRIER_SRC_BYREF | RBM_WRITE_BARRIER_DST_BYREF); } //------------------------------------------------------------------------ @@ -1614,6 +1002,155 @@ void CodeGen::genCodeForShiftLong(GenTreePtr tree) } //------------------------------------------------------------------------ +// genCodeForLclVar: Produce code for a GT_LCL_VAR node. +// +// Arguments: +// tree - the GT_LCL_VAR node +// +void CodeGen::genCodeForLclVar(GenTreeLclVar* tree) +{ + // lcl_vars are not defs + assert((tree->gtFlags & GTF_VAR_DEF) == 0); + + bool isRegCandidate = compiler->lvaTable[tree->gtLclNum].lvIsRegCandidate(); + + if (isRegCandidate && !(tree->gtFlags & GTF_VAR_DEATH)) + { + assert((tree->InReg()) || (tree->gtFlags & GTF_SPILLED)); + } + + // If this is a register candidate that has been spilled, genConsumeReg() will + // reload it at the point of use. Otherwise, if it's not in a register, we load it here. + + if (!tree->InReg() && !(tree->gtFlags & GTF_SPILLED)) + { + assert(!isRegCandidate); + getEmitter()->emitIns_R_S(ins_Load(tree->TypeGet()), emitTypeSize(tree), tree->gtRegNum, tree->gtLclNum, 0); + genProduceReg(tree); + } +} + +//------------------------------------------------------------------------ +// genCodeForStoreLclFld: Produce code for a GT_STORE_LCL_FLD node. +// +// Arguments: +// tree - the GT_STORE_LCL_FLD node +// +void CodeGen::genCodeForStoreLclFld(GenTreeLclFld* tree) +{ + var_types targetType = tree->TypeGet(); + regNumber targetReg = tree->gtRegNum; + emitter* emit = getEmitter(); + + noway_assert(targetType != TYP_STRUCT); + + // record the offset + unsigned offset = tree->gtLclOffs; + + // We must have a stack store with GT_STORE_LCL_FLD + noway_assert(!tree->InReg()); + noway_assert(targetReg == REG_NA); + + unsigned varNum = tree->gtLclNum; + assert(varNum < compiler->lvaCount); + LclVarDsc* varDsc = &(compiler->lvaTable[varNum]); + + // Ensure that lclVar nodes are typed correctly. + assert(!varDsc->lvNormalizeOnStore() || targetType == genActualType(varDsc->TypeGet())); + + GenTreePtr data = tree->gtOp1->gtEffectiveVal(); + instruction ins = ins_Store(targetType); + emitAttr attr = emitTypeSize(targetType); + if (data->isContainedIntOrIImmed()) + { + assert(data->IsIntegralConst(0)); + NYI_ARM("st.lclFld contained operand"); + } + else + { + assert(!data->isContained()); + genConsumeReg(data); + emit->emitIns_S_R(ins, attr, data->gtRegNum, varNum, offset); + } + + genUpdateLife(tree); + varDsc->lvRegNum = REG_STK; +} + +//------------------------------------------------------------------------ +// genCodeForStoreLclVar: Produce code for a GT_STORE_LCL_VAR node. +// +// Arguments: +// tree - the GT_STORE_LCL_VAR node +// +void CodeGen::genCodeForStoreLclVar(GenTreeLclVar* tree) +{ + var_types targetType = tree->TypeGet(); + regNumber targetReg = tree->gtRegNum; + emitter* emit = getEmitter(); + + unsigned varNum = tree->gtLclNum; + assert(varNum < compiler->lvaCount); + LclVarDsc* varDsc = &(compiler->lvaTable[varNum]); + + // Ensure that lclVar nodes are typed correctly. + assert(!varDsc->lvNormalizeOnStore() || targetType == genActualType(varDsc->TypeGet())); + + GenTreePtr data = tree->gtOp1->gtEffectiveVal(); + + // var = call, where call returns a multi-reg return value + // case is handled separately. + if (data->gtSkipReloadOrCopy()->IsMultiRegCall()) + { + genMultiRegCallStoreToLocal(tree); + } + else if (tree->TypeGet() == TYP_LONG) + { + genStoreLongLclVar(tree); + } + else + { + genConsumeRegs(data); + + regNumber dataReg = REG_NA; + if (data->isContainedIntOrIImmed()) + { + assert(data->IsIntegralConst(0)); + NYI_ARM("st.lclVar contained operand"); + } + else + { + assert(!data->isContained()); + dataReg = data->gtRegNum; + } + assert(dataReg != REG_NA); + + if (targetReg == REG_NA) // store into stack based LclVar + { + inst_set_SV_var(tree); + + instruction ins = ins_Store(targetType); + emitAttr attr = emitTypeSize(targetType); + + emit->emitIns_S_R(ins, attr, dataReg, varNum, /* offset */ 0); + + genUpdateLife(tree); + + varDsc->lvRegNum = REG_STK; + } + else // store into register (i.e move into register) + { + if (dataReg != targetReg) + { + // Assign into targetReg when dataReg (from op1) is not the same register + inst_RV_RV(ins_Copy(targetType), targetReg, dataReg, targetType); + } + genProduceReg(tree); + } + } +} + +//------------------------------------------------------------------------ // genLeaInstruction: Produce code for a GT_LEA subnode. // void CodeGen::genLeaInstruction(GenTreeAddrMode* lea) @@ -1641,6 +1178,254 @@ void CodeGen::genLeaInstruction(GenTreeAddrMode* lea) } //------------------------------------------------------------------------ +// genCodeForDivMod: Produce code for a GT_DIV/GT_UDIV/GT_MOD/GT_UMOD node. +// +// Arguments: +// tree - the node +// +void CodeGen::genCodeForDivMod(GenTreeOp* tree) +{ + assert(tree->OperIs(GT_DIV, GT_UDIV, GT_MOD, GT_UMOD)); + + // We shouldn't be seeing GT_MOD on float/double args as it should get morphed into a + // helper call by front-end. Similarly we shouldn't be seeing GT_UDIV and GT_UMOD + // on float/double args. + noway_assert(tree->OperIs(GT_DIV) || !varTypeIsFloating(tree)); + + var_types targetType = tree->TypeGet(); + regNumber targetReg = tree->gtRegNum; + emitter* emit = getEmitter(); + + genConsumeOperands(tree); + + noway_assert(targetReg != REG_NA); + + GenTreePtr dst = tree; + GenTreePtr src1 = tree->gtGetOp1(); + GenTreePtr src2 = tree->gtGetOp2(); + instruction ins = genGetInsForOper(tree->OperGet(), targetType); + emitAttr attr = emitTypeSize(tree); + regNumber result = REG_NA; + + // dst can only be a reg + assert(!dst->isContained()); + + // src can be only reg + assert(!src1->isContained() || !src2->isContained()); + + if (varTypeIsFloating(targetType)) + { + // Floating point divide never raises an exception + + emit->emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); + } + else // an signed integer divide operation + { + // TODO-ARM-Bug: handle zero division exception. + + emit->emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); + } + + genProduceReg(tree); +} + +//------------------------------------------------------------------------ +// genCodeForCompare: Produce code for a GT_EQ/GT_NE/GT_LT/GT_LE/GT_GE/GT_GT node. +// +// Arguments: +// tree - the node +// +void CodeGen::genCodeForCompare(GenTreeOp* tree) +{ + // TODO-ARM-CQ: Check if we can use the currently set flags. + // TODO-ARM-CQ: Check for the case where we can simply transfer the carry bit to a register + // (signed < or >= where targetReg != REG_NA) + + GenTreePtr op1 = tree->gtOp1->gtEffectiveVal(); + GenTreePtr op2 = tree->gtOp2->gtEffectiveVal(); + + 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((tree->gtRegNum != REG_NA) || !LIR::AsRange(compiler->compCurBB).TryGetUse(tree, &use)); +#endif + genCompareLong(tree); + } + else + { + regNumber targetReg = tree->gtRegNum; + emitter* emit = getEmitter(); + emitAttr cmpAttr; + + genConsumeIfReg(op1); + genConsumeIfReg(op2); + + if (varTypeIsFloating(op1)) + { + assert(op1->TypeGet() == op2->TypeGet()); + instruction ins = INS_vcmp; + cmpAttr = emitTypeSize(op1->TypeGet()); + emit->emitInsBinary(ins, cmpAttr, op1, op2); + // vmrs with register 0xf has special meaning of transferring flags + emit->emitIns_R(INS_vmrs, EA_4BYTE, REG_R15); + } + else + { + var_types op1Type = op1->TypeGet(); + var_types op2Type = op2->TypeGet(); + assert(!varTypeIsFloating(op2Type)); + instruction ins = INS_cmp; + if (op1Type == op2Type) + { + cmpAttr = emitTypeSize(op1Type); + } + else + { + var_types cmpType = TYP_INT; + bool op1Is64Bit = (varTypeIsLong(op1Type) || op1Type == TYP_REF); + bool op2Is64Bit = (varTypeIsLong(op2Type) || op2Type == TYP_REF); + NYI_IF(op1Is64Bit || op2Is64Bit, "Long compare"); + assert(!op1->isUsedFromMemory() || op1Type == op2Type); + assert(!op2->isUsedFromMemory() || op1Type == op2Type); + cmpAttr = emitTypeSize(cmpType); + } + emit->emitInsBinary(ins, cmpAttr, op1, op2); + } + + // Are we evaluating this into a register? + if (targetReg != REG_NA) + { + genSetRegToCond(targetReg, tree); + genProduceReg(tree); + } + } +} + +//------------------------------------------------------------------------ +// genCodeForJcc: Produce code for a GT_JCC node. +// +// Arguments: +// tree - the node +// +void CodeGen::genCodeForJcc(GenTreeJumpCC* tree) +{ + assert(compiler->compCurBB->bbJumpKind == BBJ_COND); + + CompareKind compareKind = ((tree->gtFlags & GTF_UNSIGNED) != 0) ? CK_UNSIGNED : CK_SIGNED; + emitJumpKind jumpKind = genJumpKindForOper(tree->gtCondition, compareKind); + + inst_JMP(jumpKind, compiler->compCurBB->bbJumpDest); +} + +//------------------------------------------------------------------------ +// genCodeForReturnTrap: Produce code for a GT_RETURNTRAP node. +// +// Arguments: +// tree - the GT_RETURNTRAP node +// +void CodeGen::genCodeForReturnTrap(GenTreeOp* tree) +{ + assert(tree->OperGet() == GT_RETURNTRAP); + + // this is nothing but a conditional call to CORINFO_HELP_STOP_FOR_GC + // based on the contents of 'data' + + GenTree* data = tree->gtOp1->gtEffectiveVal(); + genConsumeIfReg(data); + GenTreeIntCon cns = intForm(TYP_INT, 0); + getEmitter()->emitInsBinary(INS_cmp, emitTypeSize(TYP_INT), data, &cns); + + BasicBlock* skipLabel = genCreateTempLabel(); + + emitJumpKind jmpEqual = genJumpKindForOper(GT_EQ, CK_SIGNED); + inst_JMP(jmpEqual, skipLabel); + // emit the call to the EE-helper that stops for GC (or other reasons) + + genEmitHelperCall(CORINFO_HELP_STOP_FOR_GC, 0, EA_UNKNOWN); + genDefineTempLabel(skipLabel); +} + +//------------------------------------------------------------------------ +// genCodeForStoreInd: Produce code for a GT_STOREIND node. +// +// Arguments: +// tree - the GT_STOREIND node +// +void CodeGen::genCodeForStoreInd(GenTreeStoreInd* tree) +{ + GenTree* data = tree->Data(); + GenTree* addr = tree->Addr(); + var_types targetType = tree->TypeGet(); + emitter* emit = getEmitter(); + + assert(!varTypeIsFloating(targetType) || (targetType == data->TypeGet())); + + GCInfo::WriteBarrierForm writeBarrierForm = gcInfo.gcIsWriteBarrierCandidate(tree, data); + if (writeBarrierForm != GCInfo::WBF_NoBarrier) + { + // data and addr must be in registers. + // Consume both registers so that any copies of interfering + // registers are taken care of. + genConsumeOperands(tree); + +#if NOGC_WRITE_BARRIERS + NYI_ARM("NOGC_WRITE_BARRIERS"); +#else + // At this point, we should not have any interference. + // That is, 'data' must not be in REG_ARG_0, + // as that is where 'addr' must go. + noway_assert(data->gtRegNum != REG_ARG_0); + + // addr goes in REG_ARG_0 + if (addr->gtRegNum != REG_ARG_0) + { + inst_RV_RV(INS_mov, REG_ARG_0, addr->gtRegNum, addr->TypeGet()); + } + + // data goes in REG_ARG_1 + if (data->gtRegNum != REG_ARG_1) + { + inst_RV_RV(INS_mov, REG_ARG_1, data->gtRegNum, data->TypeGet()); + } +#endif // NOGC_WRITE_BARRIERS + + genGCWriteBarrier(tree, writeBarrierForm); + } + else // A normal store, not a WriteBarrier store + { + bool reverseOps = ((tree->gtFlags & GTF_REVERSE_OPS) != 0); + bool dataIsUnary = false; + + // We must consume the operands in the proper execution order, + // so that liveness is updated appropriately. + if (!reverseOps) + { + genConsumeAddress(addr); + } + + if (!data->isContained()) + { + genConsumeRegs(data); + } + + if (reverseOps) + { + genConsumeAddress(addr); + } + + emit->emitInsLoadStoreOp(ins_Store(targetType), emitTypeSize(tree), data->gtRegNum, tree); + } +} + +//------------------------------------------------------------------------ // genCompareLong: Generate code for comparing two longs when the result of the compare // is manifested in a register. // |