// 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 emitArm.cpp XX XX XX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */ #include "jitpch.h" #ifdef _MSC_VER #pragma hdrstop #endif #if defined(_TARGET_ARM_) /*****************************************************************************/ /*****************************************************************************/ #include "instr.h" #include "emit.h" #include "codegen.h" /*****************************************************************************/ 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_blx); bool maybeCallIns = (id->idIns() == INS_b) || (id->idIns() == INS_bx); // An INS_call instruction may use a "fat" direct/indirect call descriptor // except for a local call to a label (i.e. call to a finally). // Only ID_OP_CALL and ID_OP_SPEC check for this, so we enforce that the // INS_call instruction always uses one of these idOps. assert(!isCallIns || // either not a call or idOp == ID_OP_CALL || // is a direct call idOp == ID_OP_SPEC || // is an indirect call idOp == ID_OP_JMP); // is a local call to finally clause switch (idOp) { case ID_OP_NONE: break; case ID_OP_JMP: return sizeof(instrDescJmp); case ID_OP_LBL: return sizeof(instrDescLbl); case ID_OP_CALL: case ID_OP_SPEC: assert(isCallIns || maybeCallIns); if (id->idIsLargeCall()) { /* Must be a "fat" indirect 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); } } bool offsetFitsInVectorMem(int disp) { unsigned imm = unsigned_abs(disp); return ((imm & 0x03fc) == imm); } #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()) { case IF_T1_A: // T1_A ................ case IF_T2_A: // T2_A ................ ................ break; case IF_T1_B: // T1_B ........cccc.... cond case IF_T2_B: // T2_B ................ ............iiii imm4 assert(emitGetInsSC(id) < 0x10); break; case IF_T1_C: // T1_C .....iiiiinnnddd R1 R2 imm5 assert(isLowRegister(id->idReg1())); assert(isLowRegister(id->idReg2())); if (emitInsIsLoadOrStore(id->idIns())) { emitAttr size = id->idOpSize(); int imm = emitGetInsSC(id); imm = insUnscaleImm(imm, size); assert(imm < 0x20); } else { assert(id->idSmallCns() < 0x20); } break; case IF_T1_D0: // T1_D0 ........Dmmmmddd R1* R2* assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); break; case IF_T1_D1: // T1_D1 .........mmmm... R1* assert(isGeneralRegister(id->idReg1())); break; case IF_T1_D2: // T1_D2 .........mmmm... R3* assert(isGeneralRegister(id->idReg3())); break; case IF_T1_E: // T1_E ..........nnnddd R1 R2 assert(isLowRegister(id->idReg1())); assert(isLowRegister(id->idReg2())); assert(id->idSmallCns() < 0x20); break; case IF_T1_F: // T1_F .........iiiiiii SP imm7 assert(id->idReg1() == REG_SP); assert(id->idOpSize() == EA_4BYTE); assert((emitGetInsSC(id) & ~0x1FC) == 0); break; case IF_T1_G: // T1_G .......iiinnnddd R1 R2 imm3 assert(isLowRegister(id->idReg1())); assert(isLowRegister(id->idReg2())); assert(id->idSmallCns() < 0x8); break; case IF_T1_H: // T1_H .......mmmnnnddd R1 R2 R3 assert(isLowRegister(id->idReg1())); assert(isLowRegister(id->idReg2())); assert(isLowRegister(id->idReg3())); break; case IF_T1_I: // T1_I ......i.iiiiiddd R1 imm6 assert(isLowRegister(id->idReg1())); break; case IF_T1_J0: // T1_J0 .....dddiiiiiiii R1 imm8 assert(isLowRegister(id->idReg1())); assert(emitGetInsSC(id) < 0x100); break; case IF_T1_J1: // T1_J1 .....dddiiiiiiii R1 assert(isLowRegister(id->idReg1())); assert(emitGetInsSC(id) < 0x100); break; case IF_T1_J2: // T1_J2 .....dddiiiiiiii R1 SP imm8 assert(isLowRegister(id->idReg1())); assert(id->idReg2() == REG_SP); assert(id->idOpSize() == EA_4BYTE); assert((emitGetInsSC(id) & ~0x3FC) == 0); break; case IF_T1_L0: // T1_L0 ........iiiiiiii imm8 assert(emitGetInsSC(id) < 0x100); break; case IF_T1_L1: // T1_L1 .......Rrrrrrrrr assert(emitGetInsSC(id) < 0x400); break; case IF_T2_C0: // T2_C0 ...........Snnnn .iiiddddiishmmmm R1 R2 R3 S, imm5, sh assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(isGeneralRegister(id->idReg3())); assert(emitGetInsSC(id) < 0x20); break; case IF_T2_C4: // T2_C4 ...........Snnnn ....dddd....mmmm R1 R2 R3 S case IF_T2_C5: // T2_C5 ............nnnn ....dddd....mmmm R1 R2 R3 case IF_T2_G1: // T2_G1 ............nnnn ttttTTTT........ R1 R2 R3 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(isGeneralRegister(id->idReg3())); break; case IF_T2_C1: // T2_C1 ...........S.... .iiiddddiishmmmm R1 R2 S, imm5, sh case IF_T2_C2: // T2_C2 ...........S.... .iiiddddii..mmmm R1 R2 S, imm5 case IF_T2_C8: // T2_C8 ............nnnn .iii....iishmmmm R1 R2 imm5, sh assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(emitGetInsSC(id) < 0x20); break; case IF_T2_C6: // T2_C6 ................ ....dddd..iimmmm R1 R2 imm2 case IF_T2_C7: // T2_C7 ............nnnn ..........shmmmm R1 R2 imm2 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(emitGetInsSC(id) < 0x4); break; case IF_T2_C3: // T2_C3 ...........S.... ....dddd....mmmm R1 R2 S case IF_T2_C9: // T2_C9 ............nnnn ............mmmm R1 R2 case IF_T2_C10: // T2_C10 ............mmmm ....dddd....mmmm R1 R2 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); break; case IF_T2_D0: // T2_D0 ............nnnn .iiiddddii.wwwww R1 R2 imm5, imm5 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(emitGetInsSC(id) < 0x400); break; case IF_T2_D1: // T2_D1 ................ .iiiddddii.wwwww R1 imm5, imm5 assert(isGeneralRegister(id->idReg1())); assert(emitGetInsSC(id) < 0x400); break; case IF_T2_E0: // T2_E0 ............nnnn tttt......shmmmm R1 R2 R3 imm2 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); if (id->idIsLclVar()) { assert(isGeneralRegister(codeGen->rsGetRsvdReg())); } else { assert(isGeneralRegister(id->idReg3())); assert(emitGetInsSC(id) < 0x4); } break; case IF_T2_E1: // T2_E1 ............nnnn tttt............ R1 R2 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); break; case IF_T2_E2: // T2_E2 ................ tttt............ R1 assert(isGeneralRegister(id->idReg1())); break; case IF_T2_F1: // T2_F1 ............nnnn ttttdddd....mmmm R1 R2 R3 R4 case IF_T2_F2: // T2_F2 ............nnnn aaaadddd....mmmm R1 R2 R3 R4 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(isGeneralRegister(id->idReg3())); assert(isGeneralRegister(id->idReg4())); break; case IF_T2_G0: // T2_G0 .......PU.W.nnnn ttttTTTTiiiiiiii R1 R2 R3 imm8, PUW assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(isGeneralRegister(id->idReg3())); assert(unsigned_abs(emitGetInsSC(id)) < 0x100); break; case IF_T2_H0: // T2_H0 ............nnnn tttt.PUWiiiiiiii R1 R2 imm8, PUW assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(unsigned_abs(emitGetInsSC(id)) < 0x100); break; case IF_T2_H1: // T2_H1 ............nnnn tttt....iiiiiiii R1 R2 imm8 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(emitGetInsSC(id) < 0x100); break; case IF_T2_H2: // T2_H2 ............nnnn ........iiiiiiii R1 imm8 assert(isGeneralRegister(id->idReg1())); assert(emitGetInsSC(id) < 0x100); break; case IF_T2_I0: // T2_I0 ..........W.nnnn rrrrrrrrrrrrrrrr R1 W, imm16 assert(isGeneralRegister(id->idReg1())); assert(emitGetInsSC(id) < 0x10000); break; case IF_T2_N: // T2_N .....i......iiii .iiiddddiiiiiiii R1 imm16 assert(isGeneralRegister(id->idReg1())); break; case IF_T2_N2: // T2_N2 .....i......iiii .iiiddddiiiiiiii R1 imm16 assert(isGeneralRegister(id->idReg1())); assert((size_t)emitGetInsSC(id) < emitDataSize()); break; case IF_T2_I1: // T2_I1 ................ rrrrrrrrrrrrrrrr imm16 assert(emitGetInsSC(id) < 0x10000); break; case IF_T2_K1: // T2_K1 ............nnnn ttttiiiiiiiiiiii R1 R2 imm12 case IF_T2_M0: // T2_M0 .....i......nnnn .iiiddddiiiiiiii R1 R2 imm12 assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(emitGetInsSC(id) < 0x1000); break; case IF_T2_L0: // T2_L0 .....i.....Snnnn .iiiddddiiiiiiii R1 R2 S, imm8<idReg1())); assert(isGeneralRegister(id->idReg2())); assert(isModImmConst(emitGetInsSC(id))); break; case IF_T2_K4: // T2_K4 ........U....... ttttiiiiiiiiiiii R1 PC U, imm12 case IF_T2_M1: // T2_M1 .....i.......... .iiiddddiiiiiiii R1 PC imm12 assert(isGeneralRegister(id->idReg1())); assert(id->idReg2() == REG_PC); assert(emitGetInsSC(id) < 0x1000); break; case IF_T2_K3: // T2_K3 ........U....... ....iiiiiiiiiiii PC U, imm12 assert(id->idReg1() == REG_PC); assert(emitGetInsSC(id) < 0x1000); break; case IF_T2_K2: // T2_K2 ............nnnn ....iiiiiiiiiiii R1 imm12 assert(isGeneralRegister(id->idReg1())); assert(emitGetInsSC(id) < 0x1000); break; case IF_T2_L1: // T2_L1 .....i.....S.... .iiiddddiiiiiiii R1 S, imm8<idReg1())); assert(isModImmConst(emitGetInsSC(id))); break; case IF_T1_J3: // T1_J3 .....dddiiiiiiii R1 PC imm8 assert(isGeneralRegister(id->idReg1())); assert(id->idReg2() == REG_PC); assert(emitGetInsSC(id) < 0x100); break; case IF_T1_K: // T1_K ....cccciiiiiiii Branch imm8, cond4 case IF_T1_M: // T1_M .....iiiiiiiiiii Branch imm11 case IF_T2_J1: // T2_J1 .....Scccciiiiii ..j.jiiiiiiiiiii Branch imm20, cond4 case IF_T2_J2: // T2_J2 .....Siiiiiiiiii ..j.jiiiiiiiiii. Branch imm24 case IF_T2_N1: // T2_N .....i......iiii .iiiddddiiiiiiii R1 imm16 case IF_T2_J3: // T2_J3 .....Siiiiiiiiii ..j.jiiiiiiiiii. Call imm24 case IF_LARGEJMP: break; case IF_T2_VFP3: if (id->idOpSize() == EA_8BYTE) { assert(isDoubleReg(id->idReg1())); assert(isDoubleReg(id->idReg2())); assert(isDoubleReg(id->idReg3())); } else { assert(id->idOpSize() == EA_4BYTE); assert(isFloatReg(id->idReg1())); assert(isFloatReg(id->idReg2())); assert(isFloatReg(id->idReg3())); } break; case IF_T2_VFP2: assert(isFloatReg(id->idReg1())); assert(isFloatReg(id->idReg2())); break; case IF_T2_VLDST: if (id->idOpSize() == EA_8BYTE) assert(isDoubleReg(id->idReg1())); else assert(isFloatReg(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(offsetFitsInVectorMem(emitGetInsSC(id))); break; case IF_T2_VMOVD: assert(id->idOpSize() == EA_8BYTE); if (id->idIns() == INS_vmov_d2i) { assert(isGeneralRegister(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(isDoubleReg(id->idReg3())); } else { assert(id->idIns() == INS_vmov_i2d); assert(isDoubleReg(id->idReg1())); assert(isGeneralRegister(id->idReg2())); assert(isGeneralRegister(id->idReg3())); } break; case IF_T2_VMOVS: assert(id->idOpSize() == EA_4BYTE); if (id->idIns() == INS_vmov_i2f) { assert(isFloatReg(id->idReg1())); assert(isGeneralRegister(id->idReg2())); } else { assert(id->idIns() == INS_vmov_f2i); assert(isGeneralRegister(id->idReg1())); assert(isFloatReg(id->idReg2())); } 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" or "target" registers: case IF_T1_C: case IF_T1_D0: case IF_T1_E: case IF_T1_G: case IF_T1_H: case IF_T1_J0: case IF_T1_J1: case IF_T1_J2: case IF_T1_J3: case IF_T2_C0: case IF_T2_C1: case IF_T2_C2: case IF_T2_C3: case IF_T2_C4: case IF_T2_C5: case IF_T2_C6: case IF_T2_C10: case IF_T2_D0: case IF_T2_D1: case IF_T2_F1: case IF_T2_F2: case IF_T2_L0: case IF_T2_L1: case IF_T2_M0: case IF_T2_M1: case IF_T2_N: case IF_T2_N1: case IF_T2_N2: case IF_T2_VFP3: case IF_T2_VFP2: case IF_T2_VLDST: case IF_T2_E0: case IF_T2_E1: case IF_T2_E2: case IF_T2_G0: case IF_T2_G1: case IF_T2_H0: case IF_T2_H1: case IF_T2_K1: case IF_T2_K4: // Some formats with "destination" or "target" registers are actually used for store instructions, for the // "source" value written to memory. // Similarly, PUSH has a target register, indicating the start of the set of registers to push. POP // *does* write to at least one register, so we do not make that a special case. // Various compare/test instructions do not write (except to the flags). Technically "teq" does not need to // be // be in this list because it has no forms matched above, but I'm putting it here for completeness. switch (ins) { case INS_str: case INS_strb: case INS_strh: case INS_strd: case INS_strex: case INS_strexb: case INS_strexd: case INS_strexh: case INS_push: case INS_cmp: case INS_cmn: case INS_tst: case INS_teq: return false; default: return true; } case IF_T2_VMOVS: // VMOV.i2f reads from the integer register. Conversely VMOV.f2i writes to GC pointer-sized // integer register that might have previously held GC pointers, so they need to be included. assert(id->idGCref() == GCT_NONE); return (ins == INS_vmov_f2i); case IF_T2_VMOVD: // VMOV.i2d reads from the integer registers. Conversely VMOV.d2i writes to GC pointer-sized // integer registers that might have previously held GC pointers, so they need to be included. assert(id->idGCref() == GCT_NONE); return (ins == INS_vmov_d2i); 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: return true; default: return false; } } bool emitter::emitInsMayWriteMultipleRegs(instrDesc* id) { instruction ins = id->idIns(); switch (ins) { case INS_ldm: case INS_ldmdb: case INS_pop: case INS_smlal: case INS_smull: case INS_umlal: case INS_umull: case INS_vmov_d2i: return true; default: return false; } } /*****************************************************************************/ #ifdef DEBUG /***************************************************************************** * * Return a string that represents the given register. */ const char* emitter::emitRegName(regNumber reg, emitAttr attr, bool varName) { assert(reg < REG_COUNT); const char* rn = emitComp->compRegVarName(reg, varName, false); assert(strlen(rn) >= 1); return rn; } const char* emitter::emitFloatRegName(regNumber reg, emitAttr attr, bool varName) { assert(reg < REG_COUNT); const char* rn = emitComp->compRegVarName(reg, varName, true); assert(strlen(rn) >= 1); return rn; } #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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) 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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) 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 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 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 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 */ size_t emitter::emitInsCode(instruction ins, insFormat fmt) { // clang-format off const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e1, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e1, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e2, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e2, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e3, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e3, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e4, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e4, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e5, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e5, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e6, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e6, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e7, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e7, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) e8, #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e8, #include "instrs.h" }; const static size_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 INST8(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8 ) #define INST9(id, nm, fp, ldst, fmt, e1, e2, e3, e4, e5, e6, e7, e8, e9) e9, #include "instrs.h" }; const static insFormat formatEncode9[9] = { IF_T1_D0, IF_T1_H, IF_T1_J0, IF_T1_G, IF_T2_L0, IF_T2_C0, IF_T1_F, IF_T1_J2, IF_T1_J3 }; const static insFormat formatEncode8[8] = { IF_T1_H, IF_T1_C, IF_T2_E0, IF_T2_H0, IF_T2_K1, IF_T2_K4, IF_T1_J2, IF_T1_J3 }; const static insFormat formatEncode6A[6] = { IF_T1_H, IF_T1_C, IF_T2_E0, IF_T2_H0, IF_T2_K1, IF_T2_K4}; const static insFormat formatEncode6B[6] = { IF_T1_H, IF_T1_C, IF_T2_E0, IF_T2_H0, IF_T2_K1, IF_T1_J2 }; const static insFormat formatEncode5A[5] = { IF_T1_E, IF_T1_D0, IF_T1_J0, IF_T2_L1, IF_T2_C3 }; const static insFormat formatEncode5B[5] = { IF_T1_E, IF_T1_D0, IF_T1_J0, IF_T2_L2, IF_T2_C8 }; const static insFormat formatEncode4A[4] = { IF_T1_E, IF_T1_C, IF_T2_C4, IF_T2_C2 }; const static insFormat formatEncode4B[4] = { IF_T2_K2, IF_T2_H2, IF_T2_C7, IF_T2_K3 }; const static insFormat formatEncode3A[3] = { IF_T1_E, IF_T2_C0, IF_T2_L0 }; const static insFormat formatEncode3B[3] = { IF_T1_E, IF_T2_C8, IF_T2_L2 }; const static insFormat formatEncode3C[3] = { IF_T1_E, IF_T2_C1, IF_T2_L1 }; const static insFormat formatEncode3D[3] = { IF_T1_L1, IF_T2_E2, IF_T2_I1 }; const static insFormat formatEncode3E[3] = { IF_T2_N, IF_T2_N1, IF_T2_N2 }; const static insFormat formatEncode3F[3] = { IF_T1_M, IF_T2_J2, IF_T2_J3 }; const static insFormat formatEncode2A[2] = { IF_T1_K, IF_T2_J1 }; const static insFormat formatEncode2B[2] = { IF_T1_D1, IF_T1_D2 }; const static insFormat formatEncode2C[2] = { IF_T1_D2, IF_T2_J3 }; const static insFormat formatEncode2D[2] = { IF_T1_J1, IF_T2_I0 }; const static insFormat formatEncode2E[2] = { IF_T1_E, IF_T2_C6 }; const static insFormat formatEncode2F[2] = { IF_T1_E, IF_T2_C5 }; const static insFormat formatEncode2G[2] = { IF_T1_J3, IF_T2_M1 }; // clang-format on size_t code = BAD_CODE; insFormat insFmt = emitInsFormat(ins); bool found = false; int index = 0; switch (insFmt) { case IF_EN9: for (index = 0; index < 9; index++) { if (fmt == formatEncode9[index]) { found = true; break; } } break; case IF_EN8: for (index = 0; index < 8; index++) { if (fmt == formatEncode8[index]) { found = true; break; } } break; case IF_EN6A: for (index = 0; index < 6; index++) { if (fmt == formatEncode6A[index]) { found = true; break; } } break; case IF_EN6B: for (index = 0; index < 6; index++) { if (fmt == formatEncode6B[index]) { found = true; break; } } break; case IF_EN5A: for (index = 0; index < 5; index++) { if (fmt == formatEncode5A[index]) { found = true; break; } } break; case IF_EN5B: for (index = 0; index < 5; index++) { if (fmt == formatEncode5B[index]) { found = true; break; } } break; case IF_EN4A: for (index = 0; index < 4; index++) { if (fmt == formatEncode4A[index]) { found = true; break; } } break; case IF_EN4B: for (index = 0; index < 4; index++) { if (fmt == formatEncode4B[index]) { found = true; break; } } break; case IF_EN3A: for (index = 0; index < 3; index++) { if (fmt == formatEncode3A[index]) { found = true; break; } } break; case IF_EN3B: for (index = 0; index < 3; index++) { if (fmt == formatEncode3B[index]) { found = true; break; } } break; case IF_EN3C: for (index = 0; index < 3; index++) { if (fmt == formatEncode3C[index]) { found = true; break; } } break; case IF_EN3D: for (index = 0; index < 3; index++) { if (fmt == formatEncode3D[index]) { found = true; break; } } break; case IF_EN3E: for (index = 0; index < 3; index++) { if (fmt == formatEncode3E[index]) { found = true; break; } } break; case IF_EN3F: for (index = 0; index < 3; index++) { if (fmt == formatEncode3F[index]) { found = true; break; } } break; case IF_EN2A: for (index = 0; index < 2; index++) { if (fmt == formatEncode2A[index]) { found = true; break; } } break; case IF_EN2B: for (index = 0; index < 2; index++) { if (fmt == formatEncode2B[index]) { found = true; break; } } break; case IF_EN2C: for (index = 0; index < 2; index++) { if (fmt == formatEncode2C[index]) { found = true; break; } } break; case IF_EN2D: for (index = 0; index < 2; index++) { if (fmt == formatEncode2D[index]) { found = true; break; } } break; case IF_EN2E: for (index = 0; index < 2; index++) { if (fmt == formatEncode2E[index]) { found = true; break; } } break; case IF_EN2F: for (index = 0; index < 2; index++) { if (fmt == formatEncode2F[index]) { found = true; break; } } break; case IF_EN2G: for (index = 0; index < 2; index++) { if (fmt == formatEncode2G[index]) { found = true; break; } } break; default: index = 0; found = true; break; } assert(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; } /***************************************************************************** * * Return the code size of the given instruction format. The 'insSize' return type enum * indicates a 16 bit, 32 bit, or 48 bit instruction. */ emitter::insSize emitter::emitInsSize(insFormat insFmt) { if ((insFmt >= IF_T1_A) && (insFmt < IF_T2_A)) return ISZ_16BIT; if ((insFmt >= IF_T2_A) && (insFmt < IF_INVALID)) return ISZ_32BIT; if (insFmt == IF_LARGEJMP) return ISZ_48BIT; assert(!"Invalid insFormat"); return ISZ_48BIT; } /***************************************************************************** * * isModImmConst() returns true when immediate 'val32' can be encoded * using the special modified immediate constant available in Thumb */ /*static*/ bool emitter::isModImmConst(int val32) { unsigned uval32 = (unsigned)val32; unsigned imm8 = uval32 & 0xff; /* encode = 0000x */ if (imm8 == uval32) return true; unsigned imm32a = (imm8 << 16) | imm8; /* encode = 0001x */ if (imm32a == uval32) return true; unsigned imm32b = (imm32a << 8); /* encode = 0010x */ if (imm32b == uval32) return true; unsigned imm32c = (imm32a | imm32b); /* encode = 0011x */ if (imm32c == uval32) return true; unsigned mask32 = 0x00000ff; unsigned encode = 31; /* 11111 */ unsigned temp; do { mask32 <<= 1; temp = uval32 & ~mask32; if (temp == 0) return true; encode--; } while (encode >= 8); return false; } /***************************************************************************** * * encodeModImmConst() returns the special ARM 12-bit immediate encoding. * that is used to encode the immediate. (4-bits, 8-bits) * If the imm can not be encoded then 0x0BADC0DE is returned. */ /*static*/ int emitter::encodeModImmConst(int val32) { unsigned uval32 = (unsigned)val32; unsigned imm8 = uval32 & 0xff; unsigned encode = imm8 >> 7; unsigned imm32a; unsigned imm32b; unsigned imm32c; unsigned mask32; unsigned temp; /* encode = 0000x */ if (imm8 == uval32) { goto DONE; } imm32a = (imm8 << 16) | imm8; /* encode = 0001x */ if (imm32a == uval32) { encode += 2; goto DONE; } imm32b = (imm32a << 8); /* encode = 0010x */ if (imm32b == uval32) { encode += 4; goto DONE; } imm32c = (imm32a | imm32b); /* encode = 0011x */ if (imm32c == uval32) { encode += 6; goto DONE; } mask32 = 0x00000ff; encode = 31; /* 11111 */ do { mask32 <<= 1; temp = uval32 & ~mask32; if (temp == 0) { imm8 = (uval32 & mask32) >> (32 - encode); assert((imm8 & 0x80) != 0); goto DONE; } encode--; } while (encode >= 8); assert(!"encodeModImmConst failed!"); return BAD_CODE; DONE: unsigned result = (encode << 7) | (imm8 & 0x7f); assert(result <= 0x0fff); assert(result >= 0); return (int)result; } /***************************************************************************** * * emitIns_valid_imm_for_alu() returns true when the immediate 'imm' * can be encoded using the 12-bit funky Arm immediate encoding */ /*static*/ bool emitter::emitIns_valid_imm_for_alu(int imm) { if (isModImmConst(imm)) return true; return false; } /***************************************************************************** * * emitIns_valid_imm_for_mov() returns true when the immediate 'imm' * can be encoded using a single mov or mvn instruction. */ /*static*/ bool emitter::emitIns_valid_imm_for_mov(int imm) { if ((imm & 0x0000ffff) == imm) // 16-bit immediate return true; if (isModImmConst(imm)) // funky arm immediate return true; if (isModImmConst(~imm)) // funky arm immediate via mvn return true; return false; } /***************************************************************************** * * emitIns_valid_imm_for_small_mov() returns true when the immediate 'imm' * can be encoded using a single 2-byte mov instruction. */ /*static*/ bool emitter::emitIns_valid_imm_for_small_mov(regNumber reg, int imm, insFlags flags) { return isLowRegister(reg) && insSetsFlags(flags) && ((imm & 0x00ff) == imm); } /***************************************************************************** * * emitins_valid_imm_for_add() returns true when the immediate 'imm' * can be encoded using a single add or sub instruction. */ /*static*/ bool emitter::emitIns_valid_imm_for_add(int imm, insFlags flags) { if ((unsigned_abs(imm) <= 0x00000fff) && (flags != INS_FLAGS_SET)) // 12-bit immediate via add/sub return true; if (isModImmConst(imm)) // funky arm immediate return true; if (isModImmConst(-imm)) // funky arm immediate via sub return true; return false; } /***************************************************************************** * * emitins_valid_imm_for_cmp() returns true if this 'imm' * can be encoded as a input operand to an cmp instruction. */ /*static*/ bool emitter::emitIns_valid_imm_for_cmp(int imm, insFlags flags) { if (isModImmConst(imm)) // funky arm immediate return true; if (isModImmConst(-imm)) // funky arm immediate via sub return true; return false; } /***************************************************************************** * * emitIns_valid_imm_for_add_sp() returns true when the immediate 'imm' * can be encoded in "add Rd,SP,i10". */ /*static*/ bool emitter::emitIns_valid_imm_for_add_sp(int imm) { if ((imm & 0x03fc) == imm) return true; return false; } /***************************************************************************** * * emitIns_valid_imm_for_ldst_offset() returns true when the immediate 'imm' * can be encoded as the offset in a ldr/str instruction. */ /*static*/ bool emitter::emitIns_valid_imm_for_ldst_offset(int imm, emitAttr size) { if ((imm & 0x0fff) == imm) return true; // encodable using IF_T2_K1 if (unsigned_abs(imm) <= 0x0ff) return true; // encodable using IF_T2_H0 return false; } /***************************************************************************** * * Add an instruction with no operands. */ void emitter::emitIns(instruction ins) { instrDesc* id = emitNewInstrSmall(EA_4BYTE); insFormat fmt = emitInsFormat(ins); insSize isz = emitInsSize(fmt); assert((fmt == IF_T1_A) || (fmt == IF_T2_A)); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); 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; bool hasLR = false; bool hasPC = false; bool useT2 = false; bool onlyT1 = false; /* Figure out the encoding format of the instruction */ switch (ins) { #ifdef FEATURE_ITINSTRUCTION case INS_it: case INS_itt: case INS_ite: case INS_ittt: case INS_itte: case INS_itet: case INS_itee: case INS_itttt: case INS_ittte: case INS_ittet: case INS_ittee: case INS_itett: case INS_itete: case INS_iteet: case INS_iteee: assert((imm & 0x0F) == imm); fmt = IF_T1_B; attr = EA_4BYTE; break; #endif // FEATURE_ITINSTRUCTION case INS_push: assert((imm & 0xA000) == 0); // Cannot push PC or SP if (imm & 0x4000) // Is the LR being pushed? hasLR = true; goto COMMON_PUSH_POP; case INS_pop: assert((imm & 0x2000) == 0); // Cannot pop SP assert((imm & 0xC000) != 0xC000); // Cannot pop both PC and LR if (imm & 0x8000) // Is the PC being popped? hasPC = true; if (imm & 0x4000) // Is the LR being popped? { hasLR = true; useT2 = true; } COMMON_PUSH_POP: if (((imm - 1) & imm) == 0) // Is only one or zero bits set in imm? { if (((imm == 0) && !hasLR) || // imm has no bits set, but hasLR is set (!hasPC && !hasLR)) // imm has one bit set, and neither of hasPC/hasLR are set { onlyT1 = true; // if only one bit is set we must use the T1 encoding } } imm &= ~0xE000; // ensure that PC, LR and SP bits are removed from imm if (((imm & 0x00ff) == imm) && !useT2) { fmt = IF_T1_L1; } else if (!onlyT1) { fmt = IF_T2_I1; } else { // We have to use the Thumb-2 push single register encoding regNumber reg = genRegNumFromMask(imm); emitIns_R(ins, attr, reg); return; } // // Encode the PC and LR bits as the lowest two bits // imm <<= 2; if (hasPC) imm |= 2; if (hasLR) imm |= 1; assert(imm != 0); break; #if 0 // TODO-ARM-Cleanup: Enable or delete. case INS_bkpt: // Windows uses a different encoding if ((imm & 0x0000ffff) == imm) { fmt = IF_T1_L0; } else { assert(!"Instruction cannot be encoded"); } break; #endif case INS_dmb: case INS_ism: if ((imm & 0x000f) == imm) { fmt = IF_T2_B; attr = EA_4BYTE; } else { assert(!"Instruction cannot be encoded"); } break; default: unreached(); } assert((fmt == IF_T1_B) || (fmt == IF_T1_L0) || (fmt == IF_T1_L1) || (fmt == IF_T2_I1) || (fmt == IF_T2_B)); instrDesc* id = emitNewInstrSC(attr, imm); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); 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; /* Figure out the encoding format of the instruction */ switch (ins) { case INS_pop: case INS_push: if (isLowRegister(reg)) { int regmask = 1 << ((int)reg); emitIns_I(ins, attr, regmask); return; } assert(size == EA_PTRSIZE); fmt = IF_T2_E2; break; case INS_vmrs: assert(size == EA_PTRSIZE); fmt = IF_T2_E2; break; case INS_bx: assert(size == EA_PTRSIZE); fmt = IF_T1_D1; break; case INS_rsb: case INS_mvn: emitIns_R_R_I(ins, attr, reg, reg, 0); return; default: unreached(); } assert((fmt == IF_T1_D1) || (fmt == IF_T2_E2)); instrDesc* id = emitNewInstrSmall(attr); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idReg1(reg); dispIns(id); appendToCurIG(id); } /***************************************************************************** * * Add an instruction referencing a register and a constant. */ void emitter::emitIns_R_I( instruction ins, emitAttr attr, regNumber reg, int imm, insFlags flags /* = INS_FLAGS_DONT_CARE */) { insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_DONT_CARE; /* Figure out the encoding format of the instruction */ switch (ins) { case INS_add: case INS_sub: if ((reg == REG_SP) && insDoesNotSetFlags(flags) && ((imm & 0x01fc) == imm)) { fmt = IF_T1_F; sf = INS_FLAGS_NOT_SET; } else if (isLowRegister(reg) && insSetsFlags(flags) && (unsigned_abs(imm) <= 0x00ff)) { if (imm < 0) { assert((ins == INS_add) || (ins == INS_sub)); if (ins == INS_add) ins = INS_sub; else // ins == INS_sub ins = INS_add; imm = -imm; } fmt = IF_T1_J0; sf = INS_FLAGS_SET; } else { // otherwise we have to use a Thumb-2 encoding emitIns_R_R_I(ins, attr, reg, reg, imm, flags); return; } break; case INS_adc: emitIns_R_R_I(ins, attr, reg, reg, imm, flags); return; case INS_vpush: case INS_vpop: assert(imm > 0); if (attr == EA_8BYTE) { assert(isDoubleReg(reg)); assert(imm <= 16); imm *= 2; } else { assert(attr == EA_4BYTE); assert(isFloatReg(reg)); assert(imm <= 16); } assert(((reg - REG_F0) + imm) <= 32); imm *= 4; if (ins == INS_vpush) imm = -imm; sf = INS_FLAGS_NOT_SET; fmt = IF_T2_VLDST; break; case INS_stm: { sf = INS_FLAGS_NOT_SET; bool hasLR = false; bool hasPC = false; bool useT2 = false; bool onlyT1 = false; assert((imm & 0x2000) == 0); // Cannot pop SP assert((imm & 0xC000) != 0xC000); // Cannot pop both PC and LR assert((imm & 0xFFFF0000) == 0); // Can only contain lower 16 bits if (imm & 0x8000) // Is the PC being popped? hasPC = true; if (imm & 0x4000) // Is the LR being pushed? { hasLR = true; useT2 = true; } if (!isLowRegister(reg)) useT2 = true; if (((imm - 1) & imm) == 0) // Is only one or zero bits set in imm? { if (((imm == 0) && !hasLR) || // imm has no bits set, but hasLR is set (!hasPC && !hasLR)) // imm has one bit set, and neither of hasPC/hasLR are set { onlyT1 = true; // if only one bit is set we must use the T1 encoding } } imm &= ~0xE000; // ensure that PC, LR and SP bits are removed from imm if (((imm & 0x00ff) == imm) && !useT2) { fmt = IF_T1_J1; } else if (!onlyT1) { fmt = IF_T2_I0; } else { assert(!"Instruction cannot be encoded"); // We have to use the Thumb-2 str single register encoding // reg = genRegNumFromMask(imm); // emitIns_R(ins, attr, reg); return; } // // Encode the PC and LR bits as the lowest two bits // if (fmt == IF_T2_I0) { imm <<= 2; if (hasPC) imm |= 2; if (hasLR) imm |= 1; } assert(imm != 0); } break; case INS_and: case INS_bic: case INS_eor: case INS_orr: case INS_orn: case INS_rsb: case INS_sbc: case INS_ror: case INS_asr: case INS_lsl: case INS_lsr: // use the Reg, Reg, Imm encoding emitIns_R_R_I(ins, attr, reg, reg, imm, flags); return; case INS_mov: assert(!EA_IS_CNS_RELOC(attr)); if (isLowRegister(reg) && insSetsFlags(flags) && ((imm & 0x00ff) == imm)) { fmt = IF_T1_J0; sf = INS_FLAGS_SET; } else if (isModImmConst(imm)) { fmt = IF_T2_L1; sf = insMustSetFlags(flags); } else if (isModImmConst(~imm)) // See if we can use move negated instruction instead { ins = INS_mvn; imm = ~imm; fmt = IF_T2_L1; sf = insMustSetFlags(flags); } else if (insDoesNotSetFlags(flags) && ((imm & 0x0000ffff) == imm)) { // mov => movw instruction ins = INS_movw; fmt = IF_T2_N; sf = INS_FLAGS_NOT_SET; } else { assert(!"Instruction cannot be encoded"); } break; case INS_movw: case INS_movt: assert(insDoesNotSetFlags(flags)); sf = INS_FLAGS_NOT_SET; if ((imm & 0x0000ffff) == imm || EA_IS_RELOC(attr)) { fmt = IF_T2_N; } else { assert(!"Instruction cannot be encoded"); } break; case INS_mvn: if (isModImmConst(imm)) { fmt = IF_T2_L1; sf = insMustSetFlags(flags); } else { assert(!"Instruction cannot be encoded"); } break; case INS_cmp: assert(!EA_IS_CNS_RELOC(attr)); assert(insSetsFlags(flags)); sf = INS_FLAGS_SET; if (isLowRegister(reg) && ((imm & 0x0ff) == imm)) { fmt = IF_T1_J0; } else if (isModImmConst(imm)) { fmt = IF_T2_L2; } else if (isModImmConst(-imm)) { ins = INS_cmn; fmt = IF_T2_L2; imm = -imm; } else { #ifndef LEGACY_BACKEND assert(!"emitIns_R_I: immediate doesn't fit into the instruction"); #else // LEGACY_BACKEND // Load val into a register regNumber valReg = codeGen->regSet.rsGrabReg(RBM_ALLINT & ~genRegMask(reg)); codeGen->instGen_Set_Reg_To_Imm(EA_PTRSIZE, valReg, (ssize_t)imm); emitIns_R_R(ins, attr, reg, valReg, flags); #endif // LEGACY_BACKEND return; } break; case INS_cmn: case INS_tst: case INS_teq: assert(insSetsFlags(flags)); sf = INS_FLAGS_SET; if (isModImmConst(imm)) { fmt = IF_T2_L2; } else { assert(!"Instruction cannot be encoded"); } break; #ifdef FEATURE_PLI_INSTRUCTION case INS_pli: assert(insDoesNotSetFlags(flags)); if ((reg == REG_SP) && (unsigned_abs(imm) <= 0x0fff)) { fmt = IF_T2_K3; sf = INS_FLAGS_NOT_SET; } __fallthrough; #endif // FEATURE_PLI_INSTRUCTION case INS_pld: case INS_pldw: assert(insDoesNotSetFlags(flags)); sf = INS_FLAGS_NOT_SET; if ((imm >= 0) && (imm <= 0x0fff)) { fmt = IF_T2_K2; } else if ((imm < 0) && (-imm <= 0x00ff)) { imm = -imm; fmt = IF_T2_H2; } else { assert(!"Instruction cannot be encoded"); } break; default: unreached(); } assert((fmt == IF_T1_F) || (fmt == IF_T1_J0) || (fmt == IF_T1_J1) || (fmt == IF_T2_H2) || (fmt == IF_T2_I0) || (fmt == IF_T2_K2) || (fmt == IF_T2_K3) || (fmt == IF_T2_L1) || (fmt == IF_T2_L2) || (fmt == IF_T2_M1) || (fmt == IF_T2_N) || (fmt == IF_T2_VLDST)); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstrSC(attr, imm); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); 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, insFlags flags /* = INS_FLAGS_DONT_CARE */) { emitAttr size = EA_SIZE(attr); insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_DONT_CARE; /* Figure out the encoding format of the instruction */ switch (ins) { case INS_add: if (insDoesNotSetFlags(flags)) { fmt = IF_T1_D0; sf = INS_FLAGS_NOT_SET; break; } __fallthrough; case INS_sub: // Use the Thumb-1 reg,reg,reg encoding emitIns_R_R_R(ins, attr, reg1, reg1, reg2, flags); return; case INS_mov: if (insDoesNotSetFlags(flags)) { assert(reg1 != reg2); fmt = IF_T1_D0; sf = INS_FLAGS_NOT_SET; } else // insSetsFlags(flags) { sf = INS_FLAGS_SET; if (isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_E; } else { fmt = IF_T2_C3; } } break; case INS_cmp: assert(insSetsFlags(flags)); sf = INS_FLAGS_SET; if (isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_E; // both are low registers } else { fmt = IF_T1_D0; // one or both are high registers } break; case INS_vmov_f2i: assert(isGeneralRegister(reg1)); assert(isFloatReg(reg2)); fmt = IF_T2_VMOVS; sf = INS_FLAGS_NOT_SET; break; case INS_vmov_i2f: assert(isFloatReg(reg1)); assert(isGeneralRegister(reg2)); fmt = IF_T2_VMOVS; sf = INS_FLAGS_NOT_SET; break; case INS_vcvt_d2i: case INS_vcvt_d2u: case INS_vcvt_d2f: assert(isFloatReg(reg1)); assert(isDoubleReg(reg2)); goto VCVT_COMMON; case INS_vcvt_f2d: case INS_vcvt_u2d: case INS_vcvt_i2d: assert(isDoubleReg(reg1)); assert(isFloatReg(reg2)); goto VCVT_COMMON; case INS_vcvt_u2f: case INS_vcvt_i2f: case INS_vcvt_f2i: case INS_vcvt_f2u: assert(size == EA_4BYTE); assert(isFloatReg(reg1)); assert(isFloatReg(reg2)); goto VCVT_COMMON; case INS_vmov: assert(reg1 != reg2); __fallthrough; case INS_vabs: case INS_vsqrt: case INS_vcmp: case INS_vneg: if (size == EA_8BYTE) { assert(isDoubleReg(reg1)); assert(isDoubleReg(reg2)); } else { assert(isFloatReg(reg1)); assert(isFloatReg(reg2)); } __fallthrough; VCVT_COMMON: fmt = IF_T2_VFP2; sf = INS_FLAGS_NOT_SET; break; case INS_vadd: case INS_vmul: case INS_vsub: case INS_vdiv: emitIns_R_R_R(ins, attr, reg1, reg1, reg2); return; case INS_vldr: case INS_vstr: case INS_ldr: case INS_ldrb: case INS_ldrsb: case INS_ldrh: case INS_ldrsh: case INS_str: case INS_strb: case INS_strh: emitIns_R_R_I(ins, attr, reg1, reg2, 0); return; case INS_adc: case INS_and: case INS_bic: case INS_eor: case INS_orr: case INS_sbc: if (insSetsFlags(flags) && isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_E; sf = INS_FLAGS_SET; break; } __fallthrough; case INS_orn: // assert below fired for bug 281892 where the two operands of an OR were // the same static field load which got cse'd. // there's no reason why this assert would be true in general // assert(reg1 != reg2); // Use the Thumb-2 three register encoding emitIns_R_R_R_I(ins, attr, reg1, reg1, reg2, 0, flags); return; case INS_asr: case INS_lsl: case INS_lsr: case INS_ror: // assert below fired for bug 296394 where the two operands of an // arithmetic right shift were the same local variable // there's no reason why this assert would be true in general // assert(reg1 != reg2); if (insSetsFlags(flags) && isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_E; sf = INS_FLAGS_SET; } else { // Use the Thumb-2 three register encoding emitIns_R_R_R(ins, attr, reg1, reg1, reg2, flags); return; } break; case INS_mul: // We will prefer the T2 encoding, unless (flags == INS_FLAGS_SET) // The thumb-1 instruction executes much slower as it must always set the flags // if (insMustSetFlags(flags) && isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_E; sf = INS_FLAGS_SET; } else { // Use the Thumb-2 three register encoding emitIns_R_R_R(ins, attr, reg1, reg2, reg1, flags); return; } break; case INS_mvn: case INS_cmn: case INS_tst: if (insSetsFlags(flags) && isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_E; sf = INS_FLAGS_SET; } else { // Use the Thumb-2 register with shift encoding emitIns_R_R_I(ins, attr, reg1, reg2, 0, flags); return; } break; case INS_sxtb: case INS_uxtb: assert(size == EA_1BYTE); goto EXTEND_COMMON; case INS_sxth: case INS_uxth: assert(size == EA_2BYTE); EXTEND_COMMON: assert(insDoesNotSetFlags(flags)); if (isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_E; sf = INS_FLAGS_NOT_SET; } else { // Use the Thumb-2 reg,reg with rotation encoding emitIns_R_R_I(ins, attr, reg1, reg2, 0, INS_FLAGS_NOT_SET); return; } break; case INS_tbb: assert(size == EA_1BYTE); assert(insDoesNotSetFlags(flags)); fmt = IF_T2_C9; sf = INS_FLAGS_NOT_SET; break; case INS_tbh: assert(size == EA_2BYTE); assert(insDoesNotSetFlags(flags)); fmt = IF_T2_C9; sf = INS_FLAGS_NOT_SET; break; case INS_clz: assert(insDoesNotSetFlags(flags)); fmt = IF_T2_C10; sf = INS_FLAGS_NOT_SET; break; case INS_ldrexb: case INS_strexb: assert(size == EA_1BYTE); assert(insDoesNotSetFlags(flags)); fmt = IF_T2_E1; sf = INS_FLAGS_NOT_SET; break; case INS_ldrexh: case INS_strexh: assert(size == EA_2BYTE); assert(insDoesNotSetFlags(flags)); fmt = IF_T2_E1; sf = INS_FLAGS_NOT_SET; break; default: #ifdef DEBUG printf("did not expect instruction %s\n", codeGen->genInsName(ins)); #endif unreached(); } assert((fmt == IF_T1_D0) || (fmt == IF_T1_E) || (fmt == IF_T2_C3) || (fmt == IF_T2_C9) || (fmt == IF_T2_C10) || (fmt == IF_T2_VFP2) || (fmt == IF_T2_VMOVD) || (fmt == IF_T2_VMOVS) || (fmt == IF_T2_E1)); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstrSmall(attr); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); 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, int imm1, int imm2, insFlags flags /* = INS_FLAGS_DONT_CARE */) { insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_DONT_CARE; int imm = 0; // combined immediates /* Figure out the encoding format of the instruction */ switch (ins) { case INS_bfc: { int lsb = imm1; int msb = lsb + imm2 - 1; assert((lsb >= 0) && (lsb <= 31)); // required for encoding of INS_bfc assert((msb >= 0) && (msb <= 31)); // required for encoding of INS_bfc assert(msb >= lsb); // required for encoding of INS_bfc imm = (lsb << 5) | msb; assert(insDoesNotSetFlags(flags)); fmt = IF_T2_D1; sf = INS_FLAGS_NOT_SET; } break; default: unreached(); } assert(fmt == IF_T2_D1); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstrSC(attr, imm); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); 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, int imm, insFlags flags /* = INS_FLAGS_DONT_CARE */, insOpts opt /* = INS_OPTS_NONE */) { emitAttr size = EA_SIZE(attr); insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_DONT_CARE; if (ins == INS_lea) { ins = INS_add; } /* Figure out the encoding format of the instruction */ switch (ins) { case INS_add: assert(insOptsNone(opt)); // Can we possibly encode the immediate 'imm' using a Thumb-1 encoding? if ((reg2 == REG_SP) && insDoesNotSetFlags(flags) && ((imm & 0x03fc) == imm)) { if ((reg1 == REG_SP) && ((imm & 0x01fc) == imm)) { // Use Thumb-1 encoding emitIns_R_I(ins, attr, reg1, imm, flags); return; } else if (isLowRegister(reg1)) { fmt = IF_T1_J2; sf = INS_FLAGS_NOT_SET; break; } } __fallthrough; case INS_sub: assert(insOptsNone(opt)); // Is it just a mov? if (imm == 0) { // Is the mov even necessary? // Fix 383915 ARM ILGEN if (reg1 != reg2) { emitIns_R_R(INS_mov, attr, reg1, reg2, flags); } return; } // Can we encode the immediate 'imm' using a Thumb-1 encoding? else if (isLowRegister(reg1) && isLowRegister(reg2) && insSetsFlags(flags) && (unsigned_abs(imm) <= 0x0007)) { if (imm < 0) { assert((ins == INS_add) || (ins == INS_sub)); if (ins == INS_add) ins = INS_sub; else ins = INS_add; imm = -imm; } fmt = IF_T1_G; sf = INS_FLAGS_SET; } else if ((reg1 == reg2) && isLowRegister(reg1) && insSetsFlags(flags) && (unsigned_abs(imm) <= 0x00ff)) { if (imm < 0) { assert((ins == INS_add) || (ins == INS_sub)); if (ins == INS_add) ins = INS_sub; else ins = INS_add; imm = -imm; } // Use Thumb-1 encoding emitIns_R_I(ins, attr, reg1, imm, flags); return; } else if (isModImmConst(imm)) { fmt = IF_T2_L0; sf = insMustSetFlags(flags); } else if (isModImmConst(-imm)) { assert((ins == INS_add) || (ins == INS_sub)); ins = (ins == INS_add) ? INS_sub : INS_add; imm = -imm; fmt = IF_T2_L0; sf = insMustSetFlags(flags); } else if (insDoesNotSetFlags(flags) && (unsigned_abs(imm) <= 0x0fff)) { if (imm < 0) { assert((ins == INS_add) || (ins == INS_sub)); ins = (ins == INS_add) ? INS_sub : INS_add; imm = -imm; } // add/sub => addw/subw instruction // Note that even when using the w prefix the immediate is still only 12 bits? ins = (ins == INS_add) ? INS_addw : INS_subw; fmt = IF_T2_M0; sf = INS_FLAGS_NOT_SET; } else { assert(!"Instruction cannot be encoded"); } break; case INS_and: case INS_bic: case INS_orr: case INS_orn: assert(insOptsNone(opt)); if (isModImmConst(imm)) { fmt = IF_T2_L0; sf = insMustSetFlags(flags); } else if (isModImmConst(~imm)) { fmt = IF_T2_L0; sf = insMustSetFlags(flags); imm = ~imm; if (ins == INS_and) ins = INS_bic; else if (ins == INS_bic) ins = INS_and; else if (ins == INS_orr) ins = INS_orn; else if (ins == INS_orn) ins = INS_orr; else assert(!"Instruction cannot be encoded"); } else { assert(!"Instruction cannot be encoded"); } break; case INS_rsb: assert(insOptsNone(opt)); if (imm == 0 && isLowRegister(reg1) && isLowRegister(reg2) && insSetsFlags(flags)) { fmt = IF_T1_E; sf = INS_FLAGS_SET; break; } __fallthrough; case INS_adc: case INS_eor: case INS_sbc: assert(insOptsNone(opt)); if (isModImmConst(imm)) { fmt = IF_T2_L0; sf = insMustSetFlags(flags); } else { assert(!"Instruction cannot be encoded"); } break; case INS_adr: assert(insOptsNone(opt)); assert(insDoesNotSetFlags(flags)); assert(reg2 == REG_PC); sf = INS_FLAGS_NOT_SET; if (isLowRegister(reg1) && ((imm & 0x00ff) == imm)) { fmt = IF_T1_J3; } else if ((imm & 0x0fff) == imm) { fmt = IF_T2_M1; } else { assert(!"Instruction cannot be encoded"); } break; case INS_mvn: assert((imm >= 0) && (imm <= 31)); // required for encoding assert(!insOptAnyInc(opt)); if (imm == 0) { assert(insOptsNone(opt)); if (isLowRegister(reg1) && isLowRegister(reg2) && insSetsFlags(flags)) { // Use the Thumb-1 reg,reg encoding emitIns_R_R(ins, attr, reg1, reg2, flags); return; } } else // imm > 0 && imm <= 31 { assert(insOptAnyShift(opt)); } fmt = IF_T2_C1; sf = insMustSetFlags(flags); break; case INS_cmp: case INS_cmn: case INS_teq: case INS_tst: assert(insSetsFlags(flags)); assert((imm >= 0) && (imm <= 31)); // required for encoding assert(!insOptAnyInc(opt)); if (imm == 0) { assert(insOptsNone(opt)); if (ins == INS_cmp) { // Use the Thumb-1 reg,reg encoding emitIns_R_R(ins, attr, reg1, reg2, flags); return; } if (((ins == INS_cmn) || (ins == INS_tst)) && isLowRegister(reg1) && isLowRegister(reg2)) { // Use the Thumb-1 reg,reg encoding emitIns_R_R(ins, attr, reg1, reg2, flags); return; } } else // imm > 0 && imm <= 31) { assert(insOptAnyShift(opt)); if (insOptsRRX(opt)) assert(imm == 1); } fmt = IF_T2_C8; sf = INS_FLAGS_SET; break; case INS_ror: case INS_asr: case INS_lsl: case INS_lsr: assert(insOptsNone(opt)); // On ARM, the immediate shift count of LSL and ROR must be between 1 and 31. For LSR and ASR, it is between // 1 and 32, though we don't ever use 32. Although x86 allows an immediate shift count of 8-bits in // instruction encoding, the CPU looks at only the lower 5 bits. As per ECMA, specifying a shift count to // the IL SHR, SHL, or SHL.UN instruction that is greater than or equal to the width of the type will yield // an undefined value. We choose that undefined value in this case to match x86 behavior, by only using the // lower 5 bits of the constant shift count. imm &= 0x1f; if (imm == 0) { // Additional Fix 383915 ARM ILGEN if ((reg1 != reg2) || insMustSetFlags(flags)) { // Use MOV/MOVS instriction emitIns_R_R(INS_mov, attr, reg1, reg2, flags); } return; } if (insSetsFlags(flags) && (ins != INS_ror) && isLowRegister(reg1) && isLowRegister(reg2)) { fmt = IF_T1_C; sf = INS_FLAGS_SET; } else { fmt = IF_T2_C2; sf = insMustSetFlags(flags); } break; case INS_sxtb: case INS_uxtb: assert(size == EA_1BYTE); goto EXTEND_COMMON; case INS_sxth: case INS_uxth: assert(size == EA_2BYTE); EXTEND_COMMON: assert(insOptsNone(opt)); assert(insDoesNotSetFlags(flags)); assert((imm & 0x018) == imm); // required for encoding if ((imm == 0) && isLowRegister(reg1) && isLowRegister(reg2)) { // Use Thumb-1 encoding emitIns_R_R(ins, attr, reg1, reg2, INS_FLAGS_NOT_SET); return; } fmt = IF_T2_C6; sf = INS_FLAGS_NOT_SET; break; case INS_pld: case INS_pldw: #ifdef FEATURE_PLI_INSTRUCTION case INS_pli: #endif // FEATURE_PLI_INSTRUCTION assert(insOptsNone(opt)); assert(insDoesNotSetFlags(flags)); assert((imm & 0x003) == imm); // required for encoding fmt = IF_T2_C7; sf = INS_FLAGS_NOT_SET; break; case INS_ldrb: case INS_strb: assert(size == EA_1BYTE); assert(insDoesNotSetFlags(flags)); if (isLowRegister(reg1) && isLowRegister(reg2) && insOptsNone(opt) && ((imm & 0x001f) == imm)) { fmt = IF_T1_C; sf = INS_FLAGS_NOT_SET; break; } goto COMMON_THUMB2_LDST; case INS_ldrsb: assert(size == EA_1BYTE); goto COMMON_THUMB2_LDST; case INS_ldrh: case INS_strh: assert(size == EA_2BYTE); assert(insDoesNotSetFlags(flags)); if (isLowRegister(reg1) && isLowRegister(reg2) && insOptsNone(opt) && ((imm & 0x003e) == imm)) { fmt = IF_T1_C; sf = INS_FLAGS_NOT_SET; break; } goto COMMON_THUMB2_LDST; case INS_ldrsh: assert(size == EA_2BYTE); goto COMMON_THUMB2_LDST; case INS_vldr: case INS_vstr: case INS_vldm: case INS_vstm: assert(fmt == IF_NONE); assert(insDoesNotSetFlags(flags)); assert(offsetFitsInVectorMem(imm)); // required for encoding if (insOptAnyInc(opt)) { if (insOptsPostInc(opt)) { assert(imm > 0); } else // insOptsPreDec(opt) { assert(imm < 0); } } else { assert(insOptsNone(opt)); } sf = INS_FLAGS_NOT_SET; fmt = IF_T2_VLDST; break; case INS_ldr: case INS_str: assert(size == EA_4BYTE); assert(insDoesNotSetFlags(flags)); // Can we possibly encode the immediate 'imm' using a Thumb-1 encoding? if (isLowRegister(reg1) && insOptsNone(opt) && ((imm & 0x03fc) == imm)) { if (reg2 == REG_SP) { fmt = IF_T1_J2; sf = INS_FLAGS_NOT_SET; break; } else if (reg2 == REG_PC) { if (ins == INS_ldr) { fmt = IF_T1_J3; sf = INS_FLAGS_NOT_SET; break; } } else if (isLowRegister(reg2)) { // Only the smaller range 'imm' can be encoded if ((imm & 0x07c) == imm) { fmt = IF_T1_C; sf = INS_FLAGS_NOT_SET; break; } } } // // If we did not find a thumb-1 encoding above // __fallthrough; COMMON_THUMB2_LDST: assert(fmt == IF_NONE); assert(insDoesNotSetFlags(flags)); sf = INS_FLAGS_NOT_SET; if (insOptAnyInc(opt)) { if (insOptsPostInc(opt)) assert(imm > 0); else // insOptsPreDec(opt) assert(imm < 0); if (unsigned_abs(imm) <= 0x00ff) { fmt = IF_T2_H0; } else { assert(!"Instruction cannot be encoded"); } } else { assert(insOptsNone(opt)); if ((reg2 == REG_PC) && (unsigned_abs(imm) <= 0x0fff)) { fmt = IF_T2_K4; } else if ((imm & 0x0fff) == imm) { fmt = IF_T2_K1; } else if (unsigned_abs(imm) <= 0x0ff) { fmt = IF_T2_H0; } else { // Load imm into a register regNumber rsvdReg = codeGen->rsGetRsvdReg(); codeGen->instGen_Set_Reg_To_Imm(EA_4BYTE, rsvdReg, (ssize_t)imm); emitIns_R_R_R(ins, attr, reg1, reg2, rsvdReg); return; } } break; case INS_ldrex: case INS_strex: assert(insOptsNone(opt)); assert(insDoesNotSetFlags(flags)); sf = INS_FLAGS_NOT_SET; if ((imm & 0x03fc) == imm) { fmt = IF_T2_H0; } else { assert(!"Instruction cannot be encoded"); } break; default: assert(!"Unexpected instruction"); } assert((fmt == IF_T1_C) || (fmt == IF_T1_E) || (fmt == IF_T1_G) || (fmt == IF_T1_J2) || (fmt == IF_T1_J3) || (fmt == IF_T2_C1) || (fmt == IF_T2_C2) || (fmt == IF_T2_C6) || (fmt == IF_T2_C7) || (fmt == IF_T2_C8) || (fmt == IF_T2_H0) || (fmt == IF_T2_H1) || (fmt == IF_T2_K1) || (fmt == IF_T2_K4) || (fmt == IF_T2_L0) || (fmt == IF_T2_M0) || (fmt == IF_T2_VLDST) || (fmt == IF_T2_M1)); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstrSC(attr, imm); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); id->idInsOpt(opt); id->idReg1(reg1); id->idReg2(reg2); dispIns(id); appendToCurIG(id); } /***************************************************************************** * * Add an instruction referencing three registers. */ void emitter::emitIns_R_R_R(instruction ins, emitAttr attr, regNumber reg1, regNumber reg2, regNumber reg3, insFlags flags /* = INS_FLAGS_DONT_CARE */) { emitAttr size = EA_SIZE(attr); insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_DONT_CARE; /* Figure out the encoding format of the instruction */ switch (ins) { case INS_add: // Encodings do not support SP in the reg3 slot if (reg3 == REG_SP) { // Swap reg2 and reg3 reg3 = reg2; reg2 = REG_SP; } __fallthrough; case INS_sub: assert(reg3 != REG_SP); if (isLowRegister(reg1) && isLowRegister(reg2) && isLowRegister(reg3) && insSetsFlags(flags)) { fmt = IF_T1_H; sf = INS_FLAGS_SET; break; } if ((ins == INS_add) && insDoesNotSetFlags(flags)) { if (reg1 == reg2) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg3, flags); return; } if (reg1 == reg3) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg2, flags); return; } } // Use the Thumb-2 reg,reg,reg with shift encoding emitIns_R_R_R_I(ins, attr, reg1, reg2, reg3, 0, flags); return; case INS_adc: case INS_and: case INS_bic: case INS_eor: case INS_orr: case INS_sbc: if (reg1 == reg2) { // Try to encode as a Thumb-1 instruction emitIns_R_R(ins, attr, reg1, reg3, flags); return; } __fallthrough; case INS_orn: // Use the Thumb-2 three register encoding, with imm=0 emitIns_R_R_R_I(ins, attr, reg1, reg2, reg3, 0, flags); return; case INS_asr: case INS_lsl: case INS_lsr: if (reg1 == reg2 && insSetsFlags(flags) && isLowRegister(reg1) && isLowRegister(reg3)) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg3, flags); return; } __fallthrough; case INS_ror: fmt = IF_T2_C4; sf = insMustSetFlags(flags); break; case INS_mul: if (insMustSetFlags(flags)) { if ((reg1 == reg2) && isLowRegister(reg1)) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg3, flags); return; } if ((reg1 == reg3) && isLowRegister(reg1)) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg2, flags); return; } else { assert(!"Instruction cannot be encoded"); } } __fallthrough; case INS_sdiv: case INS_udiv: assert(insDoesNotSetFlags(flags)); fmt = IF_T2_C5; sf = INS_FLAGS_NOT_SET; break; case INS_ldrb: case INS_strb: case INS_ldrsb: assert(size == EA_1BYTE); goto COMMON_THUMB1_LDST; case INS_ldrsh: case INS_ldrh: case INS_strh: assert(size == EA_2BYTE); goto COMMON_THUMB1_LDST; case INS_ldr: case INS_str: assert(size == EA_4BYTE); COMMON_THUMB1_LDST: assert(insDoesNotSetFlags(flags)); if (isLowRegister(reg1) && isLowRegister(reg2) && isLowRegister(reg3)) { fmt = IF_T1_H; sf = INS_FLAGS_NOT_SET; } else { // Use the Thumb-2 reg,reg,reg with shift encoding emitIns_R_R_R_I(ins, attr, reg1, reg2, reg3, 0, flags); return; } break; case INS_vadd: case INS_vmul: case INS_vsub: case INS_vdiv: if (size == EA_8BYTE) { assert(isDoubleReg(reg1)); assert(isDoubleReg(reg2)); assert(isDoubleReg(reg3)); } else { assert(isFloatReg(reg1)); assert(isFloatReg(reg2)); assert(isFloatReg(reg3)); } fmt = IF_T2_VFP3; sf = INS_FLAGS_NOT_SET; break; case INS_vmov_i2d: assert(isDoubleReg(reg1)); assert(isGeneralRegister(reg2)); assert(isGeneralRegister(reg3)); fmt = IF_T2_VMOVD; sf = INS_FLAGS_NOT_SET; break; case INS_vmov_d2i: assert(isGeneralRegister(reg1)); assert(isGeneralRegister(reg2)); assert(isDoubleReg(reg3)); fmt = IF_T2_VMOVD; sf = INS_FLAGS_NOT_SET; break; case INS_ldrexd: case INS_strexd: assert(insDoesNotSetFlags(flags)); fmt = IF_T2_G1; sf = INS_FLAGS_NOT_SET; break; default: unreached(); } assert((fmt == IF_T1_H) || (fmt == IF_T2_C4) || (fmt == IF_T2_C5) || (fmt == IF_T2_VFP3) || (fmt == IF_T2_VMOVD) || (fmt == IF_T2_G1)); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstr(attr); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); id->idReg1(reg1); id->idReg2(reg2); id->idReg3(reg3); 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, insFlags flags /* = INS_FLAGS_DONT_CARE */) { insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_DONT_CARE; int lsb = imm1; int width = imm2; int msb = lsb + width - 1; int imm = 0; /* combined immediate */ assert((lsb >= 0) && (lsb <= 31)); // required for encodings assert((width > 0) && (width <= 32)); // required for encodings assert((msb >= 0) && (msb <= 31)); // required for encodings assert(msb >= lsb); // required for encodings /* Figure out the encoding format of the instruction */ switch (ins) { case INS_bfi: assert(insDoesNotSetFlags(flags)); imm = (lsb << 5) | msb; fmt = IF_T2_D0; sf = INS_FLAGS_NOT_SET; break; case INS_sbfx: case INS_ubfx: assert(insDoesNotSetFlags(flags)); imm = (lsb << 5) | (width - 1); fmt = IF_T2_D0; sf = INS_FLAGS_NOT_SET; break; default: unreached(); } assert((fmt == IF_T2_D0)); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstrSC(attr, imm); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); id->idReg1(reg1); id->idReg2(reg2); 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, int imm, insFlags flags /* = INS_FLAGS_DONT_CARE */, insOpts opt /* = INS_OPTS_NONE */) { emitAttr size = EA_SIZE(attr); insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_DONT_CARE; /* Figure out the encoding format of the instruction */ switch (ins) { case INS_add: case INS_sub: if (imm == 0) { if (isLowRegister(reg1) && isLowRegister(reg2) && isLowRegister(reg3) && insSetsFlags(flags)) { // Use the Thumb-1 reg,reg,reg encoding emitIns_R_R_R(ins, attr, reg1, reg2, reg3, flags); return; } if ((ins == INS_add) && insDoesNotSetFlags(flags)) { if (reg1 == reg2) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg3, flags); return; } if (reg1 == reg3) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg2, flags); return; } } } __fallthrough; case INS_adc: case INS_and: case INS_bic: case INS_eor: case INS_orn: case INS_orr: case INS_sbc: assert((imm >= 0) && (imm <= 31)); // required for encoding assert(!insOptAnyInc(opt)); if (imm == 0) { if (opt == INS_OPTS_LSL) // left shift of zero opt = INS_OPTS_NONE; // is a nop assert(insOptsNone(opt)); if (isLowRegister(reg1) && isLowRegister(reg2) && isLowRegister(reg3) && insSetsFlags(flags)) { if (reg1 == reg2) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg3, flags); return; } if ((reg1 == reg3) && (ins != INS_bic) && (ins != INS_orn) && (ins != INS_sbc)) { // Use the Thumb-1 regdest,reg encoding emitIns_R_R(ins, attr, reg1, reg2, flags); return; } } } else // imm > 0 && imm <= 31) { assert(insOptAnyShift(opt)); if (insOptsRRX(opt)) assert(imm == 1); } fmt = IF_T2_C0; sf = insMustSetFlags(flags); break; case INS_ldrb: case INS_ldrsb: case INS_strb: assert(size == EA_1BYTE); goto COMMON_THUMB2_LDST; case INS_ldrh: case INS_ldrsh: case INS_strh: assert(size == EA_2BYTE); goto COMMON_THUMB2_LDST; case INS_ldr: case INS_str: assert(size == EA_4BYTE); COMMON_THUMB2_LDST: assert(insDoesNotSetFlags(flags)); assert((imm & 0x0003) == imm); // required for encoding if ((imm == 0) && insOptsNone(opt) && isLowRegister(reg1) && isLowRegister(reg2) && isLowRegister(reg3)) { // Use the Thumb-1 reg,reg,reg encoding emitIns_R_R_R(ins, attr, reg1, reg2, reg3, flags); return; } assert(insOptsNone(opt) || insOptsLSL(opt)); fmt = IF_T2_E0; sf = INS_FLAGS_NOT_SET; break; case INS_ldrd: case INS_strd: assert(insDoesNotSetFlags(flags)); assert((imm & 0x03) == 0); sf = INS_FLAGS_NOT_SET; if (insOptAnyInc(opt)) { if (insOptsPostInc(opt)) assert(imm > 0); else // insOptsPreDec(opt) assert(imm < 0); } else { assert(insOptsNone(opt)); } if (unsigned_abs(imm) <= 0x03fc) { imm >>= 2; fmt = IF_T2_G0; } else { assert(!"Instruction cannot be encoded"); } break; default: unreached(); } assert((fmt == IF_T2_C0) || (fmt == IF_T2_E0) || (fmt == IF_T2_G0)); assert(sf != INS_FLAGS_DONT_CARE); // 3-reg ops can't use the small instrdesc instrDescCns* id = emitAllocInstrCns(attr); id->idSetIsLargeCns(); id->idcCnsVal = imm; id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(emitInsSize(fmt)); id->idInsFlags(sf); id->idInsOpt(opt); id->idReg1(reg1); id->idReg2(reg2); id->idReg3(reg3); 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) { insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_NOT_SET; /* Figure out the encoding format of the instruction */ switch (ins) { case INS_smull: case INS_umull: case INS_smlal: case INS_umlal: assert(reg1 != reg2); // Illegal encoding fmt = IF_T2_F1; break; case INS_mla: case INS_mls: fmt = IF_T2_F2; break; default: unreached(); } assert((fmt == IF_T2_F1) || (fmt == IF_T2_F2)); instrDesc* id = emitNewInstr(attr); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); id->idReg1(reg1); id->idReg2(reg2); id->idReg3(reg3); id->idReg4(reg4); 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) { if (ins == INS_mov) { assert(!"Please use ins_Load() to select the correct instruction"); } switch (ins) { case INS_add: case INS_ldr: case INS_ldrh: case INS_ldrb: case INS_ldrsh: case INS_ldrsb: case INS_vldr: case INS_vmov: case INS_movw: case INS_movt: break; case INS_lea: ins = INS_add; break; default: NYI("emitIns_R_S"); return; } insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_NOT_SET; regNumber reg2; /* Figure out the variable's frame position */ int base; int disp; unsigned undisp; base = emitComp->lvaFrameAddress(varx, emitComp->funCurrentFunc()->funKind != FUNC_ROOT, ®2, offs); disp = base + offs; undisp = unsigned_abs(disp); if (CodeGen::instIsFP(ins)) { // all fp mem ops take 8 bit immediate, multiplied by 4, plus sign // // Note if undisp is not a multiple of four we will fail later on // when we try to encode this instruction // Its better to fail later with a better error message than // to fail here when the RBM_OPT_RSVD is not available // if (undisp <= 0x03fb) { fmt = IF_T2_VLDST; } else { regNumber rsvdReg = codeGen->rsGetRsvdReg(); emitIns_genStackOffset(rsvdReg, varx, offs); emitIns_R_R(INS_add, EA_4BYTE, rsvdReg, reg2); emitIns_R_R_I(ins, attr, reg1, rsvdReg, 0); return; } } else if (emitInsIsLoadOrStore(ins)) { if (isLowRegister(reg1) && (reg2 == REG_SP) && (ins == INS_ldr) && ((disp & 0x03fc) == disp && disp <= 0x03f8)) { fmt = IF_T1_J2; } else if (disp >= 0 && disp <= 0x0ffb) { fmt = IF_T2_K1; } else if (undisp <= 0x0fb) { fmt = IF_T2_H0; } else { // Load disp into a register regNumber rsvdReg = codeGen->rsGetRsvdReg(); emitIns_genStackOffset(rsvdReg, varx, offs); fmt = IF_T2_E0; } } else if (ins == INS_add) { if (isLowRegister(reg1) && (reg2 == REG_SP) && ((disp & 0x03fc) == disp && disp <= 0x03f8)) { fmt = IF_T1_J2; } else if (undisp <= 0x0ffb) { if (disp < 0) { ins = INS_sub; disp = -disp; } // add/sub => addw/subw instruction // Note that even when using the w prefix the immediate is still only 12 bits? ins = (ins == INS_add) ? INS_addw : INS_subw; fmt = IF_T2_M0; } else { // Load disp into a register regNumber rsvdReg = codeGen->rsGetRsvdReg(); emitIns_genStackOffset(rsvdReg, varx, offs); emitIns_R_R_R(ins, attr, reg1, reg2, rsvdReg); return; } } else if (ins == INS_movw || ins == INS_movt) { fmt = IF_T2_N; } assert((fmt == IF_T1_J2) || (fmt == IF_T2_E0) || (fmt == IF_T2_H0) || (fmt == IF_T2_K1) || (fmt == IF_T2_L0) || (fmt == IF_T2_N) || (fmt == IF_T2_VLDST) || (fmt == IF_T2_M0)); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstrCns(attr, disp); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); id->idInsOpt(INS_OPTS_NONE); id->idReg1(reg1); id->idReg2(reg2); id->idAddr()->iiaLclVar.initLclVarAddr(varx, offs); id->idSetIsLclVar(); if (reg2 == REG_FP) id->idSetIsLclFPBase(); #ifdef DEBUG id->idDebugOnlyInfo()->idVarRefOffs = emitVarRefOffs; #endif dispIns(id); appendToCurIG(id); } // generate the offset of &varx + offs into a register void emitter::emitIns_genStackOffset(regNumber r, int varx, int offs) { regNumber regBase; int base; int disp; base = emitComp->lvaFrameAddress(varx, emitComp->funCurrentFunc()->funKind != FUNC_ROOT, ®Base, offs); disp = base + offs; emitIns_R_S(INS_movw, EA_4BYTE, r, varx, offs); if ((disp & 0xffff) != disp) { emitIns_R_S(INS_movt, EA_4BYTE, r, varx, offs); } } /***************************************************************************** * * 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) { if (ins == INS_mov) { assert(!"Please use ins_Store() to select the correct instruction"); } switch (ins) { case INS_str: case INS_strh: case INS_strb: case INS_vstr: break; default: NYI("emitIns_R_S"); return; } insFormat fmt = IF_NONE; insFlags sf = INS_FLAGS_NOT_SET; regNumber reg2; /* Figure out the variable's frame position */ int base; int disp; unsigned undisp; base = emitComp->lvaFrameAddress(varx, emitComp->funCurrentFunc()->funKind != FUNC_ROOT, ®2, offs); disp = base + offs; undisp = unsigned_abs(disp); if (CodeGen::instIsFP(ins)) { // all fp mem ops take 8 bit immediate, multiplied by 4, plus sign // // Note if undisp is not a multiple of four we will fail later on // when we try to encode this instruction // Its better to fail later with a better error message than // to fail here when the RBM_OPT_RSVD is not available // if (undisp <= 0x03fb) { fmt = IF_T2_VLDST; } else { regNumber rsvdReg = codeGen->rsGetRsvdReg(); emitIns_genStackOffset(rsvdReg, varx, offs); emitIns_R_R(INS_add, EA_4BYTE, rsvdReg, reg2); emitIns_R_R_I(ins, attr, reg1, rsvdReg, 0); return; } } else if (isLowRegister(reg1) && (reg2 == REG_SP) && (ins == INS_str) && ((disp & 0x03fc) == disp && disp <= 0x03f8)) { fmt = IF_T1_J2; } else if (disp >= 0 && disp <= 0x0ffb) { fmt = IF_T2_K1; } else if (undisp <= 0x0fb) { fmt = IF_T2_H0; } else { // Load disp into a register regNumber rsvdReg = codeGen->rsGetRsvdReg(); emitIns_genStackOffset(rsvdReg, varx, offs); fmt = IF_T2_E0; } assert((fmt == IF_T1_J2) || (fmt == IF_T2_E0) || (fmt == IF_T2_H0) || (fmt == IF_T2_VLDST) || (fmt == IF_T2_K1)); assert(sf != INS_FLAGS_DONT_CARE); instrDesc* id = emitNewInstrCns(attr, disp); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); id->idInsFlags(sf); id->idInsOpt(INS_OPTS_NONE); id->idReg1(reg1); id->idReg2(reg2); id->idAddr()->iiaLclVar.initLclVarAddr(varx, offs); id->idSetIsLclVar(); if (reg2 == REG_FP) id->idSetIsLclFPBase(); #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. */ void emitter::emitIns_R_C(instruction ins, emitAttr attr, regNumber reg, CORINFO_FIELD_HANDLE fldHnd, int offs) { if (ins == INS_mov) { assert(!"Please use ins_Load() to select the correct instruction"); } assert(emitInsIsLoad(ins) || (ins == INS_lea)); if (ins == INS_lea) { ins = INS_add; } int doff = Compiler::eeGetJitDataOffs(fldHnd); ssize_t addr = NULL; if (doff >= 0) { NYI_ARM("JitDataOffset static fields"); } else if (fldHnd == FLD_GLOBAL_FS) { NYI_ARM("Thread-Local-Storage static fields"); } else if (fldHnd == FLD_GLOBAL_DS) { addr = (ssize_t)offs; offs = 0; } else { assert(!jitStaticFldIsGlobAddr(fldHnd)); addr = (ssize_t)emitComp->info.compCompHnd->getFieldAddress(fldHnd, NULL); if (addr == NULL) NO_WAY("could not obtain address of static field"); } // We can use reg to load the constant address, // as long as it is not a floating point register regNumber regTmp = reg; if (isFloatReg(regTmp)) { #ifndef LEGACY_BACKEND assert(!"emitIns_R_C() cannot be called with floating point target"); #else // LEGACY_BACKEND regTmp = codeGen->regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(reg)); #endif // LEGACY_BACKEND } // Load address of CLS_VAR into a register codeGen->instGen_Set_Reg_To_Imm(EA_HANDLE_CNS_RELOC, regTmp, addr); if ((ins != INS_add) || (offs != 0) || (reg != regTmp)) { emitIns_R_R_I(ins, attr, reg, regTmp, offs); } } /***************************************************************************** * * 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) { #ifndef LEGACY_BACKEND assert(!"emitIns_C_R not supported for RyuJIT backend"); #else // LEGACY_BACKEND if (ins == INS_mov) { assert(!"Please use ins_Store() to select the correct instruction"); } assert(emitInsIsStore(ins)); int doff = Compiler::eeGetJitDataOffs(fldHnd); ssize_t addr = NULL; if (doff >= 0) { NYI_ARM("JitDataOffset static fields"); } else if (fldHnd == FLD_GLOBAL_FS) { NYI_ARM("Thread-Local-Storage static fields"); } else if (fldHnd == FLD_GLOBAL_DS) { addr = (ssize_t)offs; offs = 0; } else { assert(!jitStaticFldIsGlobAddr(fldHnd)); addr = (ssize_t)emitComp->info.compCompHnd->getFieldAddress(fldHnd, NULL); if (addr == NULL) NO_WAY("could not obtain address of static field"); } regNumber regTmp = codeGen->regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(reg)); // Load address of CLS_VAR into a register codeGen->instGen_Set_Reg_To_Imm(EA_HANDLE_CNS_RELOC, regTmp, addr); emitIns_R_R_I(ins, attr, reg, regTmp, offs); #endif // LEGACY_BACKEND } /***************************************************************************** * * Add an instruction with a static member + constant. */ void emitter::emitIns_C_I(instruction ins, emitAttr attr, CORINFO_FIELD_HANDLE fldHnd, int offs, ssize_t val) { NYI("emitIns_C_I"); } /***************************************************************************** * * The following adds instructions referencing address modes. */ void emitter::emitIns_I_AR( instruction ins, emitAttr attr, int val, regNumber reg, int offs, int memCookie, void* clsCookie) { NYI("emitIns_I_AR"); } void emitter::emitIns_R_AR(instruction ins, emitAttr attr, regNumber ireg, regNumber reg, int offs, int memCookie /* = 0 */, void* clsCookie /* = NULL */) { if (ins == INS_mov) { assert(!"Please use ins_Load() to select the correct instruction"); } if (ins == INS_lea) { if (emitter::emitIns_valid_imm_for_add(offs, INS_FLAGS_DONT_CARE)) { emitIns_R_R_I(INS_add, attr, ireg, reg, offs); } else { #ifndef LEGACY_BACKEND assert(!"emitIns_R_AR: immediate doesn't fit in the instruction"); #else // LEGACY_BACKEND // Load val into a register regNumber immReg = codeGen->regSet.rsGrabReg(RBM_ALLINT & ~genRegMask(ireg) & ~genRegMask(reg)); codeGen->instGen_Set_Reg_To_Imm(EA_PTRSIZE, immReg, (ssize_t)offs); emitIns_R_R_R(INS_add, attr, ireg, reg, immReg); #endif // LEGACY_BACKEND } return; } else if (emitInsIsLoad(ins)) { emitIns_R_R_I(ins, attr, ireg, reg, offs); return; } else if ((ins == INS_mov) || (ins == INS_ldr)) { if (EA_SIZE(attr) == EA_4BYTE) { emitIns_R_R_I(INS_ldr, attr, ireg, reg, offs); return; } } else if (ins == INS_vldr) { emitIns_R_R_I(ins, attr, ireg, reg, offs); } NYI("emitIns_R_AR"); } void emitter::emitIns_R_AI(instruction ins, emitAttr attr, regNumber ireg, ssize_t disp) { if (emitInsIsLoad(ins)) { // We can use ireg to load the constant address, // as long as it is not a floating point register regNumber regTmp = ireg; if (isFloatReg(regTmp)) { #ifndef LEGACY_BACKEND assert(!"emitIns_R_AI with floating point reg"); #else // LEGACY_BACKEND regTmp = codeGen->regSet.rsPickFreeReg(RBM_ALLINT & ~genRegMask(ireg)); #endif // LEGACY_BACKEND } codeGen->instGen_Set_Reg_To_Imm(EA_IS_RELOC(attr) ? EA_HANDLE_CNS_RELOC : EA_PTRSIZE, regTmp, disp); emitIns_R_R_I(ins, EA_TYPE(attr), ireg, regTmp, 0); return; } NYI("emitIns_R_AI"); } void emitter::emitIns_AR_R(instruction ins, emitAttr attr, regNumber ireg, regNumber reg, int offs, int memCookie /* = 0 */, void* clsCookie /* = NULL */) { if (ins == INS_mov) { assert(!"Please use ins_Store() to select the correct instruction"); } emitIns_R_R_I(ins, attr, ireg, reg, offs); } void emitter::emitIns_R_ARR(instruction ins, emitAttr attr, regNumber ireg, regNumber reg, regNumber rg2, int disp) { if (ins == INS_mov) { assert(!"Please use ins_Load() to select the correct instruction"); } if (ins == INS_lea) { emitIns_R_R_R(INS_add, attr, ireg, reg, rg2); if (disp != 0) { emitIns_R_R_I(INS_add, attr, ireg, ireg, disp); } return; } else if (emitInsIsLoad(ins)) { if (disp == 0) { emitIns_R_R_R_I(ins, attr, ireg, reg, rg2, 0, INS_FLAGS_DONT_CARE, INS_OPTS_NONE); return; } } assert(!"emitIns_R_ARR: Unexpected instruction"); } void emitter::emitIns_ARR_R(instruction ins, emitAttr attr, regNumber ireg, regNumber reg, regNumber rg2, int disp) { if (ins == INS_mov) { assert(!"Please use ins_Store() to select the correct instruction"); } if (emitInsIsStore(ins)) { if (disp == 0) { emitIns_R_R_R(ins, attr, ireg, reg, rg2); } else { emitIns_R_R_R(INS_add, attr, ireg, reg, rg2); emitIns_R_R_I(ins, attr, ireg, ireg, disp); } return; } assert(!"emitIns_ARR_R: Unexpected instruction"); } void emitter::emitIns_R_ARX( instruction ins, emitAttr attr, regNumber ireg, regNumber reg, regNumber rg2, unsigned mul, int disp) { if (ins == INS_mov) { assert(!"Please use ins_Load() to select the correct instruction"); } unsigned shift = genLog2((unsigned)mul); if ((ins == INS_lea) || emitInsIsLoad(ins)) { if (ins == INS_lea) { ins = INS_add; } if (disp == 0) { emitIns_R_R_R_I(ins, attr, ireg, reg, rg2, (int)shift, INS_FLAGS_DONT_CARE, INS_OPTS_LSL); return; } else { bool useForm2 = false; bool mustUseForm1 = ((disp % mul) != 0) || (reg == ireg); if (!mustUseForm1) { // If all of the below things are true we can generate a Thumb-1 add instruction // followed by a Thumb-2 add instruction // We also useForm1 when reg is a low register since the second instruction // can then always be generated using a Thumb-1 add // if ((reg >= REG_R8) && (ireg < REG_R8) && (rg2 < REG_R8) && ((disp >> shift) <= 7)) { useForm2 = true; } } if (useForm2) { // Form2: // Thumb-1 instruction add Rd, Rx, disp>>shift // Thumb-2 instructions ldr Rd, Rb, Rd LSL shift // emitIns_R_R_I(INS_add, EA_4BYTE, ireg, rg2, disp >> shift); emitIns_R_R_R_I(ins, attr, ireg, reg, ireg, shift, INS_FLAGS_NOT_SET, INS_OPTS_LSL); } else { // Form1: // Thumb-2 instruction add Rd, Rb, Rx LSL shift // Thumb-1/2 instructions ldr Rd, Rd, disp // emitIns_R_R_R_I(INS_add, attr, ireg, reg, rg2, shift, INS_FLAGS_NOT_SET, INS_OPTS_LSL); emitIns_R_R_I(ins, attr, ireg, ireg, disp); } return; } } assert(!"emitIns_R_ARX: Unexpected instruction"); } /***************************************************************************** * * Record that a jump instruction uses the short encoding * */ void emitter::emitSetShortJump(instrDescJmp* id) { if (id->idjKeepLong) return; if (emitIsCondJump(id)) { id->idInsFmt(IF_T1_K); } else if (emitIsCmpJump(id)) { // These are always only ever short! assert(id->idjShort); return; } else if (emitIsUncondJump(id)) { id->idInsFmt(IF_T1_M); } else if (emitIsLoadLabel(id)) { return; // Keep long - we don't know the alignment of the target } else { assert(!"Unknown instruction in emitSetShortJump()"); } id->idjShort = true; #if DEBUG_EMIT if (id->idDebugOnlyInfo()->idNum == (unsigned)INTERESTING_JUMP_NUM || INTERESTING_JUMP_NUM == 0) { printf("[8] Converting jump %u to short\n", id->idDebugOnlyInfo()->idNum); } #endif // DEBUG_EMIT insSize isz = emitInsSize(id->idInsFmt()); id->idInsSize(isz); } /***************************************************************************** * * Record that a jump instruction uses the medium encoding * */ void emitter::emitSetMediumJump(instrDescJmp* id) { if (id->idjKeepLong) return; #if DEBUG_EMIT if (id->idDebugOnlyInfo()->idNum == (unsigned)INTERESTING_JUMP_NUM || INTERESTING_JUMP_NUM == 0) { printf("[9] Converting jump %u to medium\n", id->idDebugOnlyInfo()->idNum); } #endif // DEBUG_EMIT assert(emitIsCondJump(id)); id->idInsFmt(IF_T2_J1); id->idjShort = false; insSize isz = emitInsSize(id->idInsFmt()); id->idInsSize(isz); } /***************************************************************************** * * Add a jmp instruction. * When dst is NULL, instrCount specifies number of instructions * to jump: positive is forward, negative is backward. * Unconditional branches have two sizes: short and long. * Conditional branches have three sizes: short, medium, and long. A long * branch is a pseudo-instruction that represents two instructions: * a short conditional branch to branch around a large unconditional * branch. Thus, we can handle branch offsets of imm24 instead of just imm20. */ void emitter::emitIns_J(instruction ins, BasicBlock* dst, int instrCount /* = 0 */) { insFormat fmt = IF_NONE; if (dst != NULL) { assert(dst->bbFlags & BBF_JMP_TARGET); } else { assert(instrCount != 0); } /* Figure out the encoding format of the instruction */ switch (ins) { case INS_b: fmt = IF_T2_J2; /* Assume the jump will be long */ 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: fmt = IF_LARGEJMP; /* Assume the jump will be long */ break; default: unreached(); } assert((fmt == IF_LARGEJMP) || (fmt == IF_T2_J2)); instrDescJmp* id = emitNewInstrJmp(); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(isz); #ifdef DEBUG // Mark the finally call if (ins == INS_b && emitComp->compCurBB->bbJumpKind == BBJ_CALLFINALLY) { id->idDebugOnlyInfo()->idFinallyCall = true; } #endif // DEBUG /* Assume the jump will be long */ id->idjShort = 0; if (dst != NULL) { id->idAddr()->iiaBBlabel = dst; 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 /* Figure out the max. size of the jump/call instruction */ if (!id->idjKeepLong) { insGroup* tgt = NULL; /* Can we guess at the jump distance? */ if (dst != NULL) { tgt = (insGroup*)emitCodeGetCookie(dst); } if (tgt) { UNATIVE_OFFSET srcOffs; int jmpDist; assert(JMP_SIZE_SMALL == JCC_SIZE_SMALL); /* This is a backward jump - figure out the distance */ srcOffs = emitCurCodeOffset + emitCurIGsize; /* Compute the distance estimate */ jmpDist = srcOffs - tgt->igOffs; assert(jmpDist >= 0); jmpDist += 4; // Adjustment for ARM PC switch (fmt) { case IF_T2_J2: if (JMP_DIST_SMALL_MAX_NEG <= -jmpDist) { /* This jump surely will be short */ emitSetShortJump(id); } break; case IF_LARGEJMP: if (JCC_DIST_SMALL_MAX_NEG <= -jmpDist) { /* This jump surely will be short */ emitSetShortJump(id); } else if (JCC_DIST_MEDIUM_MAX_NEG <= -jmpDist) { /* This jump surely will be medium */ emitSetMediumJump(id); } break; default: unreached(); break; } } } dispIns(id); appendToCurIG(id); } /***************************************************************************** * * Add a label instruction. */ void emitter::emitIns_R_L(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg) { insFormat fmt = IF_NONE; assert(dst->bbFlags & BBF_JMP_TARGET); /* Figure out the encoding format of the instruction */ switch (ins) { case INS_movt: case INS_movw: fmt = IF_T2_N1; break; default: unreached(); } assert(fmt == IF_T2_N1); instrDescJmp* id = emitNewInstrJmp(); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idReg1(reg); id->idInsFmt(fmt); id->idInsSize(isz); #ifdef DEBUG // Mark the catch return if (emitComp->compCurBB->bbJumpKind == BBJ_EHCATCHRET) { id->idDebugOnlyInfo()->idCatchRet = true; } #endif // DEBUG id->idAddr()->iiaBBlabel = dst; id->idjShort = false; id->idjKeepLong = true; /* 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; // Set the relocation flags - these give hint to zap to perform // relocation of the specified 32bit address. id->idSetRelocFlags(attr); #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) { noway_assert((ins == INS_movw) || (ins == INS_movt)); insFormat fmt = IF_T2_N2; instrDesc* id = emitNewInstrSC(attr, offs); insSize isz = emitInsSize(fmt); id->idIns(ins); id->idReg1(reg); id->idInsFmt(fmt); id->idInsSize(isz); if (emitComp->opts.compReloc) { // Set the relocation flags - these give hint to zap to perform // relocation of the specified 32bit address. id->idSetRelocFlags(attr); } dispIns(id); appendToCurIG(id); } void emitter::emitIns_J_R(instruction ins, emitAttr attr, BasicBlock* dst, regNumber reg) { assert(dst->bbFlags & BBF_JMP_TARGET); instrDescJmp* id; if (ins == INS_adr) { id = emitNewInstrLbl(); id->idIns(INS_adr); id->idInsFmt(IF_T2_M1); id->idInsSize(emitInsSize(IF_T2_M1)); id->idAddr()->iiaBBlabel = dst; id->idReg1(reg); id->idReg2(REG_PC); /* Assume the label reference will be long */ id->idjShort = 0; id->idjKeepLong = emitComp->fgInDifferentRegions(emitComp->compCurBB, dst); } else { assert(ins == INS_cbz || INS_cbnz); assert(isLowRegister(reg)); id = emitNewInstrJmp(); id->idIns(ins); id->idInsFmt(IF_T1_I); id->idInsSize(emitInsSize(IF_T1_I)); id->idReg1(reg); /* This jump better be short or-else! */ id->idjShort = true; id->idAddr()->iiaBBlabel = dst; id->idjKeepLong = false; } /* 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 addr is NULL, it is a recursive call * * 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, // used for pretty printing INDEBUG_LDISASM_COMMA(CORINFO_SIG_INFO* sigInfo) // used to report call sites to the EE void* addr, ssize_t argSize, emitAttr retSize, 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 */, int 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 ARM 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 % (int)sizeof(void*) == 0); argCnt = argSize / (int)sizeof(void*); /* Managed RetVal: emit sequence point for the call */ if (emitComp->opts.compDbgInfo && ilOffset != BAD_IL_OFFSET) { codeGen->genIPmappingAdd(ilOffset, false); } /* 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. The stats for a ton of classes is as follows: Direct call w/o GC vars 220,216 Indir. call w/o GC vars 144,781 Direct call with GC vars 9,440 Indir. call with GC vars 5,768 */ if (callType >= EC_INDIR_R) { /* Indirect call, virtual calls */ assert(callType == EC_INDIR_R); id = emitNewInstrCallInd(argCnt, disp, ptrVars, gcrefRegs, byrefRegs, retSize); } 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); } /* 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_bx; // INS_bx Reg } else { ins = INS_blx; // INS_blx Reg } fmt = IF_T1_D2; id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(emitInsSize(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); // if addr is nullptr then this call is treated as a recursive call. assert(addr == nullptr || codeGen->arm_Valid_Imm_For_BL((ssize_t)addr)); if (isJump) { ins = INS_b; // INS_b imm24 } else { ins = INS_bl; // INS_bl imm24 } fmt = IF_T2_J3; id->idIns(ins); id->idInsFmt(fmt); id->idInsSize(emitInsSize(fmt)); id->idAddr()->iiaAddr = (BYTE*)addr; if (callType == EC_FUNC_ADDR) { id->idSetIsCallAddr(); } if (emitComp->opts.compReloc) { // Since this is an indirect call through a pointer and we don't // currently pass in emitAttr into this function we have decided // to always mark the displacement as being relocatable. id->idSetIsDspReloc(); } } #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 an encoding for the specified register (any-reg) to be used in * a Thumb-1 encoding in the M4 position */ inline unsigned insEncodeRegT1_M4(regNumber reg) { assert(reg < REG_STK); return reg << 3; } /***************************************************************************** * * Returns an encoding for the specified register (any-reg) to be used in * a Thumb-1 encoding in the D4 position */ inline unsigned insEncodeRegT1_D4(regNumber reg) { assert(reg < REG_STK); return (reg & 0x7) | ((reg & 0x8) << 4); } /***************************************************************************** * * Returns an encoding for the specified register (low-only) to be used in * a Thumb-1 encoding in the M3 position */ inline unsigned insEncodeRegT1_M3(regNumber reg) { assert(reg < REG_R8); return reg << 6; } /***************************************************************************** * * Returns an encoding for the specified register (low-only) to be used in * a Thumb-1 encoding in the N3 position */ inline unsigned insEncodeRegT1_N3(regNumber reg) { assert(reg < REG_R8); return reg << 3; } /***************************************************************************** * * Returns an encoding for the specified register (low-only) to be used in * a Thumb-1 encoding in the D3 position */ inline unsigned insEncodeRegT1_D3(regNumber reg) { assert(reg < REG_R8); return reg; } /***************************************************************************** * * Returns an encoding for the specified register (low-only) to be used in * a Thumb-1 encoding in the DI position */ inline unsigned insEncodeRegT1_DI(regNumber reg) { assert(reg < REG_R8); return reg << 8; } /***************************************************************************** * * Returns an encoding for the specified register to be used in * a Thumb-2 encoding in the N position */ inline unsigned insEncodeRegT2_N(regNumber reg) { assert(reg < REG_STK); return reg << 16; } inline unsigned floatRegIndex(regNumber reg, int size) { // theoretically this could support quad floats as well but for now... assert(size == EA_8BYTE || size == EA_4BYTE); if (size == EA_8BYTE) assert(emitter::isDoubleReg(reg)); else assert(emitter::isFloatReg(reg)); unsigned result = reg - REG_F0; // the assumption here is that the register F8 also refers to D4 if (size == EA_8BYTE) { result >>= 1; } return result; } // variant: SOME arm VFP instructions use the convention that // for doubles, the split bit holds the msb of the register index // for singles it holds the lsb // excerpt : d = if dp_operation then UInt(D:Vd) // if single UInt(Vd:D); inline unsigned floatRegEncoding(unsigned index, int size, bool variant = false) { if (!variant || size == EA_8BYTE) return index; else { return ((index & 1) << 4) | (index >> 1); } } // thumb2 VFP M register encoding inline unsigned insEncodeRegT2_VectorM(regNumber reg, int size, bool variant) { unsigned enc = floatRegIndex(reg, size); enc = floatRegEncoding(enc, size, variant); return ((enc & 0xf) << 0) | ((enc & 0x10) << 1); } // thumb2 VFP N register encoding inline unsigned insEncodeRegT2_VectorN(regNumber reg, int size, bool variant) { unsigned enc = floatRegIndex(reg, size); enc = floatRegEncoding(enc, size, variant); return ((enc & 0xf) << 16) | ((enc & 0x10) << 3); } // thumb2 VFP D register encoding inline unsigned insEncodeRegT2_VectorD(regNumber reg, int size, bool variant) { unsigned enc = floatRegIndex(reg, size); enc = floatRegEncoding(enc, size, variant); return ((enc & 0xf) << 12) | ((enc & 0x10) << 18); } /***************************************************************************** * * Returns an encoding for the specified register to be used in * a Thumb-2 encoding in the T position */ inline unsigned insEncodeRegT2_T(regNumber reg) { assert(reg < REG_STK); return reg << 12; } /***************************************************************************** * * Returns an encoding for the specified register to be used in * a Thumb-2 encoding in the D position */ inline unsigned insEncodeRegT2_D(regNumber reg) { assert(reg < REG_STK); return reg << 8; } /***************************************************************************** * * Returns an encoding for the specified register to be used in * a Thumb-2 encoding in the M position */ inline unsigned insEncodeRegT2_M(regNumber reg) { assert(reg < REG_STK); return reg; } /***************************************************************************** * * Returns the encoding for the Set Flags bit to be used in a Thumb-2 encoding */ unsigned emitter::insEncodeSetFlags(insFlags sf) { if (sf == INS_FLAGS_SET) return (1 << 20); else return 0; } /***************************************************************************** * * Returns the encoding for the Shift Type bits to be used in a Thumb-2 encoding */ unsigned emitter::insEncodeShiftOpts(insOpts opt) { if (opt == INS_OPTS_NONE) return 0; else if (opt == INS_OPTS_LSL) return 0x00; else if (opt == INS_OPTS_LSR) return 0x10; else if (opt == INS_OPTS_ASR) return 0x20; else if (opt == INS_OPTS_ROR) return 0x30; else if (opt == INS_OPTS_RRX) return 0x30; assert(!"Invalid insOpts"); return 0; } /***************************************************************************** * * Returns the encoding for the PUW bits to be used in a T2_G0 Thumb-2 encoding */ unsigned emitter::insEncodePUW_G0(insOpts opt, int imm) { unsigned result = 0; if (opt != INS_OPTS_LDST_POST_INC) result |= (1 << 24); // The P bit if (imm >= 0) result |= (1 << 23); // The U bit if (opt != INS_OPTS_NONE) result |= (1 << 21); // The W bits return result; } /***************************************************************************** * * Returns the encoding for the PUW bits to be used in a T2_H0 Thumb-2 encoding */ unsigned emitter::insEncodePUW_H0(insOpts opt, int imm) { unsigned result = 0; if (opt != INS_OPTS_LDST_POST_INC) result |= (1 << 10); // The P bit if (imm >= 0) result |= (1 << 9); // The U bit if (opt != INS_OPTS_NONE) result |= (1 << 8); // The W bits return result; } /***************************************************************************** * * Returns the encoding for the Shift Count bits to be used in a Thumb-2 encoding */ inline unsigned insEncodeShiftCount(int imm) { unsigned result; assert((imm & 0x001F) == imm); result = (imm & 0x03) << 6; result |= (imm & 0x1C) << 10; return result; } /***************************************************************************** * * Returns the encoding for the immediate use by BFI/BFC Thumb-2 encodings */ inline unsigned insEncodeBitFieldImm(int imm) { unsigned result; assert((imm & 0x03FF) == imm); result = (imm & 0x001f); result |= (imm & 0x0060) << 1; result |= (imm & 0x0380) << 5; return result; } /***************************************************************************** * * Unscales the immediate based on the operand size in 'size' */ /*static*/ int emitter::insUnscaleImm(int imm, emitAttr size) { switch (size) { case EA_8BYTE: case EA_4BYTE: assert((imm & 0x0003) == 0); imm >>= 2; break; case EA_2BYTE: assert((imm & 0x0001) == 0); imm >>= 1; break; case EA_1BYTE: // Do nothing break; default: assert(!"Invalid value in size"); break; } return imm; } /***************************************************************************** * * Emit a Thumb-1 instruction (a 16-bit integer as code) */ /*static*/ unsigned emitter::emitOutput_Thumb1Instr(BYTE* dst, ssize_t code) { unsigned short word1 = code & 0xffff; assert(word1 == code); #ifdef DEBUG unsigned short top5bits = (word1 & 0xf800) >> 11; assert(top5bits < 29); #endif MISALIGNED_WR_I2(dst, word1); return sizeof(short); } /***************************************************************************** * * Emit a Thumb-2 instruction (two 16-bit integers as code) */ /*static*/ unsigned emitter::emitOutput_Thumb2Instr(BYTE* dst, ssize_t code) { unsigned short word1 = (code >> 16) & 0xffff; unsigned short word2 = (code)&0xffff; assert(((word1 << 16) | word2) == code); #ifdef DEBUG unsigned short top5bits = (word1 & 0xf800) >> 11; assert(top5bits >= 29); #endif MISALIGNED_WR_I2(dst, word1); dst += 2; MISALIGNED_WR_I2(dst, word2); return sizeof(short) * 2; } /***************************************************************************** * * Output a local jump instruction. * 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) { unsigned srcOffs; unsigned dstOffs; ssize_t distVal; instrDescJmp* id = (instrDescJmp*)i; instruction ins = id->idIns(); ssize_t code; bool loadLabel = false; bool isJump = false; bool relAddr = true; // does the instruction use relative-addressing? size_t sdistneg; switch (ins) { default: sdistneg = JCC_DIST_SMALL_MAX_NEG; isJump = true; break; case INS_cbz: case INS_cbnz: // One size fits all! sdistneg = 0; isJump = true; break; case INS_adr: sdistneg = LBL_DIST_SMALL_MAX_NEG; loadLabel = true; break; case INS_movw: case INS_movt: sdistneg = LBL_DIST_SMALL_MAX_NEG; relAddr = false; loadLabel = true; break; } /* Figure out the distance to the target */ srcOffs = emitCurCodeOffs(dst); 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)); } else { dstOffs = id->idAddr()->iiaIGlabel->igOffs; } if (relAddr) { if (ins == INS_adr) { // for adr, the distance is calculated from 4-byte aligned srcOffs. distVal = (ssize_t)((emitOffsetToPtr(dstOffs) - (BYTE*)(((size_t)emitOffsetToPtr(srcOffs)) & ~3)) + 1); } else { distVal = (ssize_t)(emitOffsetToPtr(dstOffs) - emitOffsetToPtr(srcOffs)); } } else { assert(ins == INS_movw || ins == INS_movt); distVal = (ssize_t)emitOffsetToPtr(dstOffs) + 1; // Or in thumb bit } if (dstOffs <= srcOffs) { /* This is a backward jump - distance is known at this point */ #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("[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 // This format only supports forward branches noway_assert(id->idInsFmt() != IF_T1_I); /* Can we use a short jump? */ if (isJump && ((unsigned)(distVal - 4) >= (unsigned)sdistneg)) { emitSetShortJump(id); } } 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 } /* Adjust the offset to emit relative to the end of the instruction */ if (relAddr) distVal -= 4; #ifdef DEBUG if (0 && emitComp->verbose) { size_t sz = 4; // Thumb-2 pretends all instructions are 4-bytes long for computing jump offsets? 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 insFormat fmt = id->idInsFmt(); if (isJump) { /* What size jump should we use? */ if (id->idjShort) { /* Short jump */ assert(!id->idjKeepLong); assert(emitJumpCrossHotColdBoundary(srcOffs, dstOffs) == false); assert(JMP_SIZE_SMALL == JCC_SIZE_SMALL); assert(JMP_SIZE_SMALL == 2); /* For forward jumps, record the address of the distance value */ id->idjTemp.idjAddr = (distVal > 0) ? dst : NULL; dst = emitOutputShortBranch(dst, ins, fmt, distVal, id); } else { /* Long jump */ /* For forward jumps, record the address of the distance value */ id->idjTemp.idjAddr = (dstOffs > srcOffs) ? dst : NULL; if (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 L_target // // The code we emit is: // // b L_not // 2 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 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). // // History: previously, we generated: // it // b L_target // but the "it" instruction was deprecated, so we can't use it. dst = emitOutputShortBranch(dst, emitJumpKindToIns(emitReverseJumpKind( emitInsToJumpKind(ins))), // reverse the conditional instruction IF_T1_K, 6 - 4, /* 6 bytes from start of this large conditional pseudo-instruction to L_not. Jumps are encoded as offset from instr address + 4. */ NULL /* only used for cbz/cbnz */); // Now, pretend we've got a normal unconditional branch, and fall through to the code to emit that. ins = INS_b; fmt = IF_T2_J2; // The distVal was computed based on the beginning of the pseudo-instruction, which is // the IT. So subtract the size of the IT from the offset, so it is relative to the // unconditional branch. distVal -= 2; } code = emitInsCode(ins, fmt); if (fmt == IF_T2_J1) { // Can't use this form for jumps between the hot and cold regions assert(!id->idjKeepLong); assert(emitJumpCrossHotColdBoundary(srcOffs, dstOffs) == false); assert((distVal & 1) == 0); assert(distVal >= -1048576); assert(distVal <= 1048574); if (distVal < 0) code |= 1 << 26; code |= ((distVal >> 1) & 0x0007ff); code |= (((distVal >> 1) & 0x01f800) << 5); code |= (((distVal >> 1) & 0x020000) >> 4); code |= (((distVal >> 1) & 0x040000) >> 7); } else if (fmt == IF_T2_J2) { assert((distVal & 1) == 0); if (emitComp->opts.compReloc && emitJumpCrossHotColdBoundary(srcOffs, dstOffs)) { // dst isn't an actual final target location, just some intermediate // location. Thus we cannot make any guarantees about distVal (not // even the direction/sign). Instead we don't encode any offset and // rely on the relocation to do all the work } else { assert(distVal >= CALL_DIST_MAX_NEG); assert(distVal <= CALL_DIST_MAX_POS); if (distVal < 0) code |= 1 << 26; code |= ((distVal >> 1) & 0x0007ff); code |= (((distVal >> 1) & 0x1ff800) << 5); bool S = (distVal < 0); bool I1 = ((distVal & 0x00800000) == 0); bool I2 = ((distVal & 0x00400000) == 0); if (S ^ I1) code |= (1 << 13); // J1 bit if (S ^ I2) code |= (1 << 11); // J2 bit } } else { assert(!"Unknown fmt"); } unsigned instrSize = emitOutput_Thumb2Instr(dst, code); if (emitComp->opts.compReloc) { if (emitJumpCrossHotColdBoundary(srcOffs, dstOffs)) { assert(id->idjKeepLong); if (emitComp->info.compMatchedVM) { void* target = emitOffsetToPtr(dstOffs); emitRecordRelocation((void*)dst, target, IMAGE_REL_BASED_THUMB_BRANCH24); } } } dst += instrSize; } } else if (loadLabel) { /* For forward jumps, record the address of the distance value */ id->idjTemp.idjAddr = (distVal > 0) ? dst : NULL; code = emitInsCode(ins, fmt); if (fmt == IF_T1_J3) { assert((dstOffs & 3) == 0); // The target label must be 4-byte aligned assert(distVal >= 0); assert(distVal <= 1022); code |= ((distVal >> 2) & 0xff); dst += emitOutput_Thumb1Instr(dst, code); } else if (fmt == IF_T2_M1) { assert(distVal >= -4095); assert(distVal <= +4095); if (distVal < 0) { code |= 0x00A0 << 16; distVal = -distVal; } assert((distVal & 0x0fff) == distVal); code |= (distVal & 0x00ff); code |= ((distVal & 0x0700) << 4); code |= ((distVal & 0x0800) << 15); code |= id->idReg1() << 8; dst += emitOutput_Thumb2Instr(dst, code); } else if (fmt == IF_T2_N1) { code |= insEncodeRegT2_D(id->idReg1()); unsigned imm = distVal; if (ins == INS_movw) { imm &= 0xffff; } else { imm = (imm >> 16) & 0xffff; } ((instrDescJmp*)id)->idjTemp.idjAddr = (dstOffs > srcOffs) ? dst : NULL; assert((imm & 0x0000ffff) == imm); code |= (imm & 0x00ff); code |= ((imm & 0x0700) << 4); code |= ((imm & 0x0800) << 15); code |= ((imm & 0xf000) << 4); dst += emitOutput_Thumb2Instr(dst, code); if (id->idIsCnsReloc() || id->idIsDspReloc()) { assert(ins == INS_movt || ins == INS_movw); if ((ins == INS_movt) && emitComp->info.compMatchedVM) emitRecordRelocation((void*)(dst - 8), (void*)distVal, IMAGE_REL_BASED_THUMB_MOV32); } } else { assert(!"Unknown fmt"); } } return dst; } /***************************************************************************** * * Output a short branch instruction. */ BYTE* emitter::emitOutputShortBranch(BYTE* dst, instruction ins, insFormat fmt, ssize_t distVal, instrDescJmp* id) { size_t code; code = emitInsCode(ins, fmt); if (fmt == IF_T1_K) { assert((distVal & 1) == 0); assert(distVal >= -256); assert(distVal <= 254); if (distVal < 0) code |= 1 << 7; code |= ((distVal >> 1) & 0x7f); } else if (fmt == IF_T1_M) { assert((distVal & 1) == 0); assert(distVal >= -2048); assert(distVal <= 2046); if (distVal < 0) code |= 1 << 10; code |= ((distVal >> 1) & 0x3ff); } else if (fmt == IF_T1_I) { assert(id != NULL); assert(ins == INS_cbz || INS_cbnz); assert((distVal & 1) == 0); assert(distVal >= 0); assert(distVal <= 126); code |= ((distVal << 3) & 0x0200); code |= ((distVal << 2) & 0x00F8); code |= (id->idReg1() & 0x0007); } else { assert(!"Unknown fmt"); } dst += emitOutput_Thumb1Instr(dst, code); return dst; } #ifdef FEATURE_ITINSTRUCTION /***************************************************************************** * The "IT" instruction is deprecated (with a very few exceptions). Don't generate it! * Don't delete this code, though, in case we ever want to bring it back. *****************************************************************************/ /***************************************************************************** * * Output an IT instruction. */ BYTE* emitter::emitOutputIT(BYTE* dst, instruction ins, insFormat fmt, ssize_t condcode) { ssize_t imm0; size_t code, mask, bit; code = emitInsCode(ins, fmt); code |= (condcode << 4); // encode firstcond imm0 = condcode & 1; // this is firstcond[0] mask = code & 0x0f; // initialize mask encoded in opcode bit = 0x08; // where in mask we are encoding while ((mask & (bit - 1)) != 0) // are the remaining bits all zeros? { // then we are done // otherwise determine the setting of bit if ((imm0 == 1) ^ ((bit & mask) != 0)) { code |= bit; // set the current bit } else { code &= ~bit; // clear the current bit } bit >>= 1; } dst += emitOutput_Thumb1Instr(dst, code); return dst; } #endif // FEATURE_ITINSTRUCTION /***************************************************************************** * * Output a 32-bit nop instruction. */ BYTE* emitter::emitOutputNOP(BYTE* dst, instruction ins, insFormat fmt) { size_t code = emitInsCode(ins, fmt); dst += emitOutput_Thumb2Instr(dst, code); return dst; } /***************************************************************************** * * 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; size_t code = 0; size_t sz = 0; instruction ins = id->idIns(); insFormat fmt = id->idInsFmt(); emitAttr size = id->idOpSize(); unsigned char callInstrSize = 0; ssize_t condcode; #ifdef DEBUG bool dspOffs = emitComp->opts.dspGCtbls || !emitComp->opts.disDiffable; #endif // DEBUG assert(REG_NA == (int)REG_NA); VARSET_TP VARSET_INIT_NOCOPY(GCvars, VarSetOps::UninitVal()); /* What instruction format have we got? */ switch (fmt) { int imm; int imm0; int mask; int bit; BYTE* addr; regMaskTP gcrefRegs; regMaskTP byrefRegs; case IF_T1_A: // T1_A ................ sz = SMALL_IDSC_SIZE; code = emitInsCode(ins, fmt); dst += emitOutput_Thumb1Instr(dst, code); break; #ifdef FEATURE_ITINSTRUCTION case IF_T1_B: // T1_B ........cccc.... cond assert(id->idGCref() == GCT_NONE); condcode = emitGetInsSC(id); dst = emitOutputIT(dst, ins, fmt, condcode); sz = SMALL_IDSC_SIZE; break; #endif // FEATURE_ITINSTRUCTION case IF_T1_C: // T1_C .....iiiiinnnddd R1 R2 imm5 sz = SMALL_IDSC_SIZE; imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT1_D3(id->idReg1()); code |= insEncodeRegT1_N3(id->idReg2()); if (emitInsIsLoadOrStore(ins)) { imm = insUnscaleImm(imm, size); } assert((imm & 0x001f) == imm); code |= (imm << 6); dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T1_D0: // T1_D0 ........Dmmmmddd R1* R2* sz = SMALL_IDSC_SIZE; code = emitInsCode(ins, fmt); code |= insEncodeRegT1_D4(id->idReg1()); code |= insEncodeRegT1_M4(id->idReg2()); dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T1_E: // T1_E ..........nnnddd R1 R2 sz = SMALL_IDSC_SIZE; code = emitInsCode(ins, fmt); code |= insEncodeRegT1_D3(id->idReg1()); code |= insEncodeRegT1_N3(id->idReg2()); dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T1_F: // T1_F .........iiiiiii SP imm7 sz = emitGetInstrDescSize(id); imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); imm = insUnscaleImm(imm, size); assert((imm & 0x007F) == imm); code |= imm; dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T1_G: // T1_G .......iiinnnddd R1 R2 imm3 sz = SMALL_IDSC_SIZE; imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT1_D3(id->idReg1()); code |= insEncodeRegT1_N3(id->idReg2()); assert((imm & 0x0007) == imm); code |= (imm << 6); dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T1_H: // T1_H .......mmmnnnddd R1 R2 R3 sz = emitGetInstrDescSize(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT1_D3(id->idReg1()); code |= insEncodeRegT1_N3(id->idReg2()); code |= insEncodeRegT1_M3(id->idReg3()); dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T1_I: // T1_I ......i.iiiiiddd R1 imm6 assert(id->idIsBound()); dst = emitOutputLJ(ig, dst, id); sz = sizeof(instrDescJmp); break; case IF_T1_J0: // T1_J0 .....dddiiiiiiii R1 imm8 case IF_T1_J1: // T1_J1 .....dddiiiiiiii R1 case IF_T1_J2: // T1_J2 .....dddiiiiiiii R1 SP imm8 sz = emitGetInstrDescSize(id); imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT1_DI(id->idReg1()); if (fmt == IF_T1_J2) { imm = insUnscaleImm(imm, size); } assert((imm & 0x00ff) == imm); code |= imm; dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T1_L0: // T1_L0 ........iiiiiiii imm8 case IF_T1_L1: // T1_L1 .......Rrrrrrrrr sz = emitGetInstrDescSize(id); imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); if (fmt == IF_T1_L1) { assert((imm & 0x3) != 0x3); if (imm & 0x3) code |= 0x0100; // R bit imm >>= 2; } assert((imm & 0x00ff) == imm); code |= imm; dst += emitOutput_Thumb1Instr(dst, code); break; case IF_T2_A: // T2_A ................ ................ sz = SMALL_IDSC_SIZE; code = emitInsCode(ins, fmt); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_B: // T2_B ................ ............iiii imm4 sz = SMALL_IDSC_SIZE; imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); assert((imm & 0x000F) == imm); code |= imm; dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_C0: // T2_C0 ...........Snnnn .iiiddddiishmmmm R1 R2 R3 S, imm5, sh case IF_T2_C4: // T2_C4 ...........Snnnn ....dddd....mmmm R1 R2 R3 S case IF_T2_C5: // T2_C5 ............nnnn ....dddd....mmmm R1 R2 R3 sz = emitGetInstrDescSize(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); code |= insEncodeRegT2_N(id->idReg2()); code |= insEncodeRegT2_M(id->idReg3()); if (fmt != IF_T2_C5) code |= insEncodeSetFlags(id->idInsFlags()); if (fmt == IF_T2_C0) { imm = emitGetInsSC(id); code |= insEncodeShiftCount(imm); code |= insEncodeShiftOpts(id->idInsOpt()); } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_C1: // T2_C1 ...........S.... .iiiddddiishmmmm R1 R2 S, imm5, sh case IF_T2_C2: // T2_C2 ...........S.... .iiiddddii..mmmm R1 R2 S, imm5 case IF_T2_C6: // T2_C6 ................ ....dddd..iimmmm R1 R2 imm2 sz = SMALL_IDSC_SIZE; imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); code |= insEncodeRegT2_M(id->idReg2()); if (fmt == IF_T2_C6) { assert((imm & 0x0018) == imm); code |= (imm << 1); } else { code |= insEncodeSetFlags(id->idInsFlags()); code |= insEncodeShiftCount(imm); if (fmt == IF_T2_C1) code |= insEncodeShiftOpts(id->idInsOpt()); } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_C3: // T2_C3 ...........S.... ....dddd....mmmm R1 R2 S sz = SMALL_IDSC_SIZE; code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); code |= insEncodeRegT2_M(id->idReg2()); code |= insEncodeSetFlags(id->idInsFlags()); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_C7: // T2_C7 ............nnnn ..........shmmmm R1 R2 imm2 case IF_T2_C8: // T2_C8 ............nnnn .iii....iishmmmm R1 R2 imm5, sh sz = SMALL_IDSC_SIZE; imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_N(id->idReg1()); code |= insEncodeRegT2_M(id->idReg2()); if (fmt == IF_T2_C7) { assert((imm & 0x0003) == imm); code |= (imm << 4); } else if (fmt == IF_T2_C8) { code |= insEncodeShiftCount(imm); code |= insEncodeShiftOpts(id->idInsOpt()); } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_C9: // T2_C9 ............nnnn ............mmmm R1 R2 sz = SMALL_IDSC_SIZE; code = emitInsCode(ins, fmt); code |= insEncodeRegT2_N(id->idReg1()); code |= insEncodeRegT2_M(id->idReg2()); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_C10: // T2_C10 ............mmmm ....dddd....mmmm R1 R2 sz = SMALL_IDSC_SIZE; code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); code |= insEncodeRegT2_M(id->idReg2()); code |= insEncodeRegT2_N(id->idReg2()); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_D0: // T2_D0 ............nnnn .iiiddddii.wwwww R1 R2 imm5, imm5 case IF_T2_D1: // T2_D1 ................ .iiiddddii.wwwww R1 imm5, imm5 sz = SMALL_IDSC_SIZE; imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); if (fmt == IF_T2_D0) code |= insEncodeRegT2_N(id->idReg2()); code |= insEncodeBitFieldImm(imm); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_E0: // T2_E0 ............nnnn tttt......shmmmm R1 R2 R3 imm2 case IF_T2_E1: // T2_E1 ............nnnn tttt............ R1 R2 case IF_T2_E2: // T2_E2 ................ tttt............ R1 code = emitInsCode(ins, fmt); code |= insEncodeRegT2_T(id->idReg1()); if (fmt == IF_T2_E0) { sz = emitGetInstrDescSize(id); code |= insEncodeRegT2_N(id->idReg2()); if (id->idIsLclVar()) { code |= insEncodeRegT2_M(codeGen->rsGetRsvdReg()); imm = 0; } else { code |= insEncodeRegT2_M(id->idReg3()); imm = emitGetInsSC(id); assert((imm & 0x0003) == imm); code |= (imm << 4); } } else { sz = SMALL_IDSC_SIZE; if (fmt != IF_T2_E2) { code |= insEncodeRegT2_N(id->idReg2()); } } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_F1: // T2_F1 ............nnnn ttttdddd....mmmm R1 R2 R3 R4 sz = emitGetInstrDescSize(id); ; code = emitInsCode(ins, fmt); code |= insEncodeRegT2_T(id->idReg1()); code |= insEncodeRegT2_D(id->idReg2()); code |= insEncodeRegT2_N(id->idReg3()); code |= insEncodeRegT2_M(id->idReg4()); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_F2: // T2_F2 ............nnnn aaaadddd....mmmm R1 R2 R3 R4 sz = emitGetInstrDescSize(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); code |= insEncodeRegT2_N(id->idReg2()); code |= insEncodeRegT2_M(id->idReg3()); code |= insEncodeRegT2_T(id->idReg4()); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_G0: // T2_G0 .......PU.W.nnnn ttttTTTTiiiiiiii R1 R2 R3 imm8, PUW case IF_T2_G1: // T2_G1 ............nnnn ttttTTTT........ R1 R2 R3 code = emitInsCode(ins, fmt); code |= insEncodeRegT2_T(id->idReg1()); code |= insEncodeRegT2_D(id->idReg2()); code |= insEncodeRegT2_N(id->idReg3()); if (fmt == IF_T2_G0) { sz = emitGetInstrDescSizeSC(id); imm = emitGetInsSC(id); assert(unsigned_abs(imm) <= 0x00ff); code |= abs(imm); code |= insEncodePUW_G0(id->idInsOpt(), imm); } else { sz = emitGetInstrDescSize(id); } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_H0: // T2_H0 ............nnnn tttt.PUWiiiiiiii R1 R2 imm8, PUW case IF_T2_H1: // T2_H1 ............nnnn tttt....iiiiiiii R1 R2 imm8 case IF_T2_H2: // T2_H2 ............nnnn ........iiiiiiii R1 imm8 sz = emitGetInstrDescSizeSC(id); imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_T(id->idReg1()); if (fmt != IF_T2_H2) code |= insEncodeRegT2_N(id->idReg2()); if (fmt == IF_T2_H0) { assert(unsigned_abs(imm) <= 0x00ff); code |= insEncodePUW_H0(id->idInsOpt(), imm); code |= unsigned_abs(imm); } else { assert((imm & 0x00ff) == imm); code |= imm; } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_I0: // T2_I0 ..........W.nnnn rrrrrrrrrrrrrrrr R1 W, imm16 case IF_T2_I1: // T2_I1 ................ rrrrrrrrrrrrrrrr imm16 sz = emitGetInstrDescSizeSC(id); code = emitInsCode(ins, fmt); if (fmt == IF_T2_I0) { code |= insEncodeRegT2_N(id->idReg1()); code |= (1 << 21); // W bit } imm = emitGetInsSC(id); assert((imm & 0x3) != 0x3); if (imm & 0x2) code |= 0x8000; // PC bit if (imm & 0x1) code |= 0x4000; // LR bit imm >>= 2; assert(imm <= 0x1fff); // 13 bits code |= imm; dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_K1: // T2_K1 ............nnnn ttttiiiiiiiiiiii R1 R2 imm12 case IF_T2_K4: // T2_K4 ........U....... ttttiiiiiiiiiiii R1 PC U, imm12 case IF_T2_K3: // T2_K3 ........U....... ....iiiiiiiiiiii PC U, imm12 sz = emitGetInstrDescSize(id); imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); if (fmt != IF_T2_K3) { code |= insEncodeRegT2_T(id->idReg1()); } if (fmt == IF_T2_K1) { code |= insEncodeRegT2_N(id->idReg2()); assert(imm <= 0xfff); // 12 bits code |= imm; } else { assert(unsigned_abs(imm) <= 0xfff); // 12 bits (signed) code |= abs(imm); if (imm >= 0) code |= (1 << 23); // U bit } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_K2: // T2_K2 ............nnnn ....iiiiiiiiiiii R1 imm12 sz = emitGetInstrDescSizeSC(id); imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_N(id->idReg1()); assert(imm <= 0xfff); // 12 bits code |= imm; dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_L0: // T2_L0 .....i.....Snnnn .iiiddddiiiiiiii R1 R2 S, imm8<idReg1()); else { code |= insEncodeSetFlags(id->idInsFlags()); code |= insEncodeRegT2_D(id->idReg1()); if (fmt == IF_T2_L0) code |= insEncodeRegT2_N(id->idReg2()); } assert(isModImmConst(imm)); // Funky ARM imm encoding imm = encodeModImmConst(imm); assert(imm <= 0xfff); // 12 bits code |= (imm & 0x00ff); code |= (imm & 0x0700) << 4; code |= (imm & 0x0800) << 15; dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_M0: // T2_M0 .....i......nnnn .iiiddddiiiiiiii R1 R2 imm12 sz = emitGetInstrDescSizeSC(id); imm = emitGetInsSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); if (fmt == IF_T2_M0) code |= insEncodeRegT2_N(id->idReg2()); imm = emitGetInsSC(id); assert(imm <= 0xfff); // 12 bits code |= (imm & 0x00ff); code |= (imm & 0x0700) << 4; code |= (imm & 0x0800) << 15; dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_N: // T2_N .....i......iiii .iiiddddiiiiiiii R1 imm16 case IF_T2_N2: // T2_N2 .....i......iiii .iiiddddiiiiiiii R1 imm16 sz = emitGetInstrDescSizeSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_D(id->idReg1()); imm = emitGetInsSC(id); if (fmt == IF_T2_N2) { assert(!id->idIsLclVar()); assert((ins == INS_movw) || (ins == INS_movt)); imm += (size_t)emitConsBlock; if (!id->idIsCnsReloc() && !id->idIsDspReloc()) { goto SPLIT_IMM; } } else if (id->idIsLclVar()) { SPLIT_IMM: if (ins == INS_movw) { imm &= 0xffff; } else { imm = (imm >> 16) & 0xffff; } } if (id->idIsCnsReloc() || id->idIsDspReloc()) { assert((ins == INS_movt) || (ins == INS_movw)); dst += emitOutput_Thumb2Instr(dst, code); if ((ins == INS_movt) && emitComp->info.compMatchedVM) emitRecordRelocation((void*)(dst - 8), (void*)imm, IMAGE_REL_BASED_THUMB_MOV32); } else { assert((imm & 0x0000ffff) == imm); code |= (imm & 0x00ff); code |= ((imm & 0x0700) << 4); code |= ((imm & 0x0800) << 15); code |= ((imm & 0xf000) << 4); dst += emitOutput_Thumb2Instr(dst, code); } break; case IF_T2_VFP3: // these are the binary operators // d = n - m sz = emitGetInstrDescSize(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_VectorN(id->idReg2(), size, true); code |= insEncodeRegT2_VectorM(id->idReg3(), size, true); code |= insEncodeRegT2_VectorD(id->idReg1(), size, true); if (size == EA_8BYTE) code |= 1 << 8; dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_VFP2: { emitAttr srcSize; emitAttr dstSize; size_t szCode = 0; switch (ins) { case INS_vcvt_i2d: case INS_vcvt_u2d: case INS_vcvt_f2d: srcSize = EA_4BYTE; dstSize = EA_8BYTE; break; case INS_vcvt_d2i: case INS_vcvt_d2u: case INS_vcvt_d2f: srcSize = EA_8BYTE; dstSize = EA_4BYTE; break; case INS_vmov: case INS_vabs: case INS_vsqrt: case INS_vcmp: case INS_vneg: if (id->idOpSize() == EA_8BYTE) szCode |= (1 << 8); __fallthrough; default: srcSize = dstSize = id->idOpSize(); break; } sz = emitGetInstrDescSize(id); code = emitInsCode(ins, fmt); code |= szCode; code |= insEncodeRegT2_VectorD(id->idReg1(), dstSize, true); code |= insEncodeRegT2_VectorM(id->idReg2(), srcSize, true); dst += emitOutput_Thumb2Instr(dst, code); break; } case IF_T2_VLDST: sz = emitGetInstrDescSizeSC(id); code = emitInsCode(ins, fmt); code |= insEncodeRegT2_N(id->idReg2()); code |= insEncodeRegT2_VectorD(id->idReg1(), size, true); imm = emitGetInsSC(id); if (imm < 0) imm = -imm; // bit 23 at 0 means negate else code |= 1 << 23; // set the positive bit // offset is +/- 1020 assert(!(imm % 4)); assert(imm >> 10 == 0); code |= imm >> 2; // bit 8 is set for doubles if (id->idOpSize() == EA_8BYTE) code |= (1 << 8); dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_VMOVD: // 3op assemble a double from two int regs (or back) sz = emitGetInstrDescSize(id); code = emitInsCode(ins, fmt); if (ins == INS_vmov_i2d) { code |= insEncodeRegT2_VectorM(id->idReg1(), size, true); code |= id->idReg2() << 12; code |= id->idReg3() << 16; } else { assert(ins == INS_vmov_d2i); code |= id->idReg1() << 12; code |= id->idReg2() << 16; code |= insEncodeRegT2_VectorM(id->idReg3(), size, true); } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T2_VMOVS: // 2op assemble a float from one int reg (or back) sz = emitGetInstrDescSize(id); code = emitInsCode(ins, fmt); if (ins == INS_vmov_f2i) { code |= insEncodeRegT2_VectorN(id->idReg2(), EA_4BYTE, true); code |= id->idReg1() << 12; } else { assert(ins == INS_vmov_i2f); code |= insEncodeRegT2_VectorN(id->idReg1(), EA_4BYTE, true); code |= id->idReg2() << 12; } dst += emitOutput_Thumb2Instr(dst, code); break; case IF_T1_J3: // T1_J3 .....dddiiiiiiii R1 PC imm8 case IF_T2_M1: // T2_M1 .....i.......... .iiiddddiiiiiiii R1 PC imm12 assert(id->idGCref() == GCT_NONE); assert(id->idIsBound()); dst = emitOutputLJ(ig, dst, id); sz = sizeof(instrDescLbl); break; case IF_T1_K: // T1_K ....cccciiiiiiii Branch imm8, cond4 case IF_T1_M: // T1_M .....iiiiiiiiiii Branch imm11 case IF_T2_J1: // T2_J1 .....Scccciiiiii ..j.jiiiiiiiiiii Branch imm20, cond4 case IF_T2_J2: // T2_J2 .....Siiiiiiiiii ..j.jiiiiiiiiii. Branch imm24 case IF_T2_N1: // T2_N .....i......iiii .iiiddddiiiiiiii R1 imm16 case IF_LARGEJMP: assert(id->idGCref() == GCT_NONE); assert(id->idIsBound()); dst = emitOutputLJ(ig, dst, id); sz = sizeof(instrDescJmp); break; case IF_T1_D1: // T1_D1 .........mmmm... R1* code = emitInsCode(ins, fmt); code |= insEncodeRegT1_M4(id->idReg1()); dst += emitOutput_Thumb1Instr(dst, code); sz = SMALL_IDSC_SIZE; break; case IF_T1_D2: // T1_D2 .........mmmm... R3* /* 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); sz = sizeof(instrDescCGCA); } else { assert(!id->idIsLargeDsp()); assert(!id->idIsLargeCns()); gcrefRegs = emitDecodeCallGCregs(id); byrefRegs = 0; VarSetOps::AssignNoCopy(emitComp, GCvars, VarSetOps::MakeEmpty(emitComp)); sz = sizeof(instrDesc); } code = emitInsCode(ins, fmt); code |= insEncodeRegT1_M4(id->idReg3()); callInstrSize = SafeCvtAssert(emitOutput_Thumb1Instr(dst, code)); dst += callInstrSize; goto DONE_CALL; case IF_T2_J3: // T2_J3 .....Siiiiiiiiii ..j.jiiiiiiiiii. Call imm24 /* 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); sz = sizeof(instrDescCGCA); } else { assert(!id->idIsLargeDsp()); assert(!id->idIsLargeCns()); gcrefRegs = emitDecodeCallGCregs(id); byrefRegs = 0; VarSetOps::AssignNoCopy(emitComp, GCvars, VarSetOps::MakeEmpty(emitComp)); sz = sizeof(instrDesc); } if (id->idAddr()->iiaAddr == NULL) /* a recursive call */ { addr = emitCodeBlock; } else { addr = id->idAddr()->iiaAddr; } code = emitInsCode(ins, fmt); if (id->idIsDspReloc()) { callInstrSize = SafeCvtAssert(emitOutput_Thumb2Instr(dst, code)); dst += callInstrSize; if (emitComp->info.compMatchedVM) emitRecordRelocation((void*)(dst - 4), addr, IMAGE_REL_BASED_THUMB_BRANCH24); } else { addr = (BYTE*)((size_t)addr & ~1); // Clear the lowest bit from target address /* Calculate PC relative displacement */ int disp = addr - (dst + 4); bool S = (disp < 0); bool I1 = ((disp & 0x00800000) == 0); bool I2 = ((disp & 0x00400000) == 0); if (S) code |= (1 << 26); // S bit if (S ^ I1) code |= (1 << 13); // J1 bit if (S ^ I2) code |= (1 << 11); // J2 bit int immLo = (disp & 0x00000ffe) >> 1; int immHi = (disp & 0x003ff000) >> 12; code |= (immHi << 16); code |= immLo; disp = abs(disp); assert((disp & 0x00fffffe) == disp); callInstrSize = SafeCvtAssert(emitOutput_Thumb2Instr(dst, code)); dst += callInstrSize; } DONE_CALL: /* 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, *dp); // If the method returns a GC ref, mark R0 appropriately. if (id->idGCref() == GCT_GCREF) gcrefRegs |= RBM_R0; else if (id->idGCref() == GCT_BYREF) byrefRegs |= RBM_R0; // If the GC register set has changed, report the new set. if (gcrefRegs != emitThisGCrefRegs) emitUpdateLiveGCregs(GCT_GCREF, gcrefRegs, dst); 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 ARM, 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); } } break; /********************************************************************/ /* oops */ /********************************************************************/ default: #ifdef DEBUG printf("unexpected format %s\n", emitIfName(id->idInsFmt())); assert(!"don't know how to encode this instruction"); #endif 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 (LDM, or POP), // then we'd need to more work here to ensure that changes in the status of GC refs are // tracked properly. if (emitInsMayWriteMultipleRegs(id)) { // We explicitly list the multiple-destination-target instruction that we expect to // be emitted outside of the prolog and epilog here. switch (ins) { case INS_smull: case INS_umull: case INS_smlal: case INS_umlal: case INS_vmov_d2i: // For each of these, idReg1() and idReg2() are the destination registers. emitGCregDeadUpd(id->idReg1(), dst); emitGCregDeadUpd(id->idReg2(), dst); break; default: assert(false); // We need to recognize this multi-target instruction... } } else { if (id->idGCref() != GCT_NONE) { emitGCregLiveUpd(id->idGCref(), id->idReg1(), dst); } else { // I also assume that "idReg1" is the destination register of all instructions that write to registers. 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)); regNumber regBase; int adr = emitComp->lvaFrameAddress(varNum, true, ®Base, ofs); 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) { // set JitEmitPrintRefRegs=1 will print out emitThisGCrefRegs and emitThisByrefRegs // at the beginning of this method. if (JitConfig.JitEmitPrintRefRegs() != 0) { printf("Before emitOutputInstr for id->idDebugOnlyInfo()->idNum=0x%02x\n", id->idDebugOnlyInfo()->idNum); printf(" emitThisGCrefRegs(0x%p)=", dspPtr(&emitThisGCrefRegs)); printRegMaskInt(emitThisGCrefRegs); emitDispRegSet(emitThisGCrefRegs); printf("\n"); printf(" emitThisByrefRegs(0x%p)=", dspPtr(&emitThisByrefRegs)); printRegMaskInt(emitThisByrefRegs); emitDispRegSet(emitThisByrefRegs); printf("\n"); } // 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 static bool insAlwaysSetFlags(instruction ins) { bool result = false; switch (ins) { case INS_cmp: case INS_cmn: case INS_teq: case INS_tst: result = true; break; default: break; } return result; } /***************************************************************************** * * Display the instruction name, optionally the instruction * can add the "s" suffix if it must set the flags. */ void emitter::emitDispInst(instruction ins, insFlags flags) { const char* insstr = codeGen->genInsName(ins); int len = strlen(insstr); /* Display the instruction name */ printf("%s", insstr); if (insSetsFlags(flags) && !insAlwaysSetFlags(ins)) { printf("s"); len++; } // // 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(", "); } #define STRICT_ARM_ASM 0 /***************************************************************************** * * Display an immediate value */ void emitter::emitDispImm(int imm, bool addComma, bool alwaysHex /* =false */) { if (!alwaysHex && (imm > -1000) && (imm < 1000)) printf("%d", imm); else if ((imm > 0) || (imm == -imm) || // -0x80000000 == 0x80000000. So we don't want to add an extra "-" at the beginning. (emitComp->opts.disDiffable && (imm == 0xD1FFAB1E))) // Don't display this as negative printf("0x%02x", imm); else // val <= -1000 printf("-0x%02x", -imm); if (addComma) printf(", "); } /***************************************************************************** * * Display an arm condition for the IT instructions */ void emitter::emitDispCond(int 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 assert(0 <= cond && (unsigned)cond < ArrLen(armCond)); printf(armCond[cond]); } /***************************************************************************** * * Display a register range in a range format */ void emitter::emitDispRegRange(regNumber reg, int len, emitAttr attr) { printf("{"); emitDispReg(reg, attr, false); if (len > 1) { printf("-"); emitDispReg((regNumber)(reg + len - 1), attr, false); } printf("}"); } /***************************************************************************** * * Display an register mask in a list format */ void emitter::emitDispRegmask(int imm, bool encodedPC_LR) { bool printedOne = false; bool hasPC; bool hasLR; if (encodedPC_LR) { hasPC = (imm & 2) != 0; hasLR = (imm & 1) != 0; imm >>= 2; } else { hasPC = (imm & RBM_PC) != 0; hasLR = (imm & RBM_LR) != 0; imm &= ~(RBM_PC | RBM_LR); } regNumber reg = REG_R0; unsigned bit = 1; printf("{"); while (imm != 0) { if (bit & imm) { if (printedOne) printf(","); printf("%s", emitRegName(reg)); printedOne = true; imm -= bit; } reg = regNumber(reg + 1); bit <<= 1; } if (hasLR) { if (printedOne) printf(","); printf("%s", emitRegName(REG_LR)); printedOne = true; } if (hasPC) { if (printedOne) printf(","); printf("%s", emitRegName(REG_PC)); printedOne = true; } printf("}"); } /***************************************************************************** * * Returns the encoding for the Shift Type bits to be used in a Thumb-2 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_RRX) printf(" RRX "); } /***************************************************************************** * * Display a register */ void emitter::emitDispReg(regNumber reg, emitAttr attr, bool addComma) { if (isFloatReg(reg)) { const char* size = attr == EA_8BYTE ? "d" : "s"; printf("%s%s", size, emitFloatRegName(reg, attr) + 1); } else { printf("%s", emitRegName(reg, attr)); } if (addComma) printf(", "); } void emitter::emitDispFloatReg(regNumber reg, emitAttr attr, bool addComma) { } /***************************************************************************** * * Display an addressing operand [reg] */ void emitter::emitDispAddrR(regNumber reg, emitAttr attr) { printf("["); emitDispReg(reg, attr, false); printf("]"); emitDispGC(attr); } /***************************************************************************** * * Display an addressing operand [reg + imm] */ void emitter::emitDispAddrRI(regNumber reg, int imm, emitAttr attr) { bool regIsSPorFP = (reg == REG_SP) || (reg == REG_FP); printf("["); emitDispReg(reg, attr, false); if (imm != 0) { if (imm >= 0) { #if STRICT_ARM_ASM printf(", "); #else printf("+"); #endif } emitDispImm(imm, false, regIsSPorFP); } printf("]"); emitDispGC(attr); } /***************************************************************************** * * Display an addressing operand [reg + reg] */ void emitter::emitDispAddrRR(regNumber reg1, regNumber reg2, emitAttr attr) { printf("["); emitDispReg(reg1, attr, false); #if STRICT_ARM_ASM printf(", "); #else printf("+"); #endif emitDispReg(reg2, attr, false); printf("]"); emitDispGC(attr); } /***************************************************************************** * * Display an addressing operand [reg + reg * imm] */ void emitter::emitDispAddrRRI(regNumber reg1, regNumber reg2, int imm, emitAttr attr) { printf("["); emitDispReg(reg1, attr, false); #if STRICT_ARM_ASM printf(", "); emitDispReg(reg2, attr, false); if (imm > 0) { printf(" LSL "); emitDispImm(1 << imm, false); } #else printf("+"); if (imm > 0) { emitDispImm(1 << imm, false); printf("*"); } emitDispReg(reg2, attr, false); #endif printf("]"); emitDispGC(attr); } /***************************************************************************** * * Display an addressing operand [reg + imm] */ void emitter::emitDispAddrPUW(regNumber reg, int imm, insOpts opt, emitAttr attr) { bool regIsSPorFP = (reg == REG_SP) || (reg == REG_FP); printf("["); emitDispReg(reg, attr, false); if (insOptAnyInc(opt)) printf("!"); if (imm != 0) { if (imm >= 0) { #if STRICT_ARM_ASM printf(", "); #else printf("+"); #endif } emitDispImm(imm, false, regIsSPorFP); } printf("]"); emitDispGC(attr); } /***************************************************************************** * * Display the gc-ness of the operand */ void emitter::emitDispGC(emitAttr attr) { #if 0 // TODO-ARM-Cleanup: Fix or delete. if (attr == EA_GCREF) printf(" @gc"); else if (attr == EA_BYREF) printf(" @byref"); #endif } /***************************************************************************** * * 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 == 2) { printf(" %04X ", (*((unsigned short*)code))); } else if (sz == 4) { printf(" %04X %04X", (*((unsigned short*)(code + 0))), (*((unsigned short*)(code + 2)))); } } } /**************************************************************************** * * Display the given instruction. */ void emitter::emitDispInsHelp( instrDesc* id, bool isNew, bool doffs, bool asmfm, unsigned offset, BYTE* code, 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 (code == 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(code, sz); printf(" "); /* Get the instruction and format */ instruction ins = id->idIns(); insFormat fmt = id->idInsFmt(); emitDispInst(ins, id->idInsFlags()); /* 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 attr; if (id->idGCref() == GCT_GCREF) attr = EA_GCREF; else if (id->idGCref() == GCT_BYREF) attr = EA_BYREF; else attr = id->idOpSize(); switch (fmt) { int imm; int offs; const char* methodName; case IF_T1_A: // None case IF_T2_A: break; case IF_T1_L0: // Imm case IF_T2_B: emitDispImm(emitGetInsSC(id), false); break; case IF_T1_B: // emitDispCond(emitGetInsSC(id)); break; case IF_T1_L1: // case IF_T2_I1: // emitDispRegmask(emitGetInsSC(id), true); break; case IF_T2_E2: // Reg if (id->idIns() == INS_vmrs) { if (id->idReg1() != REG_R15) { emitDispReg(id->idReg1(), attr, true); printf("FPSCR"); } else { printf("APSR, FPSCR"); } } else { emitDispReg(id->idReg1(), attr, false); } break; case IF_T1_D1: emitDispReg(id->idReg1(), attr, false); break; case IF_T1_D2: emitDispReg(id->idReg3(), attr, false); { CORINFO_METHOD_HANDLE handle = (CORINFO_METHOD_HANDLE)id->idDebugOnlyInfo()->idMemCookie; if (handle != 0) { methodName = emitComp->eeGetMethodFullName(handle); printf("\t\t// %s", methodName); } } break; case IF_T1_F: // SP, Imm emitDispReg(REG_SP, attr, true); emitDispImm(emitGetInsSC(id), false); break; case IF_T1_J0: // Reg, Imm case IF_T2_L1: case IF_T2_L2: case IF_T2_N: emitDispReg(id->idReg1(), attr, true); imm = emitGetInsSC(id); if (fmt == IF_T2_N) { if (emitComp->opts.disDiffable) imm = 0xD1FF; if (id->idIsCnsReloc() || id->idIsDspReloc()) { if (emitComp->opts.disDiffable) imm = 0xD1FFAB1E; printf("%s RELOC ", (id->idIns() == INS_movw) ? "LOW" : "HIGH"); } } emitDispImm(imm, false, (fmt == IF_T2_N)); break; case IF_T2_N2: emitDispReg(id->idReg1(), attr, true); imm = emitGetInsSC(id); { dataSection* jdsc = 0; NATIVE_OFFSET offs = 0; /* Find the appropriate entry in the data section list */ for (jdsc = emitConsDsc.dsdList; jdsc; jdsc = jdsc->dsNext) { UNATIVE_OFFSET size = jdsc->dsSize; /* Is this a label table? */ if (jdsc->dsType == dataSection::blockAbsoluteAddr) { if (offs == imm) break; } offs += size; } assert(jdsc != NULL); if (id->idIsDspReloc()) { printf("reloc "); } printf("%s ADDRESS J_M%03u_DS%02u", (id->idIns() == INS_movw) ? "LOW" : "HIGH", Compiler::s_compMethodsCount, imm); // After the MOVT, dump the table if (id->idIns() == INS_movt) { unsigned cnt = jdsc->dsSize / TARGET_POINTER_SIZE; BasicBlock** bbp = (BasicBlock**)jdsc->dsCont; bool isBound = (emitCodeGetCookie(*bbp) != NULL); if (isBound) { printf("\n\n J_M%03u_DS%02u LABEL DWORD", Compiler::s_compMethodsCount, imm); /* Display the label table (it's stored as "BasicBlock*" values) */ do { insGroup* lab; /* Convert the BasicBlock* value to an IG address */ lab = (insGroup*)emitCodeGetCookie(*bbp++); assert(lab); printf("\n DD G_M%03u_IG%02u", Compiler::s_compMethodsCount, lab->igNum); } while (--cnt); } } } break; case IF_T2_H2: // [Reg+imm] case IF_T2_K2: emitDispAddrRI(id->idReg1(), emitGetInsSC(id), attr); break; case IF_T2_K3: // [PC+imm] emitDispAddrRI(REG_PC, emitGetInsSC(id), attr); break; case IF_T1_J1: // reg, case IF_T2_I0: // reg, emitDispReg(id->idReg1(), attr, false); printf("!, "); emitDispRegmask(emitGetInsSC(id), false); break; case IF_T1_D0: // Reg, Reg case IF_T1_E: case IF_T2_C3: case IF_T2_C9: case IF_T2_C10: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, false); if (fmt == IF_T1_E && id->idIns() == INS_rsb) { printf(", 0"); } break; case IF_T2_E1: // Reg, [Reg] emitDispReg(id->idReg1(), attr, true); emitDispAddrR(id->idReg2(), attr); break; case IF_T2_D1: // Reg, Imm, Imm emitDispReg(id->idReg1(), attr, true); imm = emitGetInsSC(id); { int lsb = (imm >> 5) & 0x1f; int msb = imm & 0x1f; int imm1 = lsb; int imm2 = msb + 1 - lsb; emitDispImm(imm1, true); emitDispImm(imm2, false); } break; case IF_T1_C: // Reg, Reg, Imm case IF_T1_G: case IF_T2_C2: case IF_T2_H1: case IF_T2_K1: case IF_T2_L0: case IF_T2_M0: emitDispReg(id->idReg1(), attr, true); imm = emitGetInsSC(id); if (emitInsIsLoadOrStore(ins)) { emitDispAddrRI(id->idReg2(), imm, attr); } else { emitDispReg(id->idReg2(), attr, true); emitDispImm(imm, false); } break; case IF_T1_J2: emitDispReg(id->idReg1(), attr, true); imm = emitGetInsSC(id); if (emitInsIsLoadOrStore(ins)) { emitDispAddrRI(REG_SP, imm, attr); } else { emitDispReg(REG_SP, attr, true); emitDispImm(imm, false); } break; case IF_T2_K4: emitDispReg(id->idReg1(), attr, true); emitDispAddrRI(REG_PC, emitGetInsSC(id), attr); break; case IF_T2_C1: case IF_T2_C8: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, false); imm = emitGetInsSC(id); if (id->idInsOpt() == INS_OPTS_RRX) { emitDispShiftOpts(id->idInsOpt()); assert(imm == 1); } else if (imm > 0) { emitDispShiftOpts(id->idInsOpt()); emitDispImm(imm, false); } break; case IF_T2_C6: imm = emitGetInsSC(id); emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, (imm != 0)); if (imm != 0) { emitDispImm(imm, false); } break; case IF_T2_C7: emitDispAddrRRI(id->idReg1(), id->idReg2(), emitGetInsSC(id), attr); break; case IF_T2_H0: emitDispReg(id->idReg1(), attr, true); emitDispAddrPUW(id->idReg2(), emitGetInsSC(id), id->idInsOpt(), attr); break; case IF_T1_H: // Reg, Reg, Reg emitDispReg(id->idReg1(), attr, true); if (emitInsIsLoadOrStore(ins)) { emitDispAddrRR(id->idReg2(), id->idReg3(), attr); } else { emitDispReg(id->idReg2(), attr, true); emitDispReg(id->idReg3(), attr, false); } break; case IF_T2_C4: case IF_T2_C5: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, true); emitDispReg(id->idReg3(), attr, false); break; case IF_T2_VFP3: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, true); emitDispReg(id->idReg3(), attr, false); break; case IF_T2_VFP2: switch (id->idIns()) { case INS_vcvt_d2i: case INS_vcvt_d2u: case INS_vcvt_d2f: emitDispReg(id->idReg1(), EA_4BYTE, true); emitDispReg(id->idReg2(), EA_8BYTE, false); break; case INS_vcvt_i2d: case INS_vcvt_u2d: case INS_vcvt_f2d: emitDispReg(id->idReg1(), EA_8BYTE, true); emitDispReg(id->idReg2(), EA_4BYTE, false); break; // we just use the type on the instruction // unless it is an asymmetrical one like the converts default: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, false); break; } break; case IF_T2_VLDST: imm = emitGetInsSC(id); switch (id->idIns()) { case INS_vldr: case INS_vstr: emitDispReg(id->idReg1(), attr, true); emitDispAddrPUW(id->idReg2(), imm, id->idInsOpt(), attr); break; case INS_vldm: case INS_vstm: emitDispReg(id->idReg2(), attr, false); if (insOptAnyInc(id->idInsOpt())) printf("!"); printf(", "); emitDispRegRange(id->idReg1(), abs(imm) >> 2, attr); break; case INS_vpush: case INS_vpop: emitDispRegRange(id->idReg1(), abs(imm) >> 2, attr); break; default: unreached(); } break; case IF_T2_VMOVD: switch (id->idIns()) { case INS_vmov_i2d: emitDispReg(id->idReg1(), attr, true); // EA_8BYTE emitDispReg(id->idReg2(), EA_4BYTE, true); emitDispReg(id->idReg3(), EA_4BYTE, false); break; case INS_vmov_d2i: emitDispReg(id->idReg1(), EA_4BYTE, true); emitDispReg(id->idReg2(), EA_4BYTE, true); emitDispReg(id->idReg3(), attr, false); // EA_8BYTE break; default: unreached(); } break; case IF_T2_VMOVS: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, false); break; case IF_T2_G1: emitDispReg(id->idReg1(), attr, true); emitDispAddrRR(id->idReg2(), id->idReg3(), attr); break; case IF_T2_D0: // Reg, Reg, Imm, Imm emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, true); imm = emitGetInsSC(id); if (ins == INS_bfi) { int lsb = (imm >> 5) & 0x1f; int msb = imm & 0x1f; int imm1 = lsb; int imm2 = msb + 1 - lsb; emitDispImm(imm1, true); emitDispImm(imm2, false); } else { int lsb = (imm >> 5) & 0x1f; int widthm1 = imm & 0x1f; int imm1 = lsb; int imm2 = widthm1 + 1; emitDispImm(imm1, true); emitDispImm(imm2, false); } break; case IF_T2_C0: // Reg, Reg, Reg, Imm emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, true); emitDispReg(id->idReg3(), attr, false); imm = emitGetInsSC(id); if (id->idInsOpt() == INS_OPTS_RRX) { emitDispShiftOpts(id->idInsOpt()); assert(imm == 1); } else if (imm > 0) { emitDispShiftOpts(id->idInsOpt()); emitDispImm(imm, false); } break; case IF_T2_E0: emitDispReg(id->idReg1(), attr, true); if (id->idIsLclVar()) { emitDispAddrRRI(id->idReg2(), codeGen->rsGetRsvdReg(), 0, attr); } else { emitDispAddrRRI(id->idReg2(), id->idReg3(), emitGetInsSC(id), attr); } break; case IF_T2_G0: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, true); emitDispAddrPUW(id->idReg3(), emitGetInsSC(id), id->idInsOpt(), attr); break; case IF_T2_F1: // Reg, Reg, Reg, Reg case IF_T2_F2: emitDispReg(id->idReg1(), attr, true); emitDispReg(id->idReg2(), attr, true); emitDispReg(id->idReg3(), attr, true); emitDispReg(id->idReg4(), attr, false); break; case IF_T1_J3: case IF_T2_M1: // Load Label emitDispReg(id->idReg1(), attr, 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_T1_I: // Special Compare-and-branch emitDispReg(id->idReg1(), attr, true); __fallthrough; case IF_T1_K: // Special Branch, conditional case IF_T1_M: assert(((instrDescJmp*)id)->idjShort); printf("SHORT "); __fallthrough; case IF_T2_N1: if (fmt == IF_T2_N1) { emitDispReg(id->idReg1(), attr, true); printf("%s ADDRESS ", (id->idIns() == INS_movw) ? "LOW" : "HIGH"); } __fallthrough; case IF_T2_J1: case IF_T2_J2: case IF_LARGEJMP: { if (id->idAddr()->iiaHasInstrCount()) { int instrCount = id->idAddr()->iiaGetInstrCount(); if (ig == NULL) { 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_T2_J3: 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; 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"); } void emitter::emitDispIns( instrDesc* id, bool isNew, bool doffs, bool asmfm, unsigned offset, BYTE* code, size_t sz, insGroup* ig) { insFormat fmt = id->idInsFmt(); /* Special-case IF_LARGEJMP */ if ((fmt == IF_LARGEJMP) && id->idIsBound()) { // This is a pseudo-instruction format representing a large conditional branch. See the comment // in emitter::emitOutputLJ() for the full description. // // For this pseudo-instruction, we will actually generate: // // b L_not // 2 bytes. Note that we reverse the condition. // b L_target // 4 bytes // L_not: // // These instructions don't exist in the actual instruction stream, so we need to fake them // up to display them. // // Note: don't touch the actual instrDesc. If we accidentally messed it up, it would create a very // difficult to find bug. instrDescJmp idJmp; instrDescJmp* pidJmp = &idJmp; memset(&idJmp, 0, sizeof(idJmp)); pidJmp->idIns(emitJumpKindToIns(emitReverseJumpKind(emitInsToJumpKind(id->idIns())))); // reverse the // conditional // instruction pidJmp->idInsFmt(IF_T1_K); pidJmp->idInsSize(emitInsSize(IF_T1_K)); pidJmp->idjShort = 1; pidJmp->idAddr()->iiaSetInstrCount(1); pidJmp->idDebugOnlyInfo(id->idDebugOnlyInfo()); // share the idDebugOnlyInfo() field size_t bcondSizeOrZero = (code == NULL) ? 0 : 2; // branch is 2 bytes emitDispInsHelp(pidJmp, false, doffs, asmfm, offset, code, bcondSizeOrZero, NULL /* force display of pc-relative branch */); code += bcondSizeOrZero; offset += 2; // Next, display the unconditional branch // Reset the local instrDesc memset(&idJmp, 0, sizeof(idJmp)); pidJmp->idIns(INS_b); pidJmp->idInsFmt(IF_T2_J2); pidJmp->idInsSize(emitInsSize(IF_T2_J2)); pidJmp->idjShort = 0; if (id->idIsBound()) { pidJmp->idSetIsBound(); pidJmp->idAddr()->iiaIGlabel = id->idAddr()->iiaIGlabel; } else { pidJmp->idAddr()->iiaBBlabel = id->idAddr()->iiaBBlabel; } pidJmp->idDebugOnlyInfo(id->idDebugOnlyInfo()); // share the idDebugOnlyInfo() field size_t brSizeOrZero = (code == NULL) ? 0 : 4; // unconditional branch is 4 bytes emitDispInsHelp(pidJmp, isNew, doffs, asmfm, offset, code, brSizeOrZero, ig); } else { emitDispInsHelp(id, isNew, doffs, asmfm, offset, code, sz, ig); } } /***************************************************************************** * * 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 #ifndef LEGACY_BACKEND void emitter::emitInsLoadStoreOp(instruction ins, emitAttr attr, regNumber dataReg, GenTreeIndir* indir) { GenTree* addr = indir->Addr(); GenTree* data = indir->gtOp.gtOp2; 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) { regNumber tmpReg = indir->GetSingleTempReg(); if (emitIns_valid_imm_for_add(offset, INS_FLAGS_DONT_CARE)) { 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_FLAGS_DONT_CARE, 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, attr, 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, attr, dataReg, tmpReg, index->gtRegNum, lsl, INS_FLAGS_DONT_CARE, INS_OPTS_LSL); } } else // (offset == 0) { if (lsl > 0) { // Then load/store dataReg from/to [memBase + index*scale] emitIns_R_R_R_I(ins, attr, dataReg, memBase->gtRegNum, index->gtRegNum, lsl, INS_FLAGS_DONT_CARE, INS_OPTS_LSL); } else // no scale { // Then load/store dataReg from/to [memBase + index] emitIns_R_R_R(ins, attr, dataReg, memBase->gtRegNum, index->gtRegNum); } } } else // no Index { if (emitIns_valid_imm_for_ldst_offset(offset, attr)) { // Then load/store dataReg from/to [memBase + offset] emitIns_R_R_I(ins, attr, dataReg, memBase->gtRegNum, offset); } else { // We require a tmpReg to hold the offset regNumber tmpReg = indir->GetSingleTempReg(); // 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, attr, dataReg, memBase->gtRegNum, tmpReg); } } } else { emitIns_R_R(ins, attr, dataReg, addr->gtRegNum); } } // 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; } } regNumber emitter::emitInsTernary(instruction ins, emitAttr attr, GenTree* dst, GenTree* src1, GenTree* src2) { // 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()); } } insFlags flags = INS_FLAGS_DONT_CARE; bool isMulOverflow = false; if (dst->gtOverflowEx()) { if ((ins == INS_add) || (ins == INS_adc) || (ins == INS_sub) || (ins == INS_sbc)) { flags = INS_FLAGS_SET; } else if (ins == INS_mul) { isMulOverflow = true; 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(), flags); } else { if (isMulOverflow) { regNumber extraReg = dst->GetSingleTempReg(); assert(extraReg != dst->gtRegNum); if ((dst->gtFlags & GTF_UNSIGNED) != 0) { // Compute 8 byte result from 4 byte by 4 byte multiplication. emitIns_R_R_R_R(INS_umull, EA_4BYTE, dst->gtRegNum, extraReg, src1->gtRegNum, src2->gtRegNum); // Overflow exists if the result's high word is non-zero. emitIns_R_I(INS_cmp, attr, extraReg, 0); } else { // Compute 8 byte result from 4 byte by 4 byte multiplication. emitIns_R_R_R_R(INS_smull, EA_4BYTE, dst->gtRegNum, extraReg, src1->gtRegNum, src2->gtRegNum); // Overflow exists if the result's high word is not merely a sign bit. emitIns_R_R_I(INS_cmp, attr, extraReg, dst->gtRegNum, 31, INS_FLAGS_DONT_CARE, INS_OPTS_ASR); } } else { // We can just do the arithmetic, setting the flags if needed. emitIns_R_R_R(ins, attr, dst->gtRegNum, src1->gtRegNum, src2->gtRegNum, flags); } } if (dst->gtOverflowEx()) { assert(!varTypeIsFloating(dst)); emitJumpKind jumpKind; if (dst->OperGet() == GT_MUL) { jumpKind = EJ_ne; } else { bool isUnsignedOverflow = ((dst->gtFlags & GTF_UNSIGNED) != 0); jumpKind = isUnsignedOverflow ? EJ_lo : EJ_vs; if (jumpKind == EJ_lo) { if ((dst->OperGet() != GT_SUB) && (dst->OperGet() != GT_ASG_SUB) && (dst->OperGet() != GT_SUB_HI)) { jumpKind = EJ_hs; } } } // Jump to the block which will throw the exception. codeGen->genJumpToThrowHlpBlk(jumpKind, SCK_OVERFLOW); } return dst->gtRegNum; } #endif // !LEGACY_BACKEND #endif // defined(_TARGET_ARM_)