diff options
Diffstat (limited to 'src/jit/instr.cpp')
-rw-r--r-- | src/jit/instr.cpp | 4086 |
1 files changed, 4086 insertions, 0 deletions
diff --git a/src/jit/instr.cpp b/src/jit/instr.cpp new file mode 100644 index 0000000000..d516e0dea4 --- /dev/null +++ b/src/jit/instr.cpp @@ -0,0 +1,4086 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XX XX +XX Instruction XX +XX XX +XX The interface to generate a machine-instruction. XX +XX XX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +*/ + +#include "jitpch.h" +#ifdef _MSC_VER +#pragma hdrstop +#endif + +#include "codegen.h" +#include "instr.h" +#include "emit.h" + +/*****************************************************************************/ +#ifdef DEBUG + +/***************************************************************************** + * + * Returns the string representation of the given CPU instruction. + */ + +const char* CodeGen::genInsName(instruction ins) +{ + // clang-format off + static + const char * const insNames[] = + { +#if defined(_TARGET_XARCH_) + #define INST0(id, nm, fp, um, rf, wf, mr ) nm, + #define INST1(id, nm, fp, um, rf, wf, mr ) nm, + #define INST2(id, nm, fp, um, rf, wf, mr, mi ) nm, + #define INST3(id, nm, fp, um, rf, wf, mr, mi, rm ) nm, + #define INST4(id, nm, fp, um, rf, wf, mr, mi, rm, a4 ) nm, + #define INST5(id, nm, fp, um, rf, wf, mr, mi, rm, a4, rr ) nm, + #include "instrs.h" + +#elif defined(_TARGET_ARM_) + #define INST1(id, nm, fp, ldst, fmt, e1 ) nm, + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) nm, + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) nm, + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) nm, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) nm, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) nm, + #define INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) nm, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9 ) nm, + #include "instrs.h" + +#elif defined(_TARGET_ARM64_) + #define INST1(id, nm, fp, ldst, fmt, e1 ) nm, + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) nm, + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) nm, + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) nm, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) nm, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) nm, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9 ) nm, + #include "instrs.h" + +#else +#error "Unknown _TARGET_" +#endif + }; + // clang-format on + + assert((unsigned)ins < sizeof(insNames) / sizeof(insNames[0])); + assert(insNames[ins] != nullptr); + + return insNames[ins]; +} + +void __cdecl CodeGen::instDisp(instruction ins, bool noNL, const char* fmt, ...) +{ + if (compiler->opts.dspCode) + { + /* Display the instruction offset within the emit block */ + + // printf("[%08X:%04X]", getEmitter().emitCodeCurBlock(), getEmitter().emitCodeOffsInBlock()); + + /* Display the FP stack depth (before the instruction is executed) */ + + // printf("[FP=%02u] ", genGetFPstkLevel()); + + /* Display the instruction mnemonic */ + printf(" "); + + printf(" %-8s", genInsName(ins)); + + if (fmt) + { + va_list args; + va_start(args, fmt); + vprintf(fmt, args); + va_end(args); + } + + if (!noNL) + { + printf("\n"); + } + } +} + +/*****************************************************************************/ +#endif // DEBUG +/*****************************************************************************/ + +void CodeGen::instInit() +{ +} + +/***************************************************************************** + * + * Return the size string (e.g. "word ptr") appropriate for the given size. + */ + +#ifdef DEBUG + +const char* CodeGen::genSizeStr(emitAttr attr) +{ + // clang-format off + static + const char * const sizes[] = + { + "", + "byte ptr ", + "word ptr ", + nullptr, + "dword ptr ", + nullptr, + nullptr, + nullptr, + "qword ptr ", + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + nullptr, + "xmmword ptr ", + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + "ymmword ptr" + }; + // clang-format on + + unsigned size = EA_SIZE(attr); + + assert(size == 0 || size == 1 || size == 2 || size == 4 || size == 8 || size == 16 || size == 32); + + if (EA_ATTR(size) == attr) + { + return sizes[size]; + } + else if (attr == EA_GCREF) + { + return "gword ptr "; + } + else if (attr == EA_BYREF) + { + return "bword ptr "; + } + else if (EA_IS_DSP_RELOC(attr)) + { + return "rword ptr "; + } + else + { + assert(!"Unexpected"); + return "unknw ptr "; + } +} + +#endif + +/***************************************************************************** + * + * Generate an instruction. + */ + +void CodeGen::instGen(instruction ins) +{ + + getEmitter()->emitIns(ins); + +#ifdef _TARGET_XARCH_ + // A workaround necessitated by limitations of emitter + // if we are scheduled to insert a nop here, we have to delay it + // hopefully we have not missed any other prefix instructions or places + // they could be inserted + if (ins == INS_lock && getEmitter()->emitNextNop == 0) + { + getEmitter()->emitNextNop = 1; + } +#endif +} + +/***************************************************************************** + * + * Returns non-zero if the given CPU instruction is a floating-point ins. + */ + +// static inline +bool CodeGenInterface::instIsFP(instruction ins) +{ + assert((unsigned)ins < sizeof(instInfo) / sizeof(instInfo[0])); + + return (instInfo[ins] & INST_FP) != 0; +} + +#ifdef _TARGET_XARCH_ +/***************************************************************************** + * + * Generate a multi-byte NOP instruction. + */ + +void CodeGen::instNop(unsigned size) +{ + assert(size <= 15); + getEmitter()->emitIns_Nop(size); +} +#endif + +/***************************************************************************** + * + * Generate a jump instruction. + */ + +void CodeGen::inst_JMP(emitJumpKind jmp, BasicBlock* tgtBlock) +{ +#if !FEATURE_FIXED_OUT_ARGS + // On the x86 we are pushing (and changing the stack level), but on x64 and other archs we have + // a fixed outgoing args area that we store into and we never change the stack level when calling methods. + // + // Thus only on x86 do we need to assert that the stack level at the target block matches the current stack level. + // + assert(tgtBlock->bbTgtStkDepth * sizeof(int) == genStackLevel || compiler->rpFrameType != FT_ESP_FRAME); +#endif + + getEmitter()->emitIns_J(emitter::emitJumpKindToIns(jmp), tgtBlock); +} + +/***************************************************************************** + * + * Generate a set instruction. + */ + +void CodeGen::inst_SET(emitJumpKind condition, regNumber reg) +{ +#ifdef _TARGET_XARCH_ + instruction ins; + + /* Convert the condition to an instruction opcode */ + + switch (condition) + { + case EJ_js: + ins = INS_sets; + break; + case EJ_jns: + ins = INS_setns; + break; + case EJ_je: + ins = INS_sete; + break; + case EJ_jne: + ins = INS_setne; + break; + + case EJ_jl: + ins = INS_setl; + break; + case EJ_jle: + ins = INS_setle; + break; + case EJ_jge: + ins = INS_setge; + break; + case EJ_jg: + ins = INS_setg; + break; + + case EJ_jb: + ins = INS_setb; + break; + case EJ_jbe: + ins = INS_setbe; + break; + case EJ_jae: + ins = INS_setae; + break; + case EJ_ja: + ins = INS_seta; + break; + + case EJ_jpe: + ins = INS_setpe; + break; + case EJ_jpo: + ins = INS_setpo; + break; + + default: + NO_WAY("unexpected condition type"); + return; + } + + assert(genRegMask(reg) & RBM_BYTE_REGS); + + // These instructions only write the low byte of 'reg' + getEmitter()->emitIns_R(ins, EA_1BYTE, reg); +#elif defined(_TARGET_ARM64_) + insCond cond; + /* Convert the condition to an insCond value */ + switch (condition) + { + case EJ_eq: + cond = INS_COND_EQ; + break; + case EJ_ne: + cond = INS_COND_NE; + break; + case EJ_hs: + cond = INS_COND_HS; + break; + case EJ_lo: + cond = INS_COND_LO; + break; + + case EJ_mi: + cond = INS_COND_MI; + break; + case EJ_pl: + cond = INS_COND_PL; + break; + case EJ_vs: + cond = INS_COND_VS; + break; + case EJ_vc: + cond = INS_COND_VC; + break; + + case EJ_hi: + cond = INS_COND_HI; + break; + case EJ_ls: + cond = INS_COND_LS; + break; + case EJ_ge: + cond = INS_COND_GE; + break; + case EJ_lt: + cond = INS_COND_LT; + break; + + case EJ_gt: + cond = INS_COND_GT; + break; + case EJ_le: + cond = INS_COND_LE; + break; + + default: + NO_WAY("unexpected condition type"); + return; + } + getEmitter()->emitIns_R_COND(INS_cset, EA_8BYTE, reg, cond); +#else + NYI("inst_SET"); +#endif +} + +/***************************************************************************** + * + * Generate a "op reg" instruction. + */ + +void CodeGen::inst_RV(instruction ins, regNumber reg, var_types type, emitAttr size) +{ + if (size == EA_UNKNOWN) + { + size = emitActualTypeSize(type); + } + + getEmitter()->emitIns_R(ins, size, reg); +} + +/***************************************************************************** + * + * Generate a "op reg1, reg2" instruction. + */ + +void CodeGen::inst_RV_RV(instruction ins, + regNumber reg1, + regNumber reg2, + var_types type, + emitAttr size, + insFlags flags /* = INS_FLAGS_DONT_CARE */) +{ + if (size == EA_UNKNOWN) + { + size = emitActualTypeSize(type); + } + +#ifdef _TARGET_ARM_ + getEmitter()->emitIns_R_R(ins, size, reg1, reg2, flags); +#else + getEmitter()->emitIns_R_R(ins, size, reg1, reg2); +#endif +} + +/***************************************************************************** + * + * Generate a "op reg1, reg2, reg3" instruction. + */ + +void CodeGen::inst_RV_RV_RV(instruction ins, + regNumber reg1, + regNumber reg2, + regNumber reg3, + emitAttr size, + insFlags flags /* = INS_FLAGS_DONT_CARE */) +{ +#ifdef _TARGET_ARM_ + getEmitter()->emitIns_R_R_R(ins, size, reg1, reg2, reg3, flags); +#elif defined(_TARGET_XARCH_) && defined(FEATURE_AVX_SUPPORT) + getEmitter()->emitIns_R_R_R(ins, size, reg1, reg2, reg3); +#else + NYI("inst_RV_RV_RV"); +#endif +} +/***************************************************************************** + * + * Generate a "op icon" instruction. + */ + +void CodeGen::inst_IV(instruction ins, int val) +{ + getEmitter()->emitIns_I(ins, EA_PTRSIZE, val); +} + +/***************************************************************************** + * + * Generate a "op icon" instruction where icon is a handle of type specified + * by 'flags' + */ + +void CodeGen::inst_IV_handle(instruction ins, int val) +{ + getEmitter()->emitIns_I(ins, EA_HANDLE_CNS_RELOC, val); +} + +#if FEATURE_STACK_FP_X87 +/***************************************************************************** + * + * Generate a "op ST(n), ST(0)" instruction. + */ + +void CodeGen::inst_FS(instruction ins, unsigned stk) +{ + assert(stk < 8); + +#ifdef DEBUG + + switch (ins) + { + case INS_fcompp: + assert(stk == 1); + break; // Implicit operand of compp is ST(1) + case INS_fld: + case INS_fxch: + assert(!"don't do this. Do you want to use inst_FN() instead?"); + break; + default: + break; + } + +#endif + + getEmitter()->emitIns_F_F0(ins, stk); +} + +/***************************************************************************** + * + * Generate a "op ST(0), ST(n)" instruction + */ + +void CodeGenInterface::inst_FN(instruction ins, unsigned stk) +{ + assert(stk < 8); + +#ifdef DEBUG + + switch (ins) + { + case INS_fst: + case INS_fstp: + case INS_faddp: + case INS_fsubp: + case INS_fsubrp: + case INS_fmulp: + case INS_fdivp: + case INS_fdivrp: + case INS_fcompp: + assert(!"don't do this. Do you want to use inst_FS() instead?"); + break; + default: + break; + } + +#endif // DEBUG + + getEmitter()->emitIns_F0_F(ins, stk); +} +#endif // FEATURE_STACK_FP_X87 + +/***************************************************************************** + * + * Display a stack frame reference. + */ + +void CodeGen::inst_set_SV_var(GenTreePtr tree) +{ +#ifdef DEBUG + assert(tree && (tree->gtOper == GT_LCL_VAR || tree->gtOper == GT_LCL_VAR_ADDR || tree->gtOper == GT_STORE_LCL_VAR)); + assert(tree->gtLclVarCommon.gtLclNum < compiler->lvaCount); + + getEmitter()->emitVarRefOffs = tree->gtLclVar.gtLclILoffs; + +#endif // DEBUG +} + +/***************************************************************************** + * + * Generate a "op reg, icon" instruction. + */ + +void CodeGen::inst_RV_IV( + instruction ins, regNumber reg, ssize_t val, emitAttr size, insFlags flags /* = INS_FLAGS_DONT_CARE */) +{ +#if !defined(_TARGET_64BIT_) + assert(size != EA_8BYTE); +#endif + +#ifdef _TARGET_ARM_ + if (arm_Valid_Imm_For_Instr(ins, val, flags)) + { + getEmitter()->emitIns_R_I(ins, size, reg, val, flags); + } + else if (ins == INS_mov) + { + instGen_Set_Reg_To_Imm(size, reg, val); + } + else + { +#ifndef LEGACY_BACKEND + // TODO-Cleanup: Add a comment about why this is unreached() for RyuJIT backend. + unreached(); +#else // LEGACY_BACKEND + regNumber tmpReg = regSet.rsGrabReg(RBM_ALLINT & ~genRegMask(reg)); + instGen_Set_Reg_To_Imm(size, tmpReg, val); + getEmitter()->emitIns_R_R(ins, size, reg, tmpReg, flags); +#endif // LEGACY_BACKEND + } +#elif defined(_TARGET_ARM64_) + // TODO-Arm64-Bug: handle large constants! + // Probably need something like the ARM case above: if (arm_Valid_Imm_For_Instr(ins, val)) ... + assert(ins != INS_cmp); + assert(ins != INS_tst); + assert(ins != INS_mov); + getEmitter()->emitIns_R_R_I(ins, size, reg, reg, val); +#else // !_TARGET_ARM_ +#ifdef _TARGET_AMD64_ + // Instead of an 8-byte immediate load, a 4-byte immediate will do fine + // as the high 4 bytes will be zero anyway. + if (size == EA_8BYTE && ins == INS_mov && ((val & 0xFFFFFFFF00000000LL) == 0)) + { + size = EA_4BYTE; + getEmitter()->emitIns_R_I(ins, size, reg, val); + } + else if (EA_SIZE(size) == EA_8BYTE && ins != INS_mov && (((int)val != val) || EA_IS_CNS_RELOC(size))) + { +#ifndef LEGACY_BACKEND + assert(!"Invalid immediate for inst_RV_IV"); +#else // LEGACY_BACKEND + // We can't fit the immediate into this instruction, so move it into + // a register first + regNumber tmpReg = regSet.rsGrabReg(RBM_ALLINT & ~genRegMask(reg)); + instGen_Set_Reg_To_Imm(size, tmpReg, val); + + // We might have to switch back from 3-operand imul to two operand form + if (instrIs3opImul(ins)) + { + assert(getEmitter()->inst3opImulReg(ins) == reg); + ins = INS_imul; + } + getEmitter()->emitIns_R_R(ins, EA_TYPE(size), reg, tmpReg); +#endif // LEGACY_BACKEND + } + else +#endif // _TARGET_AMD64_ + { + getEmitter()->emitIns_R_I(ins, size, reg, val); + } +#endif // !_TARGET_ARM_ +} + +#if defined(LEGACY_BACKEND) +/***************************************************************************** + * Figure out the operands to address the tree. + * 'addr' can be one of (1) a pointer to be indirected + * (2) a calculation to be done with LEA_AVAILABLE + * (3) GT_ARR_ELEM + * + * On return, *baseReg, *indScale, *indReg, and *cns are set. + */ + +void CodeGen::instGetAddrMode(GenTreePtr addr, regNumber* baseReg, unsigned* indScale, regNumber* indReg, unsigned* cns) +{ + if (addr->gtOper == GT_ARR_ELEM) + { + /* For GT_ARR_ELEM, the addressibility registers are marked on + gtArrObj and gtArrInds[0] */ + + assert(addr->gtArrElem.gtArrObj->gtFlags & GTF_REG_VAL); + *baseReg = addr->gtArrElem.gtArrObj->gtRegNum; + + assert(addr->gtArrElem.gtArrInds[0]->gtFlags & GTF_REG_VAL); + *indReg = addr->gtArrElem.gtArrInds[0]->gtRegNum; + + if (jitIsScaleIndexMul(addr->gtArrElem.gtArrElemSize)) + *indScale = addr->gtArrElem.gtArrElemSize; + else + *indScale = 0; + + *cns = compiler->eeGetMDArrayDataOffset(addr->gtArrElem.gtArrElemType, addr->gtArrElem.gtArrRank); + } + else if (addr->gtOper == GT_LEA) + { + GenTreeAddrMode* lea = addr->AsAddrMode(); + GenTreePtr base = lea->Base(); + assert(!base || (base->gtFlags & GTF_REG_VAL)); + GenTreePtr index = lea->Index(); + assert(!index || (index->gtFlags & GTF_REG_VAL)); + + *baseReg = base ? base->gtRegNum : REG_NA; + *indReg = index ? index->gtRegNum : REG_NA; + *indScale = lea->gtScale; + *cns = lea->gtOffset; + return; + } + else + { + /* Figure out what complex address mode to use */ + + GenTreePtr rv1 = NULL; + GenTreePtr rv2 = NULL; + bool rev = false; + + INDEBUG(bool yes =) + genCreateAddrMode(addr, -1, true, RBM_NONE, &rev, &rv1, &rv2, +#if SCALED_ADDR_MODES + indScale, +#endif + cns); + + assert(yes); // // since we have called genMakeAddressable() on addr + // Ensure that the base and index, if used, are in registers. + if (rv1 && ((rv1->gtFlags & GTF_REG_VAL) == 0)) + { + if (rv1->gtFlags & GTF_SPILLED) + { + genRecoverReg(rv1, RBM_ALLINT, RegSet::KEEP_REG); + } + else + { + genCodeForTree(rv1, RBM_NONE); + regSet.rsMarkRegUsed(rv1, addr); + } + assert(rv1->gtFlags & GTF_REG_VAL); + } + if (rv2 && ((rv2->gtFlags & GTF_REG_VAL) == 0)) + { + if (rv2->gtFlags & GTF_SPILLED) + { + genRecoverReg(rv2, ~genRegMask(rv1->gtRegNum), RegSet::KEEP_REG); + } + else + { + genCodeForTree(rv2, RBM_NONE); + regSet.rsMarkRegUsed(rv2, addr); + } + assert(rv2->gtFlags & GTF_REG_VAL); + } + // If we did both, we might have spilled rv1. + if (rv1 && ((rv1->gtFlags & GTF_SPILLED) != 0)) + { + regSet.rsLockUsedReg(genRegMask(rv2->gtRegNum)); + genRecoverReg(rv1, ~genRegMask(rv2->gtRegNum), RegSet::KEEP_REG); + regSet.rsUnlockReg(genRegMask(rv2->gtRegNum)); + } + + *baseReg = rv1 ? rv1->gtRegNum : REG_NA; + *indReg = rv2 ? rv2->gtRegNum : REG_NA; + } +} + +#if CPU_LOAD_STORE_ARCH +/***************************************************************************** + * + * Originally this was somewhat specific to the x86 instrution format. + * For a Load/Store arch we generate the 1-8 instructions necessary to + * implement the single addressing mode instruction used on x86. + * We currently don't have an instruction scheduler enabled on any target. + * + * [Schedule] an "ins reg, [r/m]" (rdst=true), or "ins [r/m], reg" (rdst=false) + * instruction (the r/m operand given by a tree). We also allow instructions + * of the form "ins [r/m], icon", these are signaled by setting 'cons' to + * true. + * + * The longest instruction sequence emitted on the ARM is as follows: + * + * - the "addr" represents an array addressing mode, + * with a baseReg, indReg with a shift and a large offset + * (Note that typically array addressing modes do NOT have a large offset) + * - "ins" is an ALU instruction, + * - cons=true, and imm is a large constant that can not be directly encoded with "ins" + * - We may need to grab upto four additional registers: regT, rtegVal, regOffs and regImm + * + * add regT, baseReg, indReg<<shift + * movw regOffs, offsLo + * movt regOffs, offsHi + * ldr regVal, [regT + regOffs] + * movw regImm, consLo + * movt regImm, consHi + * "ins" regVal, regImm + * str regVal, [regT + regOffs] + * + */ + +void CodeGen::sched_AM(instruction ins, + emitAttr size, + regNumber ireg, + bool rdst, + GenTreePtr addr, + unsigned offs, + bool cons, + int imm, + insFlags flags) +{ + assert(addr); + assert(size != EA_UNKNOWN); + + enum INS_TYPE + { + eIT_Lea, + eIT_Load, + eIT_Store, + eIT_Other + }; + INS_TYPE insType = eIT_Other; + + if (ins == INS_lea) + { + insType = eIT_Lea; + ins = INS_add; + } + else if (getEmitter()->emitInsIsLoad(ins)) + { + insType = eIT_Load; + } + else if (getEmitter()->emitInsIsStore(ins)) + { + insType = eIT_Store; + } + + regNumber baseReg = REG_NA; + regNumber indReg = REG_NA; + unsigned indScale = 0; + + regMaskTP avoidMask = RBM_NONE; + + if (addr->gtFlags & GTF_REG_VAL) + { + /* The address is "[reg+offs]" */ + baseReg = addr->gtRegNum; + } + else if (addr->IsCnsIntOrI()) + { +#ifdef RELOC_SUPPORT + // Do we need relocations? + if (compiler->opts.compReloc && addr->IsIconHandle()) + { + size = EA_SET_FLG(size, EA_DSP_RELOC_FLG); + // offs should be smaller than ZapperModule::FixupPlaceHolder + // so that we can uniquely identify the handle + assert(offs <= 4); + } +#endif + ssize_t disp = addr->gtIntCon.gtIconVal + offs; + if ((insType == eIT_Store) && (ireg != REG_NA)) + { + // Can't use the ireg as the baseReg when we have a store instruction + avoidMask |= genRegMask(ireg); + } + baseReg = regSet.rsPickFreeReg(RBM_ALLINT & ~avoidMask); + + avoidMask |= genRegMask(baseReg); + instGen_Set_Reg_To_Imm(size, baseReg, disp); + offs = 0; + } + else + { + unsigned cns = 0; + + instGetAddrMode(addr, &baseReg, &indScale, &indReg, &cns); + + /* Add the constant offset value, if present */ + + offs += cns; + +#if SCALED_ADDR_MODES + noway_assert((baseReg != REG_NA) || (indReg != REG_NA)); + if (baseReg != REG_NA) +#endif + { + avoidMask |= genRegMask(baseReg); + } + + // I don't think this is necessary even in the non-proto-jit case, but better to be + // conservative here. It is only necessary to avoid using ireg if it is used as regT, + // in which case it will be added to avoidMask below. + + if (ireg != REG_NA) + { + avoidMask |= genRegMask(ireg); + } + + if (indReg != REG_NA) + { + avoidMask |= genRegMask(indReg); + } + } + + unsigned shift = (indScale > 0) ? genLog2((unsigned)indScale) : 0; + + regNumber regT = REG_NA; // the register where the address is computed into + regNumber regOffs = REG_NA; // a temporary register to use for the offs when it can't be directly encoded + regNumber regImm = REG_NA; // a temporary register to use for the imm when it can't be directly encoded + regNumber regVal = REG_NA; // a temporary register to use when we have to do a load/modify/store operation + + // Setup regT + if (indReg == REG_NA) + { + regT = baseReg; // We can use the baseReg, regT is read-only + } + else // We have an index register (indReg != REG_NA) + { + // Check for special case that we can encode using one instruction + if ((offs == 0) && (insType != eIT_Other) && !instIsFP(ins) && baseReg != REG_NA) + { + // ins ireg, [baseReg + indReg << shift] + getEmitter()->emitIns_R_R_R_I(ins, size, ireg, baseReg, indReg, shift, flags, INS_OPTS_LSL); + return; + } + + // Otherwise setup regT, regT is written once here + // + if (insType == eIT_Lea || (insType == eIT_Load && !instIsFP(ins))) + { + assert(ireg != REG_NA); + // ireg will be written, so we can take it as our temporary register + regT = ireg; + } + else + { + // need a new temporary reg + regT = regSet.rsPickFreeReg(RBM_ALLINT & ~avoidMask); + regTracker.rsTrackRegTrash(regT); + } + +#if SCALED_ADDR_MODES + if (baseReg == REG_NA) + { + assert(shift > 0); + // LSL regT, indReg, shift. + getEmitter()->emitIns_R_R_I(INS_lsl, EA_PTRSIZE, regT, indReg, shift & ((TARGET_POINTER_SIZE * 8) - 1)); + } + else +#endif // SCALED_ADDR_MODES + { + assert(baseReg != REG_NA); + + // add regT, baseReg, indReg<<shift. + getEmitter()->emitIns_R_R_R_I(INS_add, + // The "add" operation will yield either a pointer or byref, depending on the + // type of "addr." + varTypeIsGC(addr->TypeGet()) ? EA_BYREF : EA_PTRSIZE, regT, baseReg, indReg, + shift, INS_FLAGS_NOT_SET, INS_OPTS_LSL); + } + } + + // regT is the base register for a load/store or an operand for add when insType is eIT_Lea + // + assert(regT != REG_NA); + avoidMask |= genRegMask(regT); + + if (insType != eIT_Other) + { + assert((flags != INS_FLAGS_SET) || (insType == eIT_Lea)); + if ((insType == eIT_Lea) && (offs == 0)) + { + // If we have the same register as src and dst and we do not need to set the flags + // then we can skip emitting the instruction + if ((ireg != regT) || (flags == INS_FLAGS_SET)) + { + // mov ireg, regT + getEmitter()->emitIns_R_R(INS_mov, size, ireg, regT, flags); + } + } + else if (arm_Valid_Imm_For_Instr(ins, offs, flags)) + { + // ins ireg, [regT + offs] + getEmitter()->emitIns_R_R_I(ins, size, ireg, regT, offs, flags); + } + else + { + regOffs = regSet.rsPickFreeReg(RBM_ALLINT & ~avoidMask); + + // We cannot use [regT + regOffs] to load/store a floating register + if (emitter::isFloatReg(ireg)) + { + if (arm_Valid_Imm_For_Instr(INS_add, offs, flags)) + { + // add regOffs, regT, offs + getEmitter()->emitIns_R_R_I(INS_add, EA_4BYTE, regOffs, regT, offs, flags); + } + else + { + // movw regOffs, offs_lo16 + // movt regOffs, offs_hi16 + // add regOffs, regOffs, regT + instGen_Set_Reg_To_Imm(EA_4BYTE, regOffs, offs); + getEmitter()->emitIns_R_R_R(INS_add, EA_4BYTE, regOffs, regOffs, regT, flags); + } + // ins ireg, [regOffs] + getEmitter()->emitIns_R_R_I(ins, size, ireg, regOffs, 0, flags); + + regTracker.rsTrackRegTrash(regOffs); + } + else + { + // mov regOffs, offs + // ins ireg, [regT + regOffs] + instGen_Set_Reg_To_Imm(EA_4BYTE, regOffs, offs); + getEmitter()->emitIns_R_R_R(ins, size, ireg, regT, regOffs, flags); + } + } + } + else // (insType == eIT_Other); + { + // Setup regVal + // + + regVal = regSet.rsPickReg(RBM_ALLINT & ~avoidMask); + regTracker.rsTrackRegTrash(regVal); + avoidMask |= genRegMask(regVal); + var_types load_store_type; + switch (size) + { + case EA_4BYTE: + load_store_type = TYP_INT; + break; + + case EA_2BYTE: + load_store_type = TYP_SHORT; + break; + + case EA_1BYTE: + load_store_type = TYP_BYTE; + break; + + default: + assert(!"Unexpected size in sched_AM, eIT_Other"); + load_store_type = TYP_INT; + break; + } + + // Load the content at addr into regVal using regT + offs + if (arm_Valid_Disp_For_LdSt(offs, load_store_type)) + { + // ldrX regVal, [regT + offs] + getEmitter()->emitIns_R_R_I(ins_Load(load_store_type), size, regVal, regT, offs); + } + else + { + // mov regOffs, offs + // ldrX regVal, [regT + regOffs] + regOffs = regSet.rsPickFreeReg(RBM_ALLINT & ~avoidMask); + avoidMask |= genRegMask(regOffs); + instGen_Set_Reg_To_Imm(EA_4BYTE, regOffs, offs); + getEmitter()->emitIns_R_R_R(ins_Load(load_store_type), size, regVal, regT, regOffs); + } + + if (cons) + { + if (arm_Valid_Imm_For_Instr(ins, imm, flags)) + { + getEmitter()->emitIns_R_I(ins, size, regVal, imm, flags); + } + else + { + assert(regOffs == REG_NA); + regImm = regSet.rsPickFreeReg(RBM_ALLINT & ~avoidMask); + avoidMask |= genRegMask(regImm); + instGen_Set_Reg_To_Imm(size, regImm, imm); + getEmitter()->emitIns_R_R(ins, size, regVal, regImm, flags); + } + } + else if (rdst) + { + getEmitter()->emitIns_R_R(ins, size, ireg, regVal, flags); + } + else + { + getEmitter()->emitIns_R_R(ins, size, regVal, ireg, flags); + } + + // If we do not have a register destination we must perform the write-back store instruction + // (unless we have an instruction like INS_cmp that does not write a destination) + // + if (!rdst && ins_Writes_Dest(ins)) + { + // Store regVal into [addr] + if (regOffs == REG_NA) + { + // strX regVal, [regT + offs] + getEmitter()->emitIns_R_R_I(ins_Store(load_store_type), size, regVal, regT, offs); + } + else + { + // strX regVal, [regT + regOffs] + getEmitter()->emitIns_R_R_R(ins_Store(load_store_type), size, regVal, regT, regOffs); + } + } + } +} + +#else // !CPU_LOAD_STORE_ARCH + +/***************************************************************************** + * + * This is somewhat specific to the x86 instrution format. + * We currently don't have an instruction scheduler enabled on any target. + * + * [Schedule] an "ins reg, [r/m]" (rdst=true), or "ins [r/m], reg" (rdst=false) + * instruction (the r/m operand given by a tree). We also allow instructions + * of the form "ins [r/m], icon", these are signalled by setting 'cons' to + * true. + */ + +void CodeGen::sched_AM(instruction ins, + emitAttr size, + regNumber ireg, + bool rdst, + GenTreePtr addr, + unsigned offs, + bool cons, + int imm, + insFlags flags) +{ +#ifdef _TARGET_XARCH_ + /* Don't use this method for issuing calls. Use instEmit_xxxCall() */ + assert(ins != INS_call); +#endif + + assert(addr); + assert(size != EA_UNKNOWN); + + regNumber reg; + + /* Has the address been conveniently loaded into a register, + or is it an absolute value ? */ + + if ((addr->gtFlags & GTF_REG_VAL) || (addr->IsCnsIntOrI())) + { + if (addr->gtFlags & GTF_REG_VAL) + { + /* The address is "[reg+offs]" */ + + reg = addr->gtRegNum; + + if (cons) + getEmitter()->emitIns_I_AR(ins, size, imm, reg, offs); + else if (rdst) + getEmitter()->emitIns_R_AR(ins, size, ireg, reg, offs); + else + getEmitter()->emitIns_AR_R(ins, size, ireg, reg, offs); + } + else + { + /* The address is an absolute value */ + + assert(addr->IsCnsIntOrI()); + +#ifdef RELOC_SUPPORT + // Do we need relocations? + if (compiler->opts.compReloc && addr->IsIconHandle()) + { + size = EA_SET_FLG(size, EA_DSP_RELOC_FLG); + // offs should be smaller than ZapperModule::FixupPlaceHolder + // so that we can uniquely identify the handle + assert(offs <= 4); + } +#endif + reg = REG_NA; + ssize_t disp = addr->gtIntCon.gtIconVal + offs; + + // Cross our fingers and hope the codegenerator did the right + // thing and the constant address can be RIP-relative + + if (cons) + getEmitter()->emitIns_I_AI(ins, size, imm, disp); + else if (rdst) + getEmitter()->emitIns_R_AI(ins, size, ireg, disp); + else + getEmitter()->emitIns_AI_R(ins, size, ireg, disp); + } + + return; + } + + /* Figure out what complex address mode to use */ + + regNumber baseReg, indReg; + unsigned indScale = 0, cns = 0; + + instGetAddrMode(addr, &baseReg, &indScale, &indReg, &cns); + + /* Add the constant offset value, if present */ + + offs += cns; + + /* Is there an index reg operand? */ + + if (indReg != REG_NA) + { + /* Is the index reg operand scaled? */ + + if (indScale) + { + /* Is there a base address operand? */ + + if (baseReg != REG_NA) + { + reg = baseReg; + + /* The address is "[reg + {2/4/8} * indReg + offs]" */ + + if (cons) + getEmitter()->emitIns_I_ARX(ins, size, imm, reg, indReg, indScale, offs); + else if (rdst) + getEmitter()->emitIns_R_ARX(ins, size, ireg, reg, indReg, indScale, offs); + else + getEmitter()->emitIns_ARX_R(ins, size, ireg, reg, indReg, indScale, offs); + } + else + { + /* The address is "[{2/4/8} * indReg + offs]" */ + + if (cons) + getEmitter()->emitIns_I_AX(ins, size, imm, indReg, indScale, offs); + else if (rdst) + getEmitter()->emitIns_R_AX(ins, size, ireg, indReg, indScale, offs); + else + getEmitter()->emitIns_AX_R(ins, size, ireg, indReg, indScale, offs); + } + } + else + { + assert(baseReg != REG_NA); + reg = baseReg; + + /* The address is "[reg + indReg + offs]" */ + if (cons) + getEmitter()->emitIns_I_ARR(ins, size, imm, reg, indReg, offs); + else if (rdst) + getEmitter()->emitIns_R_ARR(ins, size, ireg, reg, indReg, offs); + else + getEmitter()->emitIns_ARR_R(ins, size, ireg, reg, indReg, offs); + } + } + else + { + unsigned cpx = 0; + CORINFO_CLASS_HANDLE cls = 0; + + /* No second operand: the address is "[reg + icon]" */ + + assert(baseReg != REG_NA); + reg = baseReg; + +#ifdef LATE_DISASM + /* + Keep in mind that non-static data members (GT_FIELD nodes) were + transformed into GT_IND nodes - we keep the CLS/CPX information + in the GT_CNS_INT node representing the field offset of the + class member + */ + + if (addr->gtOper != GT_LEA && (addr->gtOp.gtOp2->gtOper == GT_CNS_INT) && + addr->gtOp.gtOp2->IsIconHandle(GTF_ICON_FIELD_HDL)) + { + /* This is a field offset - set the CPX/CLS values to emit a fixup */ + + cpx = addr->gtOp.gtOp2->gtIntCon.gtIconFld.gtIconCPX; + cls = addr->gtOp.gtOp2->gtIntCon.gtIconFld.gtIconCls; + } +#endif + + if (cons) + { + getEmitter()->emitIns_I_AR(ins, size, imm, reg, offs, cpx, cls); + } + else if (rdst) + { + getEmitter()->emitIns_R_AR(ins, size, ireg, reg, offs, cpx, cls); + } + else + { + getEmitter()->emitIns_AR_R(ins, size, ireg, reg, offs, cpx, cls); + } + } +} + +#endif // !CPU_LOAD_STORE_ARCH +#endif // LEGACY_BACKEND + +/***************************************************************************** + * + * Emit a "call [r/m]" instruction (the r/m operand given by a tree). + */ + +void CodeGen::instEmit_indCall(GenTreePtr call, + size_t argSize, + emitAttr retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(emitAttr secondRetSize)) +{ + GenTreePtr addr; + + emitter::EmitCallType emitCallType; + + regNumber brg = REG_NA; + regNumber xrg = REG_NA; + unsigned mul = 0; + unsigned cns = 0; + + CORINFO_SIG_INFO* sigInfo = nullptr; + + assert(call->gtOper == GT_CALL); + + /* Get hold of the function address */ + + assert(call->gtCall.gtCallType == CT_INDIRECT); + addr = call->gtCall.gtCallAddr; + assert(addr); + +#ifdef DEBUG + // Pass the call signature information from the GenTree node so the emitter can associate + // native call sites with the signatures they were generated from. + sigInfo = call->gtCall.callSig; +#endif // DEBUG + +#if CPU_LOAD_STORE_ARCH + + emitCallType = emitter::EC_INDIR_R; + + if (!addr->OperIsIndir()) + { + if (!(addr->gtFlags & GTF_REG_VAL) && (addr->OperGet() == GT_CNS_INT)) + { + ssize_t funcPtr = addr->gtIntCon.gtIconVal; + + getEmitter()->emitIns_Call(emitter::EC_FUNC_ADDR, + NULL, // methHnd + INDEBUG_LDISASM_COMMA(sigInfo)(void*) funcPtr, argSize, + retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur); + return; + } + } + else + { + /* Get hold of the address of the function pointer */ + + addr = addr->gtOp.gtOp1; + } + + if (addr->gtFlags & GTF_REG_VAL) + { + /* The address is "reg" */ + + brg = addr->gtRegNum; + } + else + { + // Force the address into a register + CLANG_FORMAT_COMMENT_ANCHOR; + +#ifdef LEGACY_BACKEND + genCodeForTree(addr, RBM_NONE); +#endif // LEGACY_BACKEND + assert(addr->gtFlags & GTF_REG_VAL); + brg = addr->gtRegNum; + } + +#else // CPU_LOAD_STORE_ARCH + + /* Is there an indirection? */ + + if (!addr->OperIsIndir()) + { + if (addr->gtFlags & GTF_REG_VAL) + { + emitCallType = emitter::EC_INDIR_R; + brg = addr->gtRegNum; + } + else + { + if (addr->OperGet() != GT_CNS_INT) + { + assert(addr->OperGet() == GT_LCL_VAR); + + emitCallType = emitter::EC_INDIR_SR; + cns = addr->gtLclVarCommon.gtLclNum; + } + else + { + ssize_t funcPtr = addr->gtIntCon.gtIconVal; + + getEmitter()->emitIns_Call(emitter::EC_FUNC_ADDR, + nullptr, // methHnd + INDEBUG_LDISASM_COMMA(sigInfo)(void*) funcPtr, argSize, + retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur); + return; + } + } + } + else + { + /* This is an indirect call */ + + emitCallType = emitter::EC_INDIR_ARD; + + /* Get hold of the address of the function pointer */ + + addr = addr->gtOp.gtOp1; + + /* Has the address been conveniently loaded into a register? */ + + if (addr->gtFlags & GTF_REG_VAL) + { + /* The address is "reg" */ + + brg = addr->gtRegNum; + } + else + { + bool rev = false; + + GenTreePtr rv1 = nullptr; + GenTreePtr rv2 = nullptr; + + /* Figure out what complex address mode to use */ + + INDEBUG(bool yes =) + genCreateAddrMode(addr, -1, true, RBM_NONE, &rev, &rv1, &rv2, &mul, &cns); + + INDEBUG(PREFIX_ASSUME(yes)); // since we have called genMakeAddressable() on call->gtCall.gtCallAddr + + /* Get the additional operands if any */ + + if (rv1) + { + assert(rv1->gtFlags & GTF_REG_VAL); + brg = rv1->gtRegNum; + } + + if (rv2) + { + assert(rv2->gtFlags & GTF_REG_VAL); + xrg = rv2->gtRegNum; + } + } + } + + assert(emitCallType == emitter::EC_INDIR_R || emitCallType == emitter::EC_INDIR_SR || + emitCallType == emitter::EC_INDIR_C || emitCallType == emitter::EC_INDIR_ARD); + +#endif // CPU_LOAD_STORE_ARCH + + getEmitter()->emitIns_Call(emitCallType, + nullptr, // methHnd + INDEBUG_LDISASM_COMMA(sigInfo) nullptr, // addr + argSize, retSize MULTIREG_HAS_SECOND_GC_RET_ONLY_ARG(secondRetSize), + gcInfo.gcVarPtrSetCur, gcInfo.gcRegGCrefSetCur, gcInfo.gcRegByrefSetCur, + BAD_IL_OFFSET, // ilOffset + brg, xrg, mul, + cns); // addressing mode values +} + +#ifdef LEGACY_BACKEND +/***************************************************************************** + * + * Emit an "op [r/m]" instruction (the r/m operand given by a tree). + */ + +void CodeGen::instEmit_RM(instruction ins, GenTreePtr tree, GenTreePtr addr, unsigned offs) +{ + emitAttr size; + + if (!instIsFP(ins)) + size = emitTypeSize(tree->TypeGet()); + else + size = EA_ATTR(genTypeSize(tree->TypeGet())); + + sched_AM(ins, size, REG_NA, false, addr, offs); +} + +/***************************************************************************** + * + * Emit an "op [r/m], reg" instruction (the r/m operand given by a tree). + */ + +void CodeGen::instEmit_RM_RV(instruction ins, emitAttr size, GenTreePtr tree, regNumber reg, unsigned offs) +{ +#ifdef _TARGET_XARCH_ + assert(instIsFP(ins) == 0); +#endif + sched_AM(ins, size, reg, false, tree, offs); +} +#endif // LEGACY_BACKEND + +/***************************************************************************** + * + * Generate an instruction that has one operand given by a tree (which has + * been made addressable). + */ + +void CodeGen::inst_TT(instruction ins, GenTreePtr tree, unsigned offs, int shfv, emitAttr size) +{ + bool sizeInferred = false; + + if (size == EA_UNKNOWN) + { + sizeInferred = true; + if (instIsFP(ins)) + { + size = EA_ATTR(genTypeSize(tree->TypeGet())); + } + else + { + size = emitTypeSize(tree->TypeGet()); + } + } + +AGAIN: + + /* Is the value sitting in a register? */ + + if (tree->gtFlags & GTF_REG_VAL) + { + regNumber reg; + +#ifndef _TARGET_64BIT_ +#ifdef LEGACY_BACKEND + LONGREG_TT: +#endif // LEGACY_BACKEND +#endif + +#if FEATURE_STACK_FP_X87 + + /* Is this a floating-point instruction? */ + + if (isFloatRegType(tree->gtType)) + { + reg = tree->gtRegNum; + + assert(instIsFP(ins) && ins != INS_fst && ins != INS_fstp); + assert(shfv == 0); + + inst_FS(ins, reg + genGetFPstkLevel()); + return; + } +#endif // FEATURE_STACK_FP_X87 + + assert(!instIsFP(ins)); + +#if CPU_LONG_USES_REGPAIR + if (tree->gtType == TYP_LONG) + { + if (offs) + { + assert(offs == sizeof(int)); + reg = genRegPairHi(tree->gtRegPair); + } + else + { + reg = genRegPairLo(tree->gtRegPair); + } + } + else +#endif // CPU_LONG_USES_REGPAIR + { + reg = tree->gtRegNum; + } + + /* Make sure it is not the "stack-half" of an enregistered long */ + + if (reg != REG_STK) + { + // For short types, indicate that the value is promoted to 4 bytes. + // For longs, we are only emitting half of it so again set it to 4 bytes. + // but leave the GC tracking information alone + if (sizeInferred && EA_SIZE(size) < EA_4BYTE) + { + size = EA_SET_SIZE(size, 4); + } + + if (shfv) + { + getEmitter()->emitIns_R_I(ins, size, reg, shfv); + } + else + { + inst_RV(ins, reg, tree->TypeGet(), size); + } + + return; + } + } + + /* Is this a spilled value? */ + + if (tree->gtFlags & GTF_SPILLED) + { + assert(!"ISSUE: If this can happen, we need to generate 'ins [ebp+spill]'"); + } + + switch (tree->gtOper) + { + unsigned varNum; + + case GT_LCL_VAR: + +#ifdef LEGACY_BACKEND + /* Is this an enregistered long ? */ + + if (tree->gtType == TYP_LONG && !(tree->gtFlags & GTF_REG_VAL)) + { + /* Avoid infinite loop */ + + if (genMarkLclVar(tree)) + goto LONGREG_TT; + } +#endif // LEGACY_BACKEND + + inst_set_SV_var(tree); + goto LCL; + + case GT_LCL_FLD: + + offs += tree->gtLclFld.gtLclOffs; + goto LCL; + + LCL: + varNum = tree->gtLclVarCommon.gtLclNum; + assert(varNum < compiler->lvaCount); + + if (shfv) + { + getEmitter()->emitIns_S_I(ins, size, varNum, offs, shfv); + } + else + { + getEmitter()->emitIns_S(ins, size, varNum, offs); + } + + return; + + case GT_CLS_VAR: + // Make sure FP instruction size matches the operand size + // (We optimized constant doubles to floats when we can, just want to + // make sure that we don't mistakenly use 8 bytes when the + // constant. + assert(!isFloatRegType(tree->gtType) || genTypeSize(tree->gtType) == EA_SIZE_IN_BYTES(size)); + + if (shfv) + { + getEmitter()->emitIns_C_I(ins, size, tree->gtClsVar.gtClsVarHnd, offs, shfv); + } + else + { + getEmitter()->emitIns_C(ins, size, tree->gtClsVar.gtClsVarHnd, offs); + } + return; + + case GT_IND: + case GT_NULLCHECK: + case GT_ARR_ELEM: + { +#ifndef LEGACY_BACKEND + assert(!"inst_TT not supported for GT_IND, GT_NULLCHECK or GT_ARR_ELEM in !LEGACY_BACKEND"); +#else // LEGACY_BACKEND + GenTreePtr addr = tree->OperIsIndir() ? tree->gtOp.gtOp1 : tree; + if (shfv) + sched_AM(ins, size, REG_NA, false, addr, offs, true, shfv); + else + instEmit_RM(ins, tree, addr, offs); +#endif // LEGACY_BACKEND + } + break; + +#ifdef _TARGET_X86_ + case GT_CNS_INT: + // We will get here for GT_MKREFANY from CodeGen::genPushArgList + assert(offs == 0); + assert(!shfv); + if (tree->IsIconHandle()) + inst_IV_handle(ins, tree->gtIntCon.gtIconVal); + else + inst_IV(ins, tree->gtIntCon.gtIconVal); + break; +#endif + + case GT_COMMA: + // tree->gtOp.gtOp1 - already processed by genCreateAddrMode() + tree = tree->gtOp.gtOp2; + goto AGAIN; + + default: + assert(!"invalid address"); + } +} + +/***************************************************************************** + * + * Generate an instruction that has one operand given by a tree (which has + * been made addressable) and another that is a register. + */ + +void CodeGen::inst_TT_RV(instruction ins, GenTreePtr tree, regNumber reg, unsigned offs, emitAttr size, insFlags flags) +{ + assert(reg != REG_STK); + +AGAIN: + + /* Is the value sitting in a register? */ + + if (tree->gtFlags & GTF_REG_VAL) + { + regNumber rg2; + +#ifdef _TARGET_64BIT_ + assert(!instIsFP(ins)); + + rg2 = tree->gtRegNum; + + assert(offs == 0); + assert(rg2 != REG_STK); + + if (ins != INS_mov || rg2 != reg) + { + inst_RV_RV(ins, rg2, reg, tree->TypeGet()); + } + return; + +#else // !_TARGET_64BIT_ + +#ifdef LEGACY_BACKEND + LONGREG_TT_RV: +#endif // LEGACY_BACKEND + +#ifdef _TARGET_XARCH_ + assert(!instIsFP(ins)); +#endif + +#if CPU_LONG_USES_REGPAIR + if (tree->gtType == TYP_LONG) + { + if (offs) + { + assert(offs == sizeof(int)); + rg2 = genRegPairHi(tree->gtRegPair); + } + else + { + rg2 = genRegPairLo(tree->gtRegPair); + } + } + else +#endif // CPU_LONG_USES_REGPAIR + { + rg2 = tree->gtRegNum; + } + + if (rg2 != REG_STK) + { + if (ins != INS_mov || rg2 != reg) + inst_RV_RV(ins, rg2, reg, tree->TypeGet(), size, flags); + return; + } + +#endif // _TARGET_64BIT_ + } + + /* Is this a spilled value? */ + + if (tree->gtFlags & GTF_SPILLED) + { + assert(!"ISSUE: If this can happen, we need to generate 'ins [ebp+spill]'"); + } + + if (size == EA_UNKNOWN) + { + if (instIsFP(ins)) + { + size = EA_ATTR(genTypeSize(tree->TypeGet())); + } + else + { + size = emitTypeSize(tree->TypeGet()); + } + } + + switch (tree->gtOper) + { + unsigned varNum; + + case GT_LCL_VAR: + +#ifdef LEGACY_BACKEND + if (tree->gtType == TYP_LONG && !(tree->gtFlags & GTF_REG_VAL)) + { + /* Avoid infinite loop */ + + if (genMarkLclVar(tree)) + goto LONGREG_TT_RV; + } +#endif // LEGACY_BACKEND + + inst_set_SV_var(tree); + goto LCL; + + case GT_LCL_FLD: + case GT_STORE_LCL_FLD: + offs += tree->gtLclFld.gtLclOffs; + goto LCL; + + LCL: + + varNum = tree->gtLclVarCommon.gtLclNum; + assert(varNum < compiler->lvaCount); + +#if CPU_LOAD_STORE_ARCH + if (!getEmitter()->emitInsIsStore(ins)) + { +#ifndef LEGACY_BACKEND + // TODO-LdStArch-Bug: Should regTmp be a dst on the node or an internal reg? + // Either way, it is not currently being handled by Lowering. + regNumber regTmp = tree->gtRegNum; + assert(regTmp != REG_NA); +#else // LEGACY_BACKEND + regNumber regTmp = regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(reg)); +#endif // LEGACY_BACKEND + getEmitter()->emitIns_R_S(ins_Load(tree->TypeGet()), size, regTmp, varNum, offs); + getEmitter()->emitIns_R_R(ins, size, regTmp, reg, flags); + getEmitter()->emitIns_S_R(ins_Store(tree->TypeGet()), size, regTmp, varNum, offs); + + regTracker.rsTrackRegTrash(regTmp); + } + else +#endif + { + // ins is a Store instruction + // + getEmitter()->emitIns_S_R(ins, size, reg, varNum, offs); +#ifdef _TARGET_ARM_ + // If we need to set the flags then add an extra movs reg,reg instruction + if (flags == INS_FLAGS_SET) + getEmitter()->emitIns_R_R(INS_mov, size, reg, reg, INS_FLAGS_SET); +#endif + } + return; + + case GT_CLS_VAR: + // Make sure FP instruction size matches the operand size + // (We optimized constant doubles to floats when we can, just want to + // make sure that we don't mistakenly use 8 bytes when the + // constant). + assert(!isFloatRegType(tree->gtType) || genTypeSize(tree->gtType) == EA_SIZE_IN_BYTES(size)); + +#if CPU_LOAD_STORE_ARCH + if (!getEmitter()->emitInsIsStore(ins)) + { +#ifndef LEGACY_BACKEND + NYI("Store of GT_CLS_VAR not supported for ARM RyuJIT Backend"); +#else // LEGACY_BACKEND + regNumber regTmpAddr = regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(reg)); + regNumber regTmpArith = regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(reg) & ~genRegMask(regTmpAddr)); + + getEmitter()->emitIns_R_C(INS_lea, EA_PTRSIZE, regTmpAddr, tree->gtClsVar.gtClsVarHnd, offs); + getEmitter()->emitIns_R_R(ins_Load(tree->TypeGet()), size, regTmpArith, regTmpAddr); + getEmitter()->emitIns_R_R(ins, size, regTmpArith, reg, flags); + getEmitter()->emitIns_R_R(ins_Store(tree->TypeGet()), size, regTmpArith, regTmpAddr); + + regTracker.rsTrackRegTrash(regTmpAddr); + regTracker.rsTrackRegTrash(regTmpArith); +#endif // LEGACY_BACKEND + } + else +#endif // CPU_LOAD_STORE_ARCH + { + getEmitter()->emitIns_C_R(ins, size, tree->gtClsVar.gtClsVarHnd, reg, offs); + } + return; + + case GT_IND: + case GT_NULLCHECK: + case GT_ARR_ELEM: + { +#ifndef LEGACY_BACKEND + assert(!"inst_TT_RV not supported for GT_IND, GT_NULLCHECK or GT_ARR_ELEM in RyuJIT Backend"); +#else // LEGACY_BACKEND + GenTreePtr addr = tree->OperIsIndir() ? tree->gtOp.gtOp1 : tree; + sched_AM(ins, size, reg, false, addr, offs, false, 0, flags); +#endif // LEGACY_BACKEND + } + break; + + case GT_COMMA: + // tree->gtOp.gtOp1 - already processed by genCreateAddrMode() + tree = tree->gtOp.gtOp2; + goto AGAIN; + + default: + assert(!"invalid address"); + } +} + +regNumber CodeGen::genGetZeroRegister() +{ + regNumber zeroReg = REG_NA; + +#if REDUNDANT_LOAD + + // Is the constant already in some register? + + zeroReg = regTracker.rsIconIsInReg(0); +#endif + +#ifdef LEGACY_BACKEND + if (zeroReg == REG_NA) + { + regMaskTP freeMask = regSet.rsRegMaskFree(); + + if ((freeMask != 0) && (compiler->compCodeOpt() != Compiler::FAST_CODE)) + { + // For SMALL_CODE and BLENDED_CODE, + // we try to generate: + // + // xor reg, reg + // mov dest, reg + // + // When selecting a register to xor we try to avoid REG_TMP_0 + // when we have another CALLEE_TRASH register available. + // This will often let us reuse the zeroed register in + // several back-to-back assignments + // + if ((freeMask & RBM_CALLEE_TRASH) != RBM_TMP_0) + freeMask &= ~RBM_TMP_0; + zeroReg = regSet.rsGrabReg(freeMask); // PickReg in stress will pick 'random' registers + // We want one in the freeMask set, so just use GrabReg + genSetRegToIcon(zeroReg, 0, TYP_INT); + } + } +#endif // !LEGACY_BACKEND + + return zeroReg; +} + +/***************************************************************************** + * + * Generate an instruction that has one operand given by a tree (which has + * been made addressable) and another that is an integer constant. + */ +#ifdef LEGACY_BACKEND +void CodeGen::inst_TT_IV(instruction ins, GenTreePtr tree, ssize_t val, unsigned offs, emitAttr size, insFlags flags) +{ + bool sizeInferred = false; + + if (size == EA_UNKNOWN) + { + sizeInferred = true; + if (instIsFP(ins)) + size = EA_ATTR(genTypeSize(tree->TypeGet())); + else + size = emitTypeSize(tree->TypeGet()); + } + +AGAIN: + + /* Is the value sitting in a register? */ + + if (tree->gtFlags & GTF_REG_VAL) + { +#ifndef _TARGET_64BIT_ + LONGREG_TT_IV: +#endif + regNumber reg; + + assert(instIsFP(ins) == 0); + +#if CPU_LONG_USES_REGPAIR + if (tree->gtType == TYP_LONG) + { + if (offs == 0) + { + reg = genRegPairLo(tree->gtRegPair); + } + else // offs == 4 + { + assert(offs == sizeof(int)); + reg = genRegPairHi(tree->gtRegPair); + } +#if CPU_LOAD_STORE_ARCH + if (reg == REG_STK && !getEmitter()->emitInsIsLoadOrStore(ins)) + { + reg = regSet.rsPickFreeReg(); + inst_RV_TT(INS_mov, reg, tree, offs, EA_4BYTE, flags); + regTracker.rsTrackRegTrash(reg); + } +#endif + } + else +#endif // CPU_LONG_USES_REGPAIR + { + reg = tree->gtRegNum; + } + + if (reg != REG_STK) + { + // We always widen as part of enregistering, + // so a smaller tree in a register can be + // treated as 4 bytes + if (sizeInferred && (size < EA_4BYTE)) + { + size = EA_SET_SIZE(size, EA_4BYTE); + } + + if ((ins == INS_mov) && !EA_IS_CNS_RELOC(size)) + { + genSetRegToIcon(reg, val, tree->TypeGet(), flags); + } + else + { +#if defined(_TARGET_XARCH_) + inst_RV_IV(ins, reg, val, size); +#elif defined(_TARGET_ARM_) + if (!EA_IS_CNS_RELOC(size) && arm_Valid_Imm_For_Instr(ins, val, flags)) + { + getEmitter()->emitIns_R_I(ins, size, reg, val, flags); + } + else // We need a scratch register + { + // Load imm into a register + regMaskTP usedMask; + if (tree->gtType == TYP_LONG) + { + usedMask = genRegPairMask(tree->gtRegPair); +#if CPU_LOAD_STORE_ARCH + // In gtRegPair, this part of the long may have been on the stack + // in which case, the code above would have loaded it into 'reg' + // and so we need to also include 'reg' in the set of registers + // that are already in use. + usedMask |= genRegMask(reg); +#endif // CPU_LOAD_STORE_ARCH + } + else + { + usedMask = genRegMask(tree->gtRegNum); + } + regNumber immReg = regSet.rsGrabReg(RBM_ALLINT & ~usedMask); + noway_assert(reg != immReg); + instGen_Set_Reg_To_Imm(size, immReg, val); + if (getEmitter()->emitInsIsStore(ins)) + ins = INS_mov; + getEmitter()->emitIns_R_R(ins, size, reg, immReg, flags); + } +#else + NYI("inst_TT_IV - unknown target"); +#endif + } + return; + } + } + +#ifdef _TARGET_XARCH_ + /* Are we storing a zero? */ + + if ((ins == INS_mov) && (val == 0) && + ((genTypeSize(tree->gtType) == sizeof(int)) || (genTypeSize(tree->gtType) == REGSIZE_BYTES))) + { + regNumber zeroReg; + + zeroReg = genGetZeroRegister(); + + if (zeroReg != REG_NA) + { + inst_TT_RV(INS_mov, tree, zeroReg, offs); + return; + } + } +#endif + +#if CPU_LOAD_STORE_ARCH + /* Are we storing/comparing with a constant? */ + + if (getEmitter()->emitInsIsStore(ins) || getEmitter()->emitInsIsCompare(ins)) + { + // Load val into a register + + regNumber valReg; + valReg = regSet.rsGrabReg(RBM_ALLINT); + instGen_Set_Reg_To_Imm(EA_PTRSIZE, valReg, val); + inst_TT_RV(ins, tree, valReg, offs, size, flags); + return; + } + else if (ins == INS_mov) + { + assert(!"Please call ins_Store(type) to get the store instruction"); + } + assert(!getEmitter()->emitInsIsLoad(ins)); +#endif // CPU_LOAD_STORE_ARCH + + /* Is this a spilled value? */ + + if (tree->gtFlags & GTF_SPILLED) + { + assert(!"ISSUE: If this can happen, we need to generate 'ins [ebp+spill], icon'"); + } + +#ifdef _TARGET_AMD64_ + if ((EA_SIZE(size) == EA_8BYTE) && (((int)val != (ssize_t)val) || EA_IS_CNS_RELOC(size))) + { + // Load imm into a register + regNumber immReg = regSet.rsGrabReg(RBM_ALLINT); + instGen_Set_Reg_To_Imm(size, immReg, val); + inst_TT_RV(ins, tree, immReg, offs); + return; + } +#endif // _TARGET_AMD64_ + + int ival = (int)val; + + switch (tree->gtOper) + { + unsigned varNum; + LclVarDsc* varDsc; + + case GT_LCL_FLD: + + varNum = tree->gtLclVarCommon.gtLclNum; + assert(varNum < compiler->lvaCount); + offs += tree->gtLclFld.gtLclOffs; + + goto LCL; + + case GT_LCL_VAR: + +#ifndef _TARGET_64BIT_ + /* Is this an enregistered long ? */ + + if (tree->gtType == TYP_LONG && !(tree->gtFlags & GTF_REG_VAL)) + { + /* Avoid infinite loop */ + + if (genMarkLclVar(tree)) + goto LONGREG_TT_IV; + } +#endif // !_TARGET_64BIT_ + + inst_set_SV_var(tree); + + varNum = tree->gtLclVarCommon.gtLclNum; + assert(varNum < compiler->lvaCount); + varDsc = &compiler->lvaTable[varNum]; + + // Fix the immediate by sign extending if needed + if (size < EA_4BYTE && !varTypeIsUnsigned(varDsc->TypeGet())) + { + if (size == EA_1BYTE) + { + if ((ival & 0x7f) != ival) + ival = ival | 0xffffff00; + } + else + { + assert(size == EA_2BYTE); + if ((ival & 0x7fff) != ival) + ival = ival | 0xffff0000; + } + } + + // A local stack slot is at least 4 bytes in size, regardles of + // what the local var is typed as, so auto-promote it here + // unless the codegenerator told us a size, or it is a field + // of a promoted struct + if (sizeInferred && (size < EA_4BYTE) && !varDsc->lvIsStructField) + { + size = EA_SET_SIZE(size, EA_4BYTE); + } + + LCL: + + /* Integer instructions never operate on more than EA_PTRSIZE */ + + assert(instIsFP(ins) == false); + +#if CPU_LOAD_STORE_ARCH + if (!getEmitter()->emitInsIsStore(ins)) + { + regNumber regTmp = regSet.rsPickFreeReg(RBM_ALLINT); + getEmitter()->emitIns_R_S(ins_Load(tree->TypeGet()), size, regTmp, varNum, offs); + regTracker.rsTrackRegTrash(regTmp); + + if (arm_Valid_Imm_For_Instr(ins, val, flags)) + { + getEmitter()->emitIns_R_I(ins, size, regTmp, ival, flags); + } + else // We need a scratch register + { + // Load imm into a register + regNumber regImm = regSet.rsGrabReg(RBM_ALLINT & ~genRegMask(regTmp)); + + instGen_Set_Reg_To_Imm(size, regImm, val); + getEmitter()->emitIns_R_R(ins, size, regTmp, regImm, flags); + } + getEmitter()->emitIns_S_R(ins_Store(tree->TypeGet()), size, regTmp, varNum, offs); + } + else +#endif + { + getEmitter()->emitIns_S_I(ins, size, varNum, offs, ival); + } + return; + + case GT_CLS_VAR: + // Make sure FP instruction size matches the operand size + // (We optimize constant doubles to floats when we can) + // We just want to make sure that we don't mistakenly + // use 8 bytes when the constant is smaller. + // + assert(!isFloatRegType(tree->gtType) || genTypeSize(tree->gtType) == EA_SIZE_IN_BYTES(size)); + +#if CPU_LOAD_STORE_ARCH + regNumber regTmpAddr; + regTmpAddr = regSet.rsPickFreeReg(RBM_ALLINT); + + getEmitter()->emitIns_R_C(INS_lea, EA_PTRSIZE, regTmpAddr, tree->gtClsVar.gtClsVarHnd, offs); + regTracker.rsTrackRegTrash(regTmpAddr); + + if (!getEmitter()->emitInsIsStore(ins)) + { + regNumber regTmpArith = regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(regTmpAddr)); + + getEmitter()->emitIns_R_R(ins_Load(tree->TypeGet()), size, regTmpArith, regTmpAddr); + + if (arm_Valid_Imm_For_Instr(ins, ival, flags)) + { + getEmitter()->emitIns_R_R_I(ins, size, regTmpArith, regTmpArith, ival, flags); + } + else + { + regNumber regTmpImm = + regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(regTmpAddr) & ~genRegMask(regTmpArith)); + instGen_Set_Reg_To_Imm(EA_4BYTE, regTmpImm, (ssize_t)ival); + getEmitter()->emitIns_R_R(ins, size, regTmpArith, regTmpImm, flags); + } + regTracker.rsTrackRegTrash(regTmpArith); + + getEmitter()->emitIns_R_R(ins_Store(tree->TypeGet()), size, regTmpArith, regTmpAddr); + } + else + { + regNumber regTmpImm = regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(regTmpAddr)); + + instGen_Set_Reg_To_Imm(EA_4BYTE, regTmpImm, (ssize_t)ival, flags); + getEmitter()->emitIns_R_R(ins_Store(tree->TypeGet()), size, regTmpImm, regTmpAddr); + } +#else // !CPU_LOAD_STORE_ARCH + getEmitter()->emitIns_C_I(ins, size, tree->gtClsVar.gtClsVarHnd, offs, ival); +#endif + return; + + case GT_IND: + case GT_NULLCHECK: + case GT_ARR_ELEM: + { + GenTreePtr addr = tree->OperIsIndir() ? tree->gtOp.gtOp1 : tree; + sched_AM(ins, size, REG_NA, false, addr, offs, true, ival, flags); + } + return; + + case GT_COMMA: + // tree->gtOp.gtOp1 - already processed by genCreateAddrMode() + tree = tree->gtOp.gtOp2; + goto AGAIN; + + default: + assert(!"invalid address"); + } +} +#endif // LEGACY_BACKEND + +#ifdef LEGACY_BACKEND +/***************************************************************************** + * + * Generate an instruction that has one operand given by a register and the + * other one by an indirection tree (which has been made addressable). + */ + +void CodeGen::inst_RV_AT( + instruction ins, emitAttr size, var_types type, regNumber reg, GenTreePtr tree, unsigned offs, insFlags flags) +{ +#ifdef _TARGET_XARCH_ +#ifdef DEBUG + // If it is a GC type and the result is not, then either + // 1) it is an LEA + // 2) optOptimizeBools() optimized if (ref != 0 && ref != 0) to if (ref & ref) + // 3) optOptimizeBools() optimized if (ref == 0 || ref == 0) to if (ref | ref) + // 4) byref - byref = int + if (type == TYP_REF && !EA_IS_GCREF(size)) + assert((EA_IS_BYREF(size) && ins == INS_add) || (ins == INS_lea || ins == INS_and || ins == INS_or)); + if (type == TYP_BYREF && !EA_IS_BYREF(size)) + assert(ins == INS_lea || ins == INS_and || ins == INS_or || ins == INS_sub); + assert(!instIsFP(ins)); +#endif +#endif + + // Integer instructions never operate on more than EA_PTRSIZE. + if (EA_SIZE(size) > EA_PTRSIZE && !instIsFP(ins)) + EA_SET_SIZE(size, EA_PTRSIZE); + + GenTreePtr addr = tree; + sched_AM(ins, size, reg, true, addr, offs, false, 0, flags); +} + +/***************************************************************************** + * + * Generate an instruction that has one operand given by an indirection tree + * (which has been made addressable) and an integer constant. + */ + +void CodeGen::inst_AT_IV(instruction ins, emitAttr size, GenTreePtr baseTree, int icon, unsigned offs) +{ + sched_AM(ins, size, REG_NA, false, baseTree, offs, true, icon); +} +#endif // LEGACY_BACKEND + +/***************************************************************************** + * + * Generate an instruction that has one operand given by a register and the + * other one by a tree (which has been made addressable). + */ + +void CodeGen::inst_RV_TT(instruction ins, + regNumber reg, + GenTreePtr tree, + unsigned offs, + emitAttr size, + insFlags flags /* = INS_FLAGS_DONT_CARE */) +{ + assert(reg != REG_STK); + + if (size == EA_UNKNOWN) + { + if (!instIsFP(ins)) + { + size = emitTypeSize(tree->TypeGet()); + } + else + { + size = EA_ATTR(genTypeSize(tree->TypeGet())); + } + } + +#ifdef _TARGET_XARCH_ +#ifdef DEBUG + // If it is a GC type and the result is not, then either + // 1) it is an LEA + // 2) optOptimizeBools() optimized if (ref != 0 && ref != 0) to if (ref & ref) + // 3) optOptimizeBools() optimized if (ref == 0 || ref == 0) to if (ref | ref) + // 4) byref - byref = int + if (tree->gtType == TYP_REF && !EA_IS_GCREF(size)) + { + assert((EA_IS_BYREF(size) && ins == INS_add) || (ins == INS_lea || ins == INS_and || ins == INS_or)); + } + if (tree->gtType == TYP_BYREF && !EA_IS_BYREF(size)) + { + assert(ins == INS_lea || ins == INS_and || ins == INS_or || ins == INS_sub); + } +#endif +#endif + +#if CPU_LOAD_STORE_ARCH + if (ins == INS_mov) + { +#if defined(_TARGET_ARM_) + if (tree->TypeGet() != TYP_LONG) + { + ins = ins_Move_Extend(tree->TypeGet(), (tree->gtFlags & GTF_REG_VAL) != 0); + } + else if (offs == 0) + { + ins = ins_Move_Extend(TYP_INT, + (tree->gtFlags & GTF_REG_VAL) != 0 && genRegPairLo(tree->gtRegPair) != REG_STK); + } + else + { + ins = ins_Move_Extend(TYP_INT, + (tree->gtFlags & GTF_REG_VAL) != 0 && genRegPairHi(tree->gtRegPair) != REG_STK); + } +#elif defined(_TARGET_ARM64_) + ins = ins_Move_Extend(tree->TypeGet(), (tree->gtFlags & GTF_REG_VAL) != 0); +#else + NYI("CodeGen::inst_RV_TT with INS_mov"); +#endif + } +#endif // CPU_LOAD_STORE_ARCH + +AGAIN: + + /* Is the value sitting in a register? */ + + if (tree->gtFlags & GTF_REG_VAL) + { +#ifdef _TARGET_64BIT_ + assert(instIsFP(ins) == 0); + + regNumber rg2 = tree->gtRegNum; + + assert(offs == 0); + assert(rg2 != REG_STK); + + if ((ins != INS_mov) || (rg2 != reg)) + { + inst_RV_RV(ins, reg, rg2, tree->TypeGet(), size); + } + return; + +#else // !_TARGET_64BIT_ + +#ifdef LEGACY_BACKEND + LONGREG_RVTT: +#endif // LEGACY_BACKEND + +#ifdef _TARGET_XARCH_ + assert(instIsFP(ins) == 0); +#endif + + regNumber rg2; + +#if CPU_LONG_USES_REGPAIR + if (tree->gtType == TYP_LONG) + { + if (offs) + { + assert(offs == sizeof(int)); + + rg2 = genRegPairHi(tree->gtRegPair); + } + else + { + rg2 = genRegPairLo(tree->gtRegPair); + } + } + else +#endif // LEGACY_BACKEND + { + rg2 = tree->gtRegNum; + } + + if (rg2 != REG_STK) + { +#ifdef _TARGET_ARM_ + if (getEmitter()->emitInsIsLoad(ins) || (ins == INS_lea)) + { + ins = ins_Copy(tree->TypeGet()); + } +#endif + + bool isMoveIns = (ins == INS_mov); +#ifdef _TARGET_ARM_ + if (ins == INS_vmov) + isMoveIns = true; +#endif + if (!isMoveIns || (rg2 != reg)) + { + inst_RV_RV(ins, reg, rg2, tree->TypeGet(), size, flags); + } + return; + } + +#endif // _TARGET_64BIT_ + } + + /* Is this a spilled value? */ + + if (tree->gtFlags & GTF_SPILLED) + { + assert(!"ISSUE: If this can happen, we need to generate 'ins [ebp+spill]'"); + } + + switch (tree->gtOper) + { + unsigned varNum; + + case GT_LCL_VAR: + case GT_LCL_VAR_ADDR: + +#ifdef LEGACY_BACKEND + /* Is this an enregistered long ? */ + + if (tree->gtType == TYP_LONG && !(tree->gtFlags & GTF_REG_VAL)) + { + + /* Avoid infinite loop */ + + if (genMarkLclVar(tree)) + goto LONGREG_RVTT; + } +#endif // LEGACY_BACKEND + + inst_set_SV_var(tree); + goto LCL; + + case GT_LCL_FLD_ADDR: + case GT_LCL_FLD: + offs += tree->gtLclFld.gtLclOffs; + goto LCL; + + LCL: + varNum = tree->gtLclVarCommon.gtLclNum; + assert(varNum < compiler->lvaCount); + +#ifdef _TARGET_ARM_ + switch (ins) + { + case INS_mov: + ins = ins_Load(tree->TypeGet()); + __fallthrough; + + case INS_lea: + case INS_ldr: + case INS_ldrh: + case INS_ldrb: + case INS_ldrsh: + case INS_ldrsb: + case INS_vldr: + assert(flags != INS_FLAGS_SET); + getEmitter()->emitIns_R_S(ins, size, reg, varNum, offs); + return; + + default: + regNumber regTmp; +#ifndef LEGACY_BACKEND + if (tree->TypeGet() == TYP_LONG) + regTmp = (offs == 0) ? genRegPairLo(tree->gtRegPair) : genRegPairHi(tree->gtRegPair); + else + regTmp = tree->gtRegNum; +#else // LEGACY_BACKEND + if (varTypeIsFloating(tree)) + { + regTmp = regSet.PickRegFloat(tree->TypeGet()); + } + else + { + regTmp = regSet.rsPickReg(RBM_ALLINT & ~genRegMask(reg)); + } +#endif // LEGACY_BACKEND + + getEmitter()->emitIns_R_S(ins_Load(tree->TypeGet()), size, regTmp, varNum, offs); + getEmitter()->emitIns_R_R(ins, size, reg, regTmp, flags); + + regTracker.rsTrackRegTrash(regTmp); + return; + } +#else // !_TARGET_ARM_ + getEmitter()->emitIns_R_S(ins, size, reg, varNum, offs); + return; +#endif // !_TARGET_ARM_ + + case GT_CLS_VAR: + // Make sure FP instruction size matches the operand size + // (We optimized constant doubles to floats when we can, just want to + // make sure that we don't mistakenly use 8 bytes when the + // constant. + assert(!isFloatRegType(tree->gtType) || genTypeSize(tree->gtType) == EA_SIZE_IN_BYTES(size)); + +#if CPU_LOAD_STORE_ARCH +#ifndef LEGACY_BACKEND + assert(!"GT_CLS_VAR not supported in ARM RyuJIT backend"); +#else // LEGACY_BACKEND + switch (ins) + { + case INS_mov: + ins = ins_Load(tree->TypeGet()); + + __fallthrough; + + case INS_lea: + case INS_ldr: + case INS_ldrh: + case INS_ldrb: + case INS_ldrsh: + case INS_ldrsb: + case INS_vldr: + assert(flags != INS_FLAGS_SET); + getEmitter()->emitIns_R_C(ins, size, reg, tree->gtClsVar.gtClsVarHnd, offs); + return; + + default: + regNumber regTmp = regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(reg)); + getEmitter()->emitIns_R_C(ins_Load(tree->TypeGet()), size, regTmp, tree->gtClsVar.gtClsVarHnd, + offs); + getEmitter()->emitIns_R_R(ins, size, reg, regTmp, flags); + regTracker.rsTrackRegTrash(regTmp); + return; + } +#endif // LEGACY_BACKEND +#else // CPU_LOAD_STORE_ARCH + getEmitter()->emitIns_R_C(ins, size, reg, tree->gtClsVar.gtClsVarHnd, offs); +#endif // CPU_LOAD_STORE_ARCH + return; + + case GT_IND: + case GT_NULLCHECK: + case GT_ARR_ELEM: + case GT_LEA: + { +#ifndef LEGACY_BACKEND + assert(!"inst_RV_TT not supported for GT_IND, GT_NULLCHECK, GT_ARR_ELEM or GT_LEA in !LEGACY_BACKEND"); +#else // LEGACY_BACKEND + GenTreePtr addr = tree->OperIsIndir() ? tree->gtOp.gtOp1 : tree; + inst_RV_AT(ins, size, tree->TypeGet(), reg, addr, offs, flags); +#endif // LEGACY_BACKEND + } + break; + + case GT_CNS_INT: + + assert(offs == 0); + + inst_RV_IV(ins, reg, tree->gtIntCon.gtIconVal, emitActualTypeSize(tree->TypeGet()), flags); + break; + + case GT_CNS_LNG: + + assert(size == EA_4BYTE || size == EA_8BYTE); + +#ifdef _TARGET_AMD64_ + assert(offs == 0); +#endif // _TARGET_AMD64_ + + ssize_t constVal; + emitAttr size; + if (offs == 0) + { + constVal = (ssize_t)(tree->gtLngCon.gtLconVal); + size = EA_PTRSIZE; + } + else + { + constVal = (ssize_t)(tree->gtLngCon.gtLconVal >> 32); + size = EA_4BYTE; + } +#ifndef LEGACY_BACKEND +#ifdef _TARGET_ARM_ + if ((ins != INS_mov) && !arm_Valid_Imm_For_Instr(ins, constVal, flags)) + { + regNumber constReg = (offs == 0) ? genRegPairLo(tree->gtRegPair) : genRegPairHi(tree->gtRegPair); + instGen_Set_Reg_To_Imm(size, constReg, constVal); + getEmitter()->emitIns_R_R(ins, size, reg, constReg, flags); + break; + } +#endif // _TARGET_ARM_ +#endif // !LEGACY_BACKEND + + inst_RV_IV(ins, reg, constVal, size, flags); + break; + + case GT_COMMA: + tree = tree->gtOp.gtOp2; + goto AGAIN; + + default: + assert(!"invalid address"); + } +} + +/***************************************************************************** + * + * Generate the 3-operand imul instruction "imul reg, [tree], icon" + * which is reg=[tree]*icon + */ +#ifdef LEGACY_BACKEND +void CodeGen::inst_RV_TT_IV(instruction ins, regNumber reg, GenTreePtr tree, int val) +{ + assert(tree->gtType <= TYP_I_IMPL); + +#ifdef _TARGET_XARCH_ + /* Only 'imul' uses this instruction format. Since we don't represent + three operands for an instruction, we encode the target register as + an implicit operand */ + + assert(ins == INS_imul); + ins = getEmitter()->inst3opImulForReg(reg); + + genUpdateLife(tree); + inst_TT_IV(ins, tree, val); +#else + NYI("inst_RV_TT_IV - unknown target"); +#endif +} +#endif // LEGACY_BACKEND + +/***************************************************************************** + * + * Generate a "shift reg, icon" instruction. + */ + +void CodeGen::inst_RV_SH( + instruction ins, emitAttr size, regNumber reg, unsigned val, insFlags flags /* = INS_FLAGS_DONT_CARE */) +{ +#if defined(_TARGET_ARM_) + + if (val >= 32) + val &= 0x1f; + + getEmitter()->emitIns_R_I(ins, size, reg, val, flags); + +#elif defined(_TARGET_XARCH_) + +#ifdef _TARGET_AMD64_ + // X64 JB BE insures only encodable values make it here. + // x86 can encode 8 bits, though it masks down to 5 or 6 + // depending on 32-bit or 64-bit registers are used. + // Here we will allow anything that is encodable. + assert(val < 256); +#endif + + ins = genMapShiftInsToShiftByConstantIns(ins, val); + + if (val == 1) + { + getEmitter()->emitIns_R(ins, size, reg); + } + else + { + getEmitter()->emitIns_R_I(ins, size, reg, val); + } + +#else + NYI("inst_RV_SH - unknown target"); +#endif // _TARGET_* +} + +/***************************************************************************** + * + * Generate a "shift [r/m], icon" instruction. + */ + +void CodeGen::inst_TT_SH(instruction ins, GenTreePtr tree, unsigned val, unsigned offs) +{ +#ifdef _TARGET_XARCH_ + if (val == 0) + { + // Shift by 0 - why are you wasting our precious time???? + return; + } + + ins = genMapShiftInsToShiftByConstantIns(ins, val); + if (val == 1) + { + inst_TT(ins, tree, offs, 0, emitTypeSize(tree->TypeGet())); + } + else + { + inst_TT(ins, tree, offs, val, emitTypeSize(tree->TypeGet())); + } +#endif // _TARGET_XARCH_ + +#ifdef _TARGET_ARM_ + inst_TT(ins, tree, offs, val, emitTypeSize(tree->TypeGet())); +#endif +} + +/***************************************************************************** + * + * Generate a "shift [addr], cl" instruction. + */ + +void CodeGen::inst_TT_CL(instruction ins, GenTreePtr tree, unsigned offs) +{ + inst_TT(ins, tree, offs, 0, emitTypeSize(tree->TypeGet())); +} + +/***************************************************************************** + * + * Generate an instruction of the form "op reg1, reg2, icon". + */ + +#if defined(_TARGET_XARCH_) +void CodeGen::inst_RV_RV_IV(instruction ins, emitAttr size, regNumber reg1, regNumber reg2, unsigned ival) +{ +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) + assert(ins == INS_shld || ins == INS_shrd || ins == INS_shufps || ins == INS_shufpd || ins == INS_pshufd || + ins == INS_cmpps || ins == INS_cmppd || ins == INS_dppd || ins == INS_dpps || ins == INS_insertps); +#else // !_TARGET_XARCH_ + assert(ins == INS_shld || ins == INS_shrd); +#endif // !_TARGET_XARCH_ + + getEmitter()->emitIns_R_R_I(ins, size, reg1, reg2, ival); +} +#endif + +/***************************************************************************** + * + * Generate an instruction with two registers, the second one being a byte + * or word register (i.e. this is something like "movzx eax, cl"). + */ + +void CodeGen::inst_RV_RR(instruction ins, emitAttr size, regNumber reg1, regNumber reg2) +{ + assert(size == EA_1BYTE || size == EA_2BYTE); +#ifdef _TARGET_XARCH_ + assert(ins == INS_movsx || ins == INS_movzx); + assert(size != EA_1BYTE || (genRegMask(reg2) & RBM_BYTE_REGS)); +#endif + + getEmitter()->emitIns_R_R(ins, size, reg1, reg2); +} + +/***************************************************************************** + * + * The following should all end up inline in compiler.hpp at some point. + */ + +void CodeGen::inst_ST_RV(instruction ins, TempDsc* tmp, unsigned ofs, regNumber reg, var_types type) +{ + getEmitter()->emitIns_S_R(ins, emitActualTypeSize(type), reg, tmp->tdTempNum(), ofs); +} + +void CodeGen::inst_ST_IV(instruction ins, TempDsc* tmp, unsigned ofs, int val, var_types type) +{ + getEmitter()->emitIns_S_I(ins, emitActualTypeSize(type), tmp->tdTempNum(), ofs, val); +} + +#if FEATURE_FIXED_OUT_ARGS +/***************************************************************************** + * + * Generate an instruction that references the outgoing argument space + * like "str r3, [sp+0x04]" + */ + +void CodeGen::inst_SA_RV(instruction ins, unsigned ofs, regNumber reg, var_types type) +{ + assert(ofs < compiler->lvaOutgoingArgSpaceSize); + + getEmitter()->emitIns_S_R(ins, emitActualTypeSize(type), reg, compiler->lvaOutgoingArgSpaceVar, ofs); +} + +void CodeGen::inst_SA_IV(instruction ins, unsigned ofs, int val, var_types type) +{ + assert(ofs < compiler->lvaOutgoingArgSpaceSize); + + getEmitter()->emitIns_S_I(ins, emitActualTypeSize(type), compiler->lvaOutgoingArgSpaceVar, ofs, val); +} +#endif // FEATURE_FIXED_OUT_ARGS + +/***************************************************************************** + * + * Generate an instruction with one register and one operand that is byte + * or short (e.g. something like "movzx eax, byte ptr [edx]"). + */ + +void CodeGen::inst_RV_ST(instruction ins, emitAttr size, regNumber reg, GenTreePtr tree) +{ + assert(size == EA_1BYTE || size == EA_2BYTE); + + /* "movsx erx, rl" must be handled as a special case */ + + if (tree->gtFlags & GTF_REG_VAL) + { + inst_RV_RR(ins, size, reg, tree->gtRegNum); + } + else + { + inst_RV_TT(ins, reg, tree, 0, size); + } +} + +void CodeGen::inst_RV_ST(instruction ins, regNumber reg, TempDsc* tmp, unsigned ofs, var_types type, emitAttr size) +{ + if (size == EA_UNKNOWN) + { + size = emitActualTypeSize(type); + } + +#ifdef _TARGET_ARM_ + switch (ins) + { + case INS_mov: + assert(!"Please call ins_Load(type) to get the load instruction"); + break; + + case INS_add: + case INS_ldr: + case INS_ldrh: + case INS_ldrb: + case INS_ldrsh: + case INS_ldrsb: + case INS_lea: + case INS_vldr: + getEmitter()->emitIns_R_S(ins, size, reg, tmp->tdTempNum(), ofs); + break; + + default: +#ifndef LEGACY_BACKEND + assert(!"Default inst_RV_ST case not supported for Arm !LEGACY_BACKEND"); +#else // LEGACY_BACKEND + regNumber regTmp; + if (varTypeIsFloating(type)) + { + regTmp = regSet.PickRegFloat(type); + } + else + { + regTmp = regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(reg)); + } + getEmitter()->emitIns_R_S(ins_Load(type), size, regTmp, tmp->tdTempNum(), ofs); + regTracker.rsTrackRegTrash(regTmp); + getEmitter()->emitIns_R_R(ins, size, reg, regTmp); +#endif // LEGACY_BACKEND + break; + } +#else // !_TARGET_ARM_ + getEmitter()->emitIns_R_S(ins, size, reg, tmp->tdTempNum(), ofs); +#endif // !_TARGET_ARM_ +} + +void CodeGen::inst_mov_RV_ST(regNumber reg, GenTreePtr tree) +{ + /* Figure out the size of the value being loaded */ + + emitAttr size = EA_ATTR(genTypeSize(tree->gtType)); + instruction loadIns = ins_Move_Extend(tree->TypeGet(), (tree->gtFlags & GTF_REG_VAL) != 0); + + if (size < EA_4BYTE) + { + if ((tree->gtFlags & GTF_SMALL_OK) && (size == EA_1BYTE) +#if CPU_HAS_BYTE_REGS + && (genRegMask(reg) & RBM_BYTE_REGS) +#endif + ) + { + /* We only need to load the actual size */ + + inst_RV_TT(INS_mov, reg, tree, 0, EA_1BYTE); + } + else + { + /* Generate the "movsx/movzx" opcode */ + + inst_RV_ST(loadIns, size, reg, tree); + } + } + else + { + /* Compute op1 into the target register */ + + inst_RV_TT(loadIns, reg, tree); + } +} +#ifdef _TARGET_XARCH_ +void CodeGen::inst_FS_ST(instruction ins, emitAttr size, TempDsc* tmp, unsigned ofs) +{ + getEmitter()->emitIns_S(ins, size, tmp->tdTempNum(), ofs); +} +#endif + +#ifdef _TARGET_ARM_ +bool CodeGenInterface::validImmForInstr(instruction ins, ssize_t imm, insFlags flags) +{ + if (getEmitter()->emitInsIsLoadOrStore(ins) && !instIsFP(ins)) + { + return validDispForLdSt(imm, TYP_INT); + } + + bool result = false; + switch (ins) + { + case INS_cmp: + case INS_cmn: + if (validImmForAlu(imm) || validImmForAlu(-imm)) + result = true; + break; + + case INS_and: + case INS_bic: + case INS_orr: + case INS_orn: + case INS_mvn: + if (validImmForAlu(imm) || validImmForAlu(~imm)) + result = true; + break; + + case INS_mov: + if (validImmForMov(imm)) + result = true; + break; + + case INS_addw: + case INS_subw: + if ((unsigned_abs(imm) <= 0x00000fff) && (flags != INS_FLAGS_SET)) // 12-bit immediate + result = true; + break; + + case INS_add: + case INS_sub: + if (validImmForAdd(imm, flags)) + result = true; + break; + + case INS_tst: + case INS_eor: + case INS_teq: + case INS_adc: + case INS_sbc: + case INS_rsb: + if (validImmForAlu(imm)) + result = true; + break; + + case INS_asr: + case INS_lsl: + case INS_lsr: + case INS_ror: + if (imm > 0 && imm <= 32) + result = true; + break; + + case INS_vstr: + case INS_vldr: + if ((imm & 0x3FC) == imm) + result = true; + break; + + default: + break; + } + return result; +} +bool CodeGen::arm_Valid_Imm_For_Instr(instruction ins, ssize_t imm, insFlags flags) +{ + return validImmForInstr(ins, imm, flags); +} + +bool CodeGenInterface::validDispForLdSt(ssize_t disp, var_types type) +{ + if (varTypeIsFloating(type)) + { + if ((disp & 0x3FC) == disp) + return true; + else + return false; + } + else + { + if ((disp >= -0x00ff) && (disp <= 0x0fff)) + return true; + else + return false; + } +} +bool CodeGen::arm_Valid_Disp_For_LdSt(ssize_t disp, var_types type) +{ + return validDispForLdSt(disp, type); +} + +bool CodeGenInterface::validImmForAlu(ssize_t imm) +{ + return emitter::emitIns_valid_imm_for_alu(imm); +} +bool CodeGen::arm_Valid_Imm_For_Alu(ssize_t imm) +{ + return validImmForAlu(imm); +} + +bool CodeGenInterface::validImmForMov(ssize_t imm) +{ + return emitter::emitIns_valid_imm_for_mov(imm); +} +bool CodeGen::arm_Valid_Imm_For_Mov(ssize_t imm) +{ + return validImmForMov(imm); +} + +bool CodeGen::arm_Valid_Imm_For_Small_Mov(regNumber reg, ssize_t imm, insFlags flags) +{ + return emitter::emitIns_valid_imm_for_small_mov(reg, imm, flags); +} + +bool CodeGenInterface::validImmForAdd(ssize_t imm, insFlags flags) +{ + return emitter::emitIns_valid_imm_for_add(imm, flags); +} +bool CodeGen::arm_Valid_Imm_For_Add(ssize_t imm, insFlags flags) +{ + return emitter::emitIns_valid_imm_for_add(imm, flags); +} + +// Check "add Rd,SP,i10" +bool CodeGen::arm_Valid_Imm_For_Add_SP(ssize_t imm) +{ + return emitter::emitIns_valid_imm_for_add_sp(imm); +} + +bool CodeGenInterface::validImmForBL(ssize_t addr) +{ + return + // If we are running the altjit for NGEN, then assume we can use the "BL" instruction. + // This matches the usual behavior for NGEN, since we normally do generate "BL". + (!compiler->info.compMatchedVM && (compiler->opts.eeFlags & CORJIT_FLG_PREJIT)) || + (compiler->eeGetRelocTypeHint((void*)addr) == IMAGE_REL_BASED_THUMB_BRANCH24); +} +bool CodeGen::arm_Valid_Imm_For_BL(ssize_t addr) +{ + return validImmForBL(addr); +} + +// Returns true if this instruction writes to a destination register +// +bool CodeGen::ins_Writes_Dest(instruction ins) +{ + switch (ins) + { + + case INS_cmp: + case INS_cmn: + case INS_tst: + case INS_teq: + return false; + + default: + return true; + } +} +#endif // _TARGET_ARM_ + +/***************************************************************************** + * + * Get the machine dependent instruction for performing sign/zero extension. + * + * Parameters + * srcType - source type + * srcInReg - whether source is in a register + */ +instruction CodeGen::ins_Move_Extend(var_types srcType, bool srcInReg) +{ + instruction ins = INS_invalid; + + if (varTypeIsSIMD(srcType)) + { +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) + // SSE2/AVX requires destination to be a reg always. + // If src is in reg means, it is a reg-reg move. + // + // SSE2 Note: always prefer movaps/movups over movapd/movupd since the + // former doesn't require 66h prefix and one byte smaller than the + // latter. + // + // TODO-CQ: based on whether src type is aligned use movaps instead + + return (srcInReg) ? INS_movaps : INS_movups; +#else // !defined(_TARGET_XARCH_) || defined(LEGACY_BACKEND) + assert(!"unhandled SIMD type"); +#endif // !defined(_TARGET_XARCH_) || defined(LEGACY_BACKEND) + } + +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) + if (varTypeIsFloating(srcType)) + { + if (srcType == TYP_DOUBLE) + { + return (srcInReg) ? INS_movaps : INS_movsdsse2; + } + else if (srcType == TYP_FLOAT) + { + return (srcInReg) ? INS_movaps : INS_movss; + } + else + { + assert(!"unhandled floating type"); + } + } +#elif defined(_TARGET_ARM_) + if (varTypeIsFloating(srcType)) + return INS_vmov; +#else + assert(!varTypeIsFloating(srcType)); +#endif + +#if defined(_TARGET_XARCH_) + if (!varTypeIsSmall(srcType)) + { + ins = INS_mov; + } + else if (varTypeIsUnsigned(srcType)) + { + ins = INS_movzx; + } + else + { + ins = INS_movsx; + } +#elif defined(_TARGET_ARM_) + // + // Register to Register zero/sign extend operation + // + if (srcInReg) + { + if (!varTypeIsSmall(srcType)) + { + ins = INS_mov; + } + else if (varTypeIsUnsigned(srcType)) + { + if (varTypeIsByte(srcType)) + ins = INS_uxtb; + else + ins = INS_uxth; + } + else + { + if (varTypeIsByte(srcType)) + ins = INS_sxtb; + else + ins = INS_sxth; + } + } + else + { + ins = ins_Load(srcType); + } +#elif defined(_TARGET_ARM64_) + // + // Register to Register zero/sign extend operation + // + if (srcInReg) + { + if (varTypeIsUnsigned(srcType)) + { + if (varTypeIsByte(srcType)) + { + ins = INS_uxtb; + } + else if (varTypeIsShort(srcType)) + { + ins = INS_uxth; + } + else + { + // A mov Rd, Rm instruction performs the zero extend + // for the upper 32 bits when the size is EA_4BYTE + + ins = INS_mov; + } + } + else + { + if (varTypeIsByte(srcType)) + { + ins = INS_sxtb; + } + else if (varTypeIsShort(srcType)) + { + ins = INS_sxth; + } + else + { + if (srcType == TYP_INT) + { + ins = INS_sxtw; + } + else + { + ins = INS_mov; + } + } + } + } + else + { + ins = ins_Load(srcType); + } +#else + NYI("ins_Move_Extend"); +#endif + assert(ins != INS_invalid); + return ins; +} + +/***************************************************************************** + * + * Get the machine dependent instruction for performing a load for srcType + * + * Parameters + * srcType - source type + * aligned - whether source is 16-byte aligned if srcType is a SIMD type + */ +instruction CodeGenInterface::ins_Load(var_types srcType, bool aligned /*=false*/) +{ + instruction ins = INS_invalid; + + if (varTypeIsSIMD(srcType)) + { +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) +#ifdef FEATURE_SIMD + if (srcType == TYP_SIMD8) + { + return INS_movsdsse2; + } + else +#endif // FEATURE_SIMD + if (compiler->canUseAVX()) + { + // TODO-CQ: consider alignment of AVX vectors. + return INS_movupd; + } + else + { + // SSE2 Note: always prefer movaps/movups over movapd/movupd since the + // former doesn't require 66h prefix and one byte smaller than the + // latter. + return (aligned) ? INS_movaps : INS_movups; + } +#else + assert(!"ins_Load with SIMD type"); +#endif + } + + if (varTypeIsFloating(srcType)) + { +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) + if (srcType == TYP_DOUBLE) + { + return INS_movsdsse2; + } + else if (srcType == TYP_FLOAT) + { + return INS_movss; + } + else + { + assert(!"unhandled floating type"); + } +#elif defined(_TARGET_ARM64_) + return INS_ldr; +#elif defined(_TARGET_ARM_) + return INS_vldr; +#else + assert(!varTypeIsFloating(srcType)); +#endif + } + +#if defined(_TARGET_XARCH_) + if (!varTypeIsSmall(srcType)) + { + ins = INS_mov; + } + else if (varTypeIsUnsigned(srcType)) + { + ins = INS_movzx; + } + else + { + ins = INS_movsx; + } + +#elif defined(_TARGET_ARMARCH_) + if (!varTypeIsSmall(srcType)) + { +#if defined(_TARGET_ARM64_) + if (!varTypeIsI(srcType) && !varTypeIsUnsigned(srcType)) + { + ins = INS_ldrsw; + } + else +#endif // defined(_TARGET_ARM64_) + { + ins = INS_ldr; + } + } + else if (varTypeIsByte(srcType)) + { + if (varTypeIsUnsigned(srcType)) + ins = INS_ldrb; + else + ins = INS_ldrsb; + } + else if (varTypeIsShort(srcType)) + { + if (varTypeIsUnsigned(srcType)) + ins = INS_ldrh; + else + ins = INS_ldrsh; + } +#else + NYI("ins_Load"); +#endif + + assert(ins != INS_invalid); + return ins; +} + +/***************************************************************************** + * + * Get the machine dependent instruction for performing a reg-reg copy for dstType + * + * Parameters + * dstType - destination type + */ +instruction CodeGen::ins_Copy(var_types dstType) +{ +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) + if (varTypeIsSIMD(dstType)) + { + return INS_movaps; + } + else if (varTypeIsFloating(dstType)) + { + // Both float and double copy can use movaps + return INS_movaps; + } + else + { + return INS_mov; + } +#elif defined(_TARGET_ARM64_) + if (varTypeIsFloating(dstType)) + { + return INS_fmov; + } + else + { + return INS_mov; + } +#elif defined(_TARGET_ARM_) + assert(!varTypeIsSIMD(dstType)); + if (varTypeIsFloating(dstType)) + { + return INS_vmov; + } + else + { + return INS_mov; + } +#elif defined(_TARGET_X86_) + assert(!varTypeIsSIMD(dstType)); + assert(!varTypeIsFloating(dstType)); + return INS_mov; +#else // _TARGET_* +#error "Unknown _TARGET_" +#endif +} + +/***************************************************************************** + * + * Get the machine dependent instruction for performing a store for dstType + * + * Parameters + * dstType - destination type + * aligned - whether destination is 16-byte aligned if dstType is a SIMD type + */ +instruction CodeGenInterface::ins_Store(var_types dstType, bool aligned /*=false*/) +{ + instruction ins = INS_invalid; + +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) + if (varTypeIsSIMD(dstType)) + { +#ifdef FEATURE_SIMD + if (dstType == TYP_SIMD8) + { + return INS_movsdsse2; + } + else +#endif // FEATURE_SIMD + if (compiler->canUseAVX()) + { + // TODO-CQ: consider alignment of AVX vectors. + return INS_movupd; + } + else + { + // SSE2 Note: always prefer movaps/movups over movapd/movupd since the + // former doesn't require 66h prefix and one byte smaller than the + // latter. + return (aligned) ? INS_movaps : INS_movups; + } + } + else if (varTypeIsFloating(dstType)) + { + if (dstType == TYP_DOUBLE) + { + return INS_movsdsse2; + } + else if (dstType == TYP_FLOAT) + { + return INS_movss; + } + else + { + assert(!"unhandled floating type"); + } + } +#elif defined(_TARGET_ARM64_) + if (varTypeIsSIMD(dstType) || varTypeIsFloating(dstType)) + { + // All sizes of SIMD and FP instructions use INS_str + return INS_str; + } +#elif defined(_TARGET_ARM_) + assert(!varTypeIsSIMD(dstType)); + if (varTypeIsFloating(dstType)) + { + return INS_vstr; + } +#else + assert(!varTypeIsSIMD(dstType)); + assert(!varTypeIsFloating(dstType)); +#endif + +#if defined(_TARGET_XARCH_) + ins = INS_mov; +#elif defined(_TARGET_ARMARCH_) + if (!varTypeIsSmall(dstType)) + ins = INS_str; + else if (varTypeIsByte(dstType)) + ins = INS_strb; + else if (varTypeIsShort(dstType)) + ins = INS_strh; +#else + NYI("ins_Store"); +#endif + + assert(ins != INS_invalid); + return ins; +} + +#if defined(_TARGET_XARCH_) && !defined(LEGACY_BACKEND) + +bool CodeGen::isMoveIns(instruction ins) +{ + return (ins == INS_mov); +} + +instruction CodeGenInterface::ins_FloatLoad(var_types type) +{ + // Do Not use this routine in RyuJIT backend. Instead use ins_Load()/ins_Store() + unreached(); +} + +// everything is just an addressing mode variation on x64 +instruction CodeGen::ins_FloatStore(var_types type) +{ + // Do Not use this routine in RyuJIT backend. Instead use ins_Store() + unreached(); +} + +instruction CodeGen::ins_FloatCopy(var_types type) +{ + // Do Not use this routine in RyuJIT backend. Instead use ins_Load(). + unreached(); +} + +instruction CodeGen::ins_FloatCompare(var_types type) +{ + return (type == TYP_FLOAT) ? INS_ucomiss : INS_ucomisd; +} + +instruction CodeGen::ins_CopyIntToFloat(var_types srcType, var_types dstType) +{ + // On SSE2/AVX - the same instruction is used for moving double/quad word to XMM/YMM register. + assert((srcType == TYP_INT) || (srcType == TYP_UINT) || (srcType == TYP_LONG) || (srcType == TYP_ULONG)); + return INS_mov_i2xmm; +} + +instruction CodeGen::ins_CopyFloatToInt(var_types srcType, var_types dstType) +{ + // On SSE2/AVX - the same instruction is used for moving double/quad word of XMM/YMM to an integer register. + assert((dstType == TYP_INT) || (dstType == TYP_UINT) || (dstType == TYP_LONG) || (dstType == TYP_ULONG)); + return INS_mov_xmm2i; +} + +instruction CodeGen::ins_MathOp(genTreeOps oper, var_types type) +{ + switch (oper) + { + case GT_ADD: + case GT_ASG_ADD: + return type == TYP_DOUBLE ? INS_addsd : INS_addss; + break; + case GT_SUB: + case GT_ASG_SUB: + return type == TYP_DOUBLE ? INS_subsd : INS_subss; + break; + case GT_MUL: + case GT_ASG_MUL: + return type == TYP_DOUBLE ? INS_mulsd : INS_mulss; + break; + case GT_DIV: + case GT_ASG_DIV: + return type == TYP_DOUBLE ? INS_divsd : INS_divss; + case GT_AND: + return type == TYP_DOUBLE ? INS_andpd : INS_andps; + case GT_OR: + return type == TYP_DOUBLE ? INS_orpd : INS_orps; + case GT_XOR: + return type == TYP_DOUBLE ? INS_xorpd : INS_xorps; + default: + unreached(); + } +} + +instruction CodeGen::ins_FloatSqrt(var_types type) +{ + instruction ins = INS_invalid; + + if (type == TYP_DOUBLE) + { + ins = INS_sqrtsd; + } + else + { + // Right now sqrt of scalar single is not needed. + unreached(); + } + + return ins; +} + +// Conversions to or from floating point values +instruction CodeGen::ins_FloatConv(var_types to, var_types from) +{ + // AVX: For now we support only conversion from Int/Long -> float + + switch (from) + { + // int/long -> float/double use the same instruction but type size would be different. + case TYP_INT: + case TYP_LONG: + switch (to) + { + case TYP_FLOAT: + return INS_cvtsi2ss; + case TYP_DOUBLE: + return INS_cvtsi2sd; + default: + unreached(); + } + break; + + case TYP_FLOAT: + switch (to) + { + case TYP_INT: + return INS_cvttss2si; + case TYP_LONG: + return INS_cvttss2si; + case TYP_FLOAT: + return ins_Move_Extend(TYP_FLOAT, false); + case TYP_DOUBLE: + return INS_cvtss2sd; + default: + unreached(); + } + break; + + case TYP_DOUBLE: + switch (to) + { + case TYP_INT: + return INS_cvttsd2si; + case TYP_LONG: + return INS_cvttsd2si; + case TYP_FLOAT: + return INS_cvtsd2ss; + case TYP_DOUBLE: + return ins_Move_Extend(TYP_DOUBLE, false); + default: + unreached(); + } + break; + + default: + unreached(); + } +} + +#elif defined(_TARGET_ARM_) + +bool CodeGen::isMoveIns(instruction ins) +{ + return (ins == INS_vmov) || (ins == INS_mov); +} + +instruction CodeGenInterface::ins_FloatLoad(var_types type) +{ + assert(type == TYP_DOUBLE || type == TYP_FLOAT); + return INS_vldr; +} +instruction CodeGen::ins_FloatStore(var_types type) +{ + assert(type == TYP_DOUBLE || type == TYP_FLOAT); + return INS_vstr; +} +instruction CodeGen::ins_FloatCopy(var_types type) +{ + assert(type == TYP_DOUBLE || type == TYP_FLOAT); + return INS_vmov; +} + +instruction CodeGen::ins_CopyIntToFloat(var_types srcType, var_types dstType) +{ + // Not used and not implemented + unreached(); +} + +instruction CodeGen::ins_CopyFloatToInt(var_types srcType, var_types dstType) +{ + // Not used and not implemented + unreached(); +} + +instruction CodeGen::ins_FloatCompare(var_types type) +{ + // Not used and not implemented + unreached(); +} + +instruction CodeGen::ins_FloatSqrt(var_types type) +{ + // Not used and not implemented + unreached(); +} + +instruction CodeGen::ins_MathOp(genTreeOps oper, var_types type) +{ + switch (oper) + { + case GT_ADD: + case GT_ASG_ADD: + return INS_vadd; + break; + case GT_SUB: + case GT_ASG_SUB: + return INS_vsub; + break; + case GT_MUL: + case GT_ASG_MUL: + return INS_vmul; + break; + case GT_DIV: + case GT_ASG_DIV: + return INS_vdiv; + case GT_NEG: + return INS_vneg; + default: + unreached(); + } +} + +instruction CodeGen::ins_FloatConv(var_types to, var_types from) +{ + switch (from) + { + case TYP_INT: + switch (to) + { + case TYP_FLOAT: + return INS_vcvt_i2f; + case TYP_DOUBLE: + return INS_vcvt_i2d; + default: + unreached(); + } + break; + case TYP_UINT: + switch (to) + { + case TYP_FLOAT: + return INS_vcvt_u2f; + case TYP_DOUBLE: + return INS_vcvt_u2d; + default: + unreached(); + } + break; + case TYP_LONG: + switch (to) + { + case TYP_FLOAT: + NYI("long to float"); + case TYP_DOUBLE: + NYI("long to double"); + default: + unreached(); + } + break; + case TYP_FLOAT: + switch (to) + { + case TYP_INT: + return INS_vcvt_f2i; + case TYP_UINT: + return INS_vcvt_f2u; + case TYP_LONG: + NYI("float to long"); + case TYP_DOUBLE: + return INS_vcvt_f2d; + case TYP_FLOAT: + return INS_vmov; + default: + unreached(); + } + break; + case TYP_DOUBLE: + switch (to) + { + case TYP_INT: + return INS_vcvt_d2i; + case TYP_UINT: + return INS_vcvt_d2u; + case TYP_LONG: + NYI("double to long"); + case TYP_FLOAT: + return INS_vcvt_d2f; + case TYP_DOUBLE: + return INS_vmov; + default: + unreached(); + } + break; + default: + unreached(); + } +} + +#endif // #elif defined(_TARGET_ARM_) + +/***************************************************************************** + * + * Machine independent way to return + */ +void CodeGen::instGen_Return(unsigned stkArgSize) +{ +#if defined(_TARGET_XARCH_) + if (stkArgSize == 0) + { + instGen(INS_ret); + } + else + { + inst_IV(INS_ret, stkArgSize); + } +#elif defined(_TARGET_ARM_) +// +// The return on ARM is folded into the pop multiple instruction +// and as we do not know the exact set of registers that we will +// need to restore (pop) when we first call instGen_Return we will +// instead just not emit anything for this method on the ARM +// The return will be part of the pop multiple and that will be +// part of the epilog that is generated by genFnEpilog() +#elif defined(_TARGET_ARM64_) + // This function shouldn't be used on ARM64. + unreached(); +#else + NYI("instGen_Return"); +#endif +} + +/***************************************************************************** + * + * Emit a MemoryBarrier instruction + * + * Note: all MemoryBarriers instructions can be removed by + * SET COMPlus_JitNoMemoryBarriers=1 + */ +void CodeGen::instGen_MemoryBarrier() +{ +#ifdef DEBUG + if (JitConfig.JitNoMemoryBarriers() == 1) + { + return; + } +#endif // DEBUG + +#if defined(_TARGET_XARCH_) + instGen(INS_lock); + getEmitter()->emitIns_I_AR(INS_or, EA_4BYTE, 0, REG_SPBASE, 0); +#elif defined(_TARGET_ARM_) + getEmitter()->emitIns_I(INS_dmb, EA_4BYTE, 0xf); +#elif defined(_TARGET_ARM64_) + getEmitter()->emitIns_BARR(INS_dmb, INS_BARRIER_SY); +#else +#error "Unknown _TARGET_" +#endif +} + +/***************************************************************************** + * + * Machine independent way to move a Zero value into a register + */ +void CodeGen::instGen_Set_Reg_To_Zero(emitAttr size, regNumber reg, insFlags flags) +{ +#if defined(_TARGET_XARCH_) + getEmitter()->emitIns_R_R(INS_xor, size, reg, reg); +#elif defined(_TARGET_ARMARCH_) + getEmitter()->emitIns_R_I(INS_mov, size, reg, 0 ARM_ARG(flags)); +#else +#error "Unknown _TARGET_" +#endif + regTracker.rsTrackRegIntCns(reg, 0); +} + +#ifdef LEGACY_BACKEND +/***************************************************************************** + * + * Machine independent way to move an immediate value into a register + */ +void CodeGen::instGen_Set_Reg_To_Imm(emitAttr size, regNumber reg, ssize_t imm, insFlags flags) +{ +#if RELOC_SUPPORT + if (!compiler->opts.compReloc) +#endif // RELOC_SUPPORT + { + size = EA_SIZE(size); // Strip any Reloc flags from size if we aren't doing relocs + } + + if ((imm == 0) && !EA_IS_RELOC(size)) + { + instGen_Set_Reg_To_Zero(size, reg, flags); + } + else + { +#if defined(_TARGET_XARCH_) + getEmitter()->emitIns_R_I(INS_mov, size, reg, imm); +#elif defined(_TARGET_ARM_) + + if (EA_IS_RELOC(size)) + { + getEmitter()->emitIns_R_I(INS_movw, size, reg, imm); + getEmitter()->emitIns_R_I(INS_movt, size, reg, imm); + } + else if (arm_Valid_Imm_For_Mov(imm)) + { + getEmitter()->emitIns_R_I(INS_mov, size, reg, imm, flags); + } + else // We have to use a movw/movt pair of instructions + { + ssize_t imm_lo16 = (imm & 0xffff); + ssize_t imm_hi16 = (imm >> 16) & 0xffff; + + assert(arm_Valid_Imm_For_Mov(imm_lo16)); + assert(imm_hi16 != 0); + + getEmitter()->emitIns_R_I(INS_movw, size, reg, imm_lo16); + + // If we've got a low register, the high word is all bits set, + // and the high bit of the low word is set, we can sign extend + // halfword and save two bytes of encoding. This can happen for + // small magnitude negative numbers 'n' for -32768 <= n <= -1. + + if (getEmitter()->isLowRegister(reg) && (imm_hi16 == 0xffff) && ((imm_lo16 & 0x8000) == 0x8000)) + { + getEmitter()->emitIns_R_R(INS_sxth, EA_2BYTE, reg, reg); + } + else + { + getEmitter()->emitIns_R_I(INS_movt, size, reg, imm_hi16); + } + + if (flags == INS_FLAGS_SET) + getEmitter()->emitIns_R_R(INS_mov, size, reg, reg, INS_FLAGS_SET); + } +#elif defined(_TARGET_ARM64_) + NYI_ARM64("instGen_Set_Reg_To_Imm"); +#else +#error "Unknown _TARGET_" +#endif + } + regTracker.rsTrackRegIntCns(reg, imm); +} +#endif // LEGACY_BACKEND + +/***************************************************************************** + * + * Machine independent way to set the flags based on + * comparing a register with zero + */ +void CodeGen::instGen_Compare_Reg_To_Zero(emitAttr size, regNumber reg) +{ +#if defined(_TARGET_XARCH_) + getEmitter()->emitIns_R_R(INS_test, size, reg, reg); +#elif defined(_TARGET_ARMARCH_) + getEmitter()->emitIns_R_I(INS_cmp, size, reg, 0); +#else +#error "Unknown _TARGET_" +#endif +} + +/***************************************************************************** + * + * Machine independent way to set the flags based upon + * comparing a register with another register + */ +void CodeGen::instGen_Compare_Reg_To_Reg(emitAttr size, regNumber reg1, regNumber reg2) +{ +#if defined(_TARGET_XARCH_) || defined(_TARGET_ARMARCH_) + getEmitter()->emitIns_R_R(INS_cmp, size, reg1, reg2); +#else +#error "Unknown _TARGET_" +#endif +} + +/***************************************************************************** + * + * Machine independent way to set the flags based upon + * comparing a register with an immediate + */ +void CodeGen::instGen_Compare_Reg_To_Imm(emitAttr size, regNumber reg, ssize_t imm) +{ + if (imm == 0) + { + instGen_Compare_Reg_To_Zero(size, reg); + } + else + { +#if defined(_TARGET_XARCH_) +#if defined(_TARGET_AMD64_) + if ((EA_SIZE(size) == EA_8BYTE) && (((int)imm != (ssize_t)imm) || EA_IS_CNS_RELOC(size))) + { +#ifndef LEGACY_BACKEND + assert(!"Invalid immediate for instGen_Compare_Reg_To_Imm"); +#else // LEGACY_BACKEND + // Load imm into a register + regNumber immReg = regSet.rsGrabReg(RBM_ALLINT & ~genRegMask(reg)); + instGen_Set_Reg_To_Imm(size, immReg, (ssize_t)imm); + getEmitter()->emitIns_R_R(INS_cmp, EA_TYPE(size), reg, immReg); +#endif // LEGACY_BACKEND + } + else +#endif // _TARGET_AMD64_ + { + getEmitter()->emitIns_R_I(INS_cmp, size, reg, imm); + } +#elif defined(_TARGET_ARM_) + if (arm_Valid_Imm_For_Alu(imm) || arm_Valid_Imm_For_Alu(-imm)) + { + getEmitter()->emitIns_R_I(INS_cmp, size, reg, imm); + } + else // We need a scratch register + { +#ifndef LEGACY_BACKEND + assert(!"Invalid immediate for instGen_Compare_Reg_To_Imm"); +#else // LEGACY_BACKEND + // Load imm into a register + regNumber immReg = regSet.rsGrabReg(RBM_ALLINT & ~genRegMask(reg)); + instGen_Set_Reg_To_Imm(size, immReg, (ssize_t)imm); + getEmitter()->emitIns_R_R(INS_cmp, size, reg, immReg); +#endif // !LEGACY_BACKEND + } +#elif defined(_TARGET_ARM64_) + if (true) // TODO-ARM64-NYI: arm_Valid_Imm_For_Alu(imm) || arm_Valid_Imm_For_Alu(-imm)) + { + getEmitter()->emitIns_R_I(INS_cmp, size, reg, imm); + } + else // We need a scratch register + { + assert(!"Invalid immediate for instGen_Compare_Reg_To_Imm"); + } +#else +#error "Unknown _TARGET_" +#endif + } +} + +/***************************************************************************** + * + * Machine independent way to move a stack based local variable into a register + */ +void CodeGen::instGen_Load_Reg_From_Lcl(var_types srcType, regNumber dstReg, int varNum, int offs) +{ + emitAttr size = emitTypeSize(srcType); + + getEmitter()->emitIns_R_S(ins_Load(srcType), size, dstReg, varNum, offs); +} + +/***************************************************************************** + * + * Machine independent way to move a register into a stack based local variable + */ +void CodeGen::instGen_Store_Reg_Into_Lcl(var_types dstType, regNumber srcReg, int varNum, int offs) +{ + emitAttr size = emitTypeSize(dstType); + + getEmitter()->emitIns_S_R(ins_Store(dstType), size, srcReg, varNum, offs); +} + +/***************************************************************************** + * + * Machine independent way to move an immediate into a stack based local variable + */ +void CodeGen::instGen_Store_Imm_Into_Lcl( + var_types dstType, emitAttr sizeAttr, ssize_t imm, int varNum, int offs, regNumber regToUse) +{ +#ifdef _TARGET_XARCH_ +#ifdef _TARGET_AMD64_ + if ((EA_SIZE(sizeAttr) == EA_8BYTE) && (((int)imm != (ssize_t)imm) || EA_IS_CNS_RELOC(sizeAttr))) + { + assert(!"Invalid immediate for instGen_Store_Imm_Into_Lcl"); + } + else +#endif // _TARGET_AMD64_ + { + getEmitter()->emitIns_S_I(ins_Store(dstType), sizeAttr, varNum, offs, (int)imm); + } +#elif defined(_TARGET_ARMARCH_) + // Load imm into a register + CLANG_FORMAT_COMMENT_ANCHOR; + +#ifndef LEGACY_BACKEND + regNumber immReg = regToUse; + assert(regToUse != REG_NA); +#else // LEGACY_BACKEND + regNumber immReg = (regToUse == REG_NA) ? regSet.rsGrabReg(RBM_ALLINT) : regToUse; +#endif // LEGACY_BACKEND + instGen_Set_Reg_To_Imm(sizeAttr, immReg, (ssize_t)imm); + instGen_Store_Reg_Into_Lcl(dstType, immReg, varNum, offs); + if (EA_IS_RELOC(sizeAttr)) + { + regTracker.rsTrackRegTrash(immReg); + } +#else // _TARGET_* +#error "Unknown _TARGET_" +#endif // _TARGET_* +} + +/*****************************************************************************/ +/*****************************************************************************/ +/*****************************************************************************/ |