diff options
-rw-r--r-- | src/jit/emitxarch.cpp | 602 |
1 files changed, 324 insertions, 278 deletions
diff --git a/src/jit/emitxarch.cpp b/src/jit/emitxarch.cpp index 73f28d94c4..8181e5056d 100644 --- a/src/jit/emitxarch.cpp +++ b/src/jit/emitxarch.cpp @@ -2916,179 +2916,330 @@ void emitter::emitInsStoreLcl(instruction ins, emitAttr attr, GenTreeLclVarCommo codeGen->genUpdateLife(varNode); } -// The callee must call genConsumeReg() for all sources, including address registers -// of both source and destination, and genProduceReg() for the destination register, if any. - +//------------------------------------------------------------------------ +// emitInsBinary: Emits an instruction for a node which takes two operands +// +// Arguments: +// ins - the instruction to emit +// attr - the instruction operand size +// dst - the destination and first source operand +// src - the second source operand +// +// Assumptions: +// i) caller of this routine needs to call genConsumeReg() +// ii) caller of this routine needs to call genProduceReg() regNumber emitter::emitInsBinary(instruction ins, emitAttr attr, GenTree* dst, GenTree* src) { - // dst can only be a reg or modrm - assert(!dst->isContained() || dst->isUsedFromMemory() || instrIs3opImul(ins)); // dst on these isn't really the dst + // We can only have one memory operand and only src can be a constant operand + // However, the handling for a given operand type (mem, cns, or other) is fairly + // consistent regardless of whether they are src or dst. As such, we will find + // the type of each operand and only check them against src/dst where relevant. -#ifdef DEBUG - // src can be anything but both src and dst cannot be addr modes - // or at least cannot be contained addr modes - if (dst->isUsedFromMemory()) - { - assert(!src->isUsedFromMemory()); - } + GenTree* memOp = nullptr; + GenTree* cnsOp = nullptr; + GenTree* otherOp = nullptr; - if (src->isUsedFromMemory()) + if (dst->isContained() || (dst->isLclField() && (dst->gtRegNum == REG_NA)) || dst->isUsedFromSpillTemp()) { - assert(!dst->isUsedFromMemory()); - } -#endif - - // find which operand is a memory op (if any) - // and what its base is - GenTreeIndir* mem = nullptr; - GenTree* memBase = nullptr; - - if (dst->isContainedIndir()) - { - mem = dst->AsIndir(); - } - else if (src->isContainedIndir()) - { - mem = src->AsIndir(); - } + // dst can only be a modrm + assert(dst->isUsedFromMemory() || (dst->gtRegNum == REG_NA) || + instrIs3opImul(ins)); // dst on 3opImul isn't really the dst + assert(!src->isUsedFromMemory()); - if (mem) - { - memBase = mem->gtOp1; - } + memOp = dst; - // Find immed (if any) - it cannot be the dst - // SSE2 instructions allow only the second operand to be a memory operand. - GenTreeIntConCommon* intConst = nullptr; - GenTreeDblCon* dblConst = nullptr; - if (src->isContainedIntOrIImmed()) - { - intConst = src->AsIntConCommon(); + if (src->isContained()) + { + assert(src->IsCnsIntOrI()); + cnsOp = src; + } + else + { + otherOp = src; + } } - else if (src->isContainedFltOrDblImmed()) + else if (src->isContained() || src->isUsedFromSpillTemp()) { - dblConst = src->AsDblCon(); - } + assert(!dst->isUsedFromMemory()); + otherOp = dst; - // find local field if any - GenTreeLclFld* lclField = nullptr; - if (src->isLclFldUsedFromMemory()) - { - lclField = src->AsLclFld(); - } - else if (dst->isLclField() && dst->gtRegNum == REG_NA) - { - lclField = dst->AsLclFld(); + if ((src->IsCnsIntOrI() || src->IsCnsFltOrDbl()) && !src->isUsedFromSpillTemp()) + { + assert(!src->isUsedFromMemory() || src->IsCnsFltOrDbl()); + cnsOp = src; + } + else + { + assert(src->isUsedFromMemory()); + memOp = src; + } } - // find contained lcl var if any - GenTreeLclVar* lclVar = nullptr; - if (src->isLclVarUsedFromMemory()) - { - assert(src->IsRegOptional() || !emitComp->lvaTable[src->gtLclVar.gtLclNum].lvIsRegCandidate()); - lclVar = src->AsLclVar(); - } - if (dst->isLclVarUsedFromMemory()) - { - assert(dst->IsRegOptional() || !emitComp->lvaTable[dst->gtLclVar.gtLclNum].lvIsRegCandidate()); - lclVar = dst->AsLclVar(); - } + // At this point, we either have a memory operand or we don't. + // + // If we don't then the logic is very simple and we will either be emitting a + // `reg, immed` instruction (if src is a cns) or a `reg, reg` instruction otherwise. + // + // If we do have a memory operand, the logic is a bit more complicated as we need + // to do different things depending on the type of memory operand. These types include: + // * Spill temp + // * Indirect access + // * Local variable + // * Class variable + // * Addressing mode [base + index * scale + offset] + // * Local field + // * Local variable + // + // Most of these types (except Indirect: Class variable and Indirect: Addressing mode) + // give us a a local variable number and an offset and access memory on the stack + // + // Indirect: Class variable is used for access static class variables and gives us a handle + // to the memory location we read from + // + // Indirect: Addressing mode is used for the remaining memory accesses and will give us + // a base address, an index, a scale, and an offset. These are combined to let us easily + // access the given memory location. + // + // In all of the memory access cases, we determine which form to emit (e.g. `reg, [mem]` + // or `[mem], reg`) by comparing memOp to src to determine which `emitIns_*` method needs + // to be called. The exception is for the `[mem], immed` case (for Indirect: Class variable) + // where only src can be the immediate. - // find contained spill tmp if any - TempDsc* tmpDsc = nullptr; - if (src->isUsedFromSpillTemp()) - { - assert(src->IsRegOptional()); - tmpDsc = codeGen->getSpillTempDsc(src); - } - else if (dst->isUsedFromSpillTemp()) + if (memOp != nullptr) { - assert(dst->IsRegOptional()); - tmpDsc = codeGen->getSpillTempDsc(dst); - } + TempDsc* tmpDsc = nullptr; + unsigned varNum = BAD_VAR_NUM; + unsigned offset = (unsigned)-1; - // First handle the simple non-memory cases - // - if ((mem == nullptr) && (lclField == nullptr) && (lclVar == nullptr) && (tmpDsc == nullptr)) - { - if (intConst != nullptr) + if (memOp->isUsedFromSpillTemp()) { - // reg, immed - assert(!dst->isContained()); + assert(memOp->IsRegOptional()); - emitIns_R_I(ins, attr, dst->gtRegNum, intConst->IconValue()); - // TODO-XArch-Bug?: does the caller call regTracker.rsTrackRegTrash(dst->gtRegNum) or - // rsTrackRegIntCns(dst->gtRegNum, intConst->IconValue()) (as appropriate)? - } - else if (dblConst != nullptr) - { - // Emit a data section constant for float or double constant. - CORINFO_FIELD_HANDLE hnd = emitFltOrDblConst(dblConst->AsDblCon()->gtDconVal, emitTypeSize(dblConst)); + tmpDsc = codeGen->getSpillTempDsc(memOp); + varNum = tmpDsc->tdTempNum(); + offset = 0; - emitIns_R_C(ins, attr, dst->gtRegNum, hnd, 0); + emitComp->tmpRlsTemp(tmpDsc); } - else + else if (memOp->isIndir()) { - // reg, reg - assert(!src->isContained() && !dst->isContained()); + GenTreeIndir* memIndir = memOp->AsIndir(); + GenTree* memBase = memIndir->gtOp1; - if (instrHasImplicitRegPairDest(ins)) + switch (memBase->OperGet()) { - emitIns_R(ins, attr, src->gtRegNum); - } - else - { - emitIns_R_R(ins, attr, dst->gtRegNum, src->gtRegNum); - } - // ToDo-XArch-Bug?: does the caller call regTracker.rsTrackRegTrash(dst->gtRegNum) or, for ins=MOV: - // regTracker.rsTrackRegCopy(dst->gtRegNum, src->gtRegNum); ? - } + case GT_LCL_VAR_ADDR: + { + varNum = memBase->AsLclVarCommon()->GetLclNum(); + offset = 0; - return dst->gtRegNum; - } + // Ensure that all the GenTreeIndir values are set to their defaults. + assert(!memIndir->HasIndex()); + assert(memIndir->Scale() == 1); + assert(memIndir->Offset() == 0); - // Next handle the cases where we have a stack based local memory operand. - // - unsigned varNum = BAD_VAR_NUM; - unsigned offset = (unsigned)-1; + break; + } - if (lclField != nullptr) - { - varNum = lclField->AsLclVarCommon()->GetLclNum(); - offset = lclField->gtLclFld.gtLclOffs; - } - else if (lclVar != nullptr) - { - varNum = lclVar->AsLclVarCommon()->GetLclNum(); - offset = 0; - } - else if (tmpDsc != nullptr) - { - varNum = tmpDsc->tdTempNum(); - offset = 0; - } - else - { - // At this point we must have a memory operand that is a contained indir: if we do not, we should have handled - // this instruction above in the reg/imm or reg/reg case. - assert(mem != nullptr); - assert(memBase != nullptr); + case GT_CLS_VAR_ADDR: + { + if (memOp == src) + { + assert(otherOp == dst); + assert(cnsOp == nullptr); + + if (instrHasImplicitRegPairDest(ins)) + { + // src is a class static variable + // dst is implicit - RDX:RAX + emitIns_C(ins, attr, memBase->gtClsVar.gtClsVarHnd, 0); + } + else + { + // src is a class static variable + // dst is a register + emitIns_R_C(ins, attr, dst->gtRegNum, memBase->gtClsVar.gtClsVarHnd, 0); + } + } + else + { + assert(memOp == dst); + + if (cnsOp != nullptr) + { + assert(cnsOp == src); + assert(otherOp == nullptr); + assert(src->IsCnsIntOrI()); + + // src is an contained immediate + // dst is a class static variable + emitIns_C_I(ins, attr, memBase->gtClsVar.gtClsVarHnd, 0, + (int)src->gtIntConCommon.IconValue()); + } + else + { + assert(otherOp == src); + + // src is a register + // dst is a class static variable + emitIns_C_R(ins, attr, memBase->gtClsVar.gtClsVarHnd, src->gtRegNum, 0); + } + } + + return dst->gtRegNum; + } + + default: // Addressing mode [base + index * scale + offset] + { + instrDesc* id = nullptr; + + if (cnsOp != nullptr) + { + assert(memOp == dst); + assert(cnsOp == src); + assert(otherOp == nullptr); + assert(src->IsCnsIntOrI()); + + id = emitNewInstrAmdCns(attr, memIndir->Offset(), (int)src->gtIntConCommon.IconValue()); + } + else + { + ssize_t offset = memIndir->Offset(); + id = emitNewInstrAmd(attr, offset); + id->idIns(ins); + + GenTree* regTree = (memOp == src) ? dst : src; + + // there must be one non-contained op + assert(!regTree->isContained()); + id->idReg1(regTree->gtRegNum); + } + assert(id != nullptr); + + id->idIns(ins); // Set the instruction. + + // Determine the instruction format + insFormat fmt = IF_NONE; + + if (memOp == src) + { + assert(cnsOp == nullptr); + assert(otherOp == dst); + + if (instrHasImplicitRegPairDest(ins)) + { + fmt = emitInsModeFormat(ins, IF_ARD); + } + else + { + fmt = emitInsModeFormat(ins, IF_RRD_ARD); + } + } + else + { + assert(memOp == dst); + + if (cnsOp != nullptr) + { + assert(cnsOp == src); + assert(otherOp == nullptr); + assert(src->IsCnsIntOrI()); + + fmt = emitInsModeFormat(ins, IF_ARD_CNS); + } + else + { + assert(otherOp == src); + fmt = emitInsModeFormat(ins, IF_ARD_RRD); + } + } + assert(fmt != IF_NONE); + emitHandleMemOp(memIndir, id, fmt, ins); + + // Determine the instruction size + UNATIVE_OFFSET sz = 0; - if (memBase->OperGet() == GT_LCL_VAR_ADDR) + if (memOp == src) + { + assert(otherOp == dst); + assert(cnsOp == nullptr); + + if (instrHasImplicitRegPairDest(ins)) + { + sz = emitInsSizeAM(id, insCode(ins)); + } + else + { + sz = emitInsSizeAM(id, insCodeRM(ins)); + } + } + else + { + assert(memOp == dst); + + if (cnsOp != nullptr) + { + assert(memOp == dst); + assert(cnsOp == src); + assert(otherOp == nullptr); + + sz = emitInsSizeAM(id, insCodeMI(ins), (int)src->gtIntConCommon.IconValue()); + } + else + { + assert(otherOp == src); + sz = emitInsSizeAM(id, insCodeMR(ins)); + } + } + assert(sz != 0); + + id->idCodeSize(sz); + + dispIns(id); + emitCurIGsize += sz; + + return (memOp == src) ? dst->gtRegNum : REG_NA; + } + } + } + else { - varNum = memBase->AsLclVarCommon()->GetLclNum(); - offset = 0; + switch (memOp->OperGet()) + { + case GT_LCL_FLD: + case GT_STORE_LCL_FLD: + { + GenTreeLclFld* lclField = memOp->AsLclFld(); + varNum = lclField->GetLclNum(); + offset = lclField->gtLclFld.gtLclOffs; + break; + } + + case GT_LCL_VAR: + { + assert(memOp->IsRegOptional() || !emitComp->lvaTable[memOp->gtLclVar.gtLclNum].lvIsRegCandidate()); + varNum = memOp->AsLclVar()->GetLclNum(); + offset = 0; + break; + } + + default: + unreached(); + break; + } } - } - // Spill temp numbers are negative and start with -1 - // which also happens to be BAD_VAR_NUM. For this reason - // we also need to check 'tmpDsc != nullptr' here. - if (varNum != BAD_VAR_NUM || tmpDsc != nullptr) - { - // Is the memory op in the source position? - if (src->isUsedFromMemory()) + // Ensure we got a good varNum and offset. + // We also need to check for `tmpDsc != nullptr` since spill temp numbers + // are negative and start with -1, which also happens to be BAD_VAR_NUM. + assert((varNum != BAD_VAR_NUM) || (tmpDsc != nullptr)); + assert(offset != (unsigned)-1); + + if (memOp == src) { + assert(otherOp == dst); + assert(cnsOp == nullptr); + if (instrHasImplicitRegPairDest(ins)) { // src is a stack based local variable @@ -3102,173 +3253,68 @@ regNumber emitter::emitInsBinary(instruction ins, emitAttr attr, GenTree* dst, G emitIns_R_S(ins, attr, dst->gtRegNum, varNum, offset); } } - else // The memory op is in the dest position. + else { - assert(dst->gtRegNum == REG_NA || dst->IsRegOptional()); + assert(memOp == dst); + assert((dst->gtRegNum == REG_NA) || dst->IsRegOptional()); - // src could be int or reg - if (src->isContainedIntOrIImmed()) + if (cnsOp != nullptr) { + assert(cnsOp == src); + assert(otherOp == nullptr); + assert(src->IsCnsIntOrI()); + // src is an contained immediate // dst is a stack based local variable emitIns_S_I(ins, attr, varNum, offset, (int)src->gtIntConCommon.IconValue()); } else { - // src is a register - // dst is a stack based local variable + assert(otherOp == src); assert(!src->isContained()); - emitIns_S_R(ins, attr, src->gtRegNum, varNum, offset); - } - } - - if (tmpDsc != nullptr) - { - emitComp->tmpRlsTemp(tmpDsc); - } - - return dst->gtRegNum; - } - - // Now we are left with only the cases where the instruction has some kind of a memory operand - // - assert(mem != nullptr); - // Next handle the class static variable cases - // - if (memBase->OperGet() == GT_CLS_VAR_ADDR) - { - // Is the memory op in the source position? - if (mem == src) - { - if (instrHasImplicitRegPairDest(ins)) - { - // src is a class static variable - // dst is implicit - RDX:RAX - emitIns_C(ins, attr, memBase->gtClsVar.gtClsVarHnd, 0); - } - else - { - // src is a class static variable - // dst is a register - emitIns_R_C(ins, attr, dst->gtRegNum, memBase->gtClsVar.gtClsVarHnd, 0); - } - } - else // The memory op is in the dest position. - { - if (src->isContained()) - { - // src is an contained immediate - // dst is a class static variable - emitIns_C_I(ins, attr, memBase->gtClsVar.gtClsVarHnd, 0, (int)src->gtIntConCommon.IconValue()); - } - else - { // src is a register - // dst is a class static variable - emitIns_C_R(ins, attr, memBase->gtClsVar.gtClsVarHnd, src->gtRegNum, 0); + // dst is a stack based local variable + emitIns_S_R(ins, attr, src->gtRegNum, varNum, offset); } } - - return dst->gtRegNum; - } - - // Finally we handle addressing modes case [regBase + regIndex*scale + const] - // - // We will have to construct and fill in the instruction descriptor for this case - // - instrDesc* id = nullptr; - - // Is the src an immediate constant? - if (intConst) - { - // [mem], imm - id = emitNewInstrAmdCns(attr, mem->Offset(), (int)intConst->IconValue()); } - else // [mem], reg OR reg, [mem] + else if (cnsOp != nullptr) // reg, immed { - ssize_t offset = mem->Offset(); - id = emitNewInstrAmd(attr, offset); - id->idIns(ins); + assert(cnsOp == src); + assert(otherOp == dst); - GenTree* regTree = (src == mem) ? dst : src; - - // there must be one non-contained src - assert(!regTree->isContained()); - id->idReg1(regTree->gtRegNum); - } - assert(id != nullptr); - - id->idIns(ins); // Set the instruction. - - // Determine the instruction format - // - insFormat fmt = IF_NONE; - if (mem == dst) - { - if (!src->isContained()) + if (src->IsCnsIntOrI()) { - fmt = emitInsModeFormat(ins, IF_ARD_RRD); + assert(!dst->isContained()); + GenTreeIntConCommon* intCns = src->AsIntConCommon(); + emitIns_R_I(ins, attr, dst->gtRegNum, intCns->IconValue()); } else { - fmt = emitInsModeFormat(ins, IF_ARD_CNS); + assert(src->IsCnsFltOrDbl()); + GenTreeDblCon* dblCns = src->AsDblCon(); + + CORINFO_FIELD_HANDLE hnd = emitFltOrDblConst(dblCns->gtDconVal, emitTypeSize(dblCns)); + emitIns_R_C(ins, attr, dst->gtRegNum, hnd, 0); } } - else + else // reg, reg { - assert(!dst->isContained()); + assert(otherOp == nullptr); + assert(!src->isContained() && !dst->isContained()); + if (instrHasImplicitRegPairDest(ins)) { - fmt = emitInsModeFormat(ins, IF_ARD); + emitIns_R(ins, attr, src->gtRegNum); } else { - fmt = emitInsModeFormat(ins, IF_RRD_ARD); - } - } - assert(fmt != IF_NONE); - emitHandleMemOp(mem, id, fmt, ins); - - // Determine the instruction size - // - UNATIVE_OFFSET sz = 0; - if (intConst) - { - sz = emitInsSizeAM(id, insCodeMI(ins), (int)intConst->IconValue()); - } - else - { - if (mem == dst) - { - sz = emitInsSizeAM(id, insCodeMR(ins)); - } - else // mem == src - { - if (instrHasImplicitRegPairDest(ins)) - { - sz = emitInsSizeAM(id, insCode(ins)); - } - else - { - sz = emitInsSizeAM(id, insCodeRM(ins)); - } + emitIns_R_R(ins, attr, dst->gtRegNum, src->gtRegNum); } } - assert(sz != 0); - - regNumber result = REG_NA; - if (src == mem) - { - result = dst->gtRegNum; - } - - id->idCodeSize(sz); - - dispIns(id); - emitCurIGsize += sz; - return result; + return dst->gtRegNum; } //------------------------------------------------------------------------ |