From 5f9c731a49ea2a645177f69ddaa8b257ceb5ecc7 Mon Sep 17 00:00:00 2001 From: SingleAccretion Date: Mon, 14 Feb 2022 00:05:59 +0300 Subject: [PATCH] Clean HWI codegen up using OperandDesc --- src/coreclr/jit/hwintrinsiccodegenxarch.cpp | 543 ++++---------------- 1 file changed, 113 insertions(+), 430 deletions(-) diff --git a/src/coreclr/jit/hwintrinsiccodegenxarch.cpp b/src/coreclr/jit/hwintrinsiccodegenxarch.cpp index c2c453362f02f..34229d6fff61b 100644 --- a/src/coreclr/jit/hwintrinsiccodegenxarch.cpp +++ b/src/coreclr/jit/hwintrinsiccodegenxarch.cpp @@ -447,127 +447,48 @@ void CodeGen::genHWIntrinsic(GenTreeHWIntrinsic* node) void CodeGen::genHWIntrinsic_R_RM( GenTreeHWIntrinsic* node, instruction ins, emitAttr attr, regNumber reg, GenTree* rmOp) { - emitter* emit = GetEmitter(); - - assert(reg != REG_NA); + emitter* emit = GetEmitter(); + OperandDesc rmOpDesc = genOperandDesc(rmOp); - if (rmOp->isContained() || rmOp->isUsedFromSpillTemp()) + if (rmOpDesc.IsContained()) { assert(HWIntrinsicInfo::SupportsContainment(node->GetHWIntrinsicId())); assertIsContainableHWIntrinsicOp(compiler->m_pLowering, node, rmOp); + } - TempDsc* tmpDsc = nullptr; - unsigned varNum = BAD_VAR_NUM; - unsigned offset = (unsigned)-1; - - if (rmOp->isUsedFromSpillTemp()) - { - assert(rmOp->IsRegOptional()); + switch (rmOpDesc.GetKind()) + { + case OperandKind::ClsVar: + emit->emitIns_R_C(ins, attr, reg, rmOpDesc.GetFieldHnd(), 0); + break; - tmpDsc = getSpillTempDsc(rmOp); - varNum = tmpDsc->tdTempNum(); - offset = 0; + case OperandKind::Local: + emit->emitIns_R_S(ins, attr, reg, rmOpDesc.GetVarNum(), rmOpDesc.GetLclOffset()); + break; - regSet.tmpRlsTemp(tmpDsc); - } - else if (rmOp->isIndir() || rmOp->OperIsHWIntrinsic()) + case OperandKind::Indir: { - GenTree* addr; - GenTreeIndir* memIndir = nullptr; + // Until we improve the handling of addressing modes in the emitter, we'll create a + // temporary GT_IND to generate code with. + GenTreeIndir indirForm; + GenTreeIndir* indir = rmOpDesc.GetIndirForm(&indirForm); + emit->emitIns_R_A(ins, attr, reg, indir); + } + break; - if (rmOp->isIndir()) + case OperandKind::Reg: + if (emit->IsMovInstruction(ins)) { - memIndir = rmOp->AsIndir(); - addr = memIndir->Addr(); + emit->emitIns_Mov(ins, attr, reg, rmOp->GetRegNum(), /* canSkip */ false); } else { - assert(rmOp->AsHWIntrinsic()->OperIsMemoryLoad()); - assert(rmOp->AsHWIntrinsic()->GetOperandCount() == 1); - addr = rmOp->AsHWIntrinsic()->Op(1); - } - - switch (addr->OperGet()) - { - case GT_LCL_VAR_ADDR: - case GT_LCL_FLD_ADDR: - { - assert(addr->isContained()); - varNum = addr->AsLclVarCommon()->GetLclNum(); - offset = addr->AsLclVarCommon()->GetLclOffs(); - break; - } - - case GT_CLS_VAR_ADDR: - { - emit->emitIns_R_C(ins, attr, reg, addr->AsClsVar()->gtClsVarHnd, 0); - return; - } - - default: - { - GenTreeIndir load = indirForm(rmOp->TypeGet(), addr); - - if (memIndir == nullptr) - { - // This is the HW intrinsic load case. - // Until we improve the handling of addressing modes in the emitter, we'll create a - // temporary GT_IND to generate code with. - memIndir = &load; - } - emit->emitIns_R_A(ins, attr, reg, memIndir); - return; - } - } - } - else - { - switch (rmOp->OperGet()) - { - case GT_LCL_FLD: - varNum = rmOp->AsLclFld()->GetLclNum(); - offset = rmOp->AsLclFld()->GetLclOffs(); - break; - - case GT_LCL_VAR: - { - assert(rmOp->IsRegOptional() || !compiler->lvaGetDesc(rmOp->AsLclVar())->lvIsRegCandidate()); - varNum = rmOp->AsLclVar()->GetLclNum(); - offset = 0; - break; - } - - case GT_CNS_DBL: - { - GenTreeDblCon* cns = rmOp->AsDblCon(); - CORINFO_FIELD_HANDLE hnd = emit->emitFltOrDblConst(cns->gtDconVal, emitTypeSize(cns)); - emit->emitIns_R_C(ins, attr, reg, hnd, 0); - return; - } - - default: - { - unreached(); - break; - } + emit->emitIns_R_R(ins, attr, reg, rmOp->GetRegNum()); } - } - - // 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); + break; - emit->emitIns_R_S(ins, attr, reg, varNum, offset); - } - else if (emit->IsMovInstruction(ins)) - { - emit->emitIns_Mov(ins, attr, reg, rmOp->GetRegNum(), /* canSkip */ false); - } - else - { - emit->emitIns_R_R(ins, attr, reg, rmOp->GetRegNum()); + default: + unreached(); } } @@ -673,135 +594,59 @@ void CodeGen::genHWIntrinsic_R_R_RM_I(GenTreeHWIntrinsic* node, instruction ins, assert(targetReg != REG_NA); assert(op1Reg != REG_NA); - if (op2->isContained() || op2->isUsedFromSpillTemp()) + OperandDesc op2Desc = genOperandDesc(op2); + + if (op2Desc.IsContained()) { assert(HWIntrinsicInfo::SupportsContainment(node->GetHWIntrinsicId())); assertIsContainableHWIntrinsicOp(compiler->m_pLowering, node, op2); + } - TempDsc* tmpDsc = nullptr; - unsigned varNum = BAD_VAR_NUM; - unsigned offset = (unsigned)-1; - - if (op2->isUsedFromSpillTemp()) - { - assert(op2->IsRegOptional()); + switch (op2Desc.GetKind()) + { + case OperandKind::ClsVar: + emit->emitIns_SIMD_R_R_C_I(ins, simdSize, targetReg, op1Reg, op2Desc.GetFieldHnd(), 0, ival); + break; - tmpDsc = getSpillTempDsc(op2); - varNum = tmpDsc->tdTempNum(); - offset = 0; + case OperandKind::Local: + emit->emitIns_SIMD_R_R_S_I(ins, simdSize, targetReg, op1Reg, op2Desc.GetVarNum(), op2Desc.GetLclOffset(), + ival); + break; - regSet.tmpRlsTemp(tmpDsc); - } - else if (op2->isIndir() || op2->OperIsHWIntrinsic()) + case OperandKind::Indir: { - GenTree* addr; - GenTreeIndir* memIndir = nullptr; - - if (op2->isIndir()) - { - memIndir = op2->AsIndir(); - addr = memIndir->Addr(); - } - else - { - assert(op2->AsHWIntrinsic()->OperIsMemoryLoad()); - assert(op2->AsHWIntrinsic()->GetOperandCount() == 1); - addr = op2->AsHWIntrinsic()->Op(1); - } - - switch (addr->OperGet()) - { - case GT_LCL_VAR_ADDR: - case GT_LCL_FLD_ADDR: - { - assert(addr->isContained()); - varNum = addr->AsLclVarCommon()->GetLclNum(); - offset = addr->AsLclVarCommon()->GetLclOffs(); - break; - } - - case GT_CLS_VAR_ADDR: - { - emit->emitIns_SIMD_R_R_C_I(ins, simdSize, targetReg, op1Reg, addr->AsClsVar()->gtClsVarHnd, 0, - ival); - return; - } - - default: - { - GenTreeIndir load = indirForm(op2->TypeGet(), addr); - - if (memIndir == nullptr) - { - // This is the HW intrinsic load case. - // Until we improve the handling of addressing modes in the emitter, we'll create a - // temporary GT_IND to generate code with. - memIndir = &load; - } - emit->emitIns_SIMD_R_R_A_I(ins, simdSize, targetReg, op1Reg, memIndir, ival); - return; - } - } + // Until we improve the handling of addressing modes in the emitter, we'll create a + // temporary GT_IND to generate code with. + GenTreeIndir indirForm; + GenTreeIndir* indir = op2Desc.GetIndirForm(&indirForm); + emit->emitIns_SIMD_R_R_A_I(ins, simdSize, targetReg, op1Reg, indir, ival); } - else - { - switch (op2->OperGet()) - { - case GT_LCL_FLD: - varNum = op2->AsLclFld()->GetLclNum(); - offset = op2->AsLclFld()->GetLclOffs(); - break; - - case GT_LCL_VAR: - { - assert(op2->IsRegOptional() || - !compiler->lvaTable[op2->AsLclVar()->GetLclNum()].lvIsRegCandidate()); - varNum = op2->AsLclVar()->GetLclNum(); - offset = 0; - break; - } + break; - case GT_CNS_DBL: - { - GenTreeDblCon* cns = op2->AsDblCon(); - CORINFO_FIELD_HANDLE hnd = emit->emitFltOrDblConst(cns->gtDconVal, emitTypeSize(cns)); - emit->emitIns_SIMD_R_R_C_I(ins, simdSize, targetReg, op1Reg, hnd, 0, ival); - return; - } + case OperandKind::Reg: + { + regNumber op2Reg = op2->GetRegNum(); - default: - unreached(); - break; + if ((op1Reg != targetReg) && (op2Reg == targetReg) && node->isRMWHWIntrinsic(compiler)) + { + // We have "reg2 = reg1 op reg2" where "reg1 != reg2" on a RMW intrinsic. + // + // For non-commutative intrinsics, we should have ensured that op2 was marked + // delay free in order to prevent it from getting assigned the same register + // as target. However, for commutative intrinsics, we can just swap the operands + // in order to have "reg2 = reg2 op reg1" which will end up producing the right code. + + noway_assert(node->OperIsCommutative()); + op2Reg = op1Reg; + op1Reg = targetReg; } - } - - // 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); - - emit->emitIns_SIMD_R_R_S_I(ins, simdSize, targetReg, op1Reg, varNum, offset, ival); - } - else - { - regNumber op2Reg = op2->GetRegNum(); - if ((op1Reg != targetReg) && (op2Reg == targetReg) && node->isRMWHWIntrinsic(compiler)) - { - // We have "reg2 = reg1 op reg2" where "reg1 != reg2" on a RMW intrinsic. - // - // For non-commutative intrinsics, we should have ensured that op2 was marked - // delay free in order to prevent it from getting assigned the same register - // as target. However, for commutative intrinsics, we can just swap the operands - // in order to have "reg2 = reg2 op reg1" which will end up producing the right code. - - noway_assert(node->OperIsCommutative()); - op2Reg = op1Reg; - op1Reg = targetReg; + emit->emitIns_SIMD_R_R_R_I(ins, simdSize, targetReg, op1Reg, op2Reg, ival); } + break; - emit->emitIns_SIMD_R_R_R_I(ins, simdSize, targetReg, op1Reg, op2Reg, ival); + default: + unreached(); } } @@ -828,121 +673,41 @@ void CodeGen::genHWIntrinsic_R_R_RM_R(GenTreeHWIntrinsic* node, instruction ins, assert(op1Reg != REG_NA); assert(op3Reg != REG_NA); - if (op2->isContained() || op2->isUsedFromSpillTemp()) + OperandDesc op2Desc = genOperandDesc(op2); + + if (op2Desc.IsContained()) { assert(HWIntrinsicInfo::SupportsContainment(node->GetHWIntrinsicId())); assertIsContainableHWIntrinsicOp(compiler->m_pLowering, node, op2); + } - TempDsc* tmpDsc = nullptr; - unsigned varNum = BAD_VAR_NUM; - unsigned offset = (unsigned)-1; - - if (op2->isUsedFromSpillTemp()) - { - assert(op2->IsRegOptional()); - - // TODO-XArch-Cleanup: The getSpillTempDsc...tempRlsTemp code is a fairly common - // pattern. It could probably be extracted to its own method. - tmpDsc = getSpillTempDsc(op2); - varNum = tmpDsc->tdTempNum(); - offset = 0; - - regSet.tmpRlsTemp(tmpDsc); - } - else if (op2->isIndir() || op2->OperIsHWIntrinsic()) - { - GenTree* addr; - GenTreeIndir* memIndir = nullptr; - - if (op2->isIndir()) - { - memIndir = op2->AsIndir(); - addr = memIndir->Addr(); - } - else - { - assert(op2->AsHWIntrinsic()->OperIsMemoryLoad()); - assert(op2->AsHWIntrinsic()->GetOperandCount() == 1); - addr = op2->AsHWIntrinsic()->Op(1); - } - - switch (addr->OperGet()) - { - case GT_LCL_VAR_ADDR: - case GT_LCL_FLD_ADDR: - { - assert(addr->isContained()); - varNum = addr->AsLclVarCommon()->GetLclNum(); - offset = addr->AsLclVarCommon()->GetLclOffs(); - break; - } - - case GT_CLS_VAR_ADDR: - { - emit->emitIns_SIMD_R_R_C_R(ins, simdSize, targetReg, op1Reg, op3Reg, addr->AsClsVar()->gtClsVarHnd, - 0); - return; - } + switch (op2Desc.GetKind()) + { + case OperandKind::ClsVar: + emit->emitIns_SIMD_R_R_C_R(ins, simdSize, targetReg, op1Reg, op3Reg, op2Desc.GetFieldHnd(), 0); + break; - default: - { - GenTreeIndir load = indirForm(op2->TypeGet(), addr); + case OperandKind::Local: + emit->emitIns_SIMD_R_R_S_R(ins, simdSize, targetReg, op1Reg, op3Reg, op2Desc.GetVarNum(), + op2Desc.GetLclOffset()); + break; - if (memIndir == nullptr) - { - // This is the HW intrinsic load case. - // Until we improve the handling of addressing modes in the emitter, we'll create a - // temporary GT_IND to generate code with. - memIndir = &load; - } - emit->emitIns_SIMD_R_R_A_R(ins, simdSize, targetReg, op1Reg, op3Reg, memIndir); - return; - } - } - } - else + case OperandKind::Indir: { - switch (op2->OperGet()) - { - case GT_LCL_FLD: - varNum = op2->AsLclFld()->GetLclNum(); - offset = op2->AsLclFld()->GetLclOffs(); - break; - - case GT_LCL_VAR: - { - assert(op2->IsRegOptional() || - !compiler->lvaTable[op2->AsLclVar()->GetLclNum()].lvIsRegCandidate()); - varNum = op2->AsLclVar()->GetLclNum(); - offset = 0; - break; - } - - case GT_CNS_DBL: - { - GenTreeDblCon* cns = op2->AsDblCon(); - CORINFO_FIELD_HANDLE hnd = emit->emitFltOrDblConst(cns->gtDconVal, emitTypeSize(cns)); - emit->emitIns_SIMD_R_R_C_R(ins, simdSize, targetReg, op1Reg, op3Reg, hnd, 0); - return; - } - - default: - unreached(); - break; - } + // Until we improve the handling of addressing modes in the emitter, we'll create a + // temporary GT_IND to generate code with. + GenTreeIndir indirForm; + GenTreeIndir* indir = op2Desc.GetIndirForm(&indirForm); + emit->emitIns_SIMD_R_R_A_R(ins, simdSize, targetReg, op1Reg, op3Reg, indir); } + break; - // 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); + case OperandKind::Reg: + emit->emitIns_SIMD_R_R_R_R(ins, simdSize, targetReg, op1Reg, op2->GetRegNum(), op3Reg); + break; - emit->emitIns_SIMD_R_R_S_R(ins, simdSize, targetReg, op1Reg, op3Reg, varNum, offset); - } - else - { - emit->emitIns_SIMD_R_R_R_R(ins, simdSize, targetReg, op1Reg, op2->GetRegNum(), op3Reg); + default: + unreached(); } } @@ -965,118 +730,36 @@ void CodeGen::genHWIntrinsic_R_R_R_RM( assert(op1Reg != REG_NA); assert(op2Reg != REG_NA); - emitter* emit = GetEmitter(); + emitter* emit = GetEmitter(); + OperandDesc op3Desc = genOperandDesc(op3); - if (op3->isContained() || op3->isUsedFromSpillTemp()) + switch (op3Desc.GetKind()) { - TempDsc* tmpDsc = nullptr; - unsigned varNum = BAD_VAR_NUM; - unsigned offset = (unsigned)-1; - - if (op3->isUsedFromSpillTemp()) - { - assert(op3->IsRegOptional()); - - // TODO-XArch-Cleanup: The getSpillTempDsc...tempRlsTemp code is a fairly common - // pattern. It could probably be extracted to its own method. - tmpDsc = getSpillTempDsc(op3); - varNum = tmpDsc->tdTempNum(); - offset = 0; - - regSet.tmpRlsTemp(tmpDsc); - } - else if (op3->isIndir() || op3->OperIsHWIntrinsic()) - { - GenTree* addr; - GenTreeIndir* memIndir = nullptr; - if (op3->isIndir()) - { - memIndir = op3->AsIndir(); - addr = memIndir->Addr(); - } - else - { - assert(op3->AsHWIntrinsic()->OperIsMemoryLoad()); - assert(op3->AsHWIntrinsic()->GetOperandCount() == 1); - addr = op3->AsHWIntrinsic()->Op(1); - } - - switch (addr->OperGet()) - { - case GT_LCL_VAR_ADDR: - case GT_LCL_FLD_ADDR: - { - assert(addr->isContained()); - varNum = addr->AsLclVarCommon()->GetLclNum(); - offset = addr->AsLclVarCommon()->GetLclOffs(); - break; - } - - case GT_CLS_VAR_ADDR: - { - emit->emitIns_SIMD_R_R_R_C(ins, attr, targetReg, op1Reg, op2Reg, addr->AsClsVar()->gtClsVarHnd, 0); - return; - } + case OperandKind::ClsVar: + emit->emitIns_SIMD_R_R_R_C(ins, attr, targetReg, op1Reg, op2Reg, op3Desc.GetFieldHnd(), 0); + break; - default: - { - GenTreeIndir load = indirForm(op3->TypeGet(), addr); + case OperandKind::Local: + emit->emitIns_SIMD_R_R_R_S(ins, attr, targetReg, op1Reg, op2Reg, op3Desc.GetVarNum(), + op3Desc.GetLclOffset()); + break; - if (memIndir == nullptr) - { - // This is the HW intrinsic load case. - // Until we improve the handling of addressing modes in the emitter, we'll create a - // temporary GT_IND to generate code with. - memIndir = &load; - } - emit->emitIns_SIMD_R_R_R_A(ins, attr, targetReg, op1Reg, op2Reg, memIndir); - return; - } - } - } - else + case OperandKind::Indir: { - switch (op3->OperGet()) - { - case GT_LCL_FLD: - varNum = op3->AsLclFld()->GetLclNum(); - offset = op3->AsLclFld()->GetLclOffs(); - break; - - case GT_LCL_VAR: - { - assert(op3->IsRegOptional() || - !compiler->lvaTable[op3->AsLclVar()->GetLclNum()].lvIsRegCandidate()); - varNum = op3->AsLclVar()->GetLclNum(); - offset = 0; - break; - } - - case GT_CNS_DBL: - { - GenTreeDblCon* cns = op3->AsDblCon(); - CORINFO_FIELD_HANDLE hnd = emit->emitFltOrDblConst(cns->gtDconVal, emitTypeSize(cns)); - emit->emitIns_SIMD_R_R_R_C(ins, attr, targetReg, op1Reg, op2Reg, hnd, 0); - return; - } - - default: - unreached(); - break; - } + // Until we improve the handling of addressing modes in the emitter, we'll create a + // temporary GT_IND to generate code with. + GenTreeIndir indirForm; + GenTreeIndir* indir = op3Desc.GetIndirForm(&indirForm); + emit->emitIns_SIMD_R_R_R_A(ins, attr, targetReg, op1Reg, op2Reg, indir); } + break; - // 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); + case OperandKind::Reg: + emit->emitIns_SIMD_R_R_R_R(ins, attr, targetReg, op1Reg, op2Reg, op3->GetRegNum()); + break; - emit->emitIns_SIMD_R_R_R_S(ins, attr, targetReg, op1Reg, op2Reg, varNum, offset); - } - else - { - emit->emitIns_SIMD_R_R_R_R(ins, attr, targetReg, op1Reg, op2Reg, op3->GetRegNum()); + default: + unreached(); } }