diff options
Diffstat (limited to 'src/jit/emitarm64.cpp')
-rw-r--r-- | src/jit/emitarm64.cpp | 11167 |
1 files changed, 11167 insertions, 0 deletions
diff --git a/src/jit/emitarm64.cpp b/src/jit/emitarm64.cpp new file mode 100644 index 0000000000..a632ec12c8 --- /dev/null +++ b/src/jit/emitarm64.cpp @@ -0,0 +1,11167 @@ +// 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 emitArm64.cpp XX +XX XX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX +*/ + +#include "jitpch.h" +#ifdef _MSC_VER +#pragma hdrstop +#endif + +#if defined(_TARGET_ARM64_) + +/*****************************************************************************/ +/*****************************************************************************/ + +#include "instr.h" +#include "emit.h" +#include "codegen.h" + +/* static */ bool emitter::strictArmAsm = true; + +/*****************************************************************************/ + +const instruction emitJumpKindInstructions[] = { + INS_nop, + +#define JMP_SMALL(en, rev, ins) INS_##ins, +#include "emitjmps.h" +}; + +const emitJumpKind emitReverseJumpKinds[] = { + EJ_NONE, + +#define JMP_SMALL(en, rev, ins) EJ_##rev, +#include "emitjmps.h" +}; + +/***************************************************************************** + * Look up the instruction for a jump kind + */ + +/*static*/ instruction emitter::emitJumpKindToIns(emitJumpKind jumpKind) +{ + assert((unsigned)jumpKind < ArrLen(emitJumpKindInstructions)); + return emitJumpKindInstructions[jumpKind]; +} + +/***************************************************************************** +* Look up the jump kind for an instruction. It better be a conditional +* branch instruction with a jump kind! +*/ + +/*static*/ emitJumpKind emitter::emitInsToJumpKind(instruction ins) +{ + for (unsigned i = 0; i < ArrLen(emitJumpKindInstructions); i++) + { + if (ins == emitJumpKindInstructions[i]) + { + emitJumpKind ret = (emitJumpKind)i; + assert(EJ_NONE < ret && ret < EJ_COUNT); + return ret; + } + } + unreached(); +} + +/***************************************************************************** + * Reverse the conditional jump + */ + +/*static*/ emitJumpKind emitter::emitReverseJumpKind(emitJumpKind jumpKind) +{ + assert(jumpKind < EJ_COUNT); + return emitReverseJumpKinds[jumpKind]; +} + +/***************************************************************************** + * + * Return the allocated size (in bytes) of the given instruction descriptor. + */ + +size_t emitter::emitSizeOfInsDsc(instrDesc* id) +{ + assert(!emitIsTinyInsDsc(id)); + + if (emitIsScnsInsDsc(id)) + return SMALL_IDSC_SIZE; + + assert((unsigned)id->idInsFmt() < emitFmtCount); + + ID_OPS idOp = (ID_OPS)emitFmtToOps[id->idInsFmt()]; + bool isCallIns = (id->idIns() == INS_bl) || (id->idIns() == INS_blr) || (id->idIns() == INS_b_tail) || + (id->idIns() == INS_br_tail); + bool maybeCallIns = (id->idIns() == INS_b) || (id->idIns() == INS_br); + + switch (idOp) + { + case ID_OP_NONE: + break; + + case ID_OP_JMP: + return sizeof(instrDescJmp); + + case ID_OP_CALL: + assert(isCallIns || maybeCallIns); + if (id->idIsLargeCall()) + { + /* Must be a "fat" call descriptor */ + return sizeof(instrDescCGCA); + } + else + { + assert(!id->idIsLargeDsp()); + assert(!id->idIsLargeCns()); + return sizeof(instrDesc); + } + break; + + default: + NO_WAY("unexpected instruction descriptor format"); + break; + } + + if (id->idIsLargeCns()) + { + if (id->idIsLargeDsp()) + return sizeof(instrDescCnsDsp); + else + return sizeof(instrDescCns); + } + else + { + if (id->idIsLargeDsp()) + return sizeof(instrDescDsp); + else + return sizeof(instrDesc); + } +} + +#ifdef DEBUG +/***************************************************************************** + * + * The following called for each recorded instruction -- use for debugging. + */ +void emitter::emitInsSanityCheck(instrDesc* id) +{ + /* What instruction format have we got? */ + + switch (id->idInsFmt()) + { + instruction ins; + emitAttr elemsize; + emitAttr datasize; + emitAttr dstsize; + emitAttr srcsize; + ssize_t imm; + unsigned immShift; + ssize_t index; + ssize_t index2; + + case IF_BI_0A: // BI_0A ......iiiiiiiiii iiiiiiiiiiiiiiii simm26:00 + break; + + case IF_BI_0B: // BI_0B ......iiiiiiiiii iiiiiiiiiiii.... simm19:00 + break; + + case IF_LARGEJMP: + case IF_LARGEADR: + case IF_LARGELDC: + break; + + case IF_BI_0C: // BI_0C ......iiiiiiiiii iiiiiiiiiiiiiiii simm26:00 + break; + + case IF_BI_1A: // BI_1A ......iiiiiiiiii iiiiiiiiiiittttt Rt simm19:00 + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + break; + + case IF_BI_1B: // BI_1B B.......bbbbbiii iiiiiiiiiiittttt Rt imm6, simm14:00 + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isValidImmShift(emitGetInsSC(id), id->idOpSize())); + break; + + case IF_BR_1A: // BR_1A ................ ......nnnnn..... Rn + assert(isGeneralRegister(id->idReg1())); + break; + + case IF_BR_1B: // BR_1B ................ ......nnnnn..... Rn + assert(isGeneralRegister(id->idReg3())); + break; + + case IF_LS_1A: // LS_1A .X......iiiiiiii iiiiiiiiiiittttt Rt PC imm(1MB) + assert(isGeneralRegister(id->idReg1()) || isVectorRegister(id->idReg1())); + assert(insOptsNone(id->idInsOpt())); + break; + + case IF_LS_2A: // LS_2A .X.......X...... ......nnnnnttttt Rt Rn + assert(isIntegerRegister(id->idReg1()) || // ZR + isVectorRegister(id->idReg1())); + assert(isIntegerRegister(id->idReg2())); // SP + assert(emitGetInsSC(id) == 0); + assert(insOptsNone(id->idInsOpt())); + break; + + case IF_LS_2B: // LS_2B .X.......Xiiiiii iiiiiinnnnnttttt Rt Rn imm(0-4095) + assert(isIntegerRegister(id->idReg1()) || // ZR + isVectorRegister(id->idReg1())); + assert(isIntegerRegister(id->idReg2())); // SP + assert(isValidUimm12(emitGetInsSC(id))); + assert(insOptsNone(id->idInsOpt())); + break; + + case IF_LS_2C: // LS_2C .X.......X.iiiii iiiiPPnnnnnttttt Rt Rn imm(-256..+255) no/pre/post inc + assert(isIntegerRegister(id->idReg1()) || // ZR + isVectorRegister(id->idReg1())); + assert(isIntegerRegister(id->idReg2())); // SP + assert(emitGetInsSC(id) >= -0x100); + assert(emitGetInsSC(id) < 0x100); + assert(insOptsNone(id->idInsOpt()) || insOptsIndexed(id->idInsOpt())); + break; + + case IF_LS_3A: // LS_3A .X.......X.mmmmm oooS..nnnnnttttt Rt Rn Rm ext(Rm) LSL {} + assert(isIntegerRegister(id->idReg1()) || // ZR + isVectorRegister(id->idReg1())); + assert(isIntegerRegister(id->idReg2())); // SP + if (id->idIsLclVar()) + { + assert(isGeneralRegister(codeGen->rsGetRsvdReg())); + } + else + { + assert(isGeneralRegister(id->idReg3())); + } + assert(insOptsLSExtend(id->idInsOpt())); + break; + + case IF_LS_3B: // LS_3B X............... .aaaaannnnnttttt Rt Ra Rn + assert((isValidGeneralDatasize(id->idOpSize()) && isIntegerRegister(id->idReg1())) || + (isValidVectorLSPDatasize(id->idOpSize()) && isVectorRegister(id->idReg1()))); + assert(isIntegerRegister(id->idReg1()) || // ZR + isVectorRegister(id->idReg1())); + assert(isIntegerRegister(id->idReg2()) || // ZR + isVectorRegister(id->idReg2())); + assert(isIntegerRegister(id->idReg3())); // SP + assert(emitGetInsSC(id) == 0); + assert(insOptsNone(id->idInsOpt())); + break; + + case IF_LS_3C: // LS_3C X.........iiiiii iaaaaannnnnttttt Rt Ra Rn imm(im7,sh) + assert((isValidGeneralDatasize(id->idOpSize()) && isIntegerRegister(id->idReg1())) || + (isValidVectorLSPDatasize(id->idOpSize()) && isVectorRegister(id->idReg1()))); + assert(isIntegerRegister(id->idReg1()) || // ZR + isVectorRegister(id->idReg1())); + assert(isIntegerRegister(id->idReg2()) || // ZR + isVectorRegister(id->idReg2())); + assert(isIntegerRegister(id->idReg3())); // SP + assert(emitGetInsSC(id) >= -0x40); + assert(emitGetInsSC(id) < 0x40); + assert(insOptsNone(id->idInsOpt()) || insOptsIndexed(id->idInsOpt())); + break; + + case IF_DI_1A: // DI_1A X.......shiiiiii iiiiiinnnnn..... Rn imm(i12,sh) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isValidUimm12(emitGetInsSC(id))); + assert(insOptsNone(id->idInsOpt()) || insOptsLSL12(id->idInsOpt())); + break; + + case IF_DI_1B: // DI_1B X........hwiiiii iiiiiiiiiiiddddd Rd imm(i16,hw) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isValidImmHWVal(emitGetInsSC(id), id->idOpSize())); + break; + + case IF_DI_1C: // DI_1C X........Nrrrrrr ssssssnnnnn..... Rn imm(N,r,s) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isValidImmNRS(emitGetInsSC(id), id->idOpSize())); + break; + + case IF_DI_1D: // DI_1D X........Nrrrrrr ssssss.....ddddd Rd imm(N,r,s) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // SP + assert(isValidImmNRS(emitGetInsSC(id), id->idOpSize())); + break; + + case IF_DI_1E: // DI_1E .ii.....iiiiiiii iiiiiiiiiiiddddd Rd simm21 + assert(isGeneralRegister(id->idReg1())); + break; + + case IF_DI_1F: // DI_1F X..........iiiii cccc..nnnnn.nzcv Rn imm5 nzcv cond + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isValidImmCondFlagsImm5(emitGetInsSC(id))); + break; + + case IF_DI_2A: // DI_2A X.......shiiiiii iiiiiinnnnnddddd Rd Rn imm(i12,sh) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // SP + assert(isIntegerRegister(id->idReg2())); // SP + assert(isValidUimm12(emitGetInsSC(id))); + assert(insOptsNone(id->idInsOpt()) || insOptsLSL12(id->idInsOpt())); + break; + + case IF_DI_2B: // DI_2B X.........Xnnnnn ssssssnnnnnddddd Rd Rn imm(0-63) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isValidImmShift(emitGetInsSC(id), id->idOpSize())); + break; + + case IF_DI_2C: // DI_2C X........Nrrrrrr ssssssnnnnnddddd Rd Rn imm(N,r,s) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // SP + assert(isGeneralRegister(id->idReg2())); + assert(isValidImmNRS(emitGetInsSC(id), id->idOpSize())); + break; + + case IF_DI_2D: // DI_2D X........Nrrrrrr ssssssnnnnnddddd Rd Rn imr, imms (N,r,s) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isValidImmNRS(emitGetInsSC(id), id->idOpSize())); + break; + + case IF_DR_1D: // DR_1D X............... cccc.......ddddd Rd cond + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isValidImmCond(emitGetInsSC(id))); + break; + + case IF_DR_2A: // DR_2A X..........mmmmm ......nnnnn..... Rn Rm + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + break; + + case IF_DR_2B: // DR_2B X.......sh.mmmmm ssssssnnnnn..... Rn Rm {LSL,LSR,ASR,ROR} imm(0-63) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // ZR + assert(isGeneralRegister(id->idReg2())); + assert(isValidImmShift(emitGetInsSC(id), id->idOpSize())); + if (!insOptsNone(id->idInsOpt())) + { + if (id->idIns() == INS_tst) // tst allows ROR, cmp/cmn don't + { + assert(insOptsAnyShift(id->idInsOpt())); + } + else + { + assert(insOptsAluShift(id->idInsOpt())); + } + } + assert(insOptsNone(id->idInsOpt()) || (emitGetInsSC(id) > 0)); + break; + + case IF_DR_2C: // DR_2C X..........mmmmm ooosssnnnnn..... Rn Rm ext(Rm) LSL imm(0-4) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // SP + assert(isGeneralRegister(id->idReg2())); + assert(insOptsNone(id->idInsOpt()) || insOptsLSL(id->idInsOpt()) || insOptsAnyExtend(id->idInsOpt())); + assert(emitGetInsSC(id) >= 0); + assert(emitGetInsSC(id) <= 4); + if (insOptsLSL(id->idInsOpt())) + { + assert(emitGetInsSC(id) > 0); + } + break; + + case IF_DR_2D: // DR_2D X..........nnnnn cccc..nnnnnmmmmm Rd Rn cond + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isValidImmCond(emitGetInsSC(id))); + break; + + case IF_DR_2E: // DR_2E X..........mmmmm ...........ddddd Rd Rm + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isIntegerRegister(id->idReg2())); // ZR + break; + + case IF_DR_2F: // DR_2F X.......sh.mmmmm ssssss.....ddddd Rd Rm {LSL,LSR,ASR} imm(0-63) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isValidImmShift(emitGetInsSC(id), id->idOpSize())); + assert(insOptsNone(id->idInsOpt()) || insOptsAluShift(id->idInsOpt())); + assert(insOptsNone(id->idInsOpt()) || (emitGetInsSC(id) > 0)); + break; + + case IF_DR_2G: // DR_2G X............... ......nnnnnddddd Rd Rm + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // SP + assert(isIntegerRegister(id->idReg2())); // SP + break; + + case IF_DR_2H: // DR_2H X........X...... ......nnnnnddddd Rd Rn + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + break; + + case IF_DR_2I: // DR_2I X..........mmmmm cccc..nnnnn.nzcv Rn Rm nzcv cond + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isValidImmCondFlags(emitGetInsSC(id))); + break; + + case IF_DR_3A: // DR_3A X..........mmmmm ......nnnnnmmmmm Rd Rn Rm + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // SP + assert(isIntegerRegister(id->idReg2())); // SP + if (id->idIsLclVar()) + { + assert(isGeneralRegister(codeGen->rsGetRsvdReg())); + } + else + { + assert(isGeneralRegister(id->idReg3())); + } + assert(insOptsNone(id->idInsOpt())); + break; + + case IF_DR_3B: // DR_3B X.......sh.mmmmm ssssssnnnnnddddd Rd Rn Rm {LSL,LSR,ASR,ROR} imm(0-63) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isGeneralRegister(id->idReg3())); + assert(isValidImmShift(emitGetInsSC(id), id->idOpSize())); + assert(insOptsNone(id->idInsOpt()) || insOptsAnyShift(id->idInsOpt())); + assert(insOptsNone(id->idInsOpt()) || (emitGetInsSC(id) > 0)); + break; + + case IF_DR_3C: // DR_3C X..........mmmmm ooosssnnnnnddddd Rd Rn Rm ext(Rm) LSL imm(0-4) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isIntegerRegister(id->idReg1())); // SP + assert(isIntegerRegister(id->idReg2())); // SP + assert(isGeneralRegister(id->idReg3())); + assert(insOptsNone(id->idInsOpt()) || insOptsLSL(id->idInsOpt()) || insOptsAnyExtend(id->idInsOpt())); + assert(emitGetInsSC(id) >= 0); + assert(emitGetInsSC(id) <= 4); + if (insOptsLSL(id->idInsOpt())) + { + assert((emitGetInsSC(id) > 0) || + (id->idReg2() == REG_ZR)); // REG_ZR encodes SP and we allow a shift of zero + } + break; + + case IF_DR_3D: // DR_3D X..........mmmmm cccc..nnnnnmmmmm Rd Rn Rm cond + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isGeneralRegister(id->idReg3())); + assert(isValidImmCond(emitGetInsSC(id))); + break; + + case IF_DR_3E: // DR_3E X........X.mmmmm ssssssnnnnnddddd Rd Rn Rm imm(0-63) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isGeneralRegister(id->idReg3())); + assert(isValidImmShift(emitGetInsSC(id), id->idOpSize())); + assert(insOptsNone(id->idInsOpt())); + break; + + case IF_DR_4A: // DR_4A X..........mmmmm .aaaaannnnnddddd Rd Rn Rm Ra + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isGeneralRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + assert(isGeneralRegister(id->idReg3())); + assert(isGeneralRegister(id->idReg4())); + break; + + case IF_DV_1A: // DV_1A .........X.iiiii iii........ddddd Vd imm8 (fmov - immediate scalar) + assert(insOptsNone(id->idInsOpt())); + elemsize = id->idOpSize(); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(isVectorRegister(id->idReg1())); + assert(isValidUimm8(emitGetInsSC(id))); + break; + + case IF_DV_1B: // DV_1B .QX..........iii cmod..iiiiiddddd Vd imm8 (immediate vector) + ins = id->idIns(); + imm = emitGetInsSC(id) & 0x0ff; + immShift = (emitGetInsSC(id) & 0x700) >> 8; + assert(immShift >= 0); + datasize = id->idOpSize(); + assert(isValidVectorDatasize(datasize)); + assert(isValidArrangement(datasize, id->idInsOpt())); + elemsize = optGetElemsize(id->idInsOpt()); + if (ins == INS_fmov) + { + assert(isValidVectorElemsizeFloat(elemsize)); + assert(id->idInsOpt() != INS_OPTS_1D); // Reserved encoding + assert(immShift == 0); + } + else + { + assert(isValidVectorElemsize(elemsize)); + assert((immShift != 4) && (immShift != 7)); // always invalid values + if (ins != INS_movi) // INS_mvni, INS_orr, INS_bic + { + assert((elemsize != EA_1BYTE) && (elemsize != EA_8BYTE)); // only H or S + if (elemsize == EA_2BYTE) + { + assert(immShift < 2); + } + else // (elemsize == EA_4BYTE) + { + if (ins != INS_mvni) + { + assert(immShift < 4); + } + } + } + } + assert(isVectorRegister(id->idReg1())); + assert(isValidUimm8(imm)); + break; + + case IF_DV_1C: // DV_1C .........X...... ......nnnnn..... Vn #0.0 (fcmp - with zero) + assert(insOptsNone(id->idInsOpt())); + elemsize = id->idOpSize(); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(isVectorRegister(id->idReg1())); + break; + + case IF_DV_2A: // DV_2A .Q.......X...... ......nnnnnddddd Vd Vn (fabs, fcvt - vector) + case IF_DV_2M: // DV_2M .Q......XX...... ......nnnnnddddd Vd Vn (abs, neg - vector) + assert(isValidVectorDatasize(id->idOpSize())); + assert(isValidArrangement(id->idOpSize(), id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_2N: // DV_2N .........iiiiiii ......nnnnnddddd Vd Vn imm (shift - scalar) + assert(id->idOpSize() == EA_8BYTE); + assert(insOptsNone(id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isValidImmShift(emitGetInsSC(id), EA_8BYTE)); + break; + + case IF_DV_2O: // DV_2O .Q.......iiiiiii ......nnnnnddddd Vd Vn imm (shift - vector) + assert(isValidVectorDatasize(id->idOpSize())); + assert(isValidArrangement(id->idOpSize(), id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + elemsize = optGetElemsize(id->idInsOpt()); + assert(isValidImmShift(emitGetInsSC(id), elemsize)); + break; + + case IF_DV_2B: // DV_2B .Q.........iiiii ......nnnnnddddd Rd Vn[] (umov/smov - to general) + elemsize = id->idOpSize(); + index = emitGetInsSC(id); + assert(insOptsNone(id->idInsOpt())); + assert(isValidVectorIndex(EA_16BYTE, elemsize, index)); + assert(isValidVectorElemsize(elemsize)); + assert(isGeneralRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_2C: // DV_2C .Q.........iiiii ......nnnnnddddd Vd Rn (dup/ins - vector from general) + if (id->idIns() == INS_dup) + { + datasize = id->idOpSize(); + assert(isValidVectorDatasize(datasize)); + assert(isValidArrangement(datasize, id->idInsOpt())); + elemsize = optGetElemsize(id->idInsOpt()); + } + else // INS_ins + { + datasize = EA_16BYTE; + elemsize = id->idOpSize(); + assert(isValidVectorElemsize(elemsize)); + } + assert(isVectorRegister(id->idReg1())); + assert(isGeneralRegisterOrZR(id->idReg2())); + break; + + case IF_DV_2D: // DV_2D .Q.........iiiii ......nnnnnddddd Vd Vn[] (dup - vector) + datasize = id->idOpSize(); + assert(isValidVectorDatasize(datasize)); + assert(isValidArrangement(datasize, id->idInsOpt())); + elemsize = optGetElemsize(id->idInsOpt()); + index = emitGetInsSC(id); + assert(isValidVectorIndex(datasize, elemsize, index)); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_2E: // DV_2E ...........iiiii ......nnnnnddddd Vd Vn[] (dup - scalar) + elemsize = id->idOpSize(); + index = emitGetInsSC(id); + assert(isValidVectorIndex(EA_16BYTE, elemsize, index)); + assert(isValidVectorElemsize(elemsize)); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_2F: // DV_2F ...........iiiii .jjjj.nnnnnddddd Vd[] Vn[] (ins - element) + imm = emitGetInsSC(id); + index = (imm >> 4) & 0xf; + index2 = imm & 0xf; + elemsize = id->idOpSize(); + assert(isValidVectorElemsize(elemsize)); + assert(isValidVectorIndex(EA_16BYTE, elemsize, index)); + assert(isValidVectorIndex(EA_16BYTE, elemsize, index2)); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_2L: // DV_2L ........XX...... ......nnnnnddddd Vd Vn (abs, neg - scalar) + assert(id->idOpSize() == EA_8BYTE); // only type D is supported + __fallthrough; + + case IF_DV_2G: // DV_2G .........X...... ......nnnnnddddd Vd Vn (fmov, fcvtXX - register) + case IF_DV_2K: // DV_2K .........X.mmmmm ......nnnnn..... Vn Vm (fcmp) + assert(insOptsNone(id->idInsOpt())); + assert(isValidVectorElemsizeFloat(id->idOpSize())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_2H: // DV_2H X........X...... ......nnnnnddddd Rd Vn (fmov/fcvtXX - to general) + assert(insOptsConvertFloatToInt(id->idInsOpt())); + dstsize = optGetDstsize(id->idInsOpt()); + srcsize = optGetSrcsize(id->idInsOpt()); + assert(isValidGeneralDatasize(dstsize)); + assert(isValidVectorElemsizeFloat(srcsize)); + assert(dstsize == id->idOpSize()); + assert(isGeneralRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_2I: // DV_2I X........X...... ......nnnnnddddd Vd Rn (fmov/Xcvtf - from general) + assert(insOptsConvertIntToFloat(id->idInsOpt())); + dstsize = optGetDstsize(id->idInsOpt()); + srcsize = optGetSrcsize(id->idInsOpt()); + assert(isValidGeneralDatasize(srcsize)); + assert(isValidVectorElemsizeFloat(dstsize)); + assert(dstsize == id->idOpSize()); + assert(isVectorRegister(id->idReg1())); + assert(isGeneralRegister(id->idReg2())); + break; + + case IF_DV_2J: // DV_2J ........SS.....D D.....nnnnnddddd Vd Vn (fcvt) + assert(insOptsConvertFloatToFloat(id->idInsOpt())); + dstsize = optGetDstsize(id->idInsOpt()); + srcsize = optGetSrcsize(id->idInsOpt()); + assert(isValidVectorFcvtsize(srcsize)); + assert(isValidVectorFcvtsize(dstsize)); + assert(dstsize == id->idOpSize()); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + break; + + case IF_DV_3A: // DV_3A .Q......XX.mmmmm ......nnnnnddddd Vd Vn Vm (vector) + assert(isValidVectorDatasize(id->idOpSize())); + assert(isValidArrangement(id->idOpSize(), id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + elemsize = optGetElemsize(id->idInsOpt()); + ins = id->idIns(); + if (ins == INS_mul) + { + assert(elemsize != EA_8BYTE); // can't use 2D or 1D + } + else if (ins == INS_pmul) + { + assert(elemsize == EA_1BYTE); // only supports 8B or 16B + } + break; + + case IF_DV_3AI: // DV_3AI .Q......XXLMmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector by elem) + assert(isValidVectorDatasize(id->idOpSize())); + assert(isValidArrangement(id->idOpSize(), id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + elemsize = optGetElemsize(id->idInsOpt()); + assert(isValidVectorIndex(EA_16BYTE, elemsize, emitGetInsSC(id))); + // Only has encodings for H or S elemsize + assert((elemsize == EA_2BYTE) || (elemsize == EA_4BYTE)); + break; + + case IF_DV_3B: // DV_3B .Q.......X.mmmmm ......nnnnnddddd Vd Vn Vm (vector) + assert(isValidVectorDatasize(id->idOpSize())); + assert(isValidArrangement(id->idOpSize(), id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + break; + + case IF_DV_3BI: // DV_3BI .Q.......XLmmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector by elem) + assert(isValidVectorDatasize(id->idOpSize())); + assert(isValidArrangement(id->idOpSize(), id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + elemsize = optGetElemsize(id->idInsOpt()); + assert(isValidVectorIndex(id->idOpSize(), elemsize, emitGetInsSC(id))); + break; + + case IF_DV_3C: // DV_3C .Q.........mmmmm ......nnnnnddddd Vd Vn Vm (vector) + assert(isValidVectorDatasize(id->idOpSize())); + assert(isValidArrangement(id->idOpSize(), id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + break; + + case IF_DV_3D: // DV_3D .........X.mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + assert(isValidScalarDatasize(id->idOpSize())); + assert(insOptsNone(id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + break; + + case IF_DV_3DI: // DV_3DI .........XLmmmmm ....H.nnnnnddddd Vd Vn Vm[] (scalar by elem) + assert(isValidScalarDatasize(id->idOpSize())); + assert(insOptsNone(id->idInsOpt())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + elemsize = id->idOpSize(); + assert(isValidVectorIndex(EA_16BYTE, elemsize, emitGetInsSC(id))); + break; + + case IF_DV_3E: // DV_3E ...........mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + assert(insOptsNone(id->idInsOpt())); + assert(id->idOpSize() == EA_8BYTE); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + break; + + case IF_DV_4A: // DR_4A .........X.mmmmm .aaaaannnnnddddd Rd Rn Rm Ra (scalar) + assert(isValidGeneralDatasize(id->idOpSize())); + assert(isVectorRegister(id->idReg1())); + assert(isVectorRegister(id->idReg2())); + assert(isVectorRegister(id->idReg3())); + assert(isVectorRegister(id->idReg4())); + break; + + case IF_SN_0A: // SN_0A ................ ................ + case IF_SI_0A: // SI_0A ...........iiiii iiiiiiiiiii..... imm16 + case IF_SI_0B: // SI_0B ................ ....bbbb........ imm4 - barrier + break; + + default: + printf("unexpected format %s\n", emitIfName(id->idInsFmt())); + assert(!"Unexpected format"); + break; + } +} +#endif // DEBUG + +bool emitter::emitInsMayWriteToGCReg(instrDesc* id) +{ + instruction ins = id->idIns(); + insFormat fmt = id->idInsFmt(); + + switch (fmt) + { + + // These are the formats with "destination" registers: + + case IF_DI_1B: // DI_1B X........hwiiiii iiiiiiiiiiiddddd Rd imm(i16,hw) + case IF_DI_1D: // DI_1D X........Nrrrrrr ssssss.....ddddd Rd imm(N,r,s) + case IF_DI_1E: // DI_1E .ii.....iiiiiiii iiiiiiiiiiiddddd Rd simm21 + + case IF_DI_2A: // DI_2A X.......shiiiiii iiiiiinnnnnddddd Rd Rn imm(i12,sh) + case IF_DI_2B: // DI_2B X.........Xnnnnn ssssssnnnnnddddd Rd Rn imm(0-63) + case IF_DI_2C: // DI_2C X........Nrrrrrr ssssssnnnnnddddd Rd Rn imm(N,r,s) + case IF_DI_2D: // DI_2D X........Nrrrrrr ssssssnnnnnddddd Rd Rn imr, imms (N,r,s) + + case IF_DR_1D: // DR_1D X............... cccc.......ddddd Rd cond + + case IF_DR_2D: // DR_2D X..........nnnnn cccc..nnnnnddddd Rd Rn cond + case IF_DR_2E: // DR_2E X..........mmmmm ...........ddddd Rd Rm + case IF_DR_2F: // DR_2F X.......sh.mmmmm ssssss.....ddddd Rd Rm {LSL,LSR,ASR} imm(0-63) + case IF_DR_2G: // DR_2G X............... ......nnnnnddddd Rd Rn + case IF_DR_2H: // DR_2H X........X...... ......nnnnnddddd Rd Rn + + case IF_DR_3A: // DR_3A X..........mmmmm ......nnnnnddddd Rd Rn Rm + case IF_DR_3B: // DR_3B X.......sh.mmmmm ssssssnnnnnddddd Rd Rn Rm {LSL,LSR,ASR} imm(0-63) + case IF_DR_3C: // DR_3C X..........mmmmm xxxsssnnnnnddddd Rd Rn Rm ext(Rm) LSL imm(0-4) + case IF_DR_3D: // DR_3D X..........mmmmm cccc..nnnnnddddd Rd Rn Rm cond + case IF_DR_3E: // DR_3E X........X.mmmmm ssssssnnnnnddddd Rd Rn Rm imm(0-63) + + case IF_DR_4A: // DR_4A X..........mmmmm .aaaaannnnnddddd Rd Rn Rm Ra + + case IF_DV_2B: // DV_2B .Q.........iiiii ......nnnnnddddd Rd Vn[] (umov - to general) + case IF_DV_2H: // DV_2H X........X...... ......nnnnnddddd Rd Vn (fmov - to general) + + return true; + + case IF_DV_2C: // DV_2C .Q.........iiiii ......nnnnnddddd Vd Rn (dup/ins - vector from general) + case IF_DV_2D: // DV_2D .Q.........iiiii ......nnnnnddddd Vd Vn[] (dup - vector) + case IF_DV_2E: // DV_2E ...........iiiii ......nnnnnddddd Vd Vn[] (dup - scalar) + case IF_DV_2F: // DV_2F ...........iiiii .jjjj.nnnnnddddd Vd[] Vn[] (ins - element) + case IF_DV_2G: // DV_2G .........X...... ......nnnnnddddd Vd Vn (fmov, fcvtXX - register) + case IF_DV_2I: // DV_2I X........X...... ......nnnnnddddd Vd Rn (fmov - from general) + case IF_DV_2J: // DV_2J ........SS.....D D.....nnnnnddddd Vd Vn (fcvt) + case IF_DV_2K: // DV_2K .........X.mmmmm ......nnnnn..... Vn Vm (fcmp) + case IF_DV_2L: // DV_2L ........XX...... ......nnnnnddddd Vd Vn (abs, neg - scalar) + case IF_DV_2M: // DV_2M .Q......XX...... ......nnnnnddddd Vd Vn (abs, neg - vector) + case IF_DV_3A: // DV_3A .Q......XX.mmmmm ......nnnnnddddd Vd Vn Vm (vector) + case IF_DV_3AI: // DV_3AI .Q......XXLMmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector) + case IF_DV_3B: // DV_3B .Q.......X.mmmmm ......nnnnnddddd Vd Vn Vm (vector) + case IF_DV_3BI: // DV_3BI .Q.......XLmmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector by elem) + case IF_DV_3C: // DV_3C .Q.........mmmmm ......nnnnnddddd Vd Vn Vm (vector) + case IF_DV_3D: // DV_3D .........X.mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + case IF_DV_3DI: // DV_3DI .........XLmmmmm ....H.nnnnnddddd Vd Vn Vm[] (scalar by elem) + case IF_DV_3E: // DV_3E ...........mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + case IF_DV_4A: // DV_4A .........X.mmmmm .aaaaannnnnddddd Vd Va Vn Vm (scalar) + // Tracked GC pointers cannot be placed into the SIMD registers. + return false; + + // These are the load/store formats with "target" registers: + + case IF_LS_1A: // LS_1A XX...V..iiiiiiii iiiiiiiiiiittttt Rt PC imm(1MB) + case IF_LS_2A: // LS_2A .X.......X...... ......nnnnnttttt Rt Rn + case IF_LS_2B: // LS_2B .X.......Xiiiiii iiiiiinnnnnttttt Rt Rn imm(0-4095) + case IF_LS_2C: // LS_2C .X.......X.iiiii iiiiP.nnnnnttttt Rt Rn imm(-256..+255) pre/post inc + case IF_LS_3A: // LS_3A .X.......X.mmmmm xxxS..nnnnnttttt Rt Rn Rm ext(Rm) LSL {} + case IF_LS_3B: // LS_3B X............... .aaaaannnnnttttt Rt Ra Rn + case IF_LS_3C: // LS_3C X.........iiiiii iaaaaannnnnttttt Rt Ra Rn imm(im7,sh) + + // For the Store instructions the "target" register is actually a "source" value + + if (emitInsIsStore(ins)) + { + return false; + } + else + { + assert(emitInsIsLoad(ins)); + return true; + } + + default: + return false; + } +} + +bool emitter::emitInsWritesToLclVarStackLoc(instrDesc* id) +{ + if (!id->idIsLclVar()) + return false; + + instruction ins = id->idIns(); + + // This list is related to the list of instructions used to store local vars in emitIns_S_R(). + // We don't accept writing to float local vars. + + switch (ins) + { + case INS_strb: + case INS_strh: + case INS_str: + case INS_stur: + case INS_sturb: + case INS_sturh: + return true; + default: + return false; + } +} + +bool emitter::emitInsMayWriteMultipleRegs(instrDesc* id) +{ + instruction ins = id->idIns(); + + switch (ins) + { + case INS_ldp: + case INS_ldpsw: + case INS_ldnp: + return true; + default: + return false; + } +} + +// For the small loads/store instruction we adjust the size 'attr' +// depending upon whether we have a load or a store +// +emitAttr emitter::emitInsAdjustLoadStoreAttr(instruction ins, emitAttr attr) +{ + if (EA_SIZE(attr) <= EA_4BYTE) + { + if (emitInsIsLoad(ins)) + { + // The value of 'ins' encodes the size to load + // we use EA_8BYTE here because it is the size we will write (into dataReg) + // it is also required when ins is INS_ldrsw + // + attr = EA_8BYTE; + } + else + { + assert(emitInsIsStore(ins)); + + // The value of 'ins' encodes the size to store + // we use EA_4BYTE here because it is the size of the register + // that we want to display when storing small values + // + attr = EA_4BYTE; + } + } + return attr; +} + +// Takes an instrDesc 'id' and uses the instruction 'ins' to determine the +// size of the target register that is written or read by the instruction. +// Note that even if EA_4BYTE is returned a load instruction will still +// always zero the upper 4 bytes of the target register. +// This method is required so that we can distinguish between loads that are +// sign-extending as they can have two different sizes for their target register. +// Additionally for instructions like 'ldr' and 'str' these can load/store +// either 4 byte or 8 bytes to/from the target register. +// By convention the small unsigned load instructions are considered to write +// a 4 byte sized target register, though since these also zero the upper 4 bytes +// they could equally be considered to write the unsigned value to full 8 byte register. +// +emitAttr emitter::emitInsTargetRegSize(instrDesc* id) +{ + instruction ins = id->idIns(); + emitAttr result = EA_UNKNOWN; + + // This is used to determine the size of the target registers for a load/store instruction + + switch (ins) + { + case INS_ldrb: + case INS_strb: + case INS_ldurb: + case INS_sturb: + result = EA_4BYTE; + break; + + case INS_ldrh: + case INS_strh: + case INS_ldurh: + case INS_sturh: + result = EA_4BYTE; + break; + + case INS_ldrsb: + case INS_ldursb: + case INS_ldrsh: + case INS_ldursh: + if (id->idOpSize() == EA_8BYTE) + result = EA_8BYTE; + else + result = EA_4BYTE; + break; + + case INS_ldrsw: + case INS_ldursw: + case INS_ldpsw: + result = EA_8BYTE; + break; + + case INS_ldp: + case INS_stp: + case INS_ldnp: + case INS_stnp: + result = id->idOpSize(); + break; + + case INS_ldr: + case INS_str: + case INS_ldur: + case INS_stur: + result = id->idOpSize(); + break; + + default: + NO_WAY("unexpected instruction"); + break; + } + return result; +} + +// Takes an instrDesc and uses the instruction to determine the 'size' of the +// data that is loaded from memory. +// +emitAttr emitter::emitInsLoadStoreSize(instrDesc* id) +{ + instruction ins = id->idIns(); + emitAttr result = EA_UNKNOWN; + + // The 'result' returned is the 'size' of the data that is loaded from memory. + + switch (ins) + { + case INS_ldrb: + case INS_strb: + case INS_ldurb: + case INS_sturb: + case INS_ldrsb: + case INS_ldursb: + result = EA_1BYTE; + break; + + case INS_ldrh: + case INS_strh: + case INS_ldurh: + case INS_sturh: + case INS_ldrsh: + case INS_ldursh: + result = EA_2BYTE; + break; + + case INS_ldrsw: + case INS_ldursw: + case INS_ldpsw: + result = EA_4BYTE; + break; + + case INS_ldp: + case INS_stp: + case INS_ldnp: + case INS_stnp: + result = id->idOpSize(); + break; + + case INS_ldr: + case INS_str: + case INS_ldur: + case INS_stur: + result = id->idOpSize(); + break; + + default: + NO_WAY("unexpected instruction"); + break; + } + return result; +} + +/*****************************************************************************/ +#ifdef DEBUG + +// clang-format off +static const char * const xRegNames[] = +{ + #define REGDEF(name, rnum, mask, xname, wname) xname, + #include "register.h" +}; + +static const char * const wRegNames[] = +{ + #define REGDEF(name, rnum, mask, xname, wname) wname, + #include "register.h" +}; + +static const char * const vRegNames[] = +{ + "v0", "v1", "v2", "v3", "v4", + "v5", "v6", "v7", "v8", "v9", + "v10", "v11", "v12", "v13", "v14", + "v15", "v16", "v17", "v18", "v19", + "v20", "v21", "v22", "v23", "v24", + "v25", "v26", "v27", "v28", "v29", + "v30", "v31" +}; + +static const char * const qRegNames[] = +{ + "q0", "q1", "q2", "q3", "q4", + "q5", "q6", "q7", "q8", "q9", + "q10", "q11", "q12", "q13", "q14", + "q15", "q16", "q17", "q18", "q19", + "q20", "q21", "q22", "q23", "q24", + "q25", "q26", "q27", "q28", "q29", + "q30", "q31" +}; + +static const char * const hRegNames[] = +{ + "h0", "h1", "h2", "h3", "h4", + "h5", "h6", "h7", "h8", "h9", + "h10", "h11", "h12", "h13", "h14", + "h15", "h16", "h17", "h18", "h19", + "h20", "h21", "h22", "h23", "h24", + "h25", "h26", "h27", "h28", "h29", + "h30", "h31" +}; +static const char * const bRegNames[] = +{ + "b0", "b1", "b2", "b3", "b4", + "b5", "b6", "b7", "b8", "b9", + "b10", "b11", "b12", "b13", "b14", + "b15", "b16", "b17", "b18", "b19", + "b20", "b21", "b22", "b23", "b24", + "b25", "b26", "b27", "b28", "b29", + "b30", "b31" +}; +// clang-format on + +/***************************************************************************** + * + * Return a string that represents the given register. + */ + +const char* emitter::emitRegName(regNumber reg, emitAttr size, bool varName) +{ + assert(reg < REG_COUNT); + + const char* rn = nullptr; + + if (size == EA_8BYTE) + { + rn = xRegNames[reg]; + } + else if (size == EA_4BYTE) + { + rn = wRegNames[reg]; + } + else if (isVectorRegister(reg)) + { + if (size == EA_16BYTE) + { + rn = qRegNames[reg - REG_V0]; + } + else if (size == EA_2BYTE) + { + rn = hRegNames[reg - REG_V0]; + } + else if (size == EA_1BYTE) + { + rn = bRegNames[reg - REG_V0]; + } + } + + assert(rn != nullptr); + + return rn; +} + +/***************************************************************************** + * + * Return a string that represents the given register. + */ + +const char* emitter::emitVectorRegName(regNumber reg) +{ + assert((reg >= REG_V0) && (reg <= REG_V31)); + + int index = (int)reg - (int)REG_V0; + + return vRegNames[index]; +} +#endif // DEBUG + +/***************************************************************************** + * + * Returns the base encoding of the given CPU instruction. + */ + +emitter::insFormat emitter::emitInsFormat(instruction ins) +{ + // clang-format off + const static insFormat insFormats[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) fmt, + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) fmt, + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) fmt, + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) fmt, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) fmt, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) fmt, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) fmt, + #include "instrs.h" + }; + // clang-format on + + assert(ins < ArrLen(insFormats)); + assert((insFormats[ins] != IF_NONE)); + + return insFormats[ins]; +} + +// INST_FP is 1 +#define LD 2 +#define ST 4 +#define CMP 8 + +// clang-format off +/*static*/ const BYTE CodeGenInterface::instInfo[] = +{ + #define INST1(id, nm, fp, ldst, fmt, e1 ) ldst | INST_FP*fp, + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) ldst | INST_FP*fp, + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) ldst | INST_FP*fp, + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) ldst | INST_FP*fp, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) ldst | INST_FP*fp, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) ldst | INST_FP*fp, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) ldst | INST_FP*fp, + #include "instrs.h" +}; +// clang-format on + +/***************************************************************************** + * + * Returns true if the instruction is some kind of compare or test instruction + */ + +bool emitter::emitInsIsCompare(instruction ins) +{ + // We have pseudo ins like lea which are not included in emitInsLdStTab. + if (ins < ArrLen(CodeGenInterface::instInfo)) + return (CodeGenInterface::instInfo[ins] & CMP) ? true : false; + else + return false; +} + +/***************************************************************************** + * + * Returns true if the instruction is some kind of load instruction + */ + +bool emitter::emitInsIsLoad(instruction ins) +{ + // We have pseudo ins like lea which are not included in emitInsLdStTab. + if (ins < ArrLen(CodeGenInterface::instInfo)) + return (CodeGenInterface::instInfo[ins] & LD) ? true : false; + else + return false; +} +/***************************************************************************** + * + * Returns true if the instruction is some kind of store instruction + */ + +bool emitter::emitInsIsStore(instruction ins) +{ + // We have pseudo ins like lea which are not included in emitInsLdStTab. + if (ins < ArrLen(CodeGenInterface::instInfo)) + return (CodeGenInterface::instInfo[ins] & ST) ? true : false; + else + return false; +} + +/***************************************************************************** + * + * Returns true if the instruction is some kind of load/store instruction + */ + +bool emitter::emitInsIsLoadOrStore(instruction ins) +{ + // We have pseudo ins like lea which are not included in emitInsLdStTab. + if (ins < ArrLen(CodeGenInterface::instInfo)) + return (CodeGenInterface::instInfo[ins] & (LD | ST)) ? true : false; + else + return false; +} + +#undef LD +#undef ST +#undef CMP + +/***************************************************************************** + * + * Returns the specific encoding of the given CPU instruction and format + */ + +emitter::code_t emitter::emitInsCode(instruction ins, insFormat fmt) +{ + // clang-format off + const static code_t insCodes1[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) e1, + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) e1, + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) e1, + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) e1, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) e1, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) e1, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e1, + #include "instrs.h" + }; + const static code_t insCodes2[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) e2, + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) e2, + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) e2, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) e2, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) e2, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e2, + #include "instrs.h" + }; + const static code_t insCodes3[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) e3, + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) e3, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) e3, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) e3, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e3, + #include "instrs.h" + }; + const static code_t insCodes4[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) e4, + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) e4, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) e4, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e4, + #include "instrs.h" + }; + const static code_t insCodes5[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) e5, + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) e5, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e5, + #include "instrs.h" + }; + const static code_t insCodes6[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) e6, + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e6, + #include "instrs.h" + }; + const static code_t insCodes7[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e7, + #include "instrs.h" + }; + const static code_t insCodes8[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e8, + #include "instrs.h" + }; + const static code_t insCodes9[] = + { + #define INST1(id, nm, fp, ldst, fmt, e1 ) + #define INST2(id, nm, fp, ldst, fmt, e1, e2 ) + #define INST3(id, nm, fp, ldst, fmt, e1, e2, e3 ) + #define INST4(id, nm, fp, ldst, fmt, e1, e2, e3, e4 ) + #define INST5(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5 ) + #define INST6(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6 ) + #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e9, + #include "instrs.h" + }; + // clang-format on + + const static insFormat formatEncode9[9] = {IF_DR_2E, IF_DR_2G, IF_DI_1B, IF_DI_1D, IF_DV_3C, + IF_DV_2B, IF_DV_2C, IF_DV_2E, IF_DV_2F}; + const static insFormat formatEncode6A[6] = {IF_DR_3A, IF_DR_3B, IF_DR_3C, IF_DI_2A, IF_DV_3A, IF_DV_3E}; + const static insFormat formatEncode5A[5] = {IF_LS_2A, IF_LS_2B, IF_LS_2C, IF_LS_3A, IF_LS_1A}; + const static insFormat formatEncode5B[5] = {IF_DV_2G, IF_DV_2H, IF_DV_2I, IF_DV_1A, IF_DV_1B}; + const static insFormat formatEncode5C[5] = {IF_DR_3A, IF_DR_3B, IF_DI_2C, IF_DV_3C, IF_DV_1B}; + const static insFormat formatEncode4A[4] = {IF_LS_2A, IF_LS_2B, IF_LS_2C, IF_LS_3A}; + const static insFormat formatEncode4B[4] = {IF_DR_3A, IF_DR_3B, IF_DR_3C, IF_DI_2A}; + const static insFormat formatEncode4C[4] = {IF_DR_2A, IF_DR_2B, IF_DR_2C, IF_DI_1A}; + const static insFormat formatEncode4D[4] = {IF_DV_3B, IF_DV_3D, IF_DV_3BI, IF_DV_3DI}; + const static insFormat formatEncode4E[4] = {IF_DR_3A, IF_DR_3B, IF_DI_2C, IF_DV_3C}; + const static insFormat formatEncode4F[4] = {IF_DR_3A, IF_DR_3B, IF_DV_3C, IF_DV_1B}; + const static insFormat formatEncode4G[4] = {IF_DR_2E, IF_DR_2F, IF_DV_2M, IF_DV_2L}; + const static insFormat formatEncode3A[3] = {IF_DR_3A, IF_DR_3B, IF_DI_2C}; + const static insFormat formatEncode3B[3] = {IF_DR_2A, IF_DR_2B, IF_DI_1C}; + const static insFormat formatEncode3C[3] = {IF_DR_3A, IF_DR_3B, IF_DV_3C}; + const static insFormat formatEncode3D[3] = {IF_DV_2C, IF_DV_2D, IF_DV_2E}; + const static insFormat formatEncode3E[3] = {IF_DV_3B, IF_DV_3BI, IF_DV_3DI}; + const static insFormat formatEncode3F[3] = {IF_DV_2A, IF_DV_2G, IF_DV_2H}; + const static insFormat formatEncode3G[3] = {IF_DV_2A, IF_DV_2G, IF_DV_2I}; + const static insFormat formatEncode3H[3] = {IF_DR_3A, IF_DV_3A, IF_DV_3AI}; + const static insFormat formatEncode3I[3] = {IF_DR_2E, IF_DR_2F, IF_DV_2M}; + const static insFormat formatEncode2A[2] = {IF_DR_2E, IF_DR_2F}; + const static insFormat formatEncode2B[2] = {IF_DR_3A, IF_DR_3B}; + const static insFormat formatEncode2C[2] = {IF_DR_3A, IF_DI_2D}; + const static insFormat formatEncode2D[2] = {IF_DR_3A, IF_DI_2B}; + const static insFormat formatEncode2E[2] = {IF_LS_3B, IF_LS_3C}; + const static insFormat formatEncode2F[2] = {IF_DR_2I, IF_DI_1F}; + const static insFormat formatEncode2G[2] = {IF_DV_3B, IF_DV_3D}; + const static insFormat formatEncode2H[2] = {IF_DV_2C, IF_DV_2F}; + const static insFormat formatEncode2I[2] = {IF_DV_2K, IF_DV_1C}; + const static insFormat formatEncode2J[2] = {IF_DV_2A, IF_DV_2G}; + const static insFormat formatEncode2K[2] = {IF_DV_2M, IF_DV_2L}; + const static insFormat formatEncode2L[2] = {IF_DV_2G, IF_DV_2M}; + const static insFormat formatEncode2M[2] = {IF_DV_3A, IF_DV_3AI}; + const static insFormat formatEncode2N[2] = {IF_DV_2N, IF_DV_2O}; + + code_t code = BAD_CODE; + insFormat insFmt = emitInsFormat(ins); + bool encoding_found = false; + int index = -1; + + switch (insFmt) + { + case IF_EN9: + for (index = 0; index < 9; index++) + { + if (fmt == formatEncode9[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN6A: + for (index = 0; index < 6; index++) + { + if (fmt == formatEncode6A[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN5A: + for (index = 0; index < 5; index++) + { + if (fmt == formatEncode5A[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN5B: + for (index = 0; index < 5; index++) + { + if (fmt == formatEncode5B[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN5C: + for (index = 0; index < 5; index++) + { + if (fmt == formatEncode5C[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN4A: + for (index = 0; index < 4; index++) + { + if (fmt == formatEncode4A[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN4B: + for (index = 0; index < 4; index++) + { + if (fmt == formatEncode4B[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN4C: + for (index = 0; index < 4; index++) + { + if (fmt == formatEncode4C[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN4D: + for (index = 0; index < 4; index++) + { + if (fmt == formatEncode4D[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN4E: + for (index = 0; index < 4; index++) + { + if (fmt == formatEncode4E[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN4F: + for (index = 0; index < 4; index++) + { + if (fmt == formatEncode4F[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN4G: + for (index = 0; index < 4; index++) + { + if (fmt == formatEncode4G[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3A: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3A[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3B: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3B[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3C: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3C[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3D: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3D[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3E: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3E[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3F: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3F[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3G: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3G[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3H: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3H[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN3I: + for (index = 0; index < 3; index++) + { + if (fmt == formatEncode3I[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2A: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2A[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2B: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2B[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2C: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2C[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2D: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2D[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2E: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2E[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2F: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2F[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2G: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2G[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2H: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2H[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2I: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2I[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2J: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2J[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2K: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2K[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2L: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2L[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2M: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2M[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_EN2N: + for (index = 0; index < 2; index++) + { + if (fmt == formatEncode2N[index]) + { + encoding_found = true; + break; + } + } + break; + + case IF_BI_0A: + case IF_BI_0B: + case IF_BI_0C: + case IF_BI_1A: + case IF_BI_1B: + case IF_BR_1A: + case IF_BR_1B: + case IF_LS_1A: + case IF_LS_2A: + case IF_LS_2B: + case IF_LS_2C: + case IF_LS_3A: + case IF_LS_3B: + case IF_LS_3C: + case IF_DI_1A: + case IF_DI_1B: + case IF_DI_1C: + case IF_DI_1D: + case IF_DI_1E: + case IF_DI_1F: + case IF_DI_2A: + case IF_DI_2B: + case IF_DI_2C: + case IF_DI_2D: + case IF_DR_1D: + case IF_DR_2A: + case IF_DR_2B: + case IF_DR_2C: + case IF_DR_2D: + case IF_DR_2E: + case IF_DR_2F: + case IF_DR_2G: + case IF_DR_2H: + case IF_DR_2I: + case IF_DR_3A: + case IF_DR_3B: + case IF_DR_3C: + case IF_DR_3D: + case IF_DR_3E: + case IF_DR_4A: + case IF_DV_1A: + case IF_DV_1B: + case IF_DV_1C: + case IF_DV_2A: + case IF_DV_2B: + case IF_DV_2C: + case IF_DV_2D: + case IF_DV_2E: + case IF_DV_2F: + case IF_DV_2G: + case IF_DV_2H: + case IF_DV_2I: + case IF_DV_2J: + case IF_DV_2K: + case IF_DV_2L: + case IF_DV_2M: + case IF_DV_2N: + case IF_DV_2O: + case IF_DV_3A: + case IF_DV_3AI: + case IF_DV_3B: + case IF_DV_3BI: + case IF_DV_3C: + case IF_DV_3D: + case IF_DV_3DI: + case IF_DV_3E: + case IF_DV_4A: + case IF_SN_0A: + case IF_SI_0A: + case IF_SI_0B: + + index = 0; + encoding_found = true; + break; + + default: + + encoding_found = false; + break; + } + + assert(encoding_found); + + switch (index) + { + case 0: + assert(ins < ArrLen(insCodes1)); + code = insCodes1[ins]; + break; + case 1: + assert(ins < ArrLen(insCodes2)); + code = insCodes2[ins]; + break; + case 2: + assert(ins < ArrLen(insCodes3)); + code = insCodes3[ins]; + break; + case 3: + assert(ins < ArrLen(insCodes4)); + code = insCodes4[ins]; + break; + case 4: + assert(ins < ArrLen(insCodes5)); + code = insCodes5[ins]; + break; + case 5: + assert(ins < ArrLen(insCodes6)); + code = insCodes6[ins]; + break; + case 6: + assert(ins < ArrLen(insCodes7)); + code = insCodes7[ins]; + break; + case 7: + assert(ins < ArrLen(insCodes8)); + code = insCodes8[ins]; + break; + case 8: + assert(ins < ArrLen(insCodes9)); + code = insCodes9[ins]; + break; + } + + assert((code != BAD_CODE)); + + return code; +} + +// true if this 'imm' can be encoded as a input operand to a mov instruction +/*static*/ bool emitter::emitIns_valid_imm_for_mov(INT64 imm, emitAttr size) +{ + // Check for "MOV (wide immediate)". + if (canEncodeHalfwordImm(imm, size)) + return true; + + // Next try the ones-complement form of 'halfword immediate' imm(i16,hw), + // namely "MOV (inverted wide immediate)". + ssize_t notOfImm = NOT_helper(imm, getBitWidth(size)); + if (canEncodeHalfwordImm(notOfImm, size)) + return true; + + // Finally try "MOV (bitmask immediate)" imm(N,r,s) + if (canEncodeBitMaskImm(imm, size)) + return true; + + return false; +} + +// true if this 'imm' can be encoded as a input operand to a vector movi instruction +/*static*/ bool emitter::emitIns_valid_imm_for_movi(INT64 imm, emitAttr elemsize) +{ + if (elemsize == EA_8BYTE) + { + UINT64 uimm = imm; + while (uimm != 0) + { + INT64 loByte = uimm & 0xFF; + if ((loByte == 0) || (loByte == 0xFF)) + { + uimm >>= 8; + } + else + { + return false; + } + } + assert(uimm == 0); + return true; + } + else + { + // First try the standard 'byteShifted immediate' imm(i8,bySh) + if (canEncodeByteShiftedImm(imm, elemsize, true)) + return true; + + // Next try the ones-complement form of the 'immediate' imm(i8,bySh) + ssize_t notOfImm = NOT_helper(imm, getBitWidth(elemsize)); + if (canEncodeByteShiftedImm(notOfImm, elemsize, true)) + return true; + } + return false; +} + +// true if this 'imm' can be encoded as a input operand to a fmov instruction +/*static*/ bool emitter::emitIns_valid_imm_for_fmov(double immDbl) +{ + if (canEncodeFloatImm8(immDbl)) + return true; + + return false; +} + +// true if this 'imm' can be encoded as a input operand to an add instruction +/*static*/ bool emitter::emitIns_valid_imm_for_add(INT64 imm, emitAttr size) +{ + if (unsigned_abs(imm) <= 0x0fff) + return true; + else if (canEncodeWithShiftImmBy12(imm)) // Try the shifted by 12 encoding + return true; + + return false; +} + +// true if this 'imm' can be encoded as a input operand to an non-add/sub alu instruction +/*static*/ bool emitter::emitIns_valid_imm_for_cmp(INT64 imm, emitAttr size) +{ + return emitIns_valid_imm_for_add(imm, size); +} + +// true if this 'imm' can be encoded as a input operand to an non-add/sub alu instruction +/*static*/ bool emitter::emitIns_valid_imm_for_alu(INT64 imm, emitAttr size) +{ + if (canEncodeBitMaskImm(imm, size)) + return true; + + return false; +} + +// true if this 'imm' can be encoded as the offset in a ldr/str instruction +/*static*/ bool emitter::emitIns_valid_imm_for_ldst_offset(INT64 imm, emitAttr attr) +{ + if (imm == 0) + return true; // Encodable using IF_LS_2A + + if ((imm >= -256) && (imm <= 255)) + return true; // Encodable using IF_LS_2C (or possibly IF_LS_2B) + + if (imm < 0) + return false; // not encodable + + emitAttr size = EA_SIZE(attr); + unsigned scale = NaturalScale_helper(size); + ssize_t mask = size - 1; // the mask of low bits that must be zero to encode the immediate + + if (((imm & mask) == 0) && ((imm >> scale) < 0x1000)) + return true; // Encodable using IF_LS_2B + + return false; // not encodable +} + +/************************************************************************ + * + * A helper method to return the natural scale for an EA 'size' + */ + +/*static*/ unsigned emitter::NaturalScale_helper(emitAttr size) +{ + assert(size == EA_1BYTE || size == EA_2BYTE || size == EA_4BYTE || size == EA_8BYTE || size == EA_16BYTE); + + unsigned result = 0; + unsigned utemp = (unsigned)size; + + // Compute log base 2 of utemp (aka 'size') + while (utemp > 1) + { + result++; + utemp >>= 1; + } + + return result; +} + +/************************************************************************ + * + * A helper method to perform a Rotate-Right shift operation + * the source is 'value' and it is rotated right by 'sh' bits + * 'value' is considered to be a fixed size 'width' set of bits. + * + * Example + * value is '00001111', sh is 2 and width is 8 + * result is '11000011' + */ + +/*static*/ UINT64 emitter::ROR_helper(UINT64 value, unsigned sh, unsigned width) +{ + assert(width <= 64); + // Check that 'value' fits in 'width' bits + assert((width == 64) || (value < (1ULL << width))); + // We don't support shifts >= width + assert(sh < width); + + UINT64 result; + + unsigned rsh = sh; + unsigned lsh = width - rsh; + + result = (value >> rsh); + result |= (value << lsh); + + if (width < 64) + { + // mask off any extra bits that we got from the left shift + result &= ((1ULL << width) - 1); + } + return result; +} +/************************************************************************ + * + * A helper method to perform a 'NOT' bitwise complement operation. + * 'value' is considered to be a fixed size 'width' set of bits. + * + * Example + * value is '01001011', and width is 8 + * result is '10110100' + */ + +/*static*/ UINT64 emitter::NOT_helper(UINT64 value, unsigned width) +{ + assert(width <= 64); + + UINT64 result = ~value; + + if (width < 64) + { + // Check that 'value' fits in 'width' bits. Don't consider "sign" bits above width. + UINT64 maxVal = 1ULL << width; + UINT64 lowBitsMask = maxVal - 1; + UINT64 signBitsMask = ~lowBitsMask | (1ULL << (width - 1)); // The high bits must be set, and the top bit + // (sign bit) must be set. + assert((value < maxVal) || ((value & signBitsMask) == signBitsMask)); + + // mask off any extra bits that we got from the complement operation + result &= lowBitsMask; + } + + return result; +} + +/************************************************************************ + * + * A helper method to perform a bit Replicate operation + * the source is 'value' with a fixed size 'width' set of bits. + * value is replicated to fill out 32 or 64 bits as determined by 'size'. + * + * Example + * value is '11000011' (0xE3), width is 8 and size is EA_8BYTE + * result is '11000011 11000011 11000011 11000011 11000011 11000011 11000011 11000011' + * 0xE3E3E3E3E3E3E3E3 + */ + +/*static*/ UINT64 emitter::Replicate_helper(UINT64 value, unsigned width, emitAttr size) +{ + assert(emitter::isValidGeneralDatasize(size)); + + unsigned immWidth = (size == EA_8BYTE) ? 64 : 32; + assert(width <= immWidth); + + UINT64 result = value; + unsigned filledBits = width; + + while (filledBits < immWidth) + { + value <<= width; + result |= value; + filledBits += width; + } + return result; +} + +/************************************************************************ + * + * Convert an imm(N,r,s) into a 64-bit immediate + * inputs 'bmImm' a bitMaskImm struct + * 'size' specifies the size of the result (64 or 32 bits) + */ + +/*static*/ INT64 emitter::emitDecodeBitMaskImm(const emitter::bitMaskImm bmImm, emitAttr size) +{ + assert(isValidGeneralDatasize(size)); // Only EA_4BYTE or EA_8BYTE forms + + unsigned N = bmImm.immN; // read the N,R and S values from the 'bitMaskImm' encoding + unsigned R = bmImm.immR; + unsigned S = bmImm.immS; + + unsigned elemWidth = 64; // used when immN == 1 + + if (bmImm.immN == 0) // find the smaller elemWidth when immN == 0 + { + // Scan S for the highest bit not set + elemWidth = 32; + for (unsigned bitNum = 5; bitNum > 0; bitNum--) + { + unsigned oneBit = elemWidth; + if ((S & oneBit) == 0) + break; + elemWidth /= 2; + } + } + else + { + assert(size == EA_8BYTE); + } + + unsigned maskSR = elemWidth - 1; + + S &= maskSR; + R &= maskSR; + + // encoding for S is one less than the number of consecutive one bits + S++; // Number of consecutive ones to generate in 'welem' + + // At this point: + // + // 'elemWidth' is the number of bits that we will use for the ROR and Replicate operations + // 'S' is the number of consecutive 1 bits for the immediate + // 'R' is the number of bits that we will Rotate Right the immediate + // 'size' selects the final size of the immedate that we return (64 or 32 bits) + + assert(S < elemWidth); // 'elemWidth' consecutive one's is a reserved encoding + + UINT64 welem; + UINT64 wmask; + + welem = (1ULL << S) - 1; + + wmask = ROR_helper(welem, R, elemWidth); + wmask = Replicate_helper(wmask, elemWidth, size); + + return wmask; +} + +/***************************************************************************** + * + * Check if an immediate can use the left shifted by 12 bits encoding + */ + +/*static*/ bool emitter::canEncodeWithShiftImmBy12(INT64 imm) +{ + if (imm < 0) + { + imm = -imm; // convert to unsigned + } + + if (imm < 0) + { + return false; // Must be MIN_INT64 + } + + if ((imm & 0xfff) != 0) // Now the low 12 bits all have to be zero + { + return false; + } + + imm >>= 12; // shift right by 12 bits + + return (imm <= 0x0fff); // Does it fit in 12 bits +} + +/***************************************************************************** + * + * Normalize the 'imm' so that the upper bits, as defined by 'size' are zero + */ + +/*static*/ INT64 emitter::normalizeImm64(INT64 imm, emitAttr size) +{ + unsigned immWidth = getBitWidth(size); + INT64 result = imm; + + if (immWidth < 64) + { + // Check that 'imm' fits in 'immWidth' bits. Don't consider "sign" bits above width. + INT64 maxVal = 1LL << immWidth; + INT64 lowBitsMask = maxVal - 1; + INT64 hiBitsMask = ~lowBitsMask; + INT64 signBitsMask = + hiBitsMask | (1LL << (immWidth - 1)); // The high bits must be set, and the top bit (sign bit) must be set. + assert((imm < maxVal) || ((imm & signBitsMask) == signBitsMask)); + + // mask off the hiBits + result &= lowBitsMask; + } + return result; +} + +/***************************************************************************** + * + * Normalize the 'imm' so that the upper bits, as defined by 'size' are zero + */ + +/*static*/ INT32 emitter::normalizeImm32(INT32 imm, emitAttr size) +{ + unsigned immWidth = getBitWidth(size); + INT32 result = imm; + + if (immWidth < 32) + { + // Check that 'imm' fits in 'immWidth' bits. Don't consider "sign" bits above width. + INT32 maxVal = 1 << immWidth; + INT32 lowBitsMask = maxVal - 1; + INT32 hiBitsMask = ~lowBitsMask; + INT32 signBitsMask = hiBitsMask | (1 << (immWidth - 1)); // The high bits must be set, and the top bit + // (sign bit) must be set. + assert((imm < maxVal) || ((imm & signBitsMask) == signBitsMask)); + + // mask off the hiBits + result &= lowBitsMask; + } + return result; +} + +/************************************************************************ + * + * returns true if 'imm' of 'size bits (32/64) can be encoded + * using the ARM64 'bitmask immediate' form. + * When a non-null value is passed for 'wbBMI' then this method + * writes back the 'N','S' and 'R' values use to encode this immediate + * + */ + +/*static*/ bool emitter::canEncodeBitMaskImm(INT64 imm, emitAttr size, emitter::bitMaskImm* wbBMI) +{ + assert(isValidGeneralDatasize(size)); // Only EA_4BYTE or EA_8BYTE forms + + unsigned immWidth = (size == EA_8BYTE) ? 64 : 32; + unsigned maxLen = (size == EA_8BYTE) ? 6 : 5; + + imm = normalizeImm64(imm, size); + + // Starting with len=1, elemWidth is 2 bits + // len=2, elemWidth is 4 bits + // len=3, elemWidth is 8 bits + // len=4, elemWidth is 16 bits + // len=5, elemWidth is 32 bits + // (optionally) len=6, elemWidth is 64 bits + // + for (unsigned len = 1; (len <= maxLen); len++) + { + unsigned elemWidth = 1 << len; + UINT64 elemMask = ((UINT64)-1) >> (64 - elemWidth); + UINT64 tempImm = (UINT64)imm; // A working copy of 'imm' that we can mutate + UINT64 elemVal = tempImm & elemMask; // The low 'elemWidth' bits of 'imm' + + // Check for all 1's or 0's as these can't be encoded + if ((elemVal == 0) || (elemVal == elemMask)) + continue; + + // 'checkedBits' is the count of bits that are known to match 'elemVal' when replicated + unsigned checkedBits = elemWidth; // by definition the first 'elemWidth' bits match + + // Now check to see if each of the next bits match... + // + while (checkedBits < immWidth) + { + tempImm >>= elemWidth; + + UINT64 nextElem = tempImm & elemMask; + if (nextElem != elemVal) + { + // Not matching, exit this loop and checkedBits will not be equal to immWidth + break; + } + + // The 'nextElem' is matching, so increment 'checkedBits' + checkedBits += elemWidth; + } + + // Did the full immediate contain bits that can be formed by repeating 'elemVal'? + if (checkedBits == immWidth) + { + // We are not quite done, since the only values that we can encode as a + // 'bitmask immediate' are those that can be formed by starting with a + // bit string of 0*1* that is rotated by some number of bits. + // + // We check to see if 'elemVal' can be formed using these restrictions. + // + // Observation: + // Rotating by one bit any value that passes these restrictions + // can be xor-ed with the original value and will result it a string + // of bits that have exactly two 1 bits: 'elemRorXor' + // Further the distance between the two one bits tells us the value + // of S and the location of the 1 bits tells us the value of R + // + // Some examples: (immWidth is 8) + // + // S=4,R=0 S=5,R=3 S=3,R=6 + // elemVal: 00001111 11100011 00011100 + // elemRor: 10000111 11110001 00001110 + // elemRorXor: 10001000 00010010 00010010 + // compute S 45678--- ---5678- ---3210- + // compute R 01234567 ---34567 ------67 + + UINT64 elemRor = ROR_helper(elemVal, 1, elemWidth); // Rotate 'elemVal' Right by one bit + UINT64 elemRorXor = elemVal ^ elemRor; // Xor elemVal and elemRor + + // If we only have a two-bit change in elemROR then we can form a mask for this value + unsigned bitCount = 0; + UINT64 oneBit = 0x1; + unsigned R = elemWidth; // R is shift count for ROR (rotate right shift) + unsigned S = 0; // S is number of consecutive one bits + int incr = -1; + + // Loop over the 'elemWidth' bits in 'elemRorXor' + // + for (unsigned bitNum = 0; bitNum < elemWidth; bitNum++) + { + if (incr == -1) + { + R--; // We decrement R by one whenever incr is -1 + } + if (bitCount == 1) + { + S += incr; // We incr/decr S, after we find the first one bit in 'elemRorXor' + } + + // Is this bit position a 1 bit in 'elemRorXor'? + // + if (oneBit & elemRorXor) + { + bitCount++; + // Is this the first 1 bit that we found in 'elemRorXor'? + if (bitCount == 1) + { + // Does this 1 bit represent a transition to zero bits? + bool toZeros = ((oneBit & elemVal) != 0); + if (toZeros) + { + // S :: Count down from elemWidth + S = elemWidth; + incr = -1; + } + else // this 1 bit represent a transition to one bits. + { + // S :: Count up from zero + S = 0; + incr = +1; + } + } + else // bitCount > 1 + { + // We found the second (or third...) 1 bit in 'elemRorXor' + incr = 0; // stop decrementing 'R' + + if (bitCount > 2) + { + // More than 2 transitions from 0/1 in 'elemVal' + // This means that 'elemVal' can't be encoded + // using a 'bitmask immediate'. + // + // Furthermore, it will continue to fail + // with any larger 'len' that we try. + // so just return false. + // + return false; + } + } + } + + // shift oneBit left by one bit to test the next position + oneBit <<= 1; + } + + // We expect that bitCount will always be two at this point + // but just in case return false for any bad cases. + // + assert(bitCount == 2); + if (bitCount != 2) + return false; + + // Perform some sanity checks on the values of 'S' and 'R' + assert(S > 0); + assert(S < elemWidth); + assert(R < elemWidth); + + // Does the caller want us to return the N,R,S encoding values? + // + if (wbBMI != nullptr) + { + + // The encoding used for S is one less than the + // number of consecutive one bits + S--; + + if (len == 6) + { + wbBMI->immN = 1; + } + else + { + wbBMI->immN = 0; + // The encoding used for 'S' here is a bit peculiar. + // + // The upper bits need to be complemented, followed by a zero bit + // then the value of 'S-1' + // + unsigned upperBitsOfS = 64 - (1 << (len + 1)); + S |= upperBitsOfS; + } + wbBMI->immR = R; + wbBMI->immS = S; + + // Verify that what we are returning is correct. + assert(imm == emitDecodeBitMaskImm(*wbBMI, size)); + } + // Tell the caller that we can successfully encode this immediate + // using a 'bitmask immediate'. + // + return true; + } + } + return false; +} + +/************************************************************************ + * + * Convert a 64-bit immediate into its 'bitmask immediate' representation imm(N,r,s) + */ + +/*static*/ emitter::bitMaskImm emitter::emitEncodeBitMaskImm(INT64 imm, emitAttr size) +{ + emitter::bitMaskImm result; + result.immNRS = 0; + + bool canEncode = canEncodeBitMaskImm(imm, size, &result); + assert(canEncode); + + return result; +} + +/************************************************************************ + * + * Convert an imm(i16,hw) into a 32/64-bit immediate + * inputs 'hwImm' a halfwordImm struct + * 'size' specifies the size of the result (64 or 32 bits) + */ + +/*static*/ INT64 emitter::emitDecodeHalfwordImm(const emitter::halfwordImm hwImm, emitAttr size) +{ + assert(isValidGeneralDatasize(size)); // Only EA_4BYTE or EA_8BYTE forms + + unsigned hw = hwImm.immHW; + INT64 val = (INT64)hwImm.immVal; + + assert((hw <= 1) || (size == EA_8BYTE)); + + INT64 result = val << (16 * hw); + return result; +} + +/************************************************************************ + * + * returns true if 'imm' of 'size' bits (32/64) can be encoded + * using the ARM64 'halfword immediate' form. + * When a non-null value is passed for 'wbHWI' then this method + * writes back the 'immHW' and 'immVal' values use to encode this immediate + * + */ + +/*static*/ bool emitter::canEncodeHalfwordImm(INT64 imm, emitAttr size, emitter::halfwordImm* wbHWI) +{ + assert(isValidGeneralDatasize(size)); // Only EA_4BYTE or EA_8BYTE forms + + unsigned immWidth = (size == EA_8BYTE) ? 64 : 32; + unsigned maxHW = (size == EA_8BYTE) ? 4 : 2; + + // setup immMask to a (EA_4BYTE) 0x00000000_FFFFFFFF or (EA_8BYTE) 0xFFFFFFFF_FFFFFFFF + const UINT64 immMask = ((UINT64)-1) >> (64 - immWidth); + const INT64 mask16 = (INT64)0xFFFF; + + imm = normalizeImm64(imm, size); + + // Try each of the valid hw shift sizes + for (unsigned hw = 0; (hw < maxHW); hw++) + { + INT64 curMask = mask16 << (hw * 16); // Represents the mask of the bits in the current halfword + INT64 checkBits = immMask & ~curMask; + + // Excluding the current halfword (using ~curMask) + // does the immediate have zero bits in every other bit that we care about? + // note we care about all 64-bits for EA_8BYTE + // and we care about the lowest 32 bits for EA_4BYTE + // + if ((imm & checkBits) == 0) + { + // Does the caller want us to return the imm(i16,hw) encoding values? + // + if (wbHWI != nullptr) + { + INT64 val = ((imm & curMask) >> (hw * 16)) & mask16; + wbHWI->immHW = hw; + wbHWI->immVal = val; + + // Verify that what we are returning is correct. + assert(imm == emitDecodeHalfwordImm(*wbHWI, size)); + } + // Tell the caller that we can successfully encode this immediate + // using a 'halfword immediate'. + // + return true; + } + } + return false; +} + +/************************************************************************ + * + * Convert a 64-bit immediate into its 'halfword immediate' representation imm(i16,hw) + */ + +/*static*/ emitter::halfwordImm emitter::emitEncodeHalfwordImm(INT64 imm, emitAttr size) +{ + emitter::halfwordImm result; + result.immHWVal = 0; + + bool canEncode = canEncodeHalfwordImm(imm, size, &result); + assert(canEncode); + + return result; +} + +/************************************************************************ + * + * Convert an imm(i8,sh) into a 16/32-bit immediate + * inputs 'bsImm' a byteShiftedImm struct + * 'size' specifies the size of the result (16 or 32 bits) + */ + +/*static*/ INT32 emitter::emitDecodeByteShiftedImm(const emitter::byteShiftedImm bsImm, emitAttr size) +{ + bool onesShift = (bsImm.immOnes == 1); + unsigned bySh = bsImm.immBY; // Num Bytes to shift 0,1,2,3 + INT32 val = (INT32)bsImm.immVal; // 8-bit immediate + INT32 result = val; + + if (bySh > 0) + { + assert((size == EA_2BYTE) || (size == EA_4BYTE)); // Only EA_2BYTE or EA_4BYTE forms + if (size == EA_2BYTE) + { + assert(bySh < 2); + } + else + { + assert(bySh < 4); + } + + result <<= (8 * bySh); + + if (onesShift) + { + result |= ((1 << (8 * bySh)) - 1); + } + } + return result; +} + +/************************************************************************ + * + * returns true if 'imm' of 'size' bits (16/32) can be encoded + * using the ARM64 'byteShifted immediate' form. + * When a non-null value is passed for 'wbBSI' then this method + * writes back the 'immBY' and 'immVal' values use to encode this immediate + * + */ + +/*static*/ bool emitter::canEncodeByteShiftedImm(INT64 imm, + emitAttr size, + bool allow_MSL, + emitter::byteShiftedImm* wbBSI) +{ + bool canEncode = false; + bool onesShift = false; // true if we use the shifting ones variant + unsigned bySh = 0; // number of bytes to shift: 0, 1, 2, 3 + unsigned imm8 = 0; // immediate to use in the encoding + + imm = normalizeImm64(imm, size); + + if (size == EA_1BYTE) + { + imm8 = (unsigned)imm; + assert(imm8 < 0x100); + canEncode = true; + } + else if (size == EA_8BYTE) + { + imm8 = (unsigned)imm; + assert(imm8 < 0x100); + canEncode = true; + } + else + { + assert((size == EA_2BYTE) || (size == EA_4BYTE)); // Only EA_2BYTE or EA_4BYTE forms + + unsigned immWidth = (size == EA_4BYTE) ? 32 : 16; + unsigned maxBY = (size == EA_4BYTE) ? 4 : 2; + + // setup immMask to a (EA_2BYTE) 0x0000FFFF or (EA_4BYTE) 0xFFFFFFFF + const UINT32 immMask = ((UINT32)-1) >> (32 - immWidth); + const INT32 mask8 = (INT32)0xFF; + + // Try each of the valid by shift sizes + for (bySh = 0; (bySh < maxBY); bySh++) + { + INT32 curMask = mask8 << (bySh * 8); // Represents the mask of the bits in the current byteShifted + INT32 checkBits = immMask & ~curMask; + INT32 immCheck = (imm & checkBits); + + // Excluding the current byte (using ~curMask) + // does the immediate have zero bits in every other bit that we care about? + // or can be use the shifted one variant? + // note we care about all 32-bits for EA_4BYTE + // and we care about the lowest 16 bits for EA_2BYTE + // + if (immCheck == 0) + { + canEncode = true; + } + if (allow_MSL) + { + if ((bySh == 1) && (immCheck == 0xFF)) + { + canEncode = true; + onesShift = true; + } + else if ((bySh == 2) && (immCheck == 0xFFFF)) + { + canEncode = true; + onesShift = true; + } + } + if (canEncode) + { + imm8 = (unsigned)(((imm & curMask) >> (bySh * 8)) & mask8); + break; + } + } + } + + if (canEncode) + { + // Does the caller want us to return the imm(i8,bySh) encoding values? + // + if (wbBSI != nullptr) + { + wbBSI->immOnes = onesShift; + wbBSI->immBY = bySh; + wbBSI->immVal = imm8; + + // Verify that what we are returning is correct. + assert(imm == emitDecodeByteShiftedImm(*wbBSI, size)); + } + // Tell the caller that we can successfully encode this immediate + // using a 'byteShifted immediate'. + // + return true; + } + return false; +} + +/************************************************************************ + * + * Convert a 32-bit immediate into its 'byteShifted immediate' representation imm(i8,by) + */ + +/*static*/ emitter::byteShiftedImm emitter::emitEncodeByteShiftedImm(INT64 imm, emitAttr size, bool allow_MSL) +{ + emitter::byteShiftedImm result; + result.immBSVal = 0; + + bool canEncode = canEncodeByteShiftedImm(imm, size, allow_MSL, &result); + assert(canEncode); + + return result; +} + +/************************************************************************ + * + * Convert a 'float 8-bit immediate' into a double. + * inputs 'fpImm' a floatImm8 struct + */ + +/*static*/ double emitter::emitDecodeFloatImm8(const emitter::floatImm8 fpImm) +{ + unsigned sign = fpImm.immSign; + unsigned exp = fpImm.immExp ^ 0x4; + unsigned mant = fpImm.immMant + 16; + unsigned scale = 16 * 8; + + while (exp > 0) + { + scale /= 2; + exp--; + } + + double result = ((double)mant) / ((double)scale); + if (sign == 1) + { + result = -result; + } + + return result; +} + +/************************************************************************ + * + * returns true if the 'immDbl' can be encoded using the 'float 8-bit immediate' form. + * also returns the encoding if wbFPI is non-null + * + */ + +/*static*/ bool emitter::canEncodeFloatImm8(double immDbl, emitter::floatImm8* wbFPI) +{ + bool canEncode = false; + double val = immDbl; + + int sign = 0; + if (val < 0.0) + { + val = -val; + sign = 1; + } + + int exp = 0; + while ((val < 1.0) && (exp >= -4)) + { + val *= 2.0; + exp--; + } + while ((val >= 2.0) && (exp <= 5)) + { + val *= 0.5; + exp++; + } + exp += 3; + val *= 16.0; + int ival = (int)val; + + if ((exp >= 0) && (exp <= 7)) + { + if (val == (double)ival) + { + canEncode = true; + + if (wbFPI != nullptr) + { + ival -= 16; + assert((ival >= 0) && (ival <= 15)); + + wbFPI->immSign = sign; + wbFPI->immExp = exp ^ 0x4; + wbFPI->immMant = ival; + unsigned imm8 = wbFPI->immFPIVal; + assert((imm8 >= 0) && (imm8 <= 0xff)); + } + } + } + + return canEncode; +} + +/************************************************************************ + * + * Convert a double into its 'float 8-bit immediate' representation + */ + +/*static*/ emitter::floatImm8 emitter::emitEncodeFloatImm8(double immDbl) +{ + emitter::floatImm8 result; + result.immFPIVal = 0; + + bool canEncode = canEncodeFloatImm8(immDbl, &result); + assert(canEncode); + + return result; +} + +/***************************************************************************** + * + * For the given 'ins' returns the reverse instruction + * if one exists, otherwise returns INS_INVALID + */ + +/*static*/ instruction emitter::insReverse(instruction ins) +{ + switch (ins) + { + case INS_add: + return INS_sub; + case INS_adds: + return INS_subs; + + case INS_sub: + return INS_add; + case INS_subs: + return INS_adds; + + case INS_cmp: + return INS_cmn; + case INS_cmn: + return INS_cmp; + + case INS_ccmp: + return INS_ccmn; + case INS_ccmn: + return INS_ccmp; + + default: + return INS_invalid; + } +} + +/***************************************************************************** + * + * For the given 'datasize' and 'elemsize', make the proper arrangement option + * returns the insOpts that specifies the vector register arrangement + * if one does not exist returns INS_OPTS_NONE + */ + +/*static*/ insOpts emitter::optMakeArrangement(emitAttr datasize, emitAttr elemsize) +{ + insOpts result = INS_OPTS_NONE; + + if (datasize == EA_8BYTE) + { + switch (elemsize) + { + case EA_1BYTE: + result = INS_OPTS_8B; + break; + case EA_2BYTE: + result = INS_OPTS_4H; + break; + case EA_4BYTE: + result = INS_OPTS_2S; + break; + case EA_8BYTE: + result = INS_OPTS_1D; + break; + default: + // TODO-Cleanup: add unreached() here + break; + } + } + else if (datasize == EA_16BYTE) + { + switch (elemsize) + { + case EA_1BYTE: + result = INS_OPTS_16B; + break; + case EA_2BYTE: + result = INS_OPTS_8H; + break; + case EA_4BYTE: + result = INS_OPTS_4S; + break; + case EA_8BYTE: + result = INS_OPTS_2D; + break; + default: + // TODO-Cleanup: add unreached() here + break; + } + } + return result; +} + +/***************************************************************************** + * + * For the given 'datasize' and arrangement 'opts' + * returns true is the pair spcifies a valid arrangement + */ +/*static*/ bool emitter::isValidArrangement(emitAttr datasize, insOpts opt) +{ + if (datasize == EA_8BYTE) + { + if ((opt == INS_OPTS_8B) || (opt == INS_OPTS_4H) || (opt == INS_OPTS_2S) || (opt == INS_OPTS_1D)) + { + return true; + } + } + else if (datasize == EA_16BYTE) + { + if ((opt == INS_OPTS_16B) || (opt == INS_OPTS_8H) || (opt == INS_OPTS_4S) || (opt == INS_OPTS_2D)) + { + return true; + } + } + return false; +} + +// For the given 'arrangement' returns the 'datasize' specified by the vector register arrangement +// asserts and returns EA_UNKNOWN if an invalid 'arrangement' value is passed +// +/*static*/ emitAttr emitter::optGetDatasize(insOpts arrangement) +{ + if ((arrangement == INS_OPTS_8B) || (arrangement == INS_OPTS_4H) || (arrangement == INS_OPTS_2S) || + (arrangement == INS_OPTS_1D)) + { + return EA_8BYTE; + } + else if ((arrangement == INS_OPTS_16B) || (arrangement == INS_OPTS_8H) || (arrangement == INS_OPTS_4S) || + (arrangement == INS_OPTS_2D)) + { + return EA_16BYTE; + } + else + { + assert(!" invalid 'arrangement' value"); + return EA_UNKNOWN; + } +} + +// For the given 'arrangement' returns the 'elemsize' specified by the vector register arrangement +// asserts and returns EA_UNKNOWN if an invalid 'arrangement' value is passed +// +/*static*/ emitAttr emitter::optGetElemsize(insOpts arrangement) +{ + if ((arrangement == INS_OPTS_8B) || (arrangement == INS_OPTS_16B)) + { + return EA_1BYTE; + } + else if ((arrangement == INS_OPTS_4H) || (arrangement == INS_OPTS_8H)) + { + return EA_2BYTE; + } + else if ((arrangement == INS_OPTS_2S) || (arrangement == INS_OPTS_4S)) + { + return EA_4BYTE; + } + else if ((arrangement == INS_OPTS_1D) || (arrangement == INS_OPTS_2D)) + { + return EA_8BYTE; + } + else + { + assert(!" invalid 'arrangement' value"); + return EA_UNKNOWN; + } +} + +// For the given 'arrangement' returns the 'widen-arrangement' specified by the vector register arrangement +// asserts and returns INS_OPTS_NONE if an invalid 'arrangement' value is passed +// +/*static*/ insOpts emitter::optWidenElemsize(insOpts arrangement) +{ + if ((arrangement == INS_OPTS_8B) || (arrangement == INS_OPTS_16B)) + { + return INS_OPTS_8H; + } + else if ((arrangement == INS_OPTS_4H) || (arrangement == INS_OPTS_8H)) + { + return INS_OPTS_4S; + } + else if ((arrangement == INS_OPTS_2S) || (arrangement == INS_OPTS_4S)) + { + return INS_OPTS_2D; + } + else + { + assert(!" invalid 'arrangement' value"); + return INS_OPTS_NONE; + } +} + +// For the given 'conversion' returns the 'dstsize' specified by the conversion option +/*static*/ emitAttr emitter::optGetDstsize(insOpts conversion) +{ + switch (conversion) + { + case INS_OPTS_S_TO_8BYTE: + case INS_OPTS_D_TO_8BYTE: + case INS_OPTS_4BYTE_TO_D: + case INS_OPTS_8BYTE_TO_D: + case INS_OPTS_S_TO_D: + case INS_OPTS_H_TO_D: + + return EA_8BYTE; + + case INS_OPTS_S_TO_4BYTE: + case INS_OPTS_D_TO_4BYTE: + case INS_OPTS_4BYTE_TO_S: + case INS_OPTS_8BYTE_TO_S: + case INS_OPTS_D_TO_S: + case INS_OPTS_H_TO_S: + + return EA_4BYTE; + + case INS_OPTS_S_TO_H: + case INS_OPTS_D_TO_H: + + return EA_2BYTE; + + default: + assert(!" invalid 'conversion' value"); + return EA_UNKNOWN; + } +} + +// For the given 'conversion' returns the 'srcsize' specified by the conversion option +/*static*/ emitAttr emitter::optGetSrcsize(insOpts conversion) +{ + switch (conversion) + { + case INS_OPTS_D_TO_8BYTE: + case INS_OPTS_D_TO_4BYTE: + case INS_OPTS_8BYTE_TO_D: + case INS_OPTS_8BYTE_TO_S: + case INS_OPTS_D_TO_S: + case INS_OPTS_D_TO_H: + + return EA_8BYTE; + + case INS_OPTS_S_TO_8BYTE: + case INS_OPTS_S_TO_4BYTE: + case INS_OPTS_4BYTE_TO_S: + case INS_OPTS_4BYTE_TO_D: + case INS_OPTS_S_TO_D: + case INS_OPTS_S_TO_H: + + return EA_4BYTE; + + case INS_OPTS_H_TO_S: + case INS_OPTS_H_TO_D: + + return EA_2BYTE; + + default: + assert(!" invalid 'conversion' value"); + return EA_UNKNOWN; + } +} + +// For the given 'size' and 'index' returns true if it specifies a valid index for a vector register of 'size' +/*static*/ bool emitter::isValidVectorIndex(emitAttr datasize, emitAttr elemsize, ssize_t index) +{ + assert(isValidVectorDatasize(datasize)); + assert(isValidVectorElemsize(elemsize)); + + bool result = false; + if (index >= 0) + { + if (datasize == EA_8BYTE) + { + switch (elemsize) + { + case EA_1BYTE: + result = (index < 8); + break; + case EA_2BYTE: + result = (index < 4); + break; + case EA_4BYTE: + result = (index < 2); + break; + case EA_8BYTE: + result = (index < 1); + break; + default: + // TODO-Cleanup: add unreached() here + break; + } + } + else if (datasize == EA_16BYTE) + { + switch (elemsize) + { + case EA_1BYTE: + result = (index < 16); + break; + case EA_2BYTE: + result = (index < 8); + break; + case EA_4BYTE: + result = (index < 4); + break; + case EA_8BYTE: + result = (index < 2); + break; + default: + // TODO-Cleanup: add unreached() here + break; + } + } + } + return result; +} + +/***************************************************************************** + * + * Add an instruction with no operands. + */ + +void emitter::emitIns(instruction ins) +{ + instrDesc* id = emitNewInstrSmall(EA_8BYTE); + insFormat fmt = emitInsFormat(ins); + + assert(fmt == IF_SN_0A); + + id->idIns(ins); + id->idInsFmt(fmt); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction with a single immediate value. + */ + +void emitter::emitIns_I(instruction ins, emitAttr attr, ssize_t imm) +{ + insFormat fmt = IF_NONE; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_brk: + if ((imm & 0x0000ffff) == imm) + { + fmt = IF_SI_0A; + } + else + { + assert(!"Instruction cannot be encoded: IF_SI_0A"); + } + break; + default: + // TODO-Cleanup: add unreached() here + break; + } + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSC(attr, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing a single register. + */ + +void emitter::emitIns_R(instruction ins, emitAttr attr, regNumber reg) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + instrDesc* id = nullptr; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_br: + case INS_ret: + assert(isGeneralRegister(reg)); + id = emitNewInstrSmall(attr); + id->idReg1(reg); + fmt = IF_BR_1A; + break; + + default: + unreached(); + } + + assert(fmt != IF_NONE); + + id->idIns(ins); + id->idInsFmt(fmt); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing a register and a constant. + */ + +void emitter::emitIns_R_I(instruction ins, emitAttr attr, regNumber reg, ssize_t imm, insOpts opt /* = INS_OPTS_NONE */) +{ + emitAttr size = EA_SIZE(attr); + emitAttr elemsize = EA_UNKNOWN; + insFormat fmt = IF_NONE; + bool canEncode = false; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + bitMaskImm bmi; + halfwordImm hwi; + byteShiftedImm bsi; + ssize_t notOfImm; + + case INS_tst: + assert(insOptsNone(opt)); + assert(isGeneralRegister(reg)); + bmi.immNRS = 0; + canEncode = canEncodeBitMaskImm(imm, size, &bmi); + if (canEncode) + { + imm = bmi.immNRS; + assert(isValidImmNRS(imm, size)); + fmt = IF_DI_1C; + } + break; + + case INS_movk: + case INS_movn: + case INS_movz: + assert(isValidGeneralDatasize(size)); + assert(insOptsNone(opt)); // No LSL here (you must use emitIns_R_I_I if a shift is needed) + assert(isGeneralRegister(reg)); + assert(isValidUimm16(imm)); + + hwi.immHW = 0; + hwi.immVal = imm; + assert(imm == emitDecodeHalfwordImm(hwi, size)); + + imm = hwi.immHWVal; + canEncode = true; + fmt = IF_DI_1B; + break; + + case INS_mov: + assert(isValidGeneralDatasize(size)); + assert(insOptsNone(opt)); // No explicit LSL here + // We will automatically determine the shift based upon the imm + + // First try the standard 'halfword immediate' imm(i16,hw) + hwi.immHWVal = 0; + canEncode = canEncodeHalfwordImm(imm, size, &hwi); + if (canEncode) + { + // uses a movz encoding + assert(isGeneralRegister(reg)); + imm = hwi.immHWVal; + assert(isValidImmHWVal(imm, size)); + fmt = IF_DI_1B; + break; + } + + // Next try the ones-complement form of 'halfword immediate' imm(i16,hw) + notOfImm = NOT_helper(imm, getBitWidth(size)); + canEncode = canEncodeHalfwordImm(notOfImm, size, &hwi); + if (canEncode) + { + assert(isGeneralRegister(reg)); + imm = hwi.immHWVal; + ins = INS_movn; // uses a movn encoding + assert(isValidImmHWVal(imm, size)); + fmt = IF_DI_1B; + break; + } + + // Finally try the 'bitmask immediate' imm(N,r,s) + bmi.immNRS = 0; + canEncode = canEncodeBitMaskImm(imm, size, &bmi); + if (canEncode) + { + assert(isGeneralRegisterOrSP(reg)); + reg = encodingSPtoZR(reg); + imm = bmi.immNRS; + assert(isValidImmNRS(imm, size)); + fmt = IF_DI_1D; + break; + } + else + { + assert(!"Instruction cannot be encoded: mov imm"); + } + + break; + + case INS_movi: + assert(isValidVectorDatasize(size)); + assert(isVectorRegister(reg)); + if (insOptsNone(opt) && (size == EA_8BYTE)) + { + opt = INS_OPTS_1D; + } + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + + if (elemsize == EA_8BYTE) + { + size_t uimm = imm; + ssize_t imm8 = 0; + unsigned pos = 0; + canEncode = true; + bool failed = false; + while (uimm != 0) + { + INT64 loByte = uimm & 0xFF; + if (((loByte == 0) || (loByte == 0xFF)) && (pos < 8)) + { + if (loByte == 0xFF) + { + imm8 |= (1 << pos); + } + uimm >>= 8; + pos++; + } + else + { + canEncode = false; + break; + } + } + imm = imm8; + assert(isValidUimm8(imm)); + fmt = IF_DV_1B; + break; + } + else + { + // Vector operation + + // No explicit LSL/MSL is used for the immediate + // We will automatically determine the shift based upon the value of imm + + // First try the standard 'byteShifted immediate' imm(i8,bySh) + bsi.immBSVal = 0; + canEncode = canEncodeByteShiftedImm(imm, elemsize, true, &bsi); + if (canEncode) + { + imm = bsi.immBSVal; + assert(isValidImmBSVal(imm, size)); + fmt = IF_DV_1B; + break; + } + + // Next try the ones-complement form of the 'immediate' imm(i8,bySh) + if ((elemsize == EA_2BYTE) || (elemsize == EA_4BYTE)) // Only EA_2BYTE or EA_4BYTE forms + { + notOfImm = NOT_helper(imm, getBitWidth(elemsize)); + canEncode = canEncodeByteShiftedImm(notOfImm, elemsize, true, &bsi); + if (canEncode) + { + imm = bsi.immBSVal; + ins = INS_mvni; // uses a mvni encoding + assert(isValidImmBSVal(imm, size)); + fmt = IF_DV_1B; + break; + } + } + } + break; + + case INS_orr: + case INS_bic: + case INS_mvni: + assert(isValidVectorDatasize(size)); + assert(isVectorRegister(reg)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert((elemsize == EA_2BYTE) || (elemsize == EA_4BYTE)); // Only EA_2BYTE or EA_4BYTE forms + + // Vector operation + + // No explicit LSL/MSL is used for the immediate + // We will automatically determine the shift based upon the value of imm + + // First try the standard 'byteShifted immediate' imm(i8,bySh) + bsi.immBSVal = 0; + canEncode = canEncodeByteShiftedImm(imm, elemsize, + (ins == INS_mvni), // mvni supports the ones shifting variant (aka MSL) + &bsi); + if (canEncode) + { + imm = bsi.immBSVal; + assert(isValidImmBSVal(imm, size)); + fmt = IF_DV_1B; + break; + } + break; + + case INS_cmp: + case INS_cmn: + assert(insOptsNone(opt)); + assert(isGeneralRegister(reg)); + + if (unsigned_abs(imm) <= 0x0fff) + { + if (imm < 0) + { + ins = insReverse(ins); + imm = -imm; + } + assert(isValidUimm12(imm)); + canEncode = true; + fmt = IF_DI_1A; + } + else if (canEncodeWithShiftImmBy12(imm)) // Try the shifted by 12 encoding + { + // Encoding will use a 12-bit left shift of the immediate + opt = INS_OPTS_LSL12; + if (imm < 0) + { + ins = insReverse(ins); + imm = -imm; + } + assert((imm & 0xfff) == 0); + imm >>= 12; + assert(isValidUimm12(imm)); + canEncode = true; + fmt = IF_DI_1A; + } + else + { + assert(!"Instruction cannot be encoded: IF_DI_1A"); + } + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(canEncode); + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSC(attr, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(opt); + + id->idReg1(reg); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing a register and a floating point constant. + */ + +void emitter::emitIns_R_F( + instruction ins, emitAttr attr, regNumber reg, double immDbl, insOpts opt /* = INS_OPTS_NONE */) + +{ + emitAttr size = EA_SIZE(attr); + emitAttr elemsize = EA_UNKNOWN; + insFormat fmt = IF_NONE; + ssize_t imm = 0; + bool canEncode = false; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + floatImm8 fpi; + + case INS_fcmp: + case INS_fcmpe: + assert(insOptsNone(opt)); + assert(isValidVectorElemsizeFloat(size)); + assert(isVectorRegister(reg)); + if (immDbl == 0.0) + { + canEncode = true; + fmt = IF_DV_1C; + } + break; + + case INS_fmov: + assert(isVectorRegister(reg)); + fpi.immFPIVal = 0; + canEncode = canEncodeFloatImm8(immDbl, &fpi); + + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(opt != INS_OPTS_1D); // Reserved encoding + + if (canEncode) + { + imm = fpi.immFPIVal; + assert((imm >= 0) && (imm <= 0xff)); + fmt = IF_DV_1B; + } + } + else + { + // Scalar operation + assert(insOptsNone(opt)); + assert(isValidVectorElemsizeFloat(size)); + + if (canEncode) + { + imm = fpi.immFPIVal; + assert((imm >= 0) && (imm <= 0xff)); + fmt = IF_DV_1A; + } + } + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(canEncode); + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSC(attr, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(opt); + + id->idReg1(reg); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing two registers + */ + +void emitter::emitIns_R_R( + instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, insOpts opt /* = INS_OPTS_NONE */) +{ + emitAttr size = EA_SIZE(attr); + emitAttr elemsize = EA_UNKNOWN; + insFormat fmt = IF_NONE; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_mov: + assert(insOptsNone(opt)); + // Is the mov even necessary? + if (reg1 == reg2) + { + // A mov with a EA_4BYTE has the side-effect of clearing the upper bits + // So only eliminate mov instructions that are not clearing the upper bits + // + if (isGeneralRegisterOrSP(reg1) && (size == EA_8BYTE)) + { + return; + } + else if (isVectorRegister(reg1) && (size == EA_16BYTE)) + { + return; + } + } + + // Check for the 'mov' aliases for the vector registers + if (isVectorRegister(reg1)) + { + if (isVectorRegister(reg2) && isValidVectorDatasize(size)) + { + return emitIns_R_R_R(INS_mov, size, reg1, reg2, reg2); + } + else + { + return emitIns_R_R_I(INS_mov, size, reg1, reg2, 0); + } + } + else + { + if (isVectorRegister(reg2)) + { + assert(isGeneralRegister(reg1)); + return emitIns_R_R_I(INS_mov, size, reg1, reg2, 0); + } + } + + // Is this a MOV to/from SP instruction? + if ((reg1 == REG_SP) || (reg2 == REG_SP)) + { + assert(isGeneralRegisterOrSP(reg1)); + assert(isGeneralRegisterOrSP(reg2)); + reg1 = encodingSPtoZR(reg1); + reg2 = encodingSPtoZR(reg2); + fmt = IF_DR_2G; + } + else + { + assert(insOptsNone(opt)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegisterOrZR(reg2)); + fmt = IF_DR_2E; + } + break; + + case INS_abs: + case INS_not: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + // for 'NOT' we can construct the arrangement: 8B or 16B + if ((ins == INS_not) && insOptsNone(opt)) + { + assert(isValidVectorDatasize(size)); + elemsize = EA_1BYTE; + opt = optMakeArrangement(size, elemsize); + } + if (insOptsNone(opt)) + { + // Scalar operation + assert(size == EA_8BYTE); // Only type D is supported + fmt = IF_DV_2L; + } + else + { + // Vector operation + assert(insOptsAnyArrangement(opt)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + if (ins == INS_not) + { + assert(elemsize == EA_1BYTE); + } + fmt = IF_DV_2M; + } + break; + + case INS_mvn: + case INS_neg: + if (isVectorRegister(reg1)) + { + assert(isVectorRegister(reg2)); + // for 'mvn' we can construct the arrangement: 8B or 16b + if ((ins == INS_mvn) && insOptsNone(opt)) + { + assert(isValidVectorDatasize(size)); + elemsize = EA_1BYTE; + opt = optMakeArrangement(size, elemsize); + } + if (insOptsNone(opt)) + { + // Scalar operation + assert(size == EA_8BYTE); // Only type D is supported + fmt = IF_DV_2L; + } + else + { + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + if (ins == INS_mvn) + { + assert(elemsize == EA_1BYTE); // Only supports 8B or 16B + } + fmt = IF_DV_2M; + } + break; + } + __fallthrough; + + case INS_negs: + assert(insOptsNone(opt)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegisterOrZR(reg2)); + fmt = IF_DR_2E; + break; + + case INS_sxtw: + assert(size == EA_8BYTE); + __fallthrough; + + case INS_sxtb: + case INS_sxth: + case INS_uxtb: + case INS_uxth: + assert(insOptsNone(opt)); + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + fmt = IF_DR_2H; + break; + + case INS_sxtl: + case INS_sxtl2: + case INS_uxtl: + case INS_uxtl2: + return emitIns_R_R_I(ins, size, reg1, reg2, 0, opt); + + case INS_cls: + case INS_clz: + case INS_rbit: + case INS_rev16: + case INS_rev32: + case INS_cnt: + if (isVectorRegister(reg1)) + { + assert(isVectorRegister(reg2)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + if ((ins == INS_cls) || (ins == INS_clz)) + { + assert(elemsize != EA_8BYTE); // No encoding for type D + } + else if (ins == INS_rev32) + { + assert((elemsize == EA_2BYTE) || (elemsize == EA_1BYTE)); + } + else + { + assert(elemsize == EA_1BYTE); // Only supports 8B or 16B + } + fmt = IF_DV_2M; + break; + } + if (ins == INS_cnt) + { + // Doesn't have general register version(s) + break; + } + + __fallthrough; + + case INS_rev: + assert(insOptsNone(opt)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + if (ins == INS_rev32) + { + assert(size == EA_8BYTE); + } + else + { + assert(isValidGeneralDatasize(size)); + } + fmt = IF_DR_2G; + break; + + case INS_rev64: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(elemsize != EA_8BYTE); // No encoding for type D + fmt = IF_DV_2M; + break; + + case INS_ldr: + case INS_ldrb: + case INS_ldrh: + case INS_ldrsb: + case INS_ldrsh: + case INS_ldrsw: + case INS_str: + case INS_strb: + case INS_strh: + + case INS_cmp: + case INS_cmn: + case INS_tst: + assert(insOptsNone(opt)); + emitIns_R_R_I(ins, attr, reg1, reg2, 0, INS_OPTS_NONE); + return; + + case INS_fmov: + assert(isValidVectorElemsizeFloat(size)); + + // Is the mov even necessary? + if (reg1 == reg2) + { + return; + } + + if (isVectorRegister(reg1)) + { + if (isVectorRegister(reg2)) + { + assert(insOptsNone(opt)); + fmt = IF_DV_2G; + } + else + { + assert(isGeneralRegister(reg2)); + + // if the optional conversion specifier is not present we calculate it + if (opt == INS_OPTS_NONE) + { + opt = (size == EA_4BYTE) ? INS_OPTS_4BYTE_TO_S : INS_OPTS_8BYTE_TO_D; + } + assert(insOptsConvertIntToFloat(opt)); + + fmt = IF_DV_2I; + } + } + else + { + assert(isGeneralRegister(reg1)); + assert(isVectorRegister(reg2)); + + // if the optional conversion specifier is not present we calculate it + if (opt == INS_OPTS_NONE) + { + opt = (size == EA_4BYTE) ? INS_OPTS_S_TO_4BYTE : INS_OPTS_D_TO_8BYTE; + } + assert(insOptsConvertFloatToInt(opt)); + + fmt = IF_DV_2H; + } + break; + + case INS_fcmp: + case INS_fcmpe: + assert(insOptsNone(opt)); + assert(isValidVectorElemsizeFloat(size)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + fmt = IF_DV_2K; + break; + + case INS_fcvtns: + case INS_fcvtnu: + case INS_fcvtas: + case INS_fcvtau: + case INS_fcvtps: + case INS_fcvtpu: + case INS_fcvtms: + case INS_fcvtmu: + case INS_fcvtzs: + case INS_fcvtzu: + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_2A; + } + else + { + // Scalar operation + assert(isVectorRegister(reg2)); + if (isVectorRegister(reg1)) + { + assert(insOptsNone(opt)); + assert(isValidVectorElemsizeFloat(size)); + fmt = IF_DV_2G; + } + else + { + assert(isGeneralRegister(reg1)); + assert(insOptsConvertFloatToInt(opt)); + assert(isValidVectorElemsizeFloat(size)); + fmt = IF_DV_2H; + } + } + break; + + case INS_scvtf: + case INS_ucvtf: + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_2A; + } + else + { + // Scalar operation + assert(isVectorRegister(reg1)); + if (isVectorRegister(reg2)) + { + assert(insOptsNone(opt)); + assert(isValidVectorElemsizeFloat(size)); + fmt = IF_DV_2G; + } + else + { + assert(isGeneralRegister(reg2)); + assert(insOptsConvertIntToFloat(opt)); + assert(isValidVectorElemsizeFloat(size)); + fmt = IF_DV_2I; + } + } + break; + + case INS_fabs: + case INS_fneg: + case INS_fsqrt: + case INS_frinta: + case INS_frinti: + case INS_frintm: + case INS_frintn: + case INS_frintp: + case INS_frintx: + case INS_frintz: + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_2A; + } + else + { + // Scalar operation + assert(insOptsNone(opt)); + assert(isValidVectorElemsizeFloat(size)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + fmt = IF_DV_2G; + } + break; + + case INS_fcvt: + assert(insOptsConvertFloatToFloat(opt)); + assert(isValidVectorFcvtsize(size)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + fmt = IF_DV_2J; + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSmall(attr); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(opt); + + id->idReg1(reg1); + id->idReg2(reg2); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing a register and two constants. + */ + +void emitter::emitIns_R_I_I( + instruction ins, emitAttr attr, regNumber reg, ssize_t imm1, ssize_t imm2, insOpts opt /* = INS_OPTS_NONE */) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + size_t immOut = 0; // composed from imm1 and imm2 and stored in the instrDesc + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + bool canEncode; + halfwordImm hwi; + + case INS_mov: + ins = INS_movz; // INS_mov with LSL is an alias for INS_movz LSL + __fallthrough; + + case INS_movk: + case INS_movn: + case INS_movz: + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg)); + assert(isValidUimm16(imm1)); + assert(insOptsLSL(opt)); // Must be INS_OPTS_LSL + + if (size == EA_8BYTE) + { + assert((imm2 == 0) || (imm2 == 16) || // shift amount: 0, 16, 32 or 48 + (imm2 == 32) || (imm2 == 48)); + } + else // EA_4BYTE + { + assert((imm2 == 0) || (imm2 == 16)); // shift amount: 0 or 16 + } + + hwi.immHWVal = 0; + + switch (imm2) + { + case 0: + hwi.immHW = 0; + canEncode = true; + break; + + case 16: + hwi.immHW = 1; + canEncode = true; + break; + + case 32: + hwi.immHW = 2; + canEncode = true; + break; + + case 48: + hwi.immHW = 3; + canEncode = true; + break; + + default: + canEncode = false; + } + + if (canEncode) + { + hwi.immVal = imm1; + + immOut = hwi.immHWVal; + assert(isValidImmHWVal(immOut, size)); + fmt = IF_DI_1B; + } + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSC(attr, immOut); + + id->idIns(ins); + id->idInsFmt(fmt); + + id->idReg1(reg); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing two registers and a constant. + */ + +void emitter::emitIns_R_R_I( + instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, ssize_t imm, insOpts opt /* = INS_OPTS_NONE */) +{ + emitAttr size = EA_SIZE(attr); + emitAttr elemsize = EA_UNKNOWN; + insFormat fmt = IF_NONE; + bool isLdSt = false; + bool isSIMD = false; + bool isAddSub = false; + bool setFlags = false; + unsigned scale = 0; + bool unscaledOp = false; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + bool canEncode; + bitMaskImm bmi; + + case INS_mov: + // Check for the 'mov' aliases for the vector registers + assert(insOptsNone(opt)); + assert(isValidVectorElemsize(size)); + elemsize = size; + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + + if (isVectorRegister(reg1)) + { + if (isGeneralRegisterOrZR(reg2)) + { + fmt = IF_DV_2C; // Alias for 'ins' + break; + } + else if (isVectorRegister(reg2)) + { + fmt = IF_DV_2E; // Alias for 'dup' + break; + } + } + else // isGeneralRegister(reg1) + { + assert(isGeneralRegister(reg1)); + if (isVectorRegister(reg2)) + { + fmt = IF_DV_2B; // Alias for 'umov' + break; + } + } + assert(!" invalid INS_mov operands"); + break; + + case INS_lsl: + case INS_lsr: + case INS_asr: + assert(insOptsNone(opt)); + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isValidImmShift(imm, size)); + fmt = IF_DI_2D; + break; + + case INS_ror: + assert(insOptsNone(opt)); + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isValidImmShift(imm, size)); + fmt = IF_DI_2B; + break; + + case INS_sshr: + case INS_ssra: + case INS_srshr: + case INS_srsra: + case INS_shl: + case INS_ushr: + case INS_usra: + case INS_urshr: + case INS_ursra: + case INS_sri: + case INS_sli: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsize(elemsize)); + assert(isValidImmShift(imm, elemsize)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_2O; + break; + } + else + { + // Scalar operation + assert(insOptsNone(opt)); + assert(size == EA_8BYTE); // only supported size + assert(isValidImmShift(imm, size)); + fmt = IF_DV_2N; + } + break; + + case INS_sxtl: + case INS_uxtl: + assert(imm == 0); + __fallthrough; + + case INS_shrn: + case INS_rshrn: + case INS_sshll: + case INS_ushll: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + // Vector operation + assert(size == EA_8BYTE); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(elemsize != EA_8BYTE); // Reserved encodings + assert(isValidVectorElemsize(elemsize)); + assert(isValidImmShift(imm, elemsize)); + fmt = IF_DV_2O; + break; + + case INS_sxtl2: + case INS_uxtl2: + assert(imm == 0); + __fallthrough; + + case INS_shrn2: + case INS_rshrn2: + case INS_sshll2: + case INS_ushll2: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + // Vector operation + assert(size == EA_16BYTE); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(elemsize != EA_8BYTE); // Reserved encodings + assert(isValidVectorElemsize(elemsize)); + assert(isValidImmShift(imm, elemsize)); + fmt = IF_DV_2O; + break; + + case INS_mvn: + case INS_neg: + case INS_negs: + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegisterOrZR(reg2)); + + if (imm == 0) + { + assert(insOptsNone(opt)); // a zero imm, means no alu shift kind + + fmt = IF_DR_2E; + } + else + { + if (ins == INS_mvn) + { + assert(insOptsAnyShift(opt)); // a non-zero imm, must select shift kind + } + else // neg or negs + { + assert(insOptsAluShift(opt)); // a non-zero imm, must select shift kind, can't use ROR + } + assert(isValidImmShift(imm, size)); + fmt = IF_DR_2F; + } + break; + + case INS_tst: + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegisterOrZR(reg1)); + assert(isGeneralRegister(reg2)); + + if (insOptsAnyShift(opt)) + { + assert(isValidImmShift(imm, size) && (imm != 0)); + fmt = IF_DR_2B; + } + else + { + assert(insOptsNone(opt)); // a zero imm, means no alu shift kind + assert(imm == 0); + fmt = IF_DR_2A; + } + break; + + case INS_cmp: + case INS_cmn: + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegisterOrSP(reg1)); + assert(isGeneralRegister(reg2)); + + reg1 = encodingSPtoZR(reg1); + if (insOptsAnyExtend(opt)) + { + assert((imm >= 0) && (imm <= 4)); + + fmt = IF_DR_2C; + } + else if (imm == 0) + { + assert(insOptsNone(opt)); // a zero imm, means no alu shift kind + + fmt = IF_DR_2A; + } + else + { + assert(insOptsAnyShift(opt)); // a non-zero imm, must select shift kind + assert(isValidImmShift(imm, size)); + fmt = IF_DR_2B; + } + break; + + case INS_ands: + case INS_and: + case INS_eor: + case INS_orr: + assert(insOptsNone(opt)); + assert(isGeneralRegister(reg2)); + if (ins == INS_ands) + { + assert(isGeneralRegister(reg1)); + } + else + { + assert(isGeneralRegisterOrSP(reg1)); + reg1 = encodingSPtoZR(reg1); + } + + bmi.immNRS = 0; + canEncode = canEncodeBitMaskImm(imm, size, &bmi); + if (canEncode) + { + imm = bmi.immNRS; + assert(isValidImmNRS(imm, size)); + fmt = IF_DI_2C; + } + break; + + case INS_dup: // by element, imm selects the element of reg2 + assert(isVectorRegister(reg1)); + if (isVectorRegister(reg2)) + { + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsize(elemsize)); + assert(isValidVectorIndex(size, elemsize, imm)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_2D; + break; + } + else + { + // Scalar operation + assert(insOptsNone(opt)); + elemsize = size; + assert(isValidVectorElemsize(elemsize)); + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + fmt = IF_DV_2E; + break; + } + } + __fallthrough; + + case INS_ins: // (MOV from general) + assert(insOptsNone(opt)); + assert(isValidVectorElemsize(size)); + assert(isVectorRegister(reg1)); + assert(isGeneralRegisterOrZR(reg2)); + elemsize = size; + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + fmt = IF_DV_2C; + break; + + case INS_umov: // (MOV to general) + assert(insOptsNone(opt)); + assert(isValidVectorElemsize(size)); + assert(isGeneralRegister(reg1)); + assert(isVectorRegister(reg2)); + elemsize = size; + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + fmt = IF_DV_2B; + break; + + case INS_smov: + assert(insOptsNone(opt)); + assert(isValidVectorElemsize(size)); + assert(size != EA_8BYTE); // no encoding, use INS_umov + assert(isGeneralRegister(reg1)); + assert(isVectorRegister(reg2)); + elemsize = size; + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + fmt = IF_DV_2B; + break; + + case INS_add: + case INS_sub: + setFlags = false; + isAddSub = true; + break; + + case INS_adds: + case INS_subs: + setFlags = true; + isAddSub = true; + break; + + case INS_ldrsb: + case INS_ldursb: + // 'size' specifies how we sign-extend into 4 or 8 bytes of the target register + assert(isValidGeneralDatasize(size)); + unscaledOp = (ins == INS_ldursb); + scale = 0; + isLdSt = true; + break; + + case INS_ldrsh: + case INS_ldursh: + // 'size' specifies how we sign-extend into 4 or 8 bytes of the target register + assert(isValidGeneralDatasize(size)); + unscaledOp = (ins == INS_ldursh); + scale = 1; + isLdSt = true; + break; + + case INS_ldrsw: + case INS_ldursw: + // 'size' specifies how we sign-extend into 4 or 8 bytes of the target register + assert(size == EA_8BYTE); + unscaledOp = (ins == INS_ldursw); + scale = 2; + isLdSt = true; + break; + + case INS_ldrb: + case INS_strb: + // size is ignored + unscaledOp = false; + scale = 0; + isLdSt = true; + break; + + case INS_ldurb: + case INS_sturb: + // size is ignored + unscaledOp = true; + scale = 0; + isLdSt = true; + break; + + case INS_ldrh: + case INS_strh: + // size is ignored + unscaledOp = false; + scale = 1; + isLdSt = true; + break; + + case INS_ldurh: + case INS_sturh: + // size is ignored + unscaledOp = true; + scale = 0; + isLdSt = true; + break; + + case INS_ldr: + case INS_str: + // Is the target a vector register? + if (isVectorRegister(reg1)) + { + assert(isValidVectorLSDatasize(size)); + assert(isGeneralRegisterOrSP(reg2)); + isSIMD = true; + } + else + { + assert(isValidGeneralDatasize(size)); + } + unscaledOp = false; + scale = NaturalScale_helper(size); + isLdSt = true; + break; + + case INS_ldur: + case INS_stur: + // Is the target a vector register? + if (isVectorRegister(reg1)) + { + assert(isValidVectorLSDatasize(size)); + assert(isGeneralRegisterOrSP(reg2)); + isSIMD = true; + } + else + { + assert(isValidGeneralDatasize(size)); + } + unscaledOp = true; + scale = 0; + isLdSt = true; + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + if (isLdSt) + { + assert(!isAddSub); + + if (isSIMD) + { + assert(isValidVectorLSDatasize(size)); + assert(isVectorRegister(reg1)); + assert((scale >= 0) && (scale <= 4)); + } + else + { + assert(isValidGeneralLSDatasize(size)); + assert(isGeneralRegisterOrZR(reg1)); + assert((scale >= 0) && (scale <= 3)); + } + + assert(isGeneralRegisterOrSP(reg2)); + + // Load/Store reserved encodings: + if (insOptsIndexed(opt)) + { + assert(reg1 != reg2); + } + + reg2 = encodingSPtoZR(reg2); + + ssize_t mask = (1 << scale) - 1; // the mask of low bits that must be zero to encode the immediate + if (imm == 0) + { + assert(insOptsNone(opt)); // PRE/POST Index doesn't make sense with an immediate of zero + + fmt = IF_LS_2A; + } + else if (insOptsIndexed(opt) || unscaledOp || (imm < 0) || ((imm & mask) != 0)) + { + if ((imm >= -256) && (imm <= 255)) + { + fmt = IF_LS_2C; + } + else + { + assert(!"Instruction cannot be encoded: IF_LS_2C"); + } + } + else if (imm > 0) + { + assert(insOptsNone(opt)); + assert(!unscaledOp); + + if (((imm & mask) == 0) && ((imm >> scale) < 0x1000)) + { + imm >>= scale; // The immediate is scaled by the size of the ld/st + + fmt = IF_LS_2B; + } + else + { + assert(!"Instruction cannot be encoded: IF_LS_2B"); + } + } + } + else if (isAddSub) + { + assert(!isLdSt); + assert(insOptsNone(opt)); + + if (setFlags) // Can't encode SP with setFlags + { + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + } + else + { + assert(isGeneralRegisterOrSP(reg1)); + assert(isGeneralRegisterOrSP(reg2)); + + // Is it just a mov? + if (imm == 0) + { + // Is the mov even necessary? + if (reg1 != reg2) + { + emitIns_R_R(INS_mov, attr, reg1, reg2); + } + return; + } + + reg1 = encodingSPtoZR(reg1); + reg2 = encodingSPtoZR(reg2); + } + + if (unsigned_abs(imm) <= 0x0fff) + { + if (imm < 0) + { + ins = insReverse(ins); + imm = -imm; + } + assert(isValidUimm12(imm)); + fmt = IF_DI_2A; + } + else if (canEncodeWithShiftImmBy12(imm)) // Try the shifted by 12 encoding + { + // Encoding will use a 12-bit left shift of the immediate + opt = INS_OPTS_LSL12; + if (imm < 0) + { + ins = insReverse(ins); + imm = -imm; + } + assert((imm & 0xfff) == 0); + imm >>= 12; + assert(isValidUimm12(imm)); + fmt = IF_DI_2A; + } + else + { + assert(!"Instruction cannot be encoded: IF_DI_2A"); + } + } + + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSC(attr, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(opt); + + id->idReg1(reg1); + id->idReg2(reg2); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** +* +* Add an instruction referencing two registers and a constant. +* Also checks for a large immediate that needs a second instruction +* and will load it in reg1 +* +* - Supports instructions: add, adds, sub, subs, and, ands, eor and orr +* - Requires that reg1 is a general register and not SP or ZR +* - Requires that reg1 != reg2 +*/ +void emitter::emitIns_R_R_Imm(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, ssize_t imm) +{ + assert(isGeneralRegister(reg1)); + assert(reg1 != reg2); + + bool immFits = true; + + switch (ins) + { + case INS_add: + case INS_adds: + case INS_sub: + case INS_subs: + immFits = emitter::emitIns_valid_imm_for_add(imm, attr); + break; + + case INS_ands: + case INS_and: + case INS_eor: + case INS_orr: + immFits = emitter::emitIns_valid_imm_for_alu(imm, attr); + break; + + default: + assert(!"Unsupported instruction in emitIns_R_R_Imm"); + } + + if (immFits) + { + emitIns_R_R_I(ins, attr, reg1, reg2, imm); + } + else + { + // Load 'imm' into the reg1 register + // then issue: 'ins' reg1, reg2, reg1 + // + codeGen->instGen_Set_Reg_To_Imm(attr, reg1, imm); + emitIns_R_R_R(ins, attr, reg1, reg2, reg1); + } +} + +/***************************************************************************** + * + * Add an instruction referencing three registers. + */ + +void emitter::emitIns_R_R_R( + instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, regNumber reg3, insOpts opt) /* = INS_OPTS_NONE */ +{ + emitAttr size = EA_SIZE(attr); + emitAttr elemsize = EA_UNKNOWN; + insFormat fmt = IF_NONE; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_lsl: + case INS_lsr: + case INS_asr: + case INS_ror: + case INS_adc: + case INS_adcs: + case INS_sbc: + case INS_sbcs: + case INS_udiv: + case INS_sdiv: + case INS_mneg: + case INS_smull: + case INS_smnegl: + case INS_smulh: + case INS_umull: + case INS_umnegl: + case INS_umulh: + case INS_lslv: + case INS_lsrv: + case INS_asrv: + case INS_rorv: + assert(insOptsNone(opt)); + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isGeneralRegister(reg3)); + fmt = IF_DR_3A; + break; + + case INS_mul: + if (insOptsNone(opt)) + { + // general register + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isGeneralRegister(reg3)); + fmt = IF_DR_3A; + break; + } + __fallthrough; + + case INS_mla: + case INS_mls: + case INS_pmul: + assert(insOptsAnyArrangement(opt)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + if (ins == INS_pmul) + { + assert(elemsize == EA_1BYTE); // only supports 8B or 16B + } + else // INS_mul, INS_mla, INS_mls + { + assert(elemsize != EA_8BYTE); // can't use 2D or 1D + } + fmt = IF_DV_3A; + break; + + case INS_add: + case INS_sub: + if (isVectorRegister(reg1)) + { + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(opt != INS_OPTS_1D); // Reserved encoding + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + fmt = IF_DV_3A; + } + else + { + // Scalar operation + assert(insOptsNone(opt)); + assert(size == EA_8BYTE); + fmt = IF_DV_3E; + } + break; + } + __fallthrough; + + case INS_adds: + case INS_subs: + emitIns_R_R_R_I(ins, attr, reg1, reg2, reg3, 0, INS_OPTS_NONE); + return; + + case INS_saba: + case INS_sabd: + case INS_uaba: + case INS_uabd: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + assert(insOptsAnyArrangement(opt)); + + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(elemsize != EA_8BYTE); // can't use 2D or 1D + + fmt = IF_DV_3A; + break; + + case INS_mov: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(reg2 == reg3); + assert(isValidVectorDatasize(size)); + // INS_mov is an alias for INS_orr (vector register) + if (opt == INS_OPTS_NONE) + { + elemsize = EA_1BYTE; + opt = optMakeArrangement(size, elemsize); + } + assert(isValidArrangement(size, opt)); + fmt = IF_DV_3C; + break; + + case INS_and: + case INS_bic: + case INS_eor: + case INS_orr: + case INS_orn: + if (isVectorRegister(reg1)) + { + assert(isValidVectorDatasize(size)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + if (opt == INS_OPTS_NONE) + { + elemsize = EA_1BYTE; + opt = optMakeArrangement(size, elemsize); + } + assert(isValidArrangement(size, opt)); + fmt = IF_DV_3C; + break; + } + __fallthrough; + + case INS_ands: + case INS_bics: + case INS_eon: + emitIns_R_R_R_I(ins, attr, reg1, reg2, reg3, 0, INS_OPTS_NONE); + return; + + case INS_bsl: + case INS_bit: + case INS_bif: + assert(isValidVectorDatasize(size)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + if (opt == INS_OPTS_NONE) + { + elemsize = EA_1BYTE; + opt = optMakeArrangement(size, elemsize); + } + assert(isValidArrangement(size, opt)); + fmt = IF_DV_3C; + break; + + case INS_fadd: + case INS_fsub: + case INS_fdiv: + case INS_fmax: + case INS_fmin: + case INS_fabd: + case INS_fmul: + case INS_fmulx: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_3B; + } + else + { + // Scalar operation + assert(insOptsNone(opt)); + assert(isValidScalarDatasize(size)); + fmt = IF_DV_3D; + } + break; + + case INS_fnmul: + // Scalar operation + assert(insOptsNone(opt)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + assert(isValidScalarDatasize(size)); + fmt = IF_DV_3D; + break; + + case INS_fmla: + case INS_fmls: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + assert(insOptsAnyArrangement(opt)); // no scalar encoding, use 4-operand 'fmadd' or 'fmsub' + + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_3B; + break; + + case INS_ldr: + case INS_ldrb: + case INS_ldrh: + case INS_ldrsb: + case INS_ldrsh: + case INS_ldrsw: + case INS_str: + case INS_strb: + case INS_strh: + emitIns_R_R_R_Ext(ins, attr, reg1, reg2, reg3, opt); + return; + + case INS_ldp: + case INS_ldpsw: + case INS_ldnp: + case INS_stp: + case INS_stnp: + emitIns_R_R_R_I(ins, attr, reg1, reg2, reg3, 0); + return; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstr(attr); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(opt); + + id->idReg1(reg1); + id->idReg2(reg2); + id->idReg3(reg3); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing three registers and a constant. + */ + +void emitter::emitIns_R_R_R_I(instruction ins, + emitAttr attr, + regNumber reg1, + regNumber reg2, + regNumber reg3, + ssize_t imm, + insOpts opt /* = INS_OPTS_NONE */) +{ + emitAttr size = EA_SIZE(attr); + emitAttr elemsize = EA_UNKNOWN; + insFormat fmt = IF_NONE; + bool isLdSt = false; + bool isSIMD = false; + bool isAddSub = false; + bool setFlags = false; + unsigned scale = 0; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_extr: + assert(insOptsNone(opt)); + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isGeneralRegister(reg3)); + assert(isValidImmShift(imm, size)); + fmt = IF_DR_3E; + break; + + case INS_and: + case INS_ands: + case INS_eor: + case INS_orr: + case INS_bic: + case INS_bics: + case INS_eon: + case INS_orn: + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isGeneralRegister(reg3)); + assert(isValidImmShift(imm, size)); + if (imm == 0) + { + assert(insOptsNone(opt)); // a zero imm, means no shift kind + fmt = IF_DR_3A; + } + else + { + assert(insOptsAnyShift(opt)); // a non-zero imm, must select shift kind + fmt = IF_DR_3B; + } + break; + + case INS_fmul: // by element, imm[0..3] selects the element of reg3 + case INS_fmla: + case INS_fmls: + case INS_fmulx: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + if (insOptsAnyArrangement(opt)) + { + // Vector operation + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorElemsizeFloat(elemsize)); + assert(isValidVectorIndex(size, elemsize, imm)); + assert(opt != INS_OPTS_1D); // Reserved encoding + fmt = IF_DV_3BI; + } + else + { + // Scalar operation + assert(insOptsNone(opt)); + assert(isValidScalarDatasize(size)); + elemsize = size; + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + fmt = IF_DV_3DI; + } + break; + + case INS_mul: // by element, imm[0..7] selects the element of reg3 + case INS_mla: + case INS_mls: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + // Vector operation + assert(insOptsAnyArrangement(opt)); + assert(isValidVectorDatasize(size)); + assert(isValidArrangement(size, opt)); + elemsize = optGetElemsize(opt); + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + // Only has encodings for H or S elemsize + assert((elemsize == EA_2BYTE) || (elemsize == EA_4BYTE)); + // Only has encodings for V0..V15 + if ((elemsize == EA_2BYTE) && (reg3 >= REG_V16)) + { + noway_assert(!"Invalid reg3"); + } + fmt = IF_DV_3AI; + break; + + case INS_add: + case INS_sub: + setFlags = false; + isAddSub = true; + break; + + case INS_adds: + case INS_subs: + setFlags = true; + isAddSub = true; + break; + + case INS_ldpsw: + scale = 2; + isLdSt = true; + break; + + case INS_ldnp: + case INS_stnp: + assert(insOptsNone(opt)); // Can't use Pre/Post index on these two instructions + __fallthrough; + + case INS_ldp: + case INS_stp: + // Is the target a vector register? + if (isVectorRegister(reg1)) + { + scale = NaturalScale_helper(size); + isSIMD = true; + } + else + { + scale = (size == EA_8BYTE) ? 3 : 2; + } + isLdSt = true; + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + if (isLdSt) + { + assert(!isAddSub); + assert(isGeneralRegisterOrSP(reg3)); + assert(insOptsNone(opt) || insOptsIndexed(opt)); + + if (isSIMD) + { + assert(isValidVectorLSPDatasize(size)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert((scale >= 2) && (scale <= 4)); + } + else + { + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegisterOrZR(reg1)); + assert(isGeneralRegisterOrZR(reg2)); + assert((scale == 2) || (scale == 3)); + } + + // Load/Store Pair reserved encodings: + if (emitInsIsLoad(ins)) + { + assert(reg1 != reg2); + } + if (insOptsIndexed(opt)) + { + assert(reg1 != reg3); + assert(reg2 != reg3); + } + + reg3 = encodingSPtoZR(reg3); + + ssize_t mask = (1 << scale) - 1; // the mask of low bits that must be zero to encode the immediate + if (imm == 0) + { + assert(insOptsNone(opt)); // PRE/POST Index doesn't make sense with an immediate of zero + + fmt = IF_LS_3B; + } + else + { + if ((imm & mask) == 0) + { + imm >>= scale; // The immediate is scaled by the size of the ld/st + + if ((imm >= -64) && (imm <= 63)) + { + fmt = IF_LS_3C; + } + } +#ifdef DEBUG + if (fmt != IF_LS_3C) + { + assert(!"Instruction cannot be encoded: IF_LS_3C"); + } +#endif + } + } + else if (isAddSub) + { + bool reg2IsSP = (reg2 == REG_SP); + assert(!isLdSt); + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg3)); + + if (setFlags || insOptsAluShift(opt)) // Can't encode SP in reg1 with setFlags or AluShift option + { + assert(isGeneralRegisterOrZR(reg1)); + } + else + { + assert(isGeneralRegisterOrSP(reg1)); + reg1 = encodingSPtoZR(reg1); + } + + if (insOptsAluShift(opt)) // Can't encode SP in reg2 with AluShift option + { + assert(isGeneralRegister(reg2)); + } + else + { + assert(isGeneralRegisterOrSP(reg2)); + reg2 = encodingSPtoZR(reg2); + } + + if (insOptsAnyExtend(opt)) + { + assert((imm >= 0) && (imm <= 4)); + + fmt = IF_DR_3C; + } + else if (insOptsAluShift(opt)) + { + // imm should be non-zero and in [1..63] + assert(isValidImmShift(imm, size) && (imm != 0)); + fmt = IF_DR_3B; + } + else if (imm == 0) + { + assert(insOptsNone(opt)); + + if (reg2IsSP) + { + // To encode the SP register as reg2 we must use the IF_DR_3C encoding + // and also specify a LSL of zero (imm == 0) + opt = INS_OPTS_LSL; + fmt = IF_DR_3C; + } + else + { + fmt = IF_DR_3A; + } + } + else + { + assert(!"Instruction cannot be encoded: Add/Sub IF_DR_3A"); + } + } + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrCns(attr, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(opt); + + id->idReg1(reg1); + id->idReg2(reg2); + id->idReg3(reg3); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing three registers, with an extend option + */ + +void emitter::emitIns_R_R_R_Ext(instruction ins, + emitAttr attr, + regNumber reg1, + regNumber reg2, + regNumber reg3, + insOpts opt, /* = INS_OPTS_NONE */ + int shiftAmount) /* = -1 -- unset */ +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + bool isSIMD = false; + int scale = -1; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_ldrb: + case INS_ldrsb: + case INS_strb: + scale = 0; + break; + + case INS_ldrh: + case INS_ldrsh: + case INS_strh: + scale = 1; + break; + + case INS_ldrsw: + scale = 2; + break; + + case INS_ldr: + case INS_str: + // Is the target a vector register? + if (isVectorRegister(reg1)) + { + assert(isValidVectorLSDatasize(size)); + scale = NaturalScale_helper(size); + isSIMD = true; + } + else + { + assert(isValidGeneralDatasize(size)); + scale = (size == EA_8BYTE) ? 3 : 2; + } + + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(scale != -1); + assert(insOptsLSExtend(opt)); + + if (isSIMD) + { + assert(isValidVectorLSDatasize(size)); + assert(isVectorRegister(reg1)); + } + else + { + assert(isValidGeneralLSDatasize(size)); + assert(isGeneralRegisterOrZR(reg1)); + } + + assert(isGeneralRegisterOrSP(reg2)); + assert(isGeneralRegister(reg3)); + + // Load/Store reserved encodings: + if (insOptsIndexed(opt)) + { + assert(reg1 != reg2); + } + + if (shiftAmount == -1) + { + shiftAmount = insOptsLSL(opt) ? scale : 0; + } + assert((shiftAmount == scale) || (shiftAmount == 0)); + + reg2 = encodingSPtoZR(reg2); + fmt = IF_LS_3A; + + instrDesc* id = emitNewInstr(attr); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(opt); + + id->idReg1(reg1); + id->idReg2(reg2); + id->idReg3(reg3); + id->idReg3Scaled(shiftAmount == scale); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing two registers and two constants. + */ + +void emitter::emitIns_R_R_I_I(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, int imm1, int imm2) +{ + emitAttr size = EA_SIZE(attr); + emitAttr elemsize = EA_UNKNOWN; + insFormat fmt = IF_NONE; + size_t immOut = 0; // composed from imm1 and imm2 and stored in the instrDesc + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + int lsb; + int width; + bitMaskImm bmi; + + case INS_bfm: + case INS_sbfm: + case INS_ubfm: + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isValidImmShift(imm1, size)); + assert(isValidImmShift(imm2, size)); + bmi.immNRS = 0; + bmi.immN = (size == EA_8BYTE); + bmi.immR = imm1; + bmi.immS = imm2; + immOut = bmi.immNRS; + fmt = IF_DI_2D; + break; + + case INS_bfi: + case INS_sbfiz: + case INS_ubfiz: + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + lsb = getBitWidth(size) - imm1; + width = imm2 - 1; + assert(isValidImmShift(lsb, size)); + assert(isValidImmShift(width, size)); + bmi.immNRS = 0; + bmi.immN = (size == EA_8BYTE); + bmi.immR = lsb; + bmi.immS = width; + immOut = bmi.immNRS; + fmt = IF_DI_2D; + break; + + case INS_bfxil: + case INS_sbfx: + case INS_ubfx: + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + lsb = imm1; + width = imm2 + imm1 - 1; + assert(isValidImmShift(lsb, size)); + assert(isValidImmShift(width, size)); + bmi.immNRS = 0; + bmi.immN = (size == EA_8BYTE); + bmi.immR = imm1; + bmi.immS = imm2 + imm1 - 1; + immOut = bmi.immNRS; + fmt = IF_DI_2D; + break; + + case INS_mov: + case INS_ins: + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + elemsize = size; + assert(isValidVectorElemsize(elemsize)); + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm1)); + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm2)); + immOut = (imm1 << 4) + imm2; + fmt = IF_DV_2F; + break; + + default: + // TODO-Cleanup: add unreached() here + break; + } + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSC(attr, immOut); + + id->idIns(ins); + id->idInsFmt(fmt); + + id->idReg1(reg1); + id->idReg2(reg2); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing four registers. + */ + +void emitter::emitIns_R_R_R_R( + instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, regNumber reg3, regNumber reg4) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_madd: + case INS_msub: + case INS_smaddl: + case INS_smsubl: + case INS_umaddl: + case INS_umsubl: + assert(isValidGeneralDatasize(size)); + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isGeneralRegister(reg3)); + assert(isGeneralRegister(reg4)); + fmt = IF_DR_4A; + break; + + case INS_fmadd: + case INS_fmsub: + case INS_fnmadd: + case INS_fnmsub: + // Scalar operation + assert(isValidScalarDatasize(size)); + assert(isVectorRegister(reg1)); + assert(isVectorRegister(reg2)); + assert(isVectorRegister(reg3)); + assert(isVectorRegister(reg4)); + fmt = IF_DV_4A; + break; + + case INS_invalid: + fmt = IF_NONE; + break; + + default: + // TODO-Cleanup: add unreached() here + break; + } + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstr(attr); + + id->idIns(ins); + id->idInsFmt(fmt); + + id->idReg1(reg1); + id->idReg2(reg2); + id->idReg3(reg3); + id->idReg4(reg4); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing a register and a condition code + */ + +void emitter::emitIns_R_COND(instruction ins, emitAttr attr, regNumber reg, insCond cond) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + condFlagsImm cfi; + cfi.immCFVal = 0; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_cset: + case INS_csetm: + assert(isGeneralRegister(reg)); + cfi.cond = cond; + fmt = IF_DR_1D; + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(fmt != IF_NONE); + assert(isValidImmCond(cfi.immCFVal)); + + instrDesc* id = emitNewInstrSC(attr, cfi.immCFVal); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + id->idReg1(reg); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing two registers and a condition code + */ + +void emitter::emitIns_R_R_COND(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, insCond cond) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + condFlagsImm cfi; + cfi.immCFVal = 0; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_cinc: + case INS_cinv: + case INS_cneg: + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + cfi.cond = cond; + fmt = IF_DR_2D; + break; + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(fmt != IF_NONE); + assert(isValidImmCond(cfi.immCFVal)); + + instrDesc* id = emitNewInstrSC(attr, cfi.immCFVal); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + id->idReg1(reg1); + id->idReg2(reg2); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing two registers and a condition code + */ + +void emitter::emitIns_R_R_R_COND( + instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, regNumber reg3, insCond cond) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + condFlagsImm cfi; + cfi.immCFVal = 0; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_csel: + case INS_csinc: + case INS_csinv: + case INS_csneg: + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + assert(isGeneralRegister(reg3)); + cfi.cond = cond; + fmt = IF_DR_3D; + break; + + default: + // TODO-Cleanup: add unreached() here + break; + + } // end switch (ins) + + assert(fmt != IF_NONE); + assert(isValidImmCond(cfi.immCFVal)); + + instrDesc* id = emitNewInstr(attr); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + id->idReg1(reg1); + id->idReg2(reg2); + id->idReg3(reg3); + id->idSmallCns(cfi.immCFVal); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing two registers the flags and a condition code + */ + +void emitter::emitIns_R_R_FLAGS_COND( + instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, insCflags flags, insCond cond) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + condFlagsImm cfi; + cfi.immCFVal = 0; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_ccmp: + case INS_ccmn: + assert(isGeneralRegister(reg1)); + assert(isGeneralRegister(reg2)); + cfi.flags = flags; + cfi.cond = cond; + fmt = IF_DR_2I; + break; + default: + // TODO-Cleanup: add unreached() here + break; + } // end switch (ins) + + assert(fmt != IF_NONE); + assert(isValidImmCondFlags(cfi.immCFVal)); + + instrDesc* id = emitNewInstrSC(attr, cfi.immCFVal); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + id->idReg1(reg1); + id->idReg2(reg2); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing a register, an immediate, the flags and a condition code + */ + +void emitter::emitIns_R_I_FLAGS_COND( + instruction ins, emitAttr attr, regNumber reg, int imm, insCflags flags, insCond cond) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + condFlagsImm cfi; + cfi.immCFVal = 0; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_ccmp: + case INS_ccmn: + assert(isGeneralRegister(reg)); + if (imm < 0) + { + ins = insReverse(ins); + imm = -imm; + } + if ((imm >= 0) && (imm <= 31)) + { + cfi.imm5 = imm; + cfi.flags = flags; + cfi.cond = cond; + fmt = IF_DI_1F; + } + else + { + assert(!"Instruction cannot be encoded: ccmp/ccmn imm5"); + } + break; + default: + // TODO-Cleanup: add unreached() here + break; + } // end switch (ins) + + assert(fmt != IF_NONE); + assert(isValidImmCondFlagsImm5(cfi.immCFVal)); + + instrDesc* id = emitNewInstrSC(attr, cfi.immCFVal); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + id->idReg1(reg); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add a memory barrier instruction with a 'barrier' immediate + */ + +void emitter::emitIns_BARR(instruction ins, insBarrier barrier) +{ + insFormat fmt = IF_NONE; + ssize_t imm = 0; + + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_dsb: + case INS_dmb: + case INS_isb: + + fmt = IF_SI_0B; + imm = (ssize_t)barrier; + break; + default: + // TODO-Cleanup: add unreached() here + break; + } // end switch (ins) + + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrSC(EA_8BYTE, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction with a static data member operand. If 'size' is 0, the + * instruction operates on the address of the static member instead of its + * value (e.g. "push offset clsvar", rather than "push dword ptr [clsvar]"). + */ + +void emitter::emitIns_C(instruction ins, emitAttr attr, CORINFO_FIELD_HANDLE fldHnd, int offs) +{ + NYI("emitIns_C"); +} + +/***************************************************************************** + * + * Add an instruction referencing stack-based local variable. + */ + +void emitter::emitIns_S(instruction ins, emitAttr attr, int varx, int offs) +{ + NYI("emitIns_S"); +} + +/***************************************************************************** + * + * Add an instruction referencing a register and a stack-based local variable. + */ +void emitter::emitIns_R_S(instruction ins, emitAttr attr, regNumber reg1, int varx, int offs) +{ + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + int disp = 0; + unsigned scale = 0; + + assert(offs >= 0); + + // TODO-ARM64-CQ: use unscaled loads? + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_strb: + case INS_ldrb: + case INS_ldrsb: + scale = 0; + break; + + case INS_strh: + case INS_ldrh: + case INS_ldrsh: + scale = 1; + break; + + case INS_ldrsw: + scale = 2; + break; + + case INS_str: + case INS_ldr: + assert(isValidGeneralDatasize(size)); + scale = (size == EA_8BYTE) ? 3 : 2; + break; + + case INS_lea: + assert(size == EA_8BYTE); + scale = 0; + break; + + default: + NYI("emitIns_R_S"); // FP locals? + return; + + } // end switch (ins) + + /* Figure out the variable's frame position */ + ssize_t imm; + int base; + bool FPbased; + + base = emitComp->lvaFrameAddress(varx, &FPbased); + disp = base + offs; + assert((scale >= 0) && (scale <= 3)); + + regNumber reg2 = FPbased ? REG_FPBASE : REG_SPBASE; + reg2 = encodingSPtoZR(reg2); + + if (ins == INS_lea) + { + if (disp >= 0) + { + ins = INS_add; + imm = disp; + } + else + { + ins = INS_sub; + imm = -disp; + } + + if (imm <= 0x0fff) + { + fmt = IF_DI_2A; // add reg1,reg2,#disp + } + else + { + regNumber rsvdReg = codeGen->rsGetRsvdReg(); + codeGen->instGen_Set_Reg_To_Imm(size, rsvdReg, imm); + fmt = IF_DR_3A; // add reg1,reg2,rsvdReg + } + } + else + { + bool useRegForImm = false; + ssize_t mask = (1 << scale) - 1; // the mask of low bits that must be zero to encode the immediate + + imm = disp; + if (imm == 0) + { + fmt = IF_LS_2A; + } + else if ((imm < 0) || ((imm & mask) != 0)) + { + if ((imm >= -256) && (imm <= 255)) + { + fmt = IF_LS_2C; + } + else + { + useRegForImm = true; + } + } + else if (imm > 0) + { + if (((imm & mask) == 0) && ((imm >> scale) < 0x1000)) + { + imm >>= scale; // The immediate is scaled by the size of the ld/st + + fmt = IF_LS_2B; + } + else + { + useRegForImm = true; + } + } + + if (useRegForImm) + { + regNumber rsvdReg = codeGen->rsGetRsvdReg(); + codeGen->instGen_Set_Reg_To_Imm(size, rsvdReg, imm); + fmt = IF_LS_3A; + } + } + + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrCns(attr, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + id->idReg1(reg1); + id->idReg2(reg2); + id->idAddr()->iiaLclVar.initLclVarAddr(varx, offs); + id->idSetIsLclVar(); + +#ifdef DEBUG + id->idDebugOnlyInfo()->idVarRefOffs = emitVarRefOffs; +#endif + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing a stack-based local variable and a register + */ +void emitter::emitIns_S_R(instruction ins, emitAttr attr, regNumber reg1, int varx, int offs) +{ + assert(offs >= 0); + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + int disp = 0; + unsigned scale = 0; + bool isVectorStore = false; + + // TODO-ARM64-CQ: use unscaled loads? + /* Figure out the encoding format of the instruction */ + switch (ins) + { + case INS_strb: + scale = 0; + assert(isGeneralRegisterOrZR(reg1)); + break; + + case INS_strh: + scale = 1; + assert(isGeneralRegisterOrZR(reg1)); + break; + + case INS_str: + if (isGeneralRegisterOrZR(reg1)) + { + assert(isValidGeneralDatasize(size)); + scale = (size == EA_8BYTE) ? 3 : 2; + } + else + { + assert(isVectorRegister(reg1)); + assert(isValidVectorLSDatasize(size)); + scale = NaturalScale_helper(size); + isVectorStore = true; + } + break; + + default: + NYI("emitIns_S_R"); // FP locals? + return; + + } // end switch (ins) + + /* Figure out the variable's frame position */ + int base; + bool FPbased; + + base = emitComp->lvaFrameAddress(varx, &FPbased); + disp = base + offs; + assert(scale >= 0); + if (isVectorStore) + { + assert(scale <= 4); + } + else + { + assert(scale <= 3); + } + + // TODO-ARM64-CQ: with compLocallocUsed, should we use REG_SAVED_LOCALLOC_SP instead? + regNumber reg2 = FPbased ? REG_FPBASE : REG_SPBASE; + reg2 = encodingSPtoZR(reg2); + + bool useRegForImm = false; + ssize_t imm = disp; + ssize_t mask = (1 << scale) - 1; // the mask of low bits that must be zero to encode the immediate + if (imm == 0) + { + fmt = IF_LS_2A; + } + else if ((imm < 0) || ((imm & mask) != 0)) + { + if ((imm >= -256) && (imm <= 255)) + { + fmt = IF_LS_2C; + } + else + { + useRegForImm = true; + } + } + else if (imm > 0) + { + if (((imm & mask) == 0) && ((imm >> scale) < 0x1000)) + { + imm >>= scale; // The immediate is scaled by the size of the ld/st + + fmt = IF_LS_2B; + } + else + { + useRegForImm = true; + } + } + + if (useRegForImm) + { + // The reserved register is not stored in idReg3() since that field overlaps with iiaLclVar. + // It is instead implicit when idSetIsLclVar() is set, with this encoding format. + regNumber rsvdReg = codeGen->rsGetRsvdReg(); + codeGen->instGen_Set_Reg_To_Imm(size, rsvdReg, imm); + fmt = IF_LS_3A; + } + + assert(fmt != IF_NONE); + + instrDesc* id = emitNewInstrCns(attr, imm); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + + id->idReg1(reg1); + id->idReg2(reg2); + id->idAddr()->iiaLclVar.initLclVarAddr(varx, offs); + id->idSetIsLclVar(); + +#ifdef DEBUG + id->idDebugOnlyInfo()->idVarRefOffs = emitVarRefOffs; +#endif + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction referencing stack-based local variable and an immediate + */ +void emitter::emitIns_S_I(instruction ins, emitAttr attr, int varx, int offs, int val) +{ + NYI("emitIns_S_I"); +} + +/***************************************************************************** + * + * Add an instruction with a register + static member operands. + * Constant is stored into JIT data which is adjacent to code. + * No relocation is needed. PC-relative offset will be encoded directly into instruction. + * + */ +void emitter::emitIns_R_C( + instruction ins, emitAttr attr, regNumber reg, regNumber addrReg, CORINFO_FIELD_HANDLE fldHnd, int offs) +{ + assert(offs >= 0); + assert(instrDesc::fitsInSmallCns(offs)); + + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_NONE; + int disp = 0; + instrDescJmp* id = emitNewInstrJmp(); + + switch (ins) + { + case INS_adr: + // This is case to get address to the constant data. + fmt = IF_LARGEADR; + assert(isGeneralRegister(reg)); + assert(isValidGeneralDatasize(size)); + break; + + case INS_ldr: + fmt = IF_LARGELDC; + if (isVectorRegister(reg)) + { + assert(isValidScalarDatasize(size)); + // For vector (float/double) register, we should have an integer address reg to + // compute long address which consists of page address and page offset. + // For integer constant, this is not needed since the dest reg can be used to + // compute address as well as contain the final contents. + assert(isGeneralRegister(reg) || (addrReg != REG_NA)); + } + else + { + assert(isGeneralRegister(reg)); + assert(isValidGeneralDatasize(size)); + } + break; + default: + unreached(); + } + + assert(fmt != IF_NONE); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + id->idSmallCns(offs); + id->idOpSize(size); + id->idAddr()->iiaFieldHnd = fldHnd; + id->idSetIsBound(); // We won't patch address since we will know the exact distance once JIT code and data are + // allocated together. + + id->idReg1(reg); // destination register that will get the constant value. + if (addrReg != REG_NA) + { + id->idReg2(addrReg); // integer register to compute long address (used for vector dest when we end up with long + // address) + } + id->idjShort = false; // Assume loading constant from long address + + // Keep it long if it's in cold code. + id->idjKeepLong = emitComp->fgIsBlockCold(emitComp->compCurBB); + +#ifdef DEBUG + if (emitComp->opts.compLongAddress) + id->idjKeepLong = 1; +#endif // DEBUG + + // If it's possible to be shortened, then put it in jump list + // to be revisited by emitJumpDistBind. + if (!id->idjKeepLong) + { + /* Record the jump's IG and offset within it */ + id->idjIG = emitCurIG; + id->idjOffs = emitCurIGsize; + + /* Append this jump to this IG's jump list */ + id->idjNext = emitCurIGjmpList; + emitCurIGjmpList = id; + +#if EMITTER_STATS + emitTotalIGjmps++; +#endif + } + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add an instruction with a static member + constant. + */ + +void emitter::emitIns_C_I(instruction ins, emitAttr attr, CORINFO_FIELD_HANDLE fldHnd, ssize_t offs, ssize_t val) +{ + NYI("emitIns_C_I"); +} + +/***************************************************************************** + * + * Add an instruction with a static member + register operands. + */ + +void emitter::emitIns_C_R(instruction ins, emitAttr attr, CORINFO_FIELD_HANDLE fldHnd, regNumber reg, int offs) +{ + assert(!"emitIns_C_R not supported for RyuJIT backend"); +} + +void emitter::emitIns_R_AR(instruction ins, + emitAttr attr, + regNumber ireg, + regNumber reg, + int offs, + int memCookie /* = 0 */, + void* clsCookie /* = NULL */) +{ + NYI("emitIns_R_AR"); +} + +// This computes address from the immediate which is relocatable. +void emitter::emitIns_R_AI(instruction ins, emitAttr attr, regNumber ireg, ssize_t addr) +{ + assert(EA_IS_RELOC(attr)); + emitAttr size = EA_SIZE(attr); + insFormat fmt = IF_DI_1E; + bool needAdd = false; + instrDescJmp* id = emitNewInstrJmp(); + + switch (ins) + { + case INS_adrp: + // This computes page address. + // page offset is needed using add. + needAdd = true; + break; + case INS_adr: + break; + default: + unreached(); + } + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + id->idOpSize(size); + id->idAddr()->iiaAddr = (BYTE*)addr; + id->idReg1(ireg); + id->idSetIsDspReloc(); + + dispIns(id); + appendToCurIG(id); + + if (needAdd) + { + // add reg, reg, imm + ins = INS_add; + fmt = IF_DI_2A; + instrDesc* id = emitAllocInstr(attr); + assert(id->idIsReloc()); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idInsOpt(INS_OPTS_NONE); + id->idOpSize(size); + id->idAddr()->iiaAddr = (BYTE*)addr; + id->idReg1(ireg); + id->idReg2(ireg); + + dispIns(id); + appendToCurIG(id); + } +} + +void emitter::emitIns_AR_R(instruction ins, + emitAttr attr, + regNumber ireg, + regNumber reg, + int offs, + int memCookie /* = 0 */, + void* clsCookie /* = NULL */) +{ + NYI("emitIns_AR_R"); +} + +void emitter::emitIns_R_ARR(instruction ins, emitAttr attr, regNumber ireg, regNumber reg, regNumber rg2, int disp) +{ + NYI("emitIns_R_ARR"); +} + +void emitter::emitIns_ARR_R(instruction ins, emitAttr attr, regNumber ireg, regNumber reg, regNumber rg2, int disp) +{ + NYI("emitIns_R_ARR"); +} + +void emitter::emitIns_R_ARX( + instruction ins, emitAttr attr, regNumber ireg, regNumber reg, regNumber rg2, unsigned mul, int disp) +{ + NYI("emitIns_R_ARR"); +} + +/***************************************************************************** + * + * Record that a jump instruction uses the short encoding + * + */ +void emitter::emitSetShortJump(instrDescJmp* id) +{ + if (id->idjKeepLong) + return; + + insFormat fmt = IF_NONE; + if (emitIsCondJump(id)) + { + fmt = IF_BI_0B; + } + else if (emitIsLoadLabel(id)) + { + fmt = IF_DI_1E; + } + else if (emitIsLoadConstant(id)) + { + fmt = IF_LS_1A; + } + else + { + unreached(); + } + + id->idInsFmt(fmt); + id->idjShort = true; +} + +/***************************************************************************** + * + * Add a label instruction. + */ + +void emitter::emitIns_R_L(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg) +{ + assert(dst->bbFlags & BBF_JMP_TARGET); + + insFormat fmt = IF_NONE; + + switch (ins) + { + case INS_adr: + fmt = IF_LARGEADR; + break; + default: + unreached(); + } + + instrDescJmp* id = emitNewInstrJmp(); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idjShort = false; + id->idAddr()->iiaBBlabel = dst; + id->idReg1(reg); + id->idOpSize(EA_PTRSIZE); + +#ifdef DEBUG + // Mark the catch return + if (emitComp->compCurBB->bbJumpKind == BBJ_EHCATCHRET) + { + id->idDebugOnlyInfo()->idCatchRet = true; + } +#endif // DEBUG + + id->idjKeepLong = emitComp->fgInDifferentRegions(emitComp->compCurBB, dst); + +#ifdef DEBUG + if (emitComp->opts.compLongAddress) + id->idjKeepLong = 1; +#endif // DEBUG + + /* Record the jump's IG and offset within it */ + + id->idjIG = emitCurIG; + id->idjOffs = emitCurIGsize; + + /* Append this jump to this IG's jump list */ + + id->idjNext = emitCurIGjmpList; + emitCurIGjmpList = id; + +#if EMITTER_STATS + emitTotalIGjmps++; +#endif + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add a data label instruction. + */ + +void emitter::emitIns_R_D(instruction ins, emitAttr attr, unsigned offs, regNumber reg) +{ + NYI("emitIns_R_D"); +} + +void emitter::emitIns_J_R(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg) +{ + NYI("emitIns_J_R"); +} + +void emitter::emitIns_J(instruction ins, BasicBlock* dst, int instrCount) +{ + insFormat fmt = IF_NONE; + + if (dst != nullptr) + { + assert(dst->bbFlags & BBF_JMP_TARGET); + } + else + { + assert(instrCount != 0); + } + + /* Figure out the encoding format of the instruction */ + + bool idjShort = false; + switch (ins) + { + case INS_bl_local: + case INS_b: + // Unconditional jump is a single form. + idjShort = true; + fmt = IF_BI_0A; + break; + + case INS_beq: + case INS_bne: + case INS_bhs: + case INS_blo: + case INS_bmi: + case INS_bpl: + case INS_bvs: + case INS_bvc: + case INS_bhi: + case INS_bls: + case INS_bge: + case INS_blt: + case INS_bgt: + case INS_ble: + // Assume conditional jump is long. + fmt = IF_LARGEJMP; + break; + + default: + unreached(); + break; + } + + instrDescJmp* id = emitNewInstrJmp(); + + id->idIns(ins); + id->idInsFmt(fmt); + id->idjShort = idjShort; + +#ifdef DEBUG + // Mark the finally call + if (ins == INS_bl_local && emitComp->compCurBB->bbJumpKind == BBJ_CALLFINALLY) + { + id->idDebugOnlyInfo()->idFinallyCall = true; + } +#endif // DEBUG + + if (dst != nullptr) + { + id->idAddr()->iiaBBlabel = dst; + + // Skip unconditional jump that has a single form. + // TODO-ARM64-NYI: enable hot/cold splittingNYI. + // The target needs to be relocated. + if (!idjShort) + { + id->idjKeepLong = emitComp->fgInDifferentRegions(emitComp->compCurBB, dst); + +#ifdef DEBUG + if (emitComp->opts.compLongAddress) // Force long branches + id->idjKeepLong = 1; +#endif // DEBUG + } + } + else + { + id->idAddr()->iiaSetInstrCount(instrCount); + id->idjKeepLong = false; + /* This jump must be short */ + emitSetShortJump(id); + id->idSetIsBound(); + } + + /* Record the jump's IG and offset within it */ + + id->idjIG = emitCurIG; + id->idjOffs = emitCurIGsize; + + /* Append this jump to this IG's jump list */ + + id->idjNext = emitCurIGjmpList; + emitCurIGjmpList = id; + +#if EMITTER_STATS + emitTotalIGjmps++; +#endif + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Add a call instruction (direct or indirect). + * argSize<0 means that the caller will pop the arguments + * + * The other arguments are interpreted depending on callType as shown: + * Unless otherwise specified, ireg,xreg,xmul,disp should have default values. + * + * EC_FUNC_TOKEN : addr is the method address + * EC_FUNC_ADDR : addr is the absolute address of the function + * + * If callType is one of these emitCallTypes, addr has to be NULL. + * EC_INDIR_R : "call ireg". + * + * For ARM xreg, xmul and disp are never used and should always be 0/REG_NA. + * + * Please consult the "debugger team notification" comment in genFnProlog(). + */ + +void emitter::emitIns_Call(EmitCallType callType, + CORINFO_METHOD_HANDLE methHnd, + INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) // used to report call sites to the EE + void* addr, + ssize_t argSize, + emitAttr retSize, + emitAttr secondRetSize, + VARSET_VALARG_TP ptrVars, + regMaskTP gcrefRegs, + regMaskTP byrefRegs, + IL_OFFSETX ilOffset /* = BAD_IL_OFFSET */, + regNumber ireg /* = REG_NA */, + regNumber xreg /* = REG_NA */, + unsigned xmul /* = 0 */, + ssize_t disp /* = 0 */, + bool isJump /* = false */, + bool isNoGC /* = false */, + bool isProfLeaveCB /* = false */) +{ + /* Sanity check the arguments depending on callType */ + + assert(callType < EC_COUNT); + assert((callType != EC_FUNC_TOKEN && callType != EC_FUNC_ADDR) || + (ireg == REG_NA && xreg == REG_NA && xmul == 0 && disp == 0)); + assert(callType < EC_INDIR_R || addr == NULL); + assert(callType != EC_INDIR_R || (ireg < REG_COUNT && xreg == REG_NA && xmul == 0 && disp == 0)); + + // ARM never uses these + assert(xreg == REG_NA && xmul == 0 && disp == 0); + + // Our stack level should be always greater than the bytes of arguments we push. Just + // a sanity test. + assert((unsigned)abs(argSize) <= codeGen->genStackLevel); + + int argCnt; + instrDesc* id; + + /* This is the saved set of registers after a normal call */ + regMaskTP savedSet = RBM_CALLEE_SAVED; + + /* some special helper calls have a different saved set registers */ + + if (isNoGC) + { + assert(emitNoGChelper(Compiler::eeGetHelperNum(methHnd))); + + // This call will preserve the liveness of most registers + // + // - On the ARM64 the NOGC helpers will preserve all registers, + // except for those listed in the RBM_CALLEE_TRASH_NOGC mask + + savedSet = RBM_ALLINT & ~RBM_CALLEE_TRASH_NOGC; + + // In case of Leave profiler callback, we need to preserve liveness of REG_PROFILER_RET_SCRATCH + if (isProfLeaveCB) + { + savedSet |= RBM_PROFILER_RET_SCRATCH; + } + } + else + { + assert(!emitNoGChelper(Compiler::eeGetHelperNum(methHnd))); + } + + /* Trim out any callee-trashed registers from the live set */ + + gcrefRegs &= savedSet; + byrefRegs &= savedSet; + +#ifdef DEBUG + if (EMIT_GC_VERBOSE) + { + printf("Call: GCvars=%s ", VarSetOps::ToString(emitComp, ptrVars)); + dumpConvertedVarSet(emitComp, ptrVars); + printf(", gcrefRegs="); + printRegMaskInt(gcrefRegs); + emitDispRegSet(gcrefRegs); + printf(", byrefRegs="); + printRegMaskInt(byrefRegs); + emitDispRegSet(byrefRegs); + printf("\n"); + } +#endif + + assert(argSize % REGSIZE_BYTES == 0); + argCnt = (int)(argSize / (int)sizeof(void*)); + +#ifdef DEBUGGING_SUPPORT + /* Managed RetVal: emit sequence point for the call */ + if (emitComp->opts.compDbgInfo && ilOffset != BAD_IL_OFFSET) + { + codeGen->genIPmappingAdd(ilOffset, false); + } +#endif + + /* + We need to allocate the appropriate instruction descriptor based + on whether this is a direct/indirect call, and whether we need to + record an updated set of live GC variables. + */ + + if (callType >= EC_INDIR_R) + { + /* Indirect call, virtual calls */ + + assert(callType == EC_INDIR_R); + + id = emitNewInstrCallInd(argCnt, disp, ptrVars, gcrefRegs, byrefRegs, retSize, secondRetSize); + } + else + { + /* Helper/static/nonvirtual/function calls (direct or through handle), + and calls to an absolute addr. */ + + assert(callType == EC_FUNC_TOKEN || callType == EC_FUNC_ADDR); + + id = emitNewInstrCallDir(argCnt, ptrVars, gcrefRegs, byrefRegs, retSize, secondRetSize); + } + + /* Update the emitter's live GC ref sets */ + + VarSetOps::Assign(emitComp, emitThisGCrefVars, ptrVars); + emitThisGCrefRegs = gcrefRegs; + emitThisByrefRegs = byrefRegs; + + /* Set the instruction - special case jumping a function */ + instruction ins; + insFormat fmt = IF_NONE; + + id->idSetIsNoGC(isNoGC); + + /* Record the address: method, indirection, or funcptr */ + + if (callType > EC_FUNC_ADDR) + { + /* This is an indirect call (either a virtual call or func ptr call) */ + + switch (callType) + { + case EC_INDIR_R: // the address is in a register + + id->idSetIsCallRegPtr(); + + if (isJump) + { + ins = INS_br_tail; // INS_br_tail Reg + } + else + { + ins = INS_blr; // INS_blr Reg + } + fmt = IF_BR_1B; + + id->idIns(ins); + id->idInsFmt(fmt); + + id->idReg3(ireg); + assert(xreg == REG_NA); + break; + + default: + NO_WAY("unexpected instruction"); + break; + } + } + else + { + /* This is a simple direct call: "call helper/method/addr" */ + + assert(callType == EC_FUNC_TOKEN || callType == EC_FUNC_ADDR); + + assert(addr != NULL); + + if (isJump) + { + ins = INS_b_tail; // INS_b_tail imm28 + } + else + { + ins = INS_bl; // INS_bl imm28 + } + fmt = IF_BI_0C; + + id->idIns(ins); + id->idInsFmt(fmt); + + id->idAddr()->iiaAddr = (BYTE*)addr; + + if (callType == EC_FUNC_ADDR) + { + id->idSetIsCallAddr(); + } + +#if RELOC_SUPPORT + if (emitComp->opts.compReloc) + { + id->idSetIsDspReloc(); + } +#endif + } + +#ifdef DEBUG + if (EMIT_GC_VERBOSE) + { + if (id->idIsLargeCall()) + { + printf("[%02u] Rec call GC vars = %s\n", id->idDebugOnlyInfo()->idNum, + VarSetOps::ToString(emitComp, ((instrDescCGCA*)id)->idcGCvars)); + } + } +#endif + +#if defined(DEBUG) || defined(LATE_DISASM) + id->idDebugOnlyInfo()->idMemCookie = (size_t)methHnd; // method token + id->idDebugOnlyInfo()->idClsCookie = 0; + id->idDebugOnlyInfo()->idCallSig = sigInfo; +#endif + +#if defined(LATE_DISASM) + if (addr != nullptr) + { + codeGen->getDisAssembler().disSetMethod((size_t)addr, methHnd); + } +#endif // defined(LATE_DISASM) + + dispIns(id); + appendToCurIG(id); +} + +/***************************************************************************** + * + * Returns true if 'imm' is valid Cond encoding + */ + +/*static*/ bool emitter::isValidImmCond(ssize_t imm) +{ + // range check the ssize_t value, to make sure it is a small unsigned value + // and that only the bits in the cfi.cond are set + if ((imm < 0) || (imm > 0xF)) + return false; + + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + + return (cfi.cond <= INS_COND_LE); // Don't allow 14 & 15 (AL & NV). +} + +/***************************************************************************** + * + * Returns true if 'imm' is valid Cond/Flags encoding + */ + +/*static*/ bool emitter::isValidImmCondFlags(ssize_t imm) +{ + // range check the ssize_t value, to make sure it is a small unsigned value + // and that only the bits in the cfi.cond or cfi.flags are set + if ((imm < 0) || (imm > 0xFF)) + return false; + + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + + return (cfi.cond <= INS_COND_LE); // Don't allow 14 & 15 (AL & NV). +} + +/***************************************************************************** + * + * Returns true if 'imm' is valid Cond/Flags/Imm5 encoding + */ + +/*static*/ bool emitter::isValidImmCondFlagsImm5(ssize_t imm) +{ + // range check the ssize_t value, to make sure it is a small unsigned value + // and that only the bits in the cfi.cond, cfi.flags or cfi.imm5 are set + if ((imm < 0) || (imm > 0x1FFF)) + return false; + + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + + return (cfi.cond <= INS_COND_LE); // Don't allow 14 & 15 (AL & NV). +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Rd' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Rd(regNumber reg) +{ + assert(isIntegerRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg; + assert((ureg >= 0) && (ureg <= 31)); + return ureg; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Rt' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Rt(regNumber reg) +{ + assert(isIntegerRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg; + assert((ureg >= 0) && (ureg <= 31)); + return ureg; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Rn' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Rn(regNumber reg) +{ + assert(isIntegerRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg; + assert((ureg >= 0) && (ureg <= 31)); + return ureg << 5; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Rm' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Rm(regNumber reg) +{ + assert(isIntegerRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg; + assert((ureg >= 0) && (ureg <= 31)); + return ureg << 16; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Ra' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Ra(regNumber reg) +{ + assert(isIntegerRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg; + assert((ureg >= 0) && (ureg <= 31)); + return ureg << 10; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Vd' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Vd(regNumber reg) +{ + assert(emitter::isVectorRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_V0; + assert((ureg >= 0) && (ureg <= 31)); + return ureg; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Vt' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Vt(regNumber reg) +{ + assert(emitter::isVectorRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_V0; + assert((ureg >= 0) && (ureg <= 31)); + return ureg; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Vn' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Vn(regNumber reg) +{ + assert(emitter::isVectorRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_V0; + assert((ureg >= 0) && (ureg <= 31)); + return ureg << 5; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Vm' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Vm(regNumber reg) +{ + assert(emitter::isVectorRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_V0; + assert((ureg >= 0) && (ureg <= 31)); + return ureg << 16; +} + +/***************************************************************************** + * + * Returns an encoding for the specified register used in the 'Va' position + */ + +/*static*/ emitter::code_t emitter::insEncodeReg_Va(regNumber reg) +{ + assert(emitter::isVectorRegister(reg)); + emitter::code_t ureg = (emitter::code_t)reg - (emitter::code_t)REG_V0; + assert((ureg >= 0) && (ureg <= 31)); + return ureg << 10; +} + +/***************************************************************************** + * + * Returns an encoding for the specified condition code. + */ + +/*static*/ emitter::code_t emitter::insEncodeCond(insCond cond) +{ + emitter::code_t uimm = (emitter::code_t)cond; + return uimm << 12; +} + +/***************************************************************************** + * + * Returns an encoding for the condition code with the lowest bit inverted (marked by invert(<cond>) in the + * architecture manual). + */ + +/*static*/ emitter::code_t emitter::insEncodeInvertedCond(insCond cond) +{ + emitter::code_t uimm = (emitter::code_t)cond; + uimm ^= 1; // invert the lowest bit + return uimm << 12; +} + +/***************************************************************************** + * + * Returns an encoding for the specified flags. + */ + +/*static*/ emitter::code_t emitter::insEncodeFlags(insCflags flags) +{ + emitter::code_t uimm = (emitter::code_t)flags; + return uimm; +} + +/***************************************************************************** + * + * Returns the encoding for the Shift Count bits to be used for Arm64 encodings + */ + +/*static*/ emitter::code_t emitter::insEncodeShiftCount(ssize_t imm, emitAttr size) +{ + assert((imm & 0x003F) == imm); + assert(((imm & 0x0020) == 0) || (size == EA_8BYTE)); + + return (emitter::code_t)imm << 10; +} + +/***************************************************************************** + * + * Returns the encoding to select a 64-bit datasize for an Arm64 instruction + */ + +/*static*/ emitter::code_t emitter::insEncodeDatasize(emitAttr size) +{ + if (size == EA_8BYTE) + { + return 0x80000000; // set the bit at location 31 + } + else + { + assert(size == EA_4BYTE); + return 0; + } +} + +/***************************************************************************** + * + * Returns the encoding to select the datasize for the general load/store Arm64 instructions + * + */ + +/*static*/ emitter::code_t emitter::insEncodeDatasizeLS(emitter::code_t code, emitAttr size) +{ + if (code & 0x00800000) // Is this a sign-extending opcode? (i.e. ldrsw, ldrsh, ldrsb) + { + assert((size == EA_4BYTE) || (size == EA_8BYTE)); + if ((code & 0x80000000) == 0) // Is it a ldrsh or ldrsb and not ldrsw ? + { + if (size == EA_4BYTE) // Do we need to encode the 32-bit Rt size bit? + { + return 0x00400000; // set the bit at location 22 + } + } + } + else if (code & 0x80000000) // Is this a ldr/str/ldur/stur opcode? + { + assert((size == EA_4BYTE) || (size == EA_8BYTE)); + if (size == EA_8BYTE) // Do we need to encode the 64-bit size bit? + { + return 0x40000000; // set the bit at location 30 + } + } + return 0; +} + +/***************************************************************************** + * + * Returns the encoding to select the datasize for the vector load/store Arm64 instructions + * + */ + +/*static*/ emitter::code_t emitter::insEncodeDatasizeVLS(emitter::code_t code, emitAttr size) +{ + code_t result = 0; + + // Check bit 29 + if ((code & 0x20000000) == 0) + { + // LDR literal + + if (size == EA_16BYTE) + { + // set the operation size in bit 31 + result = 0x80000000; + } + else if (size == EA_8BYTE) + { + // set the operation size in bit 30 + result = 0x40000000; + } + else + { + assert(size == EA_4BYTE); + // no bits are set + result = 0x00000000; + } + } + else + { + // LDR non-literal + + if (size == EA_16BYTE) + { + // The operation size in bits 31 and 30 are zero + // Bit 23 specifies a 128-bit Load/Store + result = 0x00800000; + } + else if (size == EA_8BYTE) + { + // set the operation size in bits 31 and 30 + result = 0xC0000000; + } + else if (size == EA_4BYTE) + { + // set the operation size in bit 31 + result = 0x80000000; + } + else if (size == EA_2BYTE) + { + // set the operation size in bit 30 + result = 0x40000000; + } + else + { + assert(size == EA_1BYTE); + // The operation size in bits 31 and 30 are zero + result = 0x00000000; + } + } + + // Or in bit 26 to indicate a Vector register is used as 'target' + result |= 0x04000000; + + return result; +} + +/***************************************************************************** + * + * Returns the encoding to select the datasize for the vector load/store Arm64 instructions + * + */ + +/*static*/ emitter::code_t emitter::insEncodeDatasizeVPLS(emitter::code_t code, emitAttr size) +{ + code_t result = 0; + + if (size == EA_16BYTE) + { + // The operation size in bits 31 and 30 are zero + // Bit 23 specifies a 128-bit Load/Store + result = 0x80000000; + } + else if (size == EA_8BYTE) + { + // set the operation size in bits 31 and 30 + result = 0x40000000; + } + else if (size == EA_4BYTE) + { + // set the operation size in bit 31 + result = 0x00000000; + } + + // Or in bit 26 to indicate a Vector register is used as 'target' + result |= 0x04000000; + + return result; +} + +/***************************************************************************** + * + * Returns the encoding to set the size bit and the N bits for a 'bitfield' instruction + * + */ + +/*static*/ emitter::code_t emitter::insEncodeDatasizeBF(emitter::code_t code, emitAttr size) +{ + // is bit 30 equal to 0? + if ((code & 0x40000000) == 0) // is the opcode one of extr, sxtb, sxth or sxtw + { + if (size == EA_8BYTE) // Do we need to set the sf and N bits? + { + return 0x80400000; // set the sf-bit at location 31 and the N-bit at location 22 + } + } + return 0; // don't set any bits +} + +/***************************************************************************** + * + * Returns the encoding to select the 64/128-bit datasize for an Arm64 vector instruction + */ + +/*static*/ emitter::code_t emitter::insEncodeVectorsize(emitAttr size) +{ + if (size == EA_16BYTE) + { + return 0x40000000; // set the bit at location 30 + } + else + { + assert(size == EA_8BYTE); + return 0; + } +} + +/***************************************************************************** + * + * Returns the encoding to select 'index' for an Arm64 vector elem instruction + */ +/*static*/ emitter::code_t emitter::insEncodeVectorIndex(emitAttr elemsize, ssize_t index) +{ + code_t bits = (code_t)index; + if (elemsize == EA_1BYTE) + { + bits <<= 1; + bits |= 1; + } + else if (elemsize == EA_2BYTE) + { + bits <<= 2; + bits |= 2; + } + else if (elemsize == EA_4BYTE) + { + bits <<= 3; + bits |= 4; + } + else + { + assert(elemsize == EA_8BYTE); + bits <<= 4; + bits |= 8; + } + assert((bits >= 1) && (bits <= 0x1f)); + + return (bits << 16); // bits at locations [20,19,18,17,16] +} + +/***************************************************************************** + * + * Returns the encoding to select 'index2' for an Arm64 'ins' elem instruction + */ +/*static*/ emitter::code_t emitter::insEncodeVectorIndex2(emitAttr elemsize, ssize_t index2) +{ + code_t bits = (code_t)index2; + if (elemsize == EA_1BYTE) + { + // bits are correct + } + else if (elemsize == EA_2BYTE) + { + bits <<= 1; + } + else if (elemsize == EA_4BYTE) + { + bits <<= 2; + } + else + { + assert(elemsize == EA_8BYTE); + bits <<= 3; + } + assert((bits >= 0) && (bits <= 0xf)); + + return (bits << 11); // bits at locations [14,13,12,11] +} + +/***************************************************************************** + * + * Returns the encoding to select the 'index' for an Arm64 'mul' by elem instruction + */ +/*static*/ emitter::code_t emitter::insEncodeVectorIndexLMH(emitAttr elemsize, ssize_t index) +{ + code_t bits = 0; + + if (elemsize == EA_2BYTE) + { + assert((index >= 0) && (index <= 7)); + if (index & 0x4) + { + bits |= (1 << 11); // set bit 11 'H' + } + if (index & 0x2) + { + bits |= (1 << 21); // set bit 21 'L' + } + if (index & 0x1) + { + bits |= (1 << 20); // set bit 20 'M' + } + } + else if (elemsize == EA_4BYTE) + { + assert((index >= 0) && (index <= 3)); + if (index & 0x2) + { + bits |= (1 << 11); // set bit 11 'H' + } + if (index & 0x1) + { + bits |= (1 << 21); // set bit 21 'L' + } + } + else + { + assert(!"Invalid 'elemsize' value"); + } + + return bits; +} + +/***************************************************************************** + * + * Returns the encoding to shift by 'shift' for an Arm64 vector or scalar instruction + */ + +/*static*/ emitter::code_t emitter::insEncodeVectorShift(emitAttr size, ssize_t shift) +{ + assert(shift < getBitWidth(size)); + + code_t imm = (code_t)(getBitWidth(size) + shift); + + return imm << 16; +} + +/***************************************************************************** + * + * Returns the encoding to select the 1/2/4/8 byte elemsize for an Arm64 vector instruction + */ + +/*static*/ emitter::code_t emitter::insEncodeElemsize(emitAttr size) +{ + if (size == EA_8BYTE) + { + return 0x00C00000; // set the bit at location 23 and 22 + } + else if (size == EA_4BYTE) + { + return 0x00800000; // set the bit at location 23 + } + else if (size == EA_2BYTE) + { + return 0x00400000; // set the bit at location 22 + } + assert(size == EA_1BYTE); + return 0x00000000; +} + +/***************************************************************************** + * + * Returns the encoding to select the 4/8 byte elemsize for an Arm64 float vector instruction + */ + +/*static*/ emitter::code_t emitter::insEncodeFloatElemsize(emitAttr size) +{ + if (size == EA_8BYTE) + { + return 0x00400000; // set the bit at location 22 + } + assert(size == EA_4BYTE); + return 0x00000000; +} + +// Returns the encoding to select the index for an Arm64 float vector by elem instruction +/*static*/ emitter::code_t emitter::insEncodeFloatIndex(emitAttr elemsize, ssize_t index) +{ + code_t result = 0x00000000; + if (elemsize == EA_8BYTE) + { + assert((index >= 0) && (index <= 1)); + if (index == 1) + { + result |= 0x00000800; // 'H' - set the bit at location 11 + } + } + else + { + assert(elemsize == EA_4BYTE); + assert((index >= 0) && (index <= 3)); + if (index & 2) + { + result |= 0x00000800; // 'H' - set the bit at location 11 + } + if (index & 1) + { + result |= 0x00200000; // 'L' - set the bit at location 21 + } + } + return result; +} + +/***************************************************************************** + * + * Returns the encoding to select the fcvt operation for Arm64 instructions + */ +/*static*/ emitter::code_t emitter::insEncodeConvertOpt(insFormat fmt, insOpts conversion) +{ + code_t result = 0; + switch (conversion) + { + case INS_OPTS_S_TO_D: // Single to Double + assert(fmt == IF_DV_2J); + result = 0x00008000; // type=00, opc=01 + break; + + case INS_OPTS_D_TO_S: // Double to Single + assert(fmt == IF_DV_2J); + result = 0x00400000; // type=01, opc=00 + break; + + case INS_OPTS_H_TO_S: // Half to Single + assert(fmt == IF_DV_2J); + result = 0x00C00000; // type=11, opc=00 + break; + + case INS_OPTS_H_TO_D: // Half to Double + assert(fmt == IF_DV_2J); + result = 0x00C08000; // type=11, opc=01 + break; + + case INS_OPTS_S_TO_H: // Single to Half + assert(fmt == IF_DV_2J); + result = 0x00018000; // type=00, opc=11 + break; + + case INS_OPTS_D_TO_H: // Double to Half + assert(fmt == IF_DV_2J); + result = 0x00418000; // type=01, opc=11 + break; + + case INS_OPTS_S_TO_4BYTE: // Single to INT32 + assert(fmt == IF_DV_2H); + result = 0x00000000; // sf=0, type=00 + break; + + case INS_OPTS_D_TO_4BYTE: // Double to INT32 + assert(fmt == IF_DV_2H); + result = 0x00400000; // sf=0, type=01 + break; + + case INS_OPTS_S_TO_8BYTE: // Single to INT64 + assert(fmt == IF_DV_2H); + result = 0x80000000; // sf=1, type=00 + break; + + case INS_OPTS_D_TO_8BYTE: // Double to INT64 + assert(fmt == IF_DV_2H); + result = 0x80400000; // sf=1, type=01 + break; + + case INS_OPTS_4BYTE_TO_S: // INT32 to Single + assert(fmt == IF_DV_2I); + result = 0x00000000; // sf=0, type=00 + break; + + case INS_OPTS_4BYTE_TO_D: // INT32 to Double + assert(fmt == IF_DV_2I); + result = 0x00400000; // sf=0, type=01 + break; + + case INS_OPTS_8BYTE_TO_S: // INT64 to Single + assert(fmt == IF_DV_2I); + result = 0x80000000; // sf=1, type=00 + break; + + case INS_OPTS_8BYTE_TO_D: // INT64 to Double + assert(fmt == IF_DV_2I); + result = 0x80400000; // sf=1, type=01 + break; + + default: + assert(!"Invalid 'conversion' value"); + break; + } + return result; +} + +/***************************************************************************** + * + * Returns the encoding to have the Rn register be updated Pre/Post indexed + * or not updated + */ + +/*static*/ emitter::code_t emitter::insEncodeIndexedOpt(insOpts opt) +{ + assert(emitter::insOptsNone(opt) || emitter::insOptsIndexed(opt)); + + if (emitter::insOptsIndexed(opt)) + { + if (emitter::insOptsPostIndex(opt)) + { + return 0x00000400; // set the bit at location 10 + } + else + { + assert(emitter::insOptsPreIndex(opt)); + return 0x00000C00; // set the bit at location 10 and 11 + } + } + else + { + assert(emitter::insOptsNone(opt)); + return 0; // bits 10 and 11 are zero + } +} + +/***************************************************************************** + * + * Returns the encoding for a ldp/stp instruction to have the Rn register + * be updated Pre/Post indexed or not updated + */ + +/*static*/ emitter::code_t emitter::insEncodePairIndexedOpt(instruction ins, insOpts opt) +{ + assert(emitter::insOptsNone(opt) || emitter::insOptsIndexed(opt)); + + if ((ins == INS_ldnp) || (ins == INS_stnp)) + { + assert(emitter::insOptsNone(opt)); + return 0; // bits 23 and 24 are zero + } + else + { + if (emitter::insOptsIndexed(opt)) + { + if (emitter::insOptsPostIndex(opt)) + { + return 0x00800000; // set the bit at location 23 + } + else + { + assert(emitter::insOptsPreIndex(opt)); + return 0x01800000; // set the bit at location 24 and 23 + } + } + else + { + assert(emitter::insOptsNone(opt)); + return 0x01000000; // set the bit at location 24 + } + } +} + +/***************************************************************************** + * + * Returns the encoding to apply a Shift Type on the Rm register + */ + +/*static*/ emitter::code_t emitter::insEncodeShiftType(insOpts opt) +{ + if (emitter::insOptsNone(opt)) + { + // None implies the we encode LSL (with a zero immediate) + opt = INS_OPTS_LSL; + } + assert(emitter::insOptsAnyShift(opt)); + + emitter::code_t option = (emitter::code_t)opt - (emitter::code_t)INS_OPTS_LSL; + assert(option <= 3); + + return option << 22; // bits 23, 22 +} + +/***************************************************************************** + * + * Returns the encoding to apply a 12 bit left shift to the immediate + */ + +/*static*/ emitter::code_t emitter::insEncodeShiftImm12(insOpts opt) +{ + if (emitter::insOptsLSL12(opt)) + { + return 0x00400000; // set the bit at location 22 + } + return 0; +} + +/***************************************************************************** + * + * Returns the encoding to have the Rm register use an extend operation + */ + +/*static*/ emitter::code_t emitter::insEncodeExtend(insOpts opt) +{ + if (emitter::insOptsNone(opt) || (opt == INS_OPTS_LSL)) + { + // None or LSL implies the we encode UXTX + opt = INS_OPTS_UXTX; + } + assert(emitter::insOptsAnyExtend(opt)); + + emitter::code_t option = (emitter::code_t)opt - (emitter::code_t)INS_OPTS_UXTB; + assert(option <= 7); + + return option << 13; // bits 15,14,13 +} + +/***************************************************************************** + * + * Returns the encoding to scale the Rm register by {0,1,2,3,4} + * when using an extend operation + */ + +/*static*/ emitter::code_t emitter::insEncodeExtendScale(ssize_t imm) +{ + assert((imm >= 0) && (imm <= 4)); + + return (emitter::code_t)imm << 10; // bits 12,11,10 +} + +/***************************************************************************** + * + * Returns the encoding to have the Rm register be auto scaled by the ld/st size + */ + +/*static*/ emitter::code_t emitter::insEncodeReg3Scale(bool isScaled) +{ + if (isScaled) + { + return 0x00001000; // set the bit at location 12 + } + else + { + return 0; + } +} + +BYTE* emitter::emitOutputLoadLabel(BYTE* dst, BYTE* srcAddr, BYTE* dstAddr, instrDescJmp* id) +{ + instruction ins = id->idIns(); + insFormat fmt = id->idInsFmt(); + regNumber dstReg = id->idReg1(); + if (id->idjShort) + { + // adr x, [rel addr] -- compute address: current addr(ip) + rel addr. + assert(ins == INS_adr); + assert(fmt == IF_DI_1E); + ssize_t distVal = (ssize_t)(dstAddr - srcAddr); + dst = emitOutputShortAddress(dst, ins, fmt, distVal, dstReg); + } + else + { + // adrp x, [rel page addr] -- compute page address: current page addr + rel page addr + assert(fmt == IF_LARGEADR); + ssize_t relPageAddr = + (((ssize_t)dstAddr & 0xFFFFFFFFFFFFF000LL) - ((ssize_t)srcAddr & 0xFFFFFFFFFFFFF000LL)) >> 12; + dst = emitOutputShortAddress(dst, INS_adrp, IF_DI_1E, relPageAddr, dstReg); + + // add x, x, page offs -- compute address = page addr + page offs + ssize_t imm12 = (ssize_t)dstAddr & 0xFFF; // 12 bits + assert(isValidUimm12(imm12)); + code_t code = + emitInsCode(INS_add, IF_DI_2A); // DI_2A X0010001shiiiiii iiiiiinnnnnddddd 1100 0000 imm(i12, sh) + code |= insEncodeDatasize(EA_8BYTE); // X + code |= ((code_t)imm12 << 10); // iiiiiiiiiiii + code |= insEncodeReg_Rd(dstReg); // ddddd + code |= insEncodeReg_Rn(dstReg); // nnnnn + dst += emitOutput_Instr(dst, code); + } + return dst; +} + +/***************************************************************************** + * + * Output a local jump or other instruction with a pc-relative immediate. + * Note that this may be invoked to overwrite an existing jump instruction at 'dst' + * to handle forward branch patching. + */ + +BYTE* emitter::emitOutputLJ(insGroup* ig, BYTE* dst, instrDesc* i) +{ + instrDescJmp* id = (instrDescJmp*)i; + + unsigned srcOffs; + unsigned dstOffs; + BYTE* srcAddr; + BYTE* dstAddr; + ssize_t distVal; + ssize_t loBits; + + // Set default ins/fmt from id. + instruction ins = id->idIns(); + insFormat fmt = id->idInsFmt(); + + bool loadLabel = false; + bool isJump = false; + bool loadConstant = false; + + switch (ins) + { + default: + isJump = true; + break; + + case INS_tbz: + case INS_tbnz: + case INS_cbz: + case INS_cbnz: + isJump = true; + break; + + case INS_ldr: + case INS_ldrsw: + loadConstant = true; + break; + + case INS_adr: + case INS_adrp: + loadLabel = true; + break; + } + + /* Figure out the distance to the target */ + + srcOffs = emitCurCodeOffs(dst); + srcAddr = emitOffsetToPtr(srcOffs); + + if (id->idAddr()->iiaIsJitDataOffset()) + { + assert(loadConstant || loadLabel); + int doff = id->idAddr()->iiaGetJitDataOffset(); + assert(doff >= 0); + ssize_t imm = emitGetInsSC(id); + assert((imm >= 0) && (imm < 0x1000)); // 0x1000 is arbitrary, currently 'imm' is always 0 + + unsigned dataOffs = (unsigned)(doff + imm); + assert(dataOffs < emitDataSize()); + dstAddr = emitDataOffsetToPtr(dataOffs); + + regNumber dstReg = id->idReg1(); + regNumber addrReg = dstReg; // an integer register to compute long address. + emitAttr opSize = id->idOpSize(); + + if (loadConstant) + { + if (id->idjShort) + { + // ldr x/v, [rel addr] -- load constant from current addr(ip) + rel addr. + assert(ins == INS_ldr); + assert(fmt == IF_LS_1A); + distVal = (ssize_t)(dstAddr - srcAddr); + dst = emitOutputShortConstant(dst, ins, fmt, distVal, dstReg, opSize); + } + else + { + // adrp x, [rel page addr] -- compute page address: current page addr + rel page addr + assert(fmt == IF_LARGELDC); + ssize_t relPageAddr = + (((ssize_t)dstAddr & 0xFFFFFFFFFFFFF000LL) - ((ssize_t)srcAddr & 0xFFFFFFFFFFFFF000LL)) >> 12; + if (isVectorRegister(dstReg)) + { + // Update addrReg with the reserved integer register + // since we cannot use dstReg (vector) to load constant directly from memory. + addrReg = id->idReg2(); + assert(isGeneralRegister(addrReg)); + } + ins = INS_adrp; + fmt = IF_DI_1E; + dst = emitOutputShortAddress(dst, ins, fmt, relPageAddr, addrReg); + + // ldr x, [x, page offs] -- load constant from page address + page offset into integer register. + ssize_t imm12 = (ssize_t)dstAddr & 0xFFF; // 12 bits + assert(isValidUimm12(imm12)); + ins = INS_ldr; + fmt = IF_LS_2B; + dst = emitOutputShortConstant(dst, ins, fmt, imm12, addrReg, opSize); + + // fmov v, d -- copy constant in integer register to vector register. + // This is needed only for vector constant. + if (addrReg != dstReg) + { + // fmov Vd,Rn DV_2I X00111100X100111 000000nnnnnddddd 1E27 0000 Vd,Rn + // (scalar, from general) + assert(isVectorRegister(dstReg) && isGeneralRegister(addrReg)); + ins = INS_fmov; + fmt = IF_DV_2I; + code_t code = emitInsCode(ins, fmt); + + code |= insEncodeReg_Vd(dstReg); // ddddd + code |= insEncodeReg_Rn(addrReg); // nnnnn + if (id->idOpSize() == EA_8BYTE) + { + code |= 0x80400000; // X ... X + } + dst += emitOutput_Instr(dst, code); + } + } + } + else + { + assert(loadLabel); + dst = emitOutputLoadLabel(dst, srcAddr, dstAddr, id); + } + + return dst; + } + + assert(loadLabel || isJump); + + if (id->idAddr()->iiaHasInstrCount()) + { + assert(ig != NULL); + int instrCount = id->idAddr()->iiaGetInstrCount(); + unsigned insNum = emitFindInsNum(ig, id); + if (instrCount < 0) + { + // Backward branches using instruction count must be within the same instruction group. + assert(insNum + 1 >= (unsigned)(-instrCount)); + } + dstOffs = ig->igOffs + emitFindOffset(ig, (insNum + 1 + instrCount)); + dstAddr = emitOffsetToPtr(dstOffs); + } + else + { + dstOffs = id->idAddr()->iiaIGlabel->igOffs; + dstAddr = emitOffsetToPtr(dstOffs); + } + + distVal = (ssize_t)(dstAddr - srcAddr); + + if (dstOffs <= srcOffs) + { +#if DEBUG_EMIT + /* This is a backward jump - distance is known at this point */ + + if (id->idDebugOnlyInfo()->idNum == (unsigned)INTERESTING_JUMP_NUM || INTERESTING_JUMP_NUM == 0) + { + size_t blkOffs = id->idjIG->igOffs; + + if (INTERESTING_JUMP_NUM == 0) + printf("[3] Jump %u:\n", id->idDebugOnlyInfo()->idNum); + printf("[3] Jump block is at %08X - %02X = %08X\n", blkOffs, emitOffsAdj, blkOffs - emitOffsAdj); + printf("[3] Jump is at %08X - %02X = %08X\n", srcOffs, emitOffsAdj, srcOffs - emitOffsAdj); + printf("[3] Label block is at %08X - %02X = %08X\n", dstOffs, emitOffsAdj, dstOffs - emitOffsAdj); + } +#endif + } + else + { + /* This is a forward jump - distance will be an upper limit */ + + emitFwdJumps = true; + + /* The target offset will be closer by at least 'emitOffsAdj', but only if this + jump doesn't cross the hot-cold boundary. */ + + if (!emitJumpCrossHotColdBoundary(srcOffs, dstOffs)) + { + dstOffs -= emitOffsAdj; + distVal -= emitOffsAdj; + } + + /* Record the location of the jump for later patching */ + + id->idjOffs = dstOffs; + + /* Are we overflowing the id->idjOffs bitfield? */ + if (id->idjOffs != dstOffs) + IMPL_LIMITATION("Method is too large"); + +#if DEBUG_EMIT + if (id->idDebugOnlyInfo()->idNum == (unsigned)INTERESTING_JUMP_NUM || INTERESTING_JUMP_NUM == 0) + { + size_t blkOffs = id->idjIG->igOffs; + + if (INTERESTING_JUMP_NUM == 0) + printf("[4] Jump %u:\n", id->idDebugOnlyInfo()->idNum); + printf("[4] Jump block is at %08X\n", blkOffs); + printf("[4] Jump is at %08X\n", srcOffs); + printf("[4] Label block is at %08X - %02X = %08X\n", dstOffs + emitOffsAdj, emitOffsAdj, dstOffs); + } +#endif + } + +#ifdef DEBUG + if (0 && emitComp->verbose) + { + size_t sz = 4; + int distValSize = id->idjShort ? 4 : 8; + printf("; %s jump [%08X/%03u] from %0*X to %0*X: dist = %08XH\n", (dstOffs <= srcOffs) ? "Fwd" : "Bwd", + dspPtr(id), id->idDebugOnlyInfo()->idNum, distValSize, srcOffs + sz, distValSize, dstOffs, distVal); + } +#endif + + /* For forward jumps, record the address of the distance value */ + id->idjTemp.idjAddr = (distVal > 0) ? dst : NULL; + + if (emitJumpCrossHotColdBoundary(srcOffs, dstOffs)) + { + assert(!id->idjShort); + NYI_ARM64("Relocation Support for long address"); + } + + assert(insOptsNone(id->idInsOpt())); + + if (isJump) + { + if (id->idjShort) + { + // Short conditional/unconditional jump + assert(!id->idjKeepLong); + assert(emitJumpCrossHotColdBoundary(srcOffs, dstOffs) == false); + assert((fmt == IF_BI_0A) || (fmt == IF_BI_0B)); + } + else + { + // Long conditional jump + assert(fmt == IF_LARGEJMP); + // This is a pseudo-instruction format representing a large conditional branch, to allow + // us to get a greater branch target range than we can get by using a straightforward conditional + // branch. It is encoded as a short conditional branch that branches around a long unconditional + // branch. + // + // Conceptually, we have: + // + // b<cond> L_target + // + // The code we emit is: + // + // b<!cond> L_not // 4 bytes. Note that we reverse the condition. + // b L_target // 4 bytes + // L_not: + // + // Note that we don't actually insert any blocks: we simply encode "b <!cond> L_not" as a branch with + // the correct offset. Note also that this works for both integer and floating-point conditions, because + // the condition inversion takes ordered/unordered into account, preserving NaN behavior. For example, + // "GT" (greater than) is inverted to "LE" (less than, equal, or unordered). + dst = + emitOutputShortBranch(dst, + emitJumpKindToIns(emitReverseJumpKind( + emitInsToJumpKind(ins))), // reverse the conditional instruction + IF_BI_0B, + 8, /* 8 bytes from start of this large conditional pseudo-instruction to L_not. */ + nullptr /* only used for tbz/tbnzcbz/cbnz */); + + // Now, pretend we've got a normal unconditional branch, and fall through to the code to emit that. + ins = INS_b; + fmt = IF_BI_0A; + + // The distVal was computed based on the beginning of the pseudo-instruction, + // So subtract the size of the conditional branch so that it is relative to the + // unconditional branch. + distVal -= 4; + } + + dst = emitOutputShortBranch(dst, ins, fmt, distVal, id); + } + else if (loadLabel) + { + dst = emitOutputLoadLabel(dst, srcAddr, dstAddr, id); + } + + return dst; +} + +/***************************************************************************** +* +* Output a short branch instruction. +*/ +BYTE* emitter::emitOutputShortBranch(BYTE* dst, instruction ins, insFormat fmt, ssize_t distVal, instrDescJmp* id) +{ + code_t code = emitInsCode(ins, fmt); + + ssize_t loBits = (distVal & 3); + noway_assert(loBits == 0); + distVal >>= 2; // branch offset encodings are scaled by 4. + + if (fmt == IF_BI_0A) + { + // INS_b or INS_bl_local + noway_assert(isValidSimm26(distVal)); + distVal &= 0x3FFFFFFLL; + code |= distVal; + } + else if (fmt == IF_BI_0B) // BI_0B 01010100iiiiiiii iiiiiiiiiiiXXXXX simm19:00 + { + // INS_beq, INS_bne, etc... + noway_assert(isValidSimm19(distVal)); + distVal &= 0x7FFFFLL; + code |= distVal << 5; + } + else if (fmt == IF_BI_1A) // BI_1A X.......iiiiiiii iiiiiiiiiiittttt Rt simm19:00 + { + // INS_cbz or INS_cbnz + assert(id != nullptr); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + + noway_assert(isValidSimm19(distVal)); + distVal &= 0x7FFFFLL; // 19 bits + code |= distVal << 5; + } + else if (fmt == IF_BI_1B) // BI_1B B.......bbbbbiii iiiiiiiiiiittttt Rt imm6, simm14:00 + { + // INS_tbz or INS_tbnz + assert(id != nullptr); + ssize_t imm = emitGetInsSC(id); + assert(isValidImmShift(imm, id->idOpSize())); + + if (imm & 0x20) // test bit 32-63 ? + { + code |= 0x80000000; // B + } + code |= ((imm & 0x1F) << 19); // bbbbb + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + + noway_assert(isValidSimm14(distVal)); + distVal &= 0x3FFFLL; // 14 bits + code |= distVal << 5; + } + else + { + assert(!"Unknown fmt for emitOutputShortBranch"); + } + + dst += emitOutput_Instr(dst, code); + + return dst; +} + +/***************************************************************************** +* +* Output a short address instruction. +*/ +BYTE* emitter::emitOutputShortAddress(BYTE* dst, instruction ins, insFormat fmt, ssize_t distVal, regNumber reg) +{ + ssize_t loBits = (distVal & 3); + distVal >>= 2; + + code_t code = emitInsCode(ins, fmt); + if (fmt == IF_DI_1E) // DI_1E .ii.....iiiiiiii iiiiiiiiiiiddddd Rd simm21 + { + // INS_adr or INS_adrp + code |= insEncodeReg_Rd(reg); // ddddd + + noway_assert(isValidSimm19(distVal)); + distVal &= 0x7FFFFLL; // 19 bits + code |= distVal << 5; + code |= loBits << 29; // 2 bits + } + else + { + assert(!"Unknown fmt for emitOutputShortAddress"); + } + + dst += emitOutput_Instr(dst, code); + + return dst; +} + +/***************************************************************************** +* +* Output a short constant instruction. +*/ +BYTE* emitter::emitOutputShortConstant( + BYTE* dst, instruction ins, insFormat fmt, ssize_t imm, regNumber reg, emitAttr opSize) +{ + code_t code = emitInsCode(ins, fmt); + + if (fmt == IF_LS_1A) + { + // LS_1A XX...V..iiiiiiii iiiiiiiiiiittttt Rt simm21 + // INS_ldr or INS_ldrsw (PC-Relative) + + ssize_t loBits = (imm & 3); + noway_assert(loBits == 0); + ssize_t distVal = imm >>= 2; // load offset encodings are scaled by 4. + + noway_assert(isValidSimm19(distVal)); + + // Is the target a vector register? + if (isVectorRegister(reg)) + { + code |= insEncodeDatasizeVLS(code, opSize); // XX V + code |= insEncodeReg_Vt(reg); // ttttt + } + else + { + assert(isGeneralRegister(reg)); + // insEncodeDatasizeLS is not quite right for this case. + // So just specialize it. + if ((ins == INS_ldr) && (opSize == EA_8BYTE)) + { + // set the operation size in bit 30 + code |= 0x40000000; + } + + code |= insEncodeReg_Rt(reg); // ttttt + } + + distVal &= 0x7FFFFLL; // 19 bits + code |= distVal << 5; + } + else if (fmt == IF_LS_2B) + { + // ldr Rt,[Xn+pimm12] LS_2B 1X11100101iiiiii iiiiiinnnnnttttt B940 0000 imm(0-4095<<{2,3}) + // INS_ldr or INS_ldrsw (PC-Relative) + noway_assert(isValidUimm12(imm)); + assert(isGeneralRegister(reg)); + + if (opSize == EA_8BYTE) + { + // insEncodeDatasizeLS is not quite right for this case. + // So just specialize it. + if (ins == INS_ldr) + { + // set the operation size in bit 30 + code |= 0x40000000; + } + // Low 3 bits should be 0 -- 8 byte JIT data should be aligned on 8 byte. + assert((imm & 7) == 0); + imm >>= 3; + } + else + { + assert(opSize == EA_4BYTE); + // Low 2 bits should be 0 -- 4 byte aligned data. + assert((imm & 3) == 0); + imm >>= 2; + } + + code |= insEncodeReg_Rt(reg); // ttttt + code |= insEncodeReg_Rn(reg); // nnnnn + code |= imm << 10; + } + else + { + assert(!"Unknown fmt for emitOutputShortConstant"); + } + + dst += emitOutput_Instr(dst, code); + + return dst; +} +/***************************************************************************** + * + * Output a call instruction. + */ + +unsigned emitter::emitOutputCall(insGroup* ig, BYTE* dst, instrDesc* id, code_t code) +{ + const unsigned char callInstrSize = sizeof(code_t); // 4 bytes + regMaskTP gcrefRegs; + regMaskTP byrefRegs; + + VARSET_TP VARSET_INIT_NOCOPY(GCvars, VarSetOps::UninitVal()); + + // Is this a "fat" call descriptor? + if (id->idIsLargeCall()) + { + instrDescCGCA* idCall = (instrDescCGCA*)id; + gcrefRegs = idCall->idcGcrefRegs; + byrefRegs = idCall->idcByrefRegs; + VarSetOps::Assign(emitComp, GCvars, idCall->idcGCvars); + } + else + { + assert(!id->idIsLargeDsp()); + assert(!id->idIsLargeCns()); + + gcrefRegs = emitDecodeCallGCregs(id); + byrefRegs = 0; + VarSetOps::AssignNoCopy(emitComp, GCvars, VarSetOps::MakeEmpty(emitComp)); + } + + /* We update the GC info before the call as the variables cannot be + used by the call. Killing variables before the call helps with + boundary conditions if the call is CORINFO_HELP_THROW - see bug 50029. + If we ever track aliased variables (which could be used by the + call), we would have to keep them alive past the call. */ + + emitUpdateLiveGCvars(GCvars, dst); + + // Now output the call instruction and update the 'dst' pointer + // + unsigned outputInstrSize = emitOutput_Instr(dst, code); + dst += outputInstrSize; + + // All call instructions are 4-byte in size on ARM64 + // + assert(outputInstrSize == callInstrSize); + + // If the method returns a GC ref, mark INTRET (R0) appropriately. + if (id->idGCref() == GCT_GCREF) + { + gcrefRegs |= RBM_INTRET; + } + else if (id->idGCref() == GCT_BYREF) + { + byrefRegs |= RBM_INTRET; + } + + // If is a multi-register return method is called, mark INTRET_1 (X1) appropriately + if (id->idIsLargeCall()) + { + instrDescCGCA* idCall = (instrDescCGCA*)id; + if (idCall->idSecondGCref() == GCT_GCREF) + { + gcrefRegs |= RBM_INTRET_1; + } + else if (idCall->idSecondGCref() == GCT_BYREF) + { + byrefRegs |= RBM_INTRET_1; + } + } + + // If the GC register set has changed, report the new set. + if (gcrefRegs != emitThisGCrefRegs) + { + emitUpdateLiveGCregs(GCT_GCREF, gcrefRegs, dst); + } + // If the Byref register set has changed, report the new set. + if (byrefRegs != emitThisByrefRegs) + { + emitUpdateLiveGCregs(GCT_BYREF, byrefRegs, dst); + } + + // Some helper calls may be marked as not requiring GC info to be recorded. + if ((!id->idIsNoGC())) + { + // On ARM64, as on AMD64, we don't change the stack pointer to push/pop args. + // So we're not really doing a "stack pop" here (note that "args" is 0), but we use this mechanism + // to record the call for GC info purposes. (It might be best to use an alternate call, + // and protect "emitStackPop" under the EMIT_TRACK_STACK_DEPTH preprocessor variable.) + emitStackPop(dst, /*isCall*/ true, callInstrSize, /*args*/ 0); + + // Do we need to record a call location for GC purposes? + // + if (!emitFullGCinfo) + { + emitRecordGCcall(dst, callInstrSize); + } + } + return callInstrSize; +} + +/***************************************************************************** + * + * Emit a 32-bit Arm64 instruction + */ + +/*static*/ unsigned emitter::emitOutput_Instr(BYTE* dst, code_t code) +{ + assert(sizeof(code_t) == 4); + *((code_t*)dst) = code; + + return sizeof(code_t); +} + +/***************************************************************************** +* + * Append the machine code corresponding to the given instruction descriptor + * to the code block at '*dp'; the base of the code block is 'bp', and 'ig' + * is the instruction group that contains the instruction. Updates '*dp' to + * point past the generated code, and returns the size of the instruction + * descriptor in bytes. + */ + +size_t emitter::emitOutputInstr(insGroup* ig, instrDesc* id, BYTE** dp) +{ + BYTE* dst = *dp; + BYTE* odst = dst; + code_t code = 0; + size_t sz = emitGetInstrDescSize(id); // TODO-ARM64-Cleanup: on ARM, this is set in each case. why? + instruction ins = id->idIns(); + insFormat fmt = id->idInsFmt(); + emitAttr size = id->idOpSize(); + unsigned char callInstrSize = 0; + unsigned condcode; + +#ifdef DEBUG +#if DUMP_GC_TABLES + bool dspOffs = emitComp->opts.dspGCtbls; +#else + bool dspOffs = !emitComp->opts.disDiffable; +#endif +#endif // DEBUG + + assert(REG_NA == (int)REG_NA); + + VARSET_TP VARSET_INIT_NOCOPY(GCvars, VarSetOps::UninitVal()); + + /* What instruction format have we got? */ + + switch (fmt) + { + ssize_t imm; + ssize_t index; + ssize_t index2; + unsigned scale; + unsigned cmode; + unsigned immShift; + bool hasShift; + emitAttr extSize; + emitAttr elemsize; + emitAttr datasize; + + case IF_BI_0A: // BI_0A ......iiiiiiiiii iiiiiiiiiiiiiiii simm26:00 + case IF_BI_0B: // BI_0B ......iiiiiiiiii iiiiiiiiiii..... simm19:00 + case IF_LARGEJMP: + assert(id->idGCref() == GCT_NONE); + assert(id->idIsBound()); + dst = emitOutputLJ(ig, dst, id); + sz = sizeof(instrDescJmp); + break; + + case IF_BI_0C: // BI_0C ......iiiiiiiiii iiiiiiiiiiiiiiii simm26:00 + code = emitInsCode(ins, fmt); + sz = id->idIsLargeCall() ? sizeof(instrDescCGCA) : sizeof(instrDesc); + dst += emitOutputCall(ig, dst, id, code); + // Always call RecordRelocation so that we wire in a JumpStub when we don't reach + emitRecordRelocation(odst, id->idAddr()->iiaAddr, IMAGE_REL_ARM64_BRANCH26); + break; + + case IF_BI_1A: // BI_1A ......iiiiiiiiii iiiiiiiiiiittttt Rt simm19:00 + assert(insOptsNone(id->idInsOpt())); + assert(id->idIsBound()); + + dst = emitOutputLJ(ig, dst, id); + sz = sizeof(instrDescJmp); + break; + + case IF_BI_1B: // BI_1B B.......bbbbbiii iiiiiiiiiiittttt Rt imm6, simm14:00 + assert(insOptsNone(id->idInsOpt())); + assert(id->idIsBound()); + + dst = emitOutputLJ(ig, dst, id); + sz = sizeof(instrDescJmp); + break; + + case IF_BR_1A: // BR_1A ................ ......nnnnn..... Rn + assert(insOptsNone(id->idInsOpt())); + assert((ins == INS_ret) || (ins == INS_br)); + code = emitInsCode(ins, fmt); + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + + dst += emitOutput_Instr(dst, code); + break; + + case IF_BR_1B: // BR_1B ................ ......nnnnn..... Rn + assert(insOptsNone(id->idInsOpt())); + assert((ins == INS_br_tail) || (ins == INS_blr)); + code = emitInsCode(ins, fmt); + code |= insEncodeReg_Rn(id->idReg3()); // nnnnn + + sz = id->idIsLargeCall() ? sizeof(instrDescCGCA) : sizeof(instrDesc); + dst += emitOutputCall(ig, dst, id, code); + break; + + case IF_LS_1A: // LS_1A XX...V..iiiiiiii iiiiiiiiiiittttt Rt PC imm(1MB) + case IF_LARGELDC: + assert(insOptsNone(id->idInsOpt())); + assert(id->idIsBound()); + + dst = emitOutputLJ(ig, dst, id); + sz = sizeof(instrDescJmp); + break; + + case IF_LS_2A: // LS_2A .X.......X...... ......nnnnnttttt Rt Rn + assert(insOptsNone(id->idInsOpt())); + code = emitInsCode(ins, fmt); + // Is the target a vector register? + if (isVectorRegister(id->idReg1())) + { + code &= 0x3FFFFFFF; // clear the size bits + code |= insEncodeDatasizeVLS(code, id->idOpSize()); // XX + code |= insEncodeReg_Vt(id->idReg1()); // ttttt + } + else + { + code |= insEncodeDatasizeLS(code, id->idOpSize()); // .X.......X + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + } + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_LS_2B: // LS_2B .X.......Xiiiiii iiiiiinnnnnttttt Rt Rn imm(0-4095) + assert(insOptsNone(id->idInsOpt())); + imm = emitGetInsSC(id); + assert(isValidUimm12(imm)); + code = emitInsCode(ins, fmt); + // Is the target a vector register? + if (isVectorRegister(id->idReg1())) + { + code &= 0x3FFFFFFF; // clear the size bits + code |= insEncodeDatasizeVLS(code, id->idOpSize()); // XX + code |= insEncodeReg_Vt(id->idReg1()); // ttttt + } + else + { + code |= insEncodeDatasizeLS(code, id->idOpSize()); // .X.......X + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + } + code |= ((code_t)imm << 10); // iiiiiiiiiiii + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_LS_2C: // LS_2C .X.......X.iiiii iiiiPPnnnnnttttt Rt Rn imm(-256..+255) no/pre/post inc + assert(insOptsNone(id->idInsOpt()) || insOptsIndexed(id->idInsOpt())); + imm = emitGetInsSC(id); + assert((imm >= -256) && (imm <= 255)); // signed 9 bits + imm &= 0x1ff; // force into unsigned 9 bit representation + code = emitInsCode(ins, fmt); + // Is the target a vector register? + if (isVectorRegister(id->idReg1())) + { + code &= 0x3FFFFFFF; // clear the size bits + code |= insEncodeDatasizeVLS(code, id->idOpSize()); // XX + code |= insEncodeReg_Vt(id->idReg1()); // ttttt + } + else + { + code |= insEncodeDatasizeLS(code, id->idOpSize()); // .X.......X + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + } + code |= insEncodeIndexedOpt(id->idInsOpt()); // PP + code |= ((code_t)imm << 12); // iiiiiiiii + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_LS_3A: // LS_3A .X.......X.mmmmm oooS..nnnnnttttt Rt Rn Rm ext(Rm) LSL {} + assert(insOptsLSExtend(id->idInsOpt())); + code = emitInsCode(ins, fmt); + // Is the target a vector register? + if (isVectorRegister(id->idReg1())) + { + code &= 0x3FFFFFFF; // clear the size bits + code |= insEncodeDatasizeVLS(code, id->idOpSize()); // XX + code |= insEncodeReg_Vt(id->idReg1()); // ttttt + } + else + { + code |= insEncodeDatasizeLS(code, id->idOpSize()); // .X.......X + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + } + code |= insEncodeExtend(id->idInsOpt()); // ooo + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + if (id->idIsLclVar()) + { + code |= insEncodeReg_Rm(codeGen->rsGetRsvdReg()); // mmmmm + } + else + { + code |= insEncodeReg3Scale(id->idReg3Scaled()); // S + code |= insEncodeReg_Rm(id->idReg3()); // mmmmm + } + dst += emitOutput_Instr(dst, code); + break; + + case IF_LS_3B: // LS_3B X............... .aaaaannnnnddddd Rd Ra Rn + assert(insOptsNone(id->idInsOpt())); + code = emitInsCode(ins, fmt); + // Is the target a vector register? + if (isVectorRegister(id->idReg1())) + { + code &= 0x3FFFFFFF; // clear the size bits + code |= insEncodeDatasizeVPLS(code, id->idOpSize()); // XX + code |= insEncodeReg_Vt(id->idReg1()); // ttttt + code |= insEncodeReg_Va(id->idReg2()); // aaaaa + } + else + { + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + code |= insEncodeReg_Ra(id->idReg2()); // aaaaa + } + code |= insEncodeReg_Rn(id->idReg3()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_LS_3C: // LS_3C X......PP.iiiiii iaaaaannnnnddddd Rd Ra Rn imm(im7,sh) + assert(insOptsNone(id->idInsOpt()) || insOptsIndexed(id->idInsOpt())); + imm = emitGetInsSC(id); + assert((imm >= -64) && (imm <= 63)); // signed 7 bits + imm &= 0x7f; // force into unsigned 7 bit representation + code = emitInsCode(ins, fmt); + // Is the target a vector register? + if (isVectorRegister(id->idReg1())) + { + code &= 0x3FFFFFFF; // clear the size bits + code |= insEncodeDatasizeVPLS(code, id->idOpSize()); // XX + code |= insEncodeReg_Vt(id->idReg1()); // ttttt + code |= insEncodeReg_Va(id->idReg2()); // aaaaa + } + else + { + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rt(id->idReg1()); // ttttt + code |= insEncodeReg_Ra(id->idReg2()); // aaaaa + } + code |= insEncodePairIndexedOpt(ins, id->idInsOpt()); // PP + code |= ((code_t)imm << 15); // iiiiiiiii + code |= insEncodeReg_Rn(id->idReg3()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DI_1A: // DI_1A X.......shiiiiii iiiiiinnnnn..... Rn imm(i12,sh) + assert(insOptsNone(id->idInsOpt()) || insOptsLSL12(id->idInsOpt())); + imm = emitGetInsSC(id); + assert(isValidUimm12(imm)); + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeShiftImm12(id->idInsOpt()); // sh + code |= ((code_t)imm << 10); // iiiiiiiiiiii + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DI_1B: // DI_1B X........hwiiiii iiiiiiiiiiiddddd Rd imm(i16,hw) + imm = emitGetInsSC(id); + assert(isValidImmHWVal(imm, id->idOpSize())); + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= ((code_t)imm << 5); // hwiiiii iiiiiiiiiii + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + dst += emitOutput_Instr(dst, code); + break; + + case IF_DI_1C: // DI_1C X........Nrrrrrr ssssssnnnnn..... Rn imm(N,r,s) + imm = emitGetInsSC(id); + assert(isValidImmNRS(imm, id->idOpSize())); + code = emitInsCode(ins, fmt); + code |= ((code_t)imm << 10); // Nrrrrrrssssss + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DI_1D: // DI_1D X........Nrrrrrr ssssss.....ddddd Rd imm(N,r,s) + imm = emitGetInsSC(id); + assert(isValidImmNRS(imm, id->idOpSize())); + code = emitInsCode(ins, fmt); + code |= ((code_t)imm << 10); // Nrrrrrrssssss + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + dst += emitOutput_Instr(dst, code); + break; + + case IF_DI_1E: // DI_1E .ii.....iiiiiiii iiiiiiiiiiiddddd Rd simm21 + case IF_LARGEADR: + assert(insOptsNone(id->idInsOpt())); + if (id->idIsReloc()) + { + code = emitInsCode(ins, fmt); + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + dst += emitOutput_Instr(dst, code); + emitRecordRelocation(odst, id->idAddr()->iiaAddr, IMAGE_REL_ARM64_PAGEBASE_REL21); + } + else + { + // Local jmp/load case which does not need a relocation. + assert(id->idIsBound()); + dst = emitOutputLJ(ig, dst, id); + } + sz = sizeof(instrDescJmp); + break; + + case IF_DI_1F: // DI_1F X..........iiiii cccc..nnnnn.nzcv Rn imm5 nzcv cond + imm = emitGetInsSC(id); + assert(isValidImmCondFlagsImm5(imm)); + { + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + code |= ((code_t)cfi.imm5 << 16); // iiiii + code |= insEncodeFlags(cfi.flags); // nzcv + code |= insEncodeCond(cfi.cond); // cccc + dst += emitOutput_Instr(dst, code); + } + break; + + case IF_DI_2A: // DI_2A X.......shiiiiii iiiiiinnnnnddddd Rd Rn imm(i12,sh) + assert(insOptsNone(id->idInsOpt()) || insOptsLSL12(id->idInsOpt())); + imm = emitGetInsSC(id); + assert(isValidUimm12(imm)); + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeShiftImm12(id->idInsOpt()); // sh + code |= ((code_t)imm << 10); // iiiiiiiiiiii + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + + if (id->idIsReloc()) + { + assert(sz == sizeof(instrDesc)); + assert(id->idAddr()->iiaAddr != nullptr); + emitRecordRelocation(odst, id->idAddr()->iiaAddr, IMAGE_REL_ARM64_PAGEOFFSET_12A); + } + break; + + case IF_DI_2B: // DI_2B X.........Xnnnnn ssssssnnnnnddddd Rd Rn imm(0-63) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + assert(isValidImmShift(imm, id->idOpSize())); + code |= insEncodeDatasizeBF(code, id->idOpSize()); // X........X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + code |= insEncodeReg_Rm(id->idReg2()); // Reg2 also in mmmmm + code |= insEncodeShiftCount(imm, id->idOpSize()); // ssssss + dst += emitOutput_Instr(dst, code); + break; + + case IF_DI_2C: // DI_2C X........Nrrrrrr ssssssnnnnnddddd Rd Rn imm(N,r,s) + imm = emitGetInsSC(id); + assert(isValidImmNRS(imm, id->idOpSize())); + code = emitInsCode(ins, fmt); + code |= ((code_t)imm << 10); // Nrrrrrrssssss + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DI_2D: // DI_2D X........Nrrrrrr ssssssnnnnnddddd Rd Rn imr, imms (N,r,s) + if (ins == INS_asr || ins == INS_lsl || ins == INS_lsr) + { + imm = emitGetInsSC(id); + assert(isValidImmShift(imm, id->idOpSize())); + + // Shift immediates are aliases of the SBFM/UBFM instructions + // that actually take 2 registers and 2 constants, + // Since we stored the shift immediate value + // we need to calculate the N,R and S values here. + + bitMaskImm bmi; + bmi.immNRS = 0; + + bmi.immN = (size == EA_8BYTE) ? 1 : 0; + bmi.immR = imm; + bmi.immS = (size == EA_8BYTE) ? 0x3f : 0x1f; + + // immR and immS are now set correctly for INS_asr and INS_lsr + // but for INS_lsl we have to adjust the values for immR and immS + // + if (ins == INS_lsl) + { + bmi.immR = -imm & bmi.immS; + bmi.immS = bmi.immS - imm; + } + + // setup imm with the proper 13 bit value N:R:S + // + imm = bmi.immNRS; + } + else + { + // The other instructions have already have encoded N,R and S values + imm = emitGetInsSC(id); + } + assert(isValidImmNRS(imm, id->idOpSize())); + + code = emitInsCode(ins, fmt); + code |= ((code_t)imm << 10); // Nrrrrrrssssss + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_1D: // DR_1D X............... cccc.......ddddd Rd cond + imm = emitGetInsSC(id); + assert(isValidImmCond(imm)); + { + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeInvertedCond(cfi.cond); // cccc + dst += emitOutput_Instr(dst, code); + } + break; + + case IF_DR_2A: // DR_2A X..........mmmmm ......nnnnn..... Rn Rm + assert(insOptsNone(id->idInsOpt())); + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + code |= insEncodeReg_Rm(id->idReg2()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_2B: // DR_2B X.......sh.mmmmm ssssssnnnnn..... Rn Rm {LSL,LSR,ASR,ROR} imm(0-63) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + assert(isValidImmShift(imm, id->idOpSize())); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeShiftType(id->idInsOpt()); // sh + code |= insEncodeShiftCount(imm, id->idOpSize()); // ssssss + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + code |= insEncodeReg_Rm(id->idReg2()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_2C: // DR_2C X..........mmmmm ooosssnnnnn..... Rn Rm ext(Rm) LSL imm(0-4) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + assert((imm >= 0) && (imm <= 4)); // imm [0..4] + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeExtend(id->idInsOpt()); // ooo + code |= insEncodeExtendScale(imm); // sss + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + code |= insEncodeReg_Rm(id->idReg2()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_2D: // DR_2D X..........nnnnn cccc..nnnnnddddd Rd Rn cond + imm = emitGetInsSC(id); + assert(isValidImmCond(imm)); + { + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + code |= insEncodeReg_Rm(id->idReg2()); // mmmmm + code |= insEncodeInvertedCond(cfi.cond); // cccc + dst += emitOutput_Instr(dst, code); + } + break; + + case IF_DR_2E: // DR_2E X..........mmmmm ...........ddddd Rd Rm + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rm(id->idReg2()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_2F: // DR_2F X.......sh.mmmmm ssssss.....ddddd Rd Rm {LSL,LSR,ASR} imm(0-63) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + assert(isValidImmShift(imm, id->idOpSize())); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeShiftType(id->idInsOpt()); // sh + code |= insEncodeShiftCount(imm, id->idOpSize()); // ssssss + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rm(id->idReg2()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_2G: // DR_2G X............... .....xnnnnnddddd Rd Rn + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + if (ins == INS_rev) + { + if (size == EA_8BYTE) + { + code |= 0x00000400; // x - bit at location 10 + } + } + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_2H: // DR_2H X........X...... ......nnnnnddddd Rd Rn + code = emitInsCode(ins, fmt); + code |= insEncodeDatasizeBF(code, id->idOpSize()); // X........X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_2I: // DR_2I X..........mmmmm cccc..nnnnn.nzcv Rn Rm nzcv cond + imm = emitGetInsSC(id); + assert(isValidImmCondFlags(imm)); + { + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rn(id->idReg1()); // nnnnn + code |= insEncodeReg_Rm(id->idReg2()); // mmmmm + code |= insEncodeFlags(cfi.flags); // nzcv + code |= insEncodeCond(cfi.cond); // cccc + dst += emitOutput_Instr(dst, code); + } + break; + + case IF_DR_3A: // DR_3A X..........mmmmm ......nnnnnmmmmm Rd Rn Rm + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + if (id->idIsLclVar()) + { + code |= insEncodeReg_Rm(codeGen->rsGetRsvdReg()); // mmmmm + } + else + { + code |= insEncodeReg_Rm(id->idReg3()); // mmmmm + } + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_3B: // DR_3B X.......sh.mmmmm ssssssnnnnnddddd Rd Rn Rm {LSL,LSR,ASR} imm(0-63) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + assert(isValidImmShift(imm, id->idOpSize())); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + code |= insEncodeReg_Rm(id->idReg3()); // mmmmm + code |= insEncodeShiftType(id->idInsOpt()); // sh + code |= insEncodeShiftCount(imm, id->idOpSize()); // ssssss + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_3C: // DR_3C X..........mmmmm ooosssnnnnnddddd Rd Rn Rm ext(Rm) LSL imm(0-4) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + assert((imm >= 0) && (imm <= 4)); // imm [0..4] + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeExtend(id->idInsOpt()); // ooo + code |= insEncodeExtendScale(imm); // sss + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + code |= insEncodeReg_Rm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_3D: // DR_3D X..........mmmmm cccc..nnnnnddddd Rd Rn Rm cond + imm = emitGetInsSC(id); + assert(isValidImmCond(imm)); + { + condFlagsImm cfi; + cfi.immCFVal = (unsigned)imm; + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + code |= insEncodeReg_Rm(id->idReg3()); // mmmmm + code |= insEncodeCond(cfi.cond); // cccc + dst += emitOutput_Instr(dst, code); + } + break; + + case IF_DR_3E: // DR_3E X........X.mmmmm ssssssnnnnnddddd Rd Rn Rm imm(0-63) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + assert(isValidImmShift(imm, id->idOpSize())); + code |= insEncodeDatasizeBF(code, id->idOpSize()); // X........X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + code |= insEncodeReg_Rm(id->idReg3()); // mmmmm + code |= insEncodeShiftCount(imm, id->idOpSize()); // ssssss + dst += emitOutput_Instr(dst, code); + break; + + case IF_DR_4A: // DR_4A X..........mmmmm .aaaaannnnnmmmmm Rd Rn Rm Ra + code = emitInsCode(ins, fmt); + code |= insEncodeDatasize(id->idOpSize()); // X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + code |= insEncodeReg_Rm(id->idReg3()); // mmmmm + code |= insEncodeReg_Ra(id->idReg4()); // aaaaa + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_1A: // DV_1A .........X.iiiii iii........ddddd Vd imm8 (fmov - immediate scalar) + imm = emitGetInsSC(id); + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeFloatElemsize(elemsize); // X + code |= ((code_t)imm << 13); // iiiii iii + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_1B: // DV_1B .QX..........iii cmod..iiiiiddddd Vd imm8 (immediate vector) + imm = emitGetInsSC(id) & 0x0ff; + immShift = (emitGetInsSC(id) & 0x700) >> 8; + elemsize = optGetElemsize(id->idInsOpt()); + cmode = 0; + switch (elemsize) + { // cmode + case EA_1BYTE: + cmode = 0xE; // 1110 + break; + case EA_2BYTE: + cmode = 0x8; + cmode |= (immShift << 1); // 10x0 + break; + case EA_4BYTE: + if (immShift < 4) + { + cmode = 0x0; + cmode |= (immShift << 1); // 0xx0 + } + else // MSL + { + cmode = 0xC; + if (immShift & 2) + cmode |= 1; // 110x + } + break; + case EA_8BYTE: + cmode = 0xE; // 1110 + break; + default: + // TODO-Cleanup: add unreached() here + break; + } + + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(id->idOpSize()); // Q + if ((ins == INS_fmov) || (ins == INS_movi)) + { + if (elemsize == EA_8BYTE) + { + code |= 0x20000000; // X + } + } + if (ins != INS_fmov) + { + assert((cmode >= 0) && (cmode <= 0xF)); + code |= (cmode << 12); // cmod + } + code |= (((code_t)imm >> 5) << 16); // iii + code |= (((code_t)imm & 0x1f) << 5); // iiiii + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_1C: // DV_1C .........X...... ......nnnnn..... Vn #0.0 (fcmp - with zero) + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeReg_Vn(id->idReg1()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2A: // DV_2A .Q.......X...... ......nnnnnddddd Vd Vn (fabs, fcvt - vector) + elemsize = optGetElemsize(id->idInsOpt()); + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2B: // DV_2B .Q.........iiiii ......nnnnnddddd Rd Vn[] (umov/smov - to general) + elemsize = id->idOpSize(); + index = emitGetInsSC(id); + datasize = (elemsize == EA_8BYTE) ? EA_16BYTE : EA_8BYTE; + if (ins == INS_smov) + { + datasize = EA_16BYTE; + } + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(datasize); // Q + code |= insEncodeVectorIndex(elemsize, index); // iiiii + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2C: // DV_2C .Q.........iiiii ......nnnnnddddd Vd Rn (dup/ins - vector from general) + if (ins == INS_dup) + { + datasize = id->idOpSize(); + elemsize = optGetElemsize(id->idInsOpt()); + index = 0; + } + else // INS_ins + { + datasize = EA_16BYTE; + elemsize = id->idOpSize(); + index = emitGetInsSC(id); + } + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(datasize); // Q + code |= insEncodeVectorIndex(elemsize, index); // iiiii + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2D: // DV_2D .Q.........iiiii ......nnnnnddddd Vd Vn[] (dup - vector) + index = emitGetInsSC(id); + elemsize = optGetElemsize(id->idInsOpt()); + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeVectorIndex(elemsize, index); // iiiii + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2E: // DV_2E ...........iiiii ......nnnnnddddd Vd Vn[] (dup - scalar) + index = emitGetInsSC(id); + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeVectorIndex(elemsize, index); // iiiii + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2F: // DV_2F ...........iiiii .jjjj.nnnnnddddd Vd[] Vn[] (ins - element) + elemsize = id->idOpSize(); + imm = emitGetInsSC(id); + index = (imm >> 4) & 0xf; + index2 = imm & 0xf; + code = emitInsCode(ins, fmt); + code |= insEncodeVectorIndex(elemsize, index); // iiiii + code |= insEncodeVectorIndex2(elemsize, index2); // jjjj + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2G: // DV_2G .........X...... ......nnnnnddddd Vd Vn (fmov,fcvtXX - register) + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2H: // DV_2H X........X...... ......nnnnnddddd Rd Vn (fmov - to general) + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeConvertOpt(fmt, id->idInsOpt()); // X X + code |= insEncodeReg_Rd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2I: // DV_2I X........X...... ......nnnnnddddd Vd Rn (fmov - from general) + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeConvertOpt(fmt, id->idInsOpt()); // X X + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Rn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2J: // DV_2J ........SS.....D D.....nnnnnddddd Vd Vn (fcvt) + code = emitInsCode(ins, fmt); + code |= insEncodeConvertOpt(fmt, id->idInsOpt()); // SS DD + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2K: // DV_2K .........X.mmmmm ......nnnnn..... Vn Vm (fcmp) + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeReg_Vn(id->idReg1()); // nnnnn + code |= insEncodeReg_Vm(id->idReg2()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2L: // DV_2L ........XX...... ......nnnnnddddd Vd Vn (abs, neg - scalar) + elemsize = id->idOpSize(); + code = emitInsCode(ins, fmt); + code |= insEncodeElemsize(elemsize); // XX + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2M: // DV_2M .Q......XX...... ......nnnnnddddd Vd Vn (abs, neg - vector) + elemsize = optGetElemsize(id->idInsOpt()); + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeElemsize(elemsize); // XX + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2N: // DV_2N .........iiiiiii ......nnnnnddddd Vd Vn imm (shift - scalar) + imm = emitGetInsSC(id); + code = emitInsCode(ins, fmt); + code |= insEncodeVectorShift(EA_8BYTE, imm); // iiiiiii + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_2O: // DV_2O .Q.......iiiiiii ......nnnnnddddd Vd Vn imm (shift - vector) + imm = emitGetInsSC(id); + elemsize = optGetElemsize(id->idInsOpt()); + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeVectorShift(elemsize, imm); // iiiiiii + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3A: // DV_3A .Q......XX.mmmmm ......nnnnnddddd Vd Vn Vm (vector) + code = emitInsCode(ins, fmt); + elemsize = optGetElemsize(id->idInsOpt()); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeElemsize(elemsize); // XX + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3AI: // DV_3AI .Q......XXLMmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + elemsize = optGetElemsize(id->idInsOpt()); + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeElemsize(elemsize); // XX + code |= insEncodeVectorIndexLMH(elemsize, imm); // LM H + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3B: // DV_3B .Q.......X.mmmmm ......nnnnnddddd Vd Vn Vm (vector) + code = emitInsCode(ins, fmt); + elemsize = optGetElemsize(id->idInsOpt()); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3BI: // DV_3BI .Q.......XLmmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector by elem) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + elemsize = optGetElemsize(id->idInsOpt()); + assert(isValidVectorIndex(id->idOpSize(), elemsize, imm)); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeFloatIndex(elemsize, imm); // L H + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3C: // DV_3C .Q.........mmmmm ......nnnnnddddd Vd Vn Vm (vector) + code = emitInsCode(ins, fmt); + code |= insEncodeVectorsize(id->idOpSize()); // Q + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3D: // DV_3D .........X.mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + code = emitInsCode(ins, fmt); + code |= insEncodeFloatElemsize(id->idOpSize()); // X + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3DI: // DV_3DI .........XLmmmmm ....H.nnnnnddddd Vd Vn Vm[] (scalar by elem) + code = emitInsCode(ins, fmt); + imm = emitGetInsSC(id); + elemsize = id->idOpSize(); + assert(isValidVectorIndex(EA_16BYTE, elemsize, imm)); + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeFloatIndex(elemsize, imm); // L H + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_3E: // DV_3E ...........mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + code = emitInsCode(ins, fmt); + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + dst += emitOutput_Instr(dst, code); + break; + + case IF_DV_4A: // DV_4A .........X.mmmmm .aaaaannnnnddddd Vd Va Vn Vm (scalar) + code = emitInsCode(ins, fmt); + elemsize = id->idOpSize(); + code |= insEncodeFloatElemsize(elemsize); // X + code |= insEncodeReg_Vd(id->idReg1()); // ddddd + code |= insEncodeReg_Vn(id->idReg2()); // nnnnn + code |= insEncodeReg_Vm(id->idReg3()); // mmmmm + code |= insEncodeReg_Va(id->idReg4()); // aaaaa + dst += emitOutput_Instr(dst, code); + break; + + case IF_SN_0A: // SN_0A ................ ................ + code = emitInsCode(ins, fmt); + dst += emitOutput_Instr(dst, code); + break; + + case IF_SI_0A: // SI_0A ...........iiiii iiiiiiiiiii..... imm16 + imm = emitGetInsSC(id); + assert(isValidUimm16(imm)); + code = emitInsCode(ins, fmt); + code |= ((code_t)imm << 5); // iiiii iiiiiiiiiii + dst += emitOutput_Instr(dst, code); + break; + + case IF_SI_0B: // SI_0B ................ ....bbbb........ imm4 - barrier + imm = emitGetInsSC(id); + assert((imm >= 0) && (imm <= 15)); + code = emitInsCode(ins, fmt); + code |= ((code_t)imm << 8); // bbbb + dst += emitOutput_Instr(dst, code); + break; + + default: + assert(!"Unexpected format"); + break; + } + + // Determine if any registers now hold GC refs, or whether a register that was overwritten held a GC ref. + // We assume here that "id->idGCref()" is not GC_NONE only if the instruction described by "id" writes a + // GC ref to register "id->idReg1()". (It may, apparently, also not be GC_NONE in other cases, such as + // for stores, but we ignore those cases here.) + if (emitInsMayWriteToGCReg(id)) // True if "id->idIns()" writes to a register than can hold GC ref. + { + // If we ever generate instructions that write to multiple registers, + // then we'd need to more work here to ensure that changes in the status of GC refs are + // tracked properly. + if (emitInsMayWriteMultipleRegs(id)) + { + // INS_ldp etc... + // We assume that "idReg1" and "idReg2" are the destination register for all instructions + emitGCregDeadUpd(id->idReg1(), dst); + emitGCregDeadUpd(id->idReg2(), dst); + } + else + { + // We assume that "idReg1" is the destination register for all instructions + if (id->idGCref() != GCT_NONE) + { + emitGCregLiveUpd(id->idGCref(), id->idReg1(), dst); + } + else + { + emitGCregDeadUpd(id->idReg1(), dst); + } + } + } + + // Now we determine if the instruction has written to a (local variable) stack location, and either written a GC + // ref or overwritten one. + if (emitInsWritesToLclVarStackLoc(id)) + { + int varNum = id->idAddr()->iiaLclVar.lvaVarNum(); + unsigned ofs = AlignDown(id->idAddr()->iiaLclVar.lvaOffset(), sizeof(size_t)); + bool FPbased; + int adr = emitComp->lvaFrameAddress(varNum, &FPbased); + if (id->idGCref() != GCT_NONE) + { + emitGCvarLiveUpd(adr + ofs, varNum, id->idGCref(), dst); + } + else + { + // If the type of the local is a gc ref type, update the liveness. + var_types vt; + if (varNum >= 0) + { + // "Regular" (non-spill-temp) local. + vt = var_types(emitComp->lvaTable[varNum].lvType); + } + else + { + TempDsc* tmpDsc = emitComp->tmpFindNum(varNum); + vt = tmpDsc->tdTempType(); + } + if (vt == TYP_REF || vt == TYP_BYREF) + emitGCvarDeadUpd(adr + ofs, dst); + } + } + +#ifdef DEBUG + /* Make sure we set the instruction descriptor size correctly */ + + size_t expected = emitSizeOfInsDsc(id); + assert(sz == expected); + + if (emitComp->opts.disAsm || emitComp->opts.dspEmit || emitComp->verbose) + { + emitDispIns(id, false, dspOffs, true, emitCurCodeOffs(odst), *dp, (dst - *dp), ig); + } + + if (emitComp->compDebugBreak) + { + // For example, set JitBreakEmitOutputInstr=a6 will break when this method is called for + // emitting instruction a6, (i.e. IN00a6 in jitdump). + if ((unsigned)JitConfig.JitBreakEmitOutputInstr() == id->idDebugOnlyInfo()->idNum) + { + assert(!"JitBreakEmitOutputInstr reached"); + } + } +#endif + + /* All instructions are expected to generate code */ + + assert(*dp != dst); + + *dp = dst; + + return sz; +} + +/*****************************************************************************/ +/*****************************************************************************/ + +#ifdef DEBUG + +/***************************************************************************** + * + * Display the instruction name + */ +void emitter::emitDispInst(instruction ins) +{ + const char* insstr = codeGen->genInsName(ins); + size_t len = strlen(insstr); + + /* Display the instruction name */ + + printf("%s", insstr); + + // + // Add at least one space after the instruction name + // and add spaces until we have reach the normal size of 8 + do + { + printf(" "); + len++; + } while (len < 8); +} + +/***************************************************************************** + * + * Display an reloc value + * If we are formatting for an assembly listing don't print the hex value + * since it will prevent us from doing assembly diffs + */ +void emitter::emitDispReloc(int value, bool addComma) +{ + if (emitComp->opts.disAsm) + { + printf("(reloc)"); + } + else + { + printf("(reloc 0x%x)", dspPtr(value)); + } + + if (addComma) + printf(", "); +} + +/***************************************************************************** + * + * Display an immediate value + */ +void emitter::emitDispImm(ssize_t imm, bool addComma, bool alwaysHex /* =false */) +{ + if (strictArmAsm) + { + printf("#"); + } + + // Munge any pointers if we want diff-able disassembly + if (emitComp->opts.disDiffable) + { + ssize_t top44bits = (imm >> 20); + if ((top44bits != 0) && (top44bits != -1)) + imm = 0xD1FFAB1E; + } + + if (!alwaysHex && (imm > -1000) && (imm < 1000)) + { + printf("%d", imm); + } + else + { + if ((imm < 0) && ((imm & 0xFFFFFFFF00000000LL) == 0xFFFFFFFF00000000LL)) + { + printf("-"); + imm = -imm; + } + + if ((imm & 0xFFFFFFFF00000000LL) != 0) + { + printf("0x%llx", imm); + } + else + { + printf("0x%02x", imm); + } + } + + if (addComma) + printf(", "); +} + +/***************************************************************************** + * + * Display a float zero constant + */ +void emitter::emitDispFloatZero() +{ + if (strictArmAsm) + { + printf("#"); + } + printf("0.0"); +} + +/***************************************************************************** + * + * Display an encoded float constant value + */ +void emitter::emitDispFloatImm(ssize_t imm8) +{ + assert((0 <= imm8) && (imm8 <= 0x0ff)); + if (strictArmAsm) + { + printf("#"); + } + + floatImm8 fpImm; + fpImm.immFPIVal = (unsigned)imm8; + double result = emitDecodeFloatImm8(fpImm); + + printf("%.4f", result); +} + +/***************************************************************************** + * + * Display an immediate that is optionally LSL12. + */ +void emitter::emitDispImmOptsLSL12(ssize_t imm, insOpts opt) +{ + if (!strictArmAsm && insOptsLSL12(opt)) + { + imm <<= 12; + } + emitDispImm(imm, false); + if (strictArmAsm && insOptsLSL12(opt)) + { + printf(", LSL #12"); + } +} + +/***************************************************************************** + * + * Display an ARM64 condition code for the conditional instructions + */ +void emitter::emitDispCond(insCond cond) +{ + const static char* armCond[16] = {"eq", "ne", "hs", "lo", "mi", "pl", "vs", "vc", + "hi", "ls", "ge", "lt", "gt", "le", "AL", "NV"}; // The last two are invalid + unsigned imm = (unsigned)cond; + assert((0 <= imm) && (imm < ArrLen(armCond))); + printf(armCond[imm]); +} + +/***************************************************************************** + * + * Display an ARM64 flags for the conditional instructions + */ +void emitter::emitDispFlags(insCflags flags) +{ + const static char* armFlags[16] = {"0", "v", "c", "cv", "z", "zv", "zc", "zcv", + "n", "nv", "nc", "ncv", "nz", "nzv", "nzc", "nzcv"}; + unsigned imm = (unsigned)flags; + assert((0 <= imm) && (imm < ArrLen(armFlags))); + printf(armFlags[imm]); +} + +/***************************************************************************** + * + * Display an ARM64 'barrier' for the memory barrier instructions + */ +void emitter::emitDispBarrier(insBarrier barrier) +{ + const static char* armBarriers[16] = {"#0", "oshld", "oshst", "osh", "#4", "nshld", "nshst", "nsh", + "#8", "ishld", "ishst", "ish", "#12", "ld", "st", "sy"}; + unsigned imm = (unsigned)barrier; + assert((0 <= imm) && (imm < ArrLen(armBarriers))); + printf(armBarriers[imm]); +} + +/***************************************************************************** + * + * Prints the encoding for the Shift Type encoding + */ + +void emitter::emitDispShiftOpts(insOpts opt) +{ + if (opt == INS_OPTS_LSL) + printf(" LSL "); + else if (opt == INS_OPTS_LSR) + printf(" LSR "); + else if (opt == INS_OPTS_ASR) + printf(" ASR "); + else if (opt == INS_OPTS_ROR) + printf(" ROR "); + else if (opt == INS_OPTS_MSL) + printf(" MSL "); + else + assert(!"Bad value"); +} + +/***************************************************************************** + * + * Prints the encoding for the Extend Type encoding + */ + +void emitter::emitDispExtendOpts(insOpts opt) +{ + if (opt == INS_OPTS_UXTB) + printf("UXTB"); + else if (opt == INS_OPTS_UXTH) + printf("UXTH"); + else if (opt == INS_OPTS_UXTW) + printf("UXTW"); + else if (opt == INS_OPTS_UXTX) + printf("UXTX"); + else if (opt == INS_OPTS_SXTB) + printf("SXTB"); + else if (opt == INS_OPTS_SXTH) + printf("SXTH"); + else if (opt == INS_OPTS_SXTW) + printf("SXTW"); + else if (opt == INS_OPTS_SXTX) + printf("SXTX"); + else + assert(!"Bad value"); +} + +/***************************************************************************** + * + * Prints the encoding for the Extend Type encoding in loads/stores + */ + +void emitter::emitDispLSExtendOpts(insOpts opt) +{ + if (opt == INS_OPTS_LSL) + printf("LSL"); + else if (opt == INS_OPTS_UXTW) + printf("UXTW"); + else if (opt == INS_OPTS_UXTX) + printf("UXTX"); + else if (opt == INS_OPTS_SXTW) + printf("SXTW"); + else if (opt == INS_OPTS_SXTX) + printf("SXTX"); + else + assert(!"Bad value"); +} + +/***************************************************************************** + * + * Display a register + */ +void emitter::emitDispReg(regNumber reg, emitAttr attr, bool addComma) +{ + emitAttr size = EA_SIZE(attr); + printf(emitRegName(reg, size)); + + if (addComma) + printf(", "); +} + +/***************************************************************************** + * + * Display a vector register with an arrangement suffix + */ +void emitter::emitDispVectorReg(regNumber reg, insOpts opt, bool addComma) +{ + assert(isVectorRegister(reg)); + printf(emitVectorRegName(reg)); + emitDispArrangement(opt); + + if (addComma) + printf(", "); +} + +/***************************************************************************** + * + * Display an vector register index suffix + */ +void emitter::emitDispVectorRegIndex(regNumber reg, emitAttr elemsize, ssize_t index, bool addComma) +{ + assert(isVectorRegister(reg)); + printf(emitVectorRegName(reg)); + + switch (elemsize) + { + case EA_1BYTE: + printf(".b"); + break; + case EA_2BYTE: + printf(".h"); + break; + case EA_4BYTE: + printf(".s"); + break; + case EA_8BYTE: + printf(".d"); + break; + default: + assert(!"invalid elemsize"); + break; + } + + printf("[%d]", index); + + if (addComma) + printf(", "); +} + +/***************************************************************************** + * + * Display an arrangement suffix + */ +void emitter::emitDispArrangement(insOpts opt) +{ + const char* str = "???"; + + switch (opt) + { + case INS_OPTS_8B: + str = "8b"; + break; + case INS_OPTS_16B: + str = "16b"; + break; + case INS_OPTS_4H: + str = "4h"; + break; + case INS_OPTS_8H: + str = "8h"; + break; + case INS_OPTS_2S: + str = "2s"; + break; + case INS_OPTS_4S: + str = "4s"; + break; + case INS_OPTS_1D: + str = "1d"; + break; + case INS_OPTS_2D: + str = "2d"; + break; + + default: + assert(!"Invalid insOpt for vector register"); + } + printf("."); + printf(str); +} + +/***************************************************************************** + * + * Display a register with an optional shift operation + */ +void emitter::emitDispShiftedReg(regNumber reg, insOpts opt, ssize_t imm, emitAttr attr) +{ + emitAttr size = EA_SIZE(attr); + assert((imm & 0x003F) == imm); + assert(((imm & 0x0020) == 0) || (size == EA_8BYTE)); + + printf(emitRegName(reg, size)); + + if (imm > 0) + { + if (strictArmAsm) + { + printf(","); + } + emitDispShiftOpts(opt); + emitDispImm(imm, false); + } +} + +/***************************************************************************** + * + * Display a register with an optional extend and scale operations + */ +void emitter::emitDispExtendReg(regNumber reg, insOpts opt, ssize_t imm) +{ + assert((imm >= 0) && (imm <= 4)); + assert(insOptsNone(opt) || insOptsAnyExtend(opt) || (opt == INS_OPTS_LSL)); + + // size is based on the extend option, not the instr size. + emitAttr size = insOpts32BitExtend(opt) ? EA_4BYTE : EA_8BYTE; + + if (strictArmAsm) + { + if (insOptsNone(opt)) + { + emitDispReg(reg, size, false); + } + else + { + emitDispReg(reg, size, true); + if (opt == INS_OPTS_LSL) + printf("LSL"); + else + emitDispExtendOpts(opt); + if ((imm > 0) || (opt == INS_OPTS_LSL)) + { + printf(" "); + emitDispImm(imm, false); + } + } + } + else // !strictArmAsm + { + if (insOptsNone(opt)) + { + emitDispReg(reg, size, false); + } + else + { + if (opt != INS_OPTS_LSL) + { + emitDispExtendOpts(opt); + printf("("); + emitDispReg(reg, size, false); + printf(")"); + } + } + if (imm > 0) + { + printf("*"); + emitDispImm(1 << imm, false); + } + } +} + +/***************************************************************************** + * + * Display an addressing operand [reg + imm] + */ +void emitter::emitDispAddrRI(regNumber reg, insOpts opt, ssize_t imm) +{ + reg = encodingZRtoSP(reg); // ZR (R31) encodes the SP register + + if (strictArmAsm) + { + printf("["); + + emitDispReg(reg, EA_8BYTE, false); + + if (!insOptsPostIndex(opt) && (imm != 0)) + { + printf(","); + emitDispImm(imm, false); + } + printf("]"); + + if (insOptsPreIndex(opt)) + { + printf("!"); + } + else if (insOptsPostIndex(opt)) + { + printf(","); + emitDispImm(imm, false); + } + } + else // !strictArmAsm + { + printf("["); + + const char* operStr = "++"; + if (imm < 0) + { + operStr = "--"; + imm = -imm; + } + + if (insOptsPreIndex(opt)) + { + printf(operStr); + } + + emitDispReg(reg, EA_8BYTE, false); + + if (insOptsPostIndex(opt)) + { + printf(operStr); + } + + if (insOptsIndexed(opt)) + { + printf(", "); + } + else + { + printf("%c", operStr[1]); + } + emitDispImm(imm, false); + printf("]"); + } +} + +/***************************************************************************** + * + * Display an addressing operand [reg + extended reg] + */ +void emitter::emitDispAddrRRExt(regNumber reg1, regNumber reg2, insOpts opt, bool isScaled, emitAttr size) +{ + reg1 = encodingZRtoSP(reg1); // ZR (R31) encodes the SP register + + unsigned scale = 0; + if (isScaled) + { + scale = NaturalScale_helper(size); + } + + printf("["); + + if (strictArmAsm) + { + emitDispReg(reg1, EA_8BYTE, true); + emitDispExtendReg(reg2, opt, scale); + } + else // !strictArmAsm + { + emitDispReg(reg1, EA_8BYTE, false); + printf("+"); + emitDispExtendReg(reg2, opt, scale); + } + + printf("]"); +} + +/***************************************************************************** + * + * Display (optionally) the instruction encoding in hex + */ + +void emitter::emitDispInsHex(BYTE* code, size_t sz) +{ + // We do not display the instruction hex if we want diff-able disassembly + if (!emitComp->opts.disDiffable) + { + if (sz == 4) + { + printf(" %08X ", (*((code_t*)code))); + } + else + { + printf(" "); + } + } +} + +/**************************************************************************** + * + * Display the given instruction. + */ + +void emitter::emitDispIns( + instrDesc* id, bool isNew, bool doffs, bool asmfm, unsigned offset, BYTE* pCode, size_t sz, insGroup* ig) +{ + if (EMITVERBOSE) + { + unsigned idNum = + id->idDebugOnlyInfo()->idNum; // Do not remove this! It is needed for VisualStudio conditional breakpoints + + printf("IN%04x: ", idNum); + } + + if (pCode == NULL) + sz = 0; + + if (!emitComp->opts.dspEmit && !isNew && !asmfm && sz) + doffs = true; + + /* Display the instruction offset */ + + emitDispInsOffs(offset, doffs); + + /* Display the instruction hex code */ + + emitDispInsHex(pCode, sz); + + printf(" "); + + /* Get the instruction and format */ + + instruction ins = id->idIns(); + insFormat fmt = id->idInsFmt(); + + emitDispInst(ins); + + /* If this instruction has just been added, check its size */ + + assert(isNew == false || (int)emitSizeOfInsDsc(id) == emitCurIGfreeNext - (BYTE*)id); + + /* Figure out the operand size */ + emitAttr size = id->idOpSize(); + emitAttr attr = size; + if (id->idGCref() == GCT_GCREF) + attr = EA_GCREF; + else if (id->idGCref() == GCT_BYREF) + attr = EA_BYREF; + + switch (fmt) + { + code_t code; + ssize_t imm; + int doffs; + bool isExtendAlias; + bool canEncode; + bitMaskImm bmi; + halfwordImm hwi; + condFlagsImm cfi; + unsigned scale; + unsigned immShift; + bool hasShift; + ssize_t offs; + const char* methodName; + emitAttr elemsize; + emitAttr datasize; + emitAttr srcsize; + emitAttr dstsize; + ssize_t index; + ssize_t index2; + + case IF_BI_0A: // BI_0A ......iiiiiiiiii iiiiiiiiiiiiiiii simm26:00 + case IF_BI_0B: // BI_0B ......iiiiiiiiii iiiiiiiiiii..... simm19:00 + case IF_LARGEJMP: + { + if (fmt == IF_LARGEJMP) + { + printf("(LARGEJMP)"); + } + if (id->idAddr()->iiaHasInstrCount()) + { + int instrCount = id->idAddr()->iiaGetInstrCount(); + + if (ig == nullptr) + { + printf("pc%s%d instructions", (instrCount >= 0) ? "+" : "", instrCount); + } + else + { + unsigned insNum = emitFindInsNum(ig, id); + UNATIVE_OFFSET srcOffs = ig->igOffs + emitFindOffset(ig, insNum + 1); + UNATIVE_OFFSET dstOffs = ig->igOffs + emitFindOffset(ig, insNum + 1 + instrCount); + ssize_t relOffs = (ssize_t)(emitOffsetToPtr(dstOffs) - emitOffsetToPtr(srcOffs)); + printf("pc%s%d (%d instructions)", (relOffs >= 0) ? "+" : "", relOffs, instrCount); + } + } + else if (id->idIsBound()) + { + printf("G_M%03u_IG%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaIGlabel->igNum); + } + else + { + printf("L_M%03u_BB%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaBBlabel->bbNum); + } + } + break; + + case IF_BI_0C: // BI_0C ......iiiiiiiiii iiiiiiiiiiiiiiii simm26:00 + if (id->idIsCallAddr()) + { + offs = (ssize_t)id->idAddr()->iiaAddr; + methodName = ""; + } + else + { + offs = 0; + methodName = emitComp->eeGetMethodFullName((CORINFO_METHOD_HANDLE)id->idDebugOnlyInfo()->idMemCookie); + } + + if (offs) + { + if (id->idIsDspReloc()) + printf("reloc "); + printf("%08X", offs); + } + else + { + printf("%s", methodName); + } + break; + + case IF_BI_1A: // BI_1A ......iiiiiiiiii iiiiiiiiiiittttt Rt simm19:00 + assert(insOptsNone(id->idInsOpt())); + emitDispReg(id->idReg1(), size, true); + if (id->idIsBound()) + { + printf("G_M%03u_IG%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaIGlabel->igNum); + } + else + { + printf("L_M%03u_BB%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaBBlabel->bbNum); + } + break; + + case IF_BI_1B: // BI_1B B.......bbbbbiii iiiiiiiiiiittttt Rt imm6, simm14:00 + assert(insOptsNone(id->idInsOpt())); + emitDispReg(id->idReg1(), size, true); + emitDispImm(emitGetInsSC(id), true); + if (id->idIsBound()) + { + printf("G_M%03u_IG%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaIGlabel->igNum); + } + else + { + printf("L_M%03u_BB%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaBBlabel->bbNum); + } + break; + + case IF_BR_1A: // BR_1A ................ ......nnnnn..... Rn + assert(insOptsNone(id->idInsOpt())); + emitDispReg(id->idReg1(), size, false); + break; + + case IF_BR_1B: // BR_1B ................ ......nnnnn..... Rn + assert(insOptsNone(id->idInsOpt())); + emitDispReg(id->idReg3(), size, false); + break; + + case IF_LS_1A: // LS_1A XX...V..iiiiiiii iiiiiiiiiiittttt Rt PC imm(1MB) + case IF_DI_1E: // DI_1E .ii.....iiiiiiii iiiiiiiiiiiddddd Rd simm21 + case IF_LARGELDC: + case IF_LARGEADR: + assert(insOptsNone(id->idInsOpt())); + emitDispReg(id->idReg1(), size, true); + imm = emitGetInsSC(id); + + /* Is this actually a reference to a data section? */ + if (fmt == IF_LARGEADR) + { + printf("(LARGEADR)"); + } + else if (fmt == IF_LARGELDC) + { + printf("(LARGELDC)"); + } + + printf("["); + if (id->idAddr()->iiaIsJitDataOffset()) + { + doffs = Compiler::eeGetJitDataOffs(id->idAddr()->iiaFieldHnd); + /* Display a data section reference */ + + if (doffs & 1) + printf("@CNS%02u", doffs - 1); + else + printf("@RWD%02u", doffs); + + if (imm != 0) + printf("%+Id", imm); + } + else + { + assert(imm == 0); + if (id->idIsReloc()) + { + printf("RELOC "); + emitDispImm((ssize_t)id->idAddr()->iiaAddr, false); + } + else if (id->idIsBound()) + { + printf("G_M%03u_IG%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaIGlabel->igNum); + } + else + { + printf("L_M%03u_BB%02u", Compiler::s_compMethodsCount, id->idAddr()->iiaBBlabel->bbNum); + } + } + printf("]"); + break; + + case IF_LS_2A: // LS_2A .X.......X...... ......nnnnnttttt Rt Rn + assert(insOptsNone(id->idInsOpt())); + assert(emitGetInsSC(id) == 0); + emitDispReg(id->idReg1(), emitInsTargetRegSize(id), true); + emitDispAddrRI(id->idReg2(), id->idInsOpt(), 0); + break; + + case IF_LS_2B: // LS_2B .X.......Xiiiiii iiiiiinnnnnttttt Rt Rn imm(0-4095) + assert(insOptsNone(id->idInsOpt())); + imm = emitGetInsSC(id); + scale = NaturalScale_helper(emitInsLoadStoreSize(id)); + imm <<= scale; // The immediate is scaled by the size of the ld/st + emitDispReg(id->idReg1(), emitInsTargetRegSize(id), true); + emitDispAddrRI(id->idReg2(), id->idInsOpt(), imm); + break; + + case IF_LS_2C: // LS_2C .X.......X.iiiii iiiiPPnnnnnttttt Rt Rn imm(-256..+255) no/pre/post inc + assert(insOptsNone(id->idInsOpt()) || insOptsIndexed(id->idInsOpt())); + imm = emitGetInsSC(id); + emitDispReg(id->idReg1(), emitInsTargetRegSize(id), true); + emitDispAddrRI(id->idReg2(), id->idInsOpt(), imm); + break; + + case IF_LS_3A: // LS_3A .X.......X.mmmmm oooS..nnnnnttttt Rt Rn Rm ext(Rm) LSL {} + assert(insOptsLSExtend(id->idInsOpt())); + emitDispReg(id->idReg1(), emitInsTargetRegSize(id), true); + if (id->idIsLclVar()) + { + emitDispAddrRRExt(id->idReg2(), codeGen->rsGetRsvdReg(), id->idInsOpt(), false, size); + } + else + { + emitDispAddrRRExt(id->idReg2(), id->idReg3(), id->idInsOpt(), id->idReg3Scaled(), size); + } + break; + + case IF_LS_3B: // LS_3B X............... .aaaaannnnnddddd Rt Ra Rn + assert(insOptsNone(id->idInsOpt())); + assert(emitGetInsSC(id) == 0); + emitDispReg(id->idReg1(), emitInsTargetRegSize(id), true); + emitDispReg(id->idReg2(), emitInsTargetRegSize(id), true); + emitDispAddrRI(id->idReg3(), id->idInsOpt(), 0); + break; + + case IF_LS_3C: // LS_3C X.........iiiiii iaaaaannnnnddddd Rt Ra Rn imm(im7,sh) + assert(insOptsNone(id->idInsOpt()) || insOptsIndexed(id->idInsOpt())); + imm = emitGetInsSC(id); + scale = NaturalScale_helper(emitInsLoadStoreSize(id)); + imm <<= scale; + emitDispReg(id->idReg1(), emitInsTargetRegSize(id), true); + emitDispReg(id->idReg2(), emitInsTargetRegSize(id), true); + emitDispAddrRI(id->idReg3(), id->idInsOpt(), imm); + break; + + case IF_DI_1A: // DI_1A X.......shiiiiii iiiiiinnnnn..... Rn imm(i12,sh) + emitDispReg(id->idReg1(), size, true); + emitDispImmOptsLSL12(emitGetInsSC(id), id->idInsOpt()); + break; + + case IF_DI_1B: // DI_1B X........hwiiiii iiiiiiiiiiiddddd Rd imm(i16,hw) + emitDispReg(id->idReg1(), size, true); + hwi.immHWVal = (unsigned)emitGetInsSC(id); + if (ins == INS_mov) + { + emitDispImm(emitDecodeHalfwordImm(hwi, size), false); + } + else // movz, movn, movk + { + emitDispImm(hwi.immVal, false); + if (hwi.immHW != 0) + { + emitDispShiftOpts(INS_OPTS_LSL); + emitDispImm(hwi.immHW * 16, false); + } + } + break; + + case IF_DI_1C: // DI_1C X........Nrrrrrr ssssssnnnnn..... Rn imm(N,r,s) + emitDispReg(id->idReg1(), size, true); + bmi.immNRS = (unsigned)emitGetInsSC(id); + emitDispImm(emitDecodeBitMaskImm(bmi, size), false); + break; + + case IF_DI_1D: // DI_1D X........Nrrrrrr ssssss.....ddddd Rd imm(N,r,s) + emitDispReg(encodingZRtoSP(id->idReg1()), size, true); + bmi.immNRS = (unsigned)emitGetInsSC(id); + emitDispImm(emitDecodeBitMaskImm(bmi, size), false); + break; + + case IF_DI_2A: // DI_2A X.......shiiiiii iiiiiinnnnnddddd Rd Rn imm(i12,sh) + if ((ins == INS_add) || (ins == INS_sub)) + { + emitDispReg(encodingZRtoSP(id->idReg1()), size, true); + emitDispReg(encodingZRtoSP(id->idReg2()), size, true); + } + else + { + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + } + emitDispImmOptsLSL12(emitGetInsSC(id), id->idInsOpt()); + break; + + case IF_DI_2B: // DI_2B X........X.nnnnn ssssssnnnnnddddd Rd Rn imm(0-63) + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + emitDispImm(emitGetInsSC(id), false); + break; + + case IF_DI_2C: // DI_2C X........Nrrrrrr ssssssnnnnnddddd Rd Rn imm(N,r,s) + if (ins == INS_ands) + { + emitDispReg(id->idReg1(), size, true); + } + else + { + emitDispReg(encodingZRtoSP(id->idReg1()), size, true); + } + emitDispReg(id->idReg2(), size, true); + bmi.immNRS = (unsigned)emitGetInsSC(id); + emitDispImm(emitDecodeBitMaskImm(bmi, size), false); + break; + + case IF_DI_2D: // DI_2D X........Nrrrrrr ssssssnnnnnddddd Rd Rn imr, ims (N,r,s) + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + + imm = emitGetInsSC(id); + bmi.immNRS = (unsigned)imm; + + switch (ins) + { + case INS_bfm: + case INS_sbfm: + case INS_ubfm: + emitDispImm(bmi.immR, true); + emitDispImm(bmi.immS, false); + break; + + case INS_bfi: + case INS_sbfiz: + case INS_ubfiz: + emitDispImm(getBitWidth(size) - bmi.immR, true); + emitDispImm(bmi.immS + 1, false); + break; + + case INS_bfxil: + case INS_sbfx: + case INS_ubfx: + emitDispImm(bmi.immR, true); + emitDispImm(bmi.immS - bmi.immR + 1, false); + break; + + case INS_asr: + case INS_lsr: + case INS_lsl: + emitDispImm(imm, false); + break; + + default: + assert(!"Unexpected instruction in IF_DI_2D"); + } + + break; + + case IF_DI_1F: // DI_1F X..........iiiii cccc..nnnnn.nzcv Rn imm5 nzcv cond + emitDispReg(id->idReg1(), size, true); + cfi.immCFVal = (unsigned)emitGetInsSC(id); + emitDispImm(cfi.imm5, true); + emitDispFlags(cfi.flags); + printf(","); + emitDispCond(cfi.cond); + break; + + case IF_DR_1D: // DR_1D X............... cccc.......mmmmm Rd cond + emitDispReg(id->idReg1(), size, true); + cfi.immCFVal = (unsigned)emitGetInsSC(id); + emitDispCond(cfi.cond); + break; + + case IF_DR_2A: // DR_2A X..........mmmmm ......nnnnn..... Rn Rm + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, false); + break; + + case IF_DR_2B: // DR_2B X.......sh.mmmmm ssssssnnnnn..... Rn Rm {LSL,LSR,ASR,ROR} imm(0-63) + emitDispReg(id->idReg1(), size, true); + emitDispShiftedReg(id->idReg2(), id->idInsOpt(), emitGetInsSC(id), size); + break; + + case IF_DR_2C: // DR_2C X..........mmmmm ooosssnnnnn..... Rn Rm ext(Rm) LSL imm(0-4) + emitDispReg(encodingZRtoSP(id->idReg1()), size, true); + imm = emitGetInsSC(id); + emitDispExtendReg(id->idReg2(), id->idInsOpt(), imm); + break; + + case IF_DR_2D: // DR_2D X..........nnnnn cccc..nnnnnddddd Rd Rn cond + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + cfi.immCFVal = (unsigned)emitGetInsSC(id); + emitDispCond(cfi.cond); + break; + + case IF_DR_2E: // DR_2E X..........mmmmm ...........ddddd Rd Rm + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, false); + break; + + case IF_DR_2F: // DR_2F X.......sh.mmmmm ssssss.....ddddd Rd Rm {LSL,LSR,ASR} imm(0-63) + emitDispReg(id->idReg1(), size, true); + emitDispShiftedReg(id->idReg2(), id->idInsOpt(), emitGetInsSC(id), size); + break; + + case IF_DR_2G: // DR_2G X............... ......nnnnnddddd Rd Rn + emitDispReg(encodingZRtoSP(id->idReg1()), size, true); + emitDispReg(encodingZRtoSP(id->idReg2()), size, false); + break; + + case IF_DR_2H: // DR_2H X........X...... ......nnnnnddddd Rd Rn + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, false); + break; + + case IF_DR_2I: // DR_2I X..........mmmmm cccc..nnnnn.nzcv Rn Rm nzcv cond + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + cfi.immCFVal = (unsigned)emitGetInsSC(id); + emitDispFlags(cfi.flags); + printf(","); + emitDispCond(cfi.cond); + break; + + case IF_DR_3A: // DR_3A X..........mmmmm ......nnnnnmmmmm Rd Rn Rm + if ((ins == INS_add) || (ins == INS_sub)) + { + emitDispReg(encodingZRtoSP(id->idReg1()), size, true); + emitDispReg(encodingZRtoSP(id->idReg2()), size, true); + } + else + { + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + } + if (id->idIsLclVar()) + { + emitDispReg(codeGen->rsGetRsvdReg(), size, false); + } + else + { + emitDispReg(id->idReg3(), size, false); + } + + break; + + case IF_DR_3B: // DR_3B X.......sh.mmmmm ssssssnnnnnddddd Rd Rn Rm {LSL,LSR,ASR} imm(0-63) + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + emitDispShiftedReg(id->idReg3(), id->idInsOpt(), emitGetInsSC(id), size); + break; + + case IF_DR_3C: // DR_3C X..........mmmmm ooosssnnnnnddddd Rd Rn Rm ext(Rm) LSL imm(0-4) + emitDispReg(encodingZRtoSP(id->idReg1()), size, true); + emitDispReg(encodingZRtoSP(id->idReg2()), size, true); + imm = emitGetInsSC(id); + emitDispExtendReg(id->idReg3(), id->idInsOpt(), imm); + break; + + case IF_DR_3D: // DR_3D X..........mmmmm cccc..nnnnnmmmmm Rd Rn Rm cond + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + emitDispReg(id->idReg3(), size, true); + cfi.immCFVal = (unsigned)emitGetInsSC(id); + emitDispCond(cfi.cond); + break; + + case IF_DR_3E: // DR_3E X........X.mmmmm ssssssnnnnnddddd Rd Rn Rm imm(0-63) + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + emitDispReg(id->idReg3(), size, true); + emitDispImm(emitGetInsSC(id), false); + break; + + case IF_DR_4A: // DR_4A X..........mmmmm .aaaaannnnnmmmmm Rd Rn Rm Ra + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + emitDispReg(id->idReg3(), size, true); + emitDispReg(id->idReg4(), size, false); + break; + + case IF_DV_1A: // DV_1A .........X.iiiii iii........ddddd Vd imm8 (fmov - immediate scalar) + elemsize = id->idOpSize(); + emitDispReg(id->idReg1(), elemsize, true); + emitDispFloatImm(emitGetInsSC(id)); + break; + + case IF_DV_1B: // DV_1B .QX..........iii cmod..iiiiiddddd Vd imm8 (immediate vector) + imm = emitGetInsSC(id) & 0x0ff; + immShift = (emitGetInsSC(id) & 0x700) >> 8; + hasShift = (immShift != 0); + elemsize = optGetElemsize(id->idInsOpt()); + if (id->idInsOpt() == INS_OPTS_1D) + { + assert(elemsize == size); + emitDispReg(id->idReg1(), size, true); + } + else + { + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + } + if (ins == INS_fmov) + { + emitDispFloatImm(imm); + assert(hasShift == false); + } + else + { + if (elemsize == EA_8BYTE) + { + assert(ins == INS_movi); + ssize_t imm64 = 0; + const ssize_t mask8 = 0xFF; + for (unsigned b = 0; b < 8; b++) + { + if (imm & (1 << b)) + { + imm64 |= (mask8 << (b * 8)); + } + } + emitDispImm(imm64, hasShift, true); + } + else + { + emitDispImm(imm, hasShift, true); + } + if (hasShift) + { + insOpts opt = (immShift & 0x4) ? INS_OPTS_MSL : INS_OPTS_LSL; + unsigned shift = (immShift & 0x3) * 8; + emitDispShiftOpts(opt); + emitDispImm(shift, false); + } + } + break; + + case IF_DV_1C: // DV_1C .........X...... ......nnnnn..... Vn #0.0 (fcmp - with zero) + elemsize = id->idOpSize(); + emitDispReg(id->idReg1(), elemsize, true); + emitDispFloatZero(); + break; + + case IF_DV_2A: // DV_2A .Q.......X...... ......nnnnnddddd Vd Vn (fabs, fcvt - vector) + case IF_DV_2M: // DV_2M .Q......XX...... ......nnnnnddddd Vd Vn (abs, neg - vector) + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + emitDispVectorReg(id->idReg2(), id->idInsOpt(), false); + break; + + case IF_DV_2N: // DV_2N .........iiiiiii ......nnnnnddddd Vd Vn imm (shift - scalar) + elemsize = id->idOpSize(); + emitDispReg(id->idReg1(), elemsize, true); + emitDispReg(id->idReg2(), elemsize, true); + emitDispImm(emitGetInsSC(id), false); + break; + + case IF_DV_2O: // DV_2O .Q.......iiiiiii ......nnnnnddddd Vd Vn imm (shift - vector) + imm = emitGetInsSC(id); + // Do we have a sxtl or uxtl instruction? + isExtendAlias = ((ins == INS_sxtl) || (ins == INS_sxtl2) || (ins == INS_uxtl) || (ins == INS_uxtl2)); + code = emitInsCode(ins, fmt); + if (code & 0x00008000) // widen/narrow opcodes + { + if (code & 0x00002000) // SHL opcodes + { + emitDispVectorReg(id->idReg1(), optWidenElemsize(id->idInsOpt()), true); + emitDispVectorReg(id->idReg2(), id->idInsOpt(), !isExtendAlias); + } + else // SHR opcodes + { + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + emitDispVectorReg(id->idReg2(), optWidenElemsize(id->idInsOpt()), !isExtendAlias); + } + } + else + { + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + emitDispVectorReg(id->idReg2(), id->idInsOpt(), !isExtendAlias); + } + // Print the immediate unless we have a sxtl or uxtl instruction + if (!isExtendAlias) + { + emitDispImm(imm, false); + } + break; + + case IF_DV_2B: // DV_2B .Q.........iiiii ......nnnnnddddd Rd Vn[] (umov/smov - to general) + srcsize = id->idOpSize(); + index = emitGetInsSC(id); + if (ins == INS_smov) + { + dstsize = EA_8BYTE; + } + else // INS_umov or INS_mov + { + dstsize = (srcsize == EA_8BYTE) ? EA_8BYTE : EA_4BYTE; + } + emitDispReg(id->idReg1(), dstsize, true); + emitDispVectorRegIndex(id->idReg2(), srcsize, index, false); + break; + + case IF_DV_2C: // DV_2C .Q.........iiiii ......nnnnnddddd Vd Rn (dup/ins - vector from general) + if (ins == INS_dup) + { + datasize = id->idOpSize(); + assert(isValidVectorDatasize(datasize)); + assert(isValidArrangement(datasize, id->idInsOpt())); + elemsize = optGetElemsize(id->idInsOpt()); + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + } + else // INS_ins + { + elemsize = id->idOpSize(); + index = emitGetInsSC(id); + assert(isValidVectorElemsize(elemsize)); + emitDispVectorRegIndex(id->idReg1(), elemsize, index, true); + } + emitDispReg(id->idReg2(), (elemsize == EA_8BYTE) ? EA_8BYTE : EA_4BYTE, false); + break; + + case IF_DV_2D: // DV_2D .Q.........iiiii ......nnnnnddddd Vd Vn[] (dup - vector) + datasize = id->idOpSize(); + assert(isValidVectorDatasize(datasize)); + assert(isValidArrangement(datasize, id->idInsOpt())); + elemsize = optGetElemsize(id->idInsOpt()); + index = emitGetInsSC(id); + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + emitDispVectorRegIndex(id->idReg2(), elemsize, index, false); + break; + + case IF_DV_2E: // DV_2E ...........iiiii ......nnnnnddddd Vd Vn[] (dup - scalar) + elemsize = id->idOpSize(); + index = emitGetInsSC(id); + emitDispReg(id->idReg1(), elemsize, true); + emitDispVectorRegIndex(id->idReg2(), elemsize, index, false); + break; + + case IF_DV_2F: // DV_2F ...........iiiii .jjjj.nnnnnddddd Vd[] Vn[] (ins - element) + imm = emitGetInsSC(id); + index = (imm >> 4) & 0xf; + index2 = imm & 0xf; + elemsize = id->idOpSize(); + emitDispVectorRegIndex(id->idReg1(), elemsize, index, true); + emitDispVectorRegIndex(id->idReg2(), elemsize, index2, false); + break; + + case IF_DV_2G: // DV_2G .........X...... ......nnnnnddddd Vd Vn (fmov, fcvtXX - register) + case IF_DV_2K: // DV_2K .........X.mmmmm ......nnnnn..... Vn Vm (fcmp) + case IF_DV_2L: // DV_2L ........XX...... ......nnnnnddddd Vd Vn (abs, neg - scalar) + elemsize = id->idOpSize(); + emitDispReg(id->idReg1(), elemsize, true); + emitDispReg(id->idReg2(), elemsize, false); + break; + + case IF_DV_2H: // DV_2H X........X...... ......nnnnnddddd Rd Vn (fmov, fcvtXX - to general) + case IF_DV_2I: // DV_2I X........X...... ......nnnnnddddd Vd Rn (fmov, Xcvtf - from general) + case IF_DV_2J: // DV_2J ........SS.....D D.....nnnnnddddd Vd Vn (fcvt) + dstsize = optGetDstsize(id->idInsOpt()); + srcsize = optGetSrcsize(id->idInsOpt()); + + emitDispReg(id->idReg1(), dstsize, true); + emitDispReg(id->idReg2(), srcsize, false); + break; + + case IF_DV_3A: // DV_3A .Q......XX.mmmmm ......nnnnnddddd Vd Vn Vm (vector) + case IF_DV_3B: // DV_3B .Q.........mmmmm ......nnnnnddddd Vd Vn Vm (vector) + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + emitDispVectorReg(id->idReg2(), id->idInsOpt(), true); + emitDispVectorReg(id->idReg3(), id->idInsOpt(), false); + break; + + case IF_DV_3C: // DV_3C .Q.........mmmmm ......nnnnnddddd Vd Vn Vm (vector) + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + if (ins != INS_mov) + { + emitDispVectorReg(id->idReg2(), id->idInsOpt(), true); + } + emitDispVectorReg(id->idReg3(), id->idInsOpt(), false); + break; + + case IF_DV_3AI: // DV_3AI .Q......XXLMmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector by elem) + case IF_DV_3BI: // DV_3BI .Q........Lmmmmm ....H.nnnnnddddd Vd Vn Vm[] (vector by elem) + emitDispVectorReg(id->idReg1(), id->idInsOpt(), true); + emitDispVectorReg(id->idReg2(), id->idInsOpt(), true); + elemsize = optGetElemsize(id->idInsOpt()); + emitDispVectorRegIndex(id->idReg3(), elemsize, emitGetInsSC(id), false); + break; + + case IF_DV_3D: // DV_3D .........X.mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + case IF_DV_3E: // DV_3E ...........mmmmm ......nnnnnddddd Vd Vn Vm (scalar) + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + emitDispReg(id->idReg3(), size, false); + break; + + case IF_DV_3DI: // DV_3DI .........XLmmmmm ....H.nnnnnddddd Vd Vn Vm[] (scalar by elem) + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + elemsize = size; + emitDispVectorRegIndex(id->idReg3(), elemsize, emitGetInsSC(id), false); + break; + + case IF_DV_4A: // DV_4A .........X.mmmmm .aaaaannnnnddddd Vd Va Vn Vm (scalar) + emitDispReg(id->idReg1(), size, true); + emitDispReg(id->idReg2(), size, true); + emitDispReg(id->idReg3(), size, true); + emitDispReg(id->idReg4(), size, false); + break; + + case IF_SN_0A: // SN_0A ................ ................ + break; + + case IF_SI_0A: // SI_0A ...........iiiii iiiiiiiiiii..... imm16 + emitDispImm(emitGetInsSC(id), false); + break; + + case IF_SI_0B: // SI_0B ................ ....bbbb........ imm4 - barrier + emitDispBarrier((insBarrier)emitGetInsSC(id)); + break; + + default: + printf("unexpected format %s", emitIfName(id->idInsFmt())); + assert(!"unexpectedFormat"); + break; + } + + if (id->idDebugOnlyInfo()->idVarRefOffs) + { + printf("\t// "); + emitDispFrameRef(id->idAddr()->iiaLclVar.lvaVarNum(), id->idAddr()->iiaLclVar.lvaOffset(), + id->idDebugOnlyInfo()->idVarRefOffs, asmfm); + } + + printf("\n"); +} + +/***************************************************************************** + * + * Display a stack frame reference. + */ + +void emitter::emitDispFrameRef(int varx, int disp, int offs, bool asmfm) +{ + printf("["); + + if (varx < 0) + printf("TEMP_%02u", -varx); + else + emitComp->gtDispLclVar(+varx, false); + + if (disp < 0) + printf("-0x%02x", -disp); + else if (disp > 0) + printf("+0x%02x", +disp); + + printf("]"); + + if (varx >= 0 && emitComp->opts.varNames) + { + LclVarDsc* varDsc; + const char* varName; + + assert((unsigned)varx < emitComp->lvaCount); + varDsc = emitComp->lvaTable + varx; + varName = emitComp->compLocalVarName(varx, offs); + + if (varName) + { + printf("'%s", varName); + + if (disp < 0) + printf("-%d", -disp); + else if (disp > 0) + printf("+%d", +disp); + + printf("'"); + } + } +} + +#endif // DEBUG + +// Generate code for a load or store operation with a potentially complex addressing mode +// This method handles the case of a GT_IND with contained GT_LEA op1 of the x86 form [base + index*sccale + offset] +// Since Arm64 does not directly support this complex of an addressing mode +// we may generates up to three instructions for this for Arm64 +// +void emitter::emitInsLoadStoreOp(instruction ins, emitAttr attr, regNumber dataReg, GenTreeIndir* indir) +{ + emitAttr ldstAttr = isVectorRegister(dataReg) ? attr : emitInsAdjustLoadStoreAttr(ins, attr); + + GenTree* addr = indir->Addr(); + + if (addr->isContained()) + { + assert(addr->OperGet() == GT_LCL_VAR_ADDR || addr->OperGet() == GT_LEA); + + int offset = 0; + DWORD lsl = 0; + + if (addr->OperGet() == GT_LEA) + { + offset = (int)addr->AsAddrMode()->gtOffset; + if (addr->AsAddrMode()->gtScale > 0) + { + assert(isPow2(addr->AsAddrMode()->gtScale)); + BitScanForward(&lsl, addr->AsAddrMode()->gtScale); + } + } + + GenTree* memBase = indir->Base(); + + if (indir->HasIndex()) + { + GenTree* index = indir->Index(); + + if (offset != 0) + { + regMaskTP tmpRegMask = indir->gtRsvdRegs; + regNumber tmpReg = genRegNumFromMask(tmpRegMask); + noway_assert(tmpReg != REG_NA); + + if (emitIns_valid_imm_for_add(offset, EA_8BYTE)) + { + if (lsl > 0) + { + // Generate code to set tmpReg = base + index*scale + emitIns_R_R_R_I(INS_add, EA_PTRSIZE, tmpReg, memBase->gtRegNum, index->gtRegNum, lsl, + INS_OPTS_LSL); + } + else // no scale + { + // Generate code to set tmpReg = base + index + emitIns_R_R_R(INS_add, EA_PTRSIZE, tmpReg, memBase->gtRegNum, index->gtRegNum); + } + + noway_assert(emitInsIsLoad(ins) || (tmpReg != dataReg)); + + // Then load/store dataReg from/to [tmpReg + offset] + emitIns_R_R_I(ins, ldstAttr, dataReg, tmpReg, offset); + ; + } + else // large offset + { + // First load/store tmpReg with the large offset constant + codeGen->instGen_Set_Reg_To_Imm(EA_PTRSIZE, tmpReg, offset); + // Then add the base register + // rd = rd + base + emitIns_R_R_R(INS_add, EA_PTRSIZE, tmpReg, tmpReg, memBase->gtRegNum); + + noway_assert(emitInsIsLoad(ins) || (tmpReg != dataReg)); + noway_assert(tmpReg != index->gtRegNum); + + // Then load/store dataReg from/to [tmpReg + index*scale] + emitIns_R_R_R_I(ins, ldstAttr, dataReg, tmpReg, index->gtRegNum, lsl, INS_OPTS_LSL); + } + } + else // (offset == 0) + { + if (lsl > 0) + { + // Then load/store dataReg from/to [memBase + index*scale] + emitIns_R_R_R_I(ins, ldstAttr, dataReg, memBase->gtRegNum, index->gtRegNum, lsl, INS_OPTS_LSL); + } + else // no scale + { + // Then load/store dataReg from/to [memBase + index] + emitIns_R_R_R(ins, ldstAttr, dataReg, memBase->gtRegNum, index->gtRegNum); + } + } + } + else // no Index register + { + if (emitIns_valid_imm_for_ldst_offset(offset, EA_SIZE(attr))) + { + // Then load/store dataReg from/to [memBase + offset] + emitIns_R_R_I(ins, ldstAttr, dataReg, memBase->gtRegNum, offset); + } + else + { + // We require a tmpReg to hold the offset + regMaskTP tmpRegMask = indir->gtRsvdRegs; + regNumber tmpReg = genRegNumFromMask(tmpRegMask); + noway_assert(tmpReg != REG_NA); + + // First load/store tmpReg with the large offset constant + codeGen->instGen_Set_Reg_To_Imm(EA_PTRSIZE, tmpReg, offset); + + // Then load/store dataReg from/to [memBase + tmpReg] + emitIns_R_R_R(ins, ldstAttr, dataReg, memBase->gtRegNum, tmpReg); + } + } + } + else // addr is not contained, so we evaluate it into a register + { + codeGen->genConsumeReg(addr); + // Then load/store dataReg from/to [addrReg] + emitIns_R_R(ins, ldstAttr, dataReg, addr->gtRegNum); + } +} + +// Generates an integer data section constant and returns a field handle representing +// the data offset to access the constant via a load instruction. +// This is called during ngen for any relocatable constants +// +CORINFO_FIELD_HANDLE emitter::emitLiteralConst(ssize_t cnsValIn, emitAttr attr /*=EA_8BYTE*/) +{ + ssize_t constValue = cnsValIn; + void* cnsAddr = &constValue; + bool dblAlign; + + if (attr == EA_4BYTE) + { + dblAlign = false; + } + else + { + assert(attr == EA_8BYTE); + dblAlign = true; + } + + // Access to inline data is 'abstracted' by a special type of static member + // (produced by eeFindJitDataOffs) which the emitter recognizes as being a reference + // to constant data, not a real static field. + + UNATIVE_OFFSET cnsSize = (attr == EA_4BYTE) ? 4 : 8; + UNATIVE_OFFSET cnum = emitDataConst(cnsAddr, cnsSize, dblAlign); + return emitComp->eeFindJitDataOffs(cnum); +} + +// Generates a float or double data section constant and returns field handle representing +// the data offset to access the constant. This is called by emitInsBinary() in case +// of contained float of double constants. +CORINFO_FIELD_HANDLE emitter::emitFltOrDblConst(GenTreeDblCon* tree, emitAttr attr /*=EA_UNKNOWN*/) +{ + if (attr == EA_UNKNOWN) + { + attr = emitTypeSize(tree->TypeGet()); + } + else + { + assert(emitTypeSize(tree->TypeGet()) == attr); + } + + double constValue = tree->gtDblCon.gtDconVal; + void* cnsAddr; + float f; + bool dblAlign; + + if (attr == EA_4BYTE) + { + f = forceCastToFloat(constValue); + cnsAddr = &f; + dblAlign = false; + } + else + { + cnsAddr = &constValue; + dblAlign = true; + } + + // Access to inline data is 'abstracted' by a special type of static member + // (produced by eeFindJitDataOffs) which the emitter recognizes as being a reference + // to constant data, not a real static field. + + UNATIVE_OFFSET cnsSize = (attr == EA_4BYTE) ? 4 : 8; + UNATIVE_OFFSET cnum = emitDataConst(cnsAddr, cnsSize, dblAlign); + return emitComp->eeFindJitDataOffs(cnum); +} + +// The callee must call genConsumeReg() for any non-contained srcs +// and genProduceReg() for any non-contained dsts. + +regNumber emitter::emitInsBinary(instruction ins, emitAttr attr, GenTree* dst, GenTree* src) +{ + regNumber result = REG_NA; + + // dst can only be a reg + assert(!dst->isContained()); + + // src can be immed or reg + assert(!src->isContained() || src->isContainedIntOrIImmed()); + + // find immed (if any) - it cannot be a dst + GenTreeIntConCommon* intConst = nullptr; + if (src->isContainedIntOrIImmed()) + { + intConst = src->AsIntConCommon(); + } + + if (intConst) + { + emitIns_R_I(ins, attr, dst->gtRegNum, intConst->IconValue()); + return dst->gtRegNum; + } + else + { + emitIns_R_R(ins, attr, dst->gtRegNum, src->gtRegNum); + return dst->gtRegNum; + } +} + +// The callee must call genConsumeReg() for any non-contained srcs +// and genProduceReg() for any non-contained dsts. + +regNumber emitter::emitInsTernary(instruction ins, emitAttr attr, GenTree* dst, GenTree* src1, GenTree* src2) +{ + regNumber result = REG_NA; + + // dst can only be a reg + assert(!dst->isContained()); + + // find immed (if any) - it cannot be a dst + // Only one src can be an int. + GenTreeIntConCommon* intConst = nullptr; + GenTree* nonIntReg = nullptr; + + if (varTypeIsFloating(dst)) + { + // src1 can only be a reg + assert(!src1->isContained()); + // src2 can only be a reg + assert(!src2->isContained()); + } + else // not floating point + { + // src2 can be immed or reg + assert(!src2->isContained() || src2->isContainedIntOrIImmed()); + + // Check src2 first as we can always allow it to be a contained immediate + if (src2->isContainedIntOrIImmed()) + { + intConst = src2->AsIntConCommon(); + nonIntReg = src1; + } + // Only for commutative operations do we check src1 and allow it to be a contained immediate + else if (dst->OperIsCommutative()) + { + // src1 can be immed or reg + assert(!src1->isContained() || src1->isContainedIntOrIImmed()); + + // Check src1 and allow it to be a contained immediate + if (src1->isContainedIntOrIImmed()) + { + assert(!src2->isContainedIntOrIImmed()); + intConst = src1->AsIntConCommon(); + nonIntReg = src2; + } + } + else + { + // src1 can only be a reg + assert(!src1->isContained()); + } + } + bool isMulOverflow = false; + bool isUnsignedMul = false; + regNumber extraReg = REG_NA; + if (dst->gtOverflowEx()) + { + if (ins == INS_add) + { + ins = INS_adds; + } + else if (ins == INS_sub) + { + ins = INS_subs; + } + else if (ins == INS_mul) + { + isMulOverflow = true; + isUnsignedMul = ((dst->gtFlags & GTF_UNSIGNED) != 0); + assert(intConst == nullptr); // overflow format doesn't support an int constant operand + } + else + { + assert(!"Invalid ins for overflow check"); + } + } + if (intConst != nullptr) + { + emitIns_R_R_I(ins, attr, dst->gtRegNum, nonIntReg->gtRegNum, intConst->IconValue()); + } + else + { + if (isMulOverflow) + { + // Make sure that we have an internal register + assert(genCountBits(dst->gtRsvdRegs) == 2); + + // There will be two bits set in tmpRegsMask. + // Remove the bit for 'dst->gtRegNum' from 'tmpRegsMask' + regMaskTP tmpRegsMask = dst->gtRsvdRegs & ~genRegMask(dst->gtRegNum); + assert(tmpRegsMask != RBM_NONE); + regMaskTP tmpRegMask = genFindLowestBit(tmpRegsMask); // set tmpRegMsk to a one-bit mask + extraReg = genRegNumFromMask(tmpRegMask); // set tmpReg from that mask + + if (isUnsignedMul) + { + if (attr == EA_4BYTE) + { + // Compute 8 byte results from 4 byte by 4 byte multiplication. + emitIns_R_R_R(INS_umull, EA_8BYTE, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); + + // Get the high result by shifting dst. + emitIns_R_R_I(INS_lsr, EA_8BYTE, extraReg, dst->gtRegNum, 32); + } + else + { + assert(attr == EA_8BYTE); + // Compute the high result. + emitIns_R_R_R(INS_umulh, attr, extraReg, src1->gtRegNum, src2->gtRegNum); + + // Now multiply without skewing the high result. + emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); + } + + // zero-sign bit comparision to detect overflow. + emitIns_R_I(INS_cmp, attr, extraReg, 0); + } + else + { + int bitShift = 0; + if (attr == EA_4BYTE) + { + // Compute 8 byte results from 4 byte by 4 byte multiplication. + emitIns_R_R_R(INS_smull, EA_8BYTE, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); + + // Get the high result by shifting dst. + emitIns_R_R_I(INS_lsr, EA_8BYTE, extraReg, dst->gtRegNum, 32); + + bitShift = 31; + } + else + { + assert(attr == EA_8BYTE); + // Save the high result in a temporary register. + emitIns_R_R_R(INS_smulh, attr, extraReg, src1->gtRegNum, src2->gtRegNum); + + // Now multiply without skewing the high result. + emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); + + bitShift = 63; + } + + // Sign bit comparision to detect overflow. + emitIns_R_R_I(INS_cmp, attr, extraReg, dst->gtRegNum, bitShift, INS_OPTS_ASR); + } + } + else + { + // We can just multiply. + emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum); + } + } + + if (dst->gtOverflowEx()) + { + assert(!varTypeIsFloating(dst)); + codeGen->genCheckOverflow(dst); + } + + return dst->gtRegNum; +} + +#endif // defined(_TARGET_ARM64_) |