//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "Backend.h" #undef MACRO #define MACRO(name, jnLayout, attrib, byte2, legalforms, opbyte, ...) legalforms, static const LegalInstrForms _InstrForms[] = { #include "MdOpCodes.h" }; static LegalForms LegalDstForms(IR::Instr * instr) { Assert(instr->IsLowered()); return _InstrForms[instr->m_opcode - (Js::OpCode::MDStart+1)].dst; } static LegalForms LegalSrcForms(IR::Instr * instr, uint opndNum) { Assert(instr->IsLowered()); return _InstrForms[instr->m_opcode - (Js::OpCode::MDStart+1)].src[opndNum-1]; } void LegalizeMD::LegalizeInstr(IR::Instr * instr, bool fPostRegAlloc) { if (!instr->IsLowered()) { AssertMsg(UNREACHED, "Unlowered instruction in m.d. legalizer"); return; } LegalizeDst(instr, fPostRegAlloc); LegalizeSrc(instr, instr->GetSrc1(), 1, fPostRegAlloc); LegalizeSrc(instr, instr->GetSrc2(), 2, fPostRegAlloc); } void LegalizeMD::LegalizeDst(IR::Instr * instr, bool fPostRegAlloc) { LegalForms forms = LegalDstForms(instr); IR::Opnd * opnd = instr->GetDst(); if (opnd == NULL) { #ifdef DBG // No legalization possible, just report error. if (forms != 0) { IllegalInstr(instr, _u("Expected dst opnd")); } #endif return; } switch (opnd->GetKind()) { case IR::OpndKindReg: #ifdef DBG // No legalization possible, just report error. if (!(forms & L_RegMask)) { IllegalInstr(instr, _u("Unexpected reg dst")); } #endif break; case IR::OpndKindMemRef: { // MemRefOpnd is a deference of the memory location. // So extract the location, load it to register, replace the MemRefOpnd with an IndirOpnd taking the // register as base, and fall through to legalize the IndirOpnd. intptr_t memLoc = opnd->AsMemRefOpnd()->GetMemLoc(); IR::RegOpnd *newReg = IR::RegOpnd::New(TyMachPtr, instr->m_func); if (fPostRegAlloc) { newReg->SetReg(SCRATCH_REG); } IR::Instr *newInstr = IR::Instr::New(Js::OpCode::LDIMM, newReg, IR::AddrOpnd::New(memLoc, opnd->AsMemRefOpnd()->GetAddrKind(), instr->m_func, true), instr->m_func); instr->InsertBefore(newInstr); LegalizeMD::LegalizeInstr(newInstr, fPostRegAlloc); IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(newReg, 0, opnd->GetType(), instr->m_func); opnd = instr->ReplaceDst(indirOpnd); } // FALL THROUGH case IR::OpndKindIndir: if (!(forms & L_IndirMask)) { instr = LegalizeStore(instr, forms, fPostRegAlloc); forms = LegalDstForms(instr); } LegalizeIndirOffset(instr, opnd->AsIndirOpnd(), forms, fPostRegAlloc); break; case IR::OpndKindSym: if (!(forms & L_SymMask)) { instr = LegalizeStore(instr, forms, fPostRegAlloc); forms = LegalDstForms(instr); } if (fPostRegAlloc) { // In order to legalize SymOffset we need to know final argument area, which is only available after lowerer. // So, don't legalize sym offset here, but it will be done as part of register allocator. LegalizeSymOffset(instr, opnd->AsSymOpnd(), forms, fPostRegAlloc); } break; default: AssertMsg(UNREACHED, "Unexpected dst opnd kind"); break; } } IR::Instr * LegalizeMD::LegalizeStore(IR::Instr *instr, LegalForms forms, bool fPostRegAlloc) { if (LowererMD::IsAssign(instr) && instr->GetSrc1()->IsRegOpnd()) { // We can just change this to a store in place. instr->m_opcode = LowererMD::GetStoreOp(instr->GetSrc1()->GetType()); } else { // Sink the mem opnd. The caller will verify the offset. // We don't expect to hit this point after register allocation, because we // can't guarantee that the instruction will be legal. Assert(!fPostRegAlloc); instr = instr->SinkDst(LowererMD::GetStoreOp(instr->GetDst()->GetType()), RegNOREG); } return instr; } void LegalizeMD::LegalizeSrc(IR::Instr * instr, IR::Opnd * opnd, uint opndNum, bool fPostRegAlloc) { LegalForms forms = LegalSrcForms(instr, opndNum); if (opnd == NULL) { #ifdef DBG // No legalization possible, just report error. if (forms != 0) { IllegalInstr(instr, _u("Expected src %d opnd"), opndNum); } #endif return; } switch (opnd->GetKind()) { case IR::OpndKindReg: // No legalization possible, just report error. #ifdef DBG if (!(forms & L_RegMask)) { IllegalInstr(instr, _u("Unexpected reg as src%d opnd"), opndNum); } #endif break; case IR::OpndKindAddr: case IR::OpndKindHelperCall: case IR::OpndKindIntConst: LegalizeImmed(instr, opnd, opndNum, opnd->GetImmediateValueAsInt32(instr->m_func), forms, fPostRegAlloc); break; case IR::OpndKindLabel: LegalizeLabelOpnd(instr, opnd, opndNum, fPostRegAlloc); break; case IR::OpndKindMemRef: { // MemRefOpnd is a deference of the memory location. // So extract the location, load it to register, replace the MemRefOpnd with an IndirOpnd taking the // register as base, and fall through to legalize the IndirOpnd. intptr_t memLoc = opnd->AsMemRefOpnd()->GetMemLoc(); IR::RegOpnd *newReg = IR::RegOpnd::New(TyMachPtr, instr->m_func); if (fPostRegAlloc) { newReg->SetReg(SCRATCH_REG); } IR::Instr *newInstr = IR::Instr::New(Js::OpCode::LDIMM, newReg, IR::AddrOpnd::New(memLoc, IR::AddrOpndKindDynamicMisc, instr->m_func), instr->m_func); instr->InsertBefore(newInstr); LegalizeMD::LegalizeInstr(newInstr, fPostRegAlloc); IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(newReg, 0, opnd->GetType(), instr->m_func); if (opndNum == 1) { opnd = instr->ReplaceSrc1(indirOpnd); } else { opnd = instr->ReplaceSrc2(indirOpnd); } } // FALL THROUGH case IR::OpndKindIndir: if (!(forms & L_IndirMask)) { instr = LegalizeLoad(instr, opndNum, forms, fPostRegAlloc); forms = LegalSrcForms(instr, 1); } LegalizeIndirOffset(instr, opnd->AsIndirOpnd(), forms, fPostRegAlloc); break; case IR::OpndKindSym: if (!(forms & L_SymMask)) { instr = LegalizeLoad(instr, opndNum, forms, fPostRegAlloc); forms = LegalSrcForms(instr, 1); } if (fPostRegAlloc) { // In order to legalize SymOffset we need to know final argument area, which is only available after lowerer. // So, don't legalize sym offset here, but it will be done as part of register allocator. LegalizeSymOffset(instr, opnd->AsSymOpnd(), forms, fPostRegAlloc); } break; default: AssertMsg(UNREACHED, "Unexpected src opnd kind"); break; } } IR::Instr * LegalizeMD::LegalizeLoad(IR::Instr *instr, uint opndNum, LegalForms forms, bool fPostRegAlloc) { if (LowererMD::IsAssign(instr) && instr->GetDst()->IsRegOpnd()) { // We can just change this to a load in place. instr->m_opcode = LowererMD::GetLoadOp(instr->GetDst()->GetType()); } else { // Hoist the memory opnd. The caller will verify the offset. if (opndNum == 1) { AssertMsg(!fPostRegAlloc || instr->GetSrc1()->GetType() == TyMachReg, "Post RegAlloc other types disallowed"); instr = instr->HoistSrc1(LowererMD::GetLoadOp(instr->GetSrc1()->GetType()), fPostRegAlloc ? SCRATCH_REG : RegNOREG); } else { AssertMsg(!fPostRegAlloc || instr->GetSrc2()->GetType() == TyMachReg, "Post RegAlloc other types disallowed"); instr = instr->HoistSrc2(LowererMD::GetLoadOp(instr->GetSrc2()->GetType()), fPostRegAlloc ? SCRATCH_REG : RegNOREG); } } return instr; } void LegalizeMD::LegalizeIndirOffset(IR::Instr * instr, IR::IndirOpnd * indirOpnd, LegalForms forms, bool fPostRegAlloc) { if (forms & (L_VIndirI11)) { // Vfp doesn't support register indirect operation LegalizeMD::LegalizeIndirOpndForVFP(instr, indirOpnd, fPostRegAlloc); return; } int32 offset = indirOpnd->GetOffset(); if (indirOpnd->GetIndexOpnd() != NULL && offset != 0) { IR::Instr *addInstr = instr->HoistIndirOffset(indirOpnd, fPostRegAlloc ? SCRATCH_REG : RegNOREG); LegalizeMD::LegalizeInstr(addInstr, fPostRegAlloc); return; } if (forms & (L_IndirI8 | L_IndirU12I8)) { if (IS_CONST_INT8(offset)) { return; } } if (forms & (L_IndirU12I8 | L_IndirU12)) { if (IS_CONST_UINT12(offset)) { return; } } // Offset is too large, so hoist it and replace it with an index, only valid for Thumb & Thumb2 IR::Instr *addInstr = instr->HoistIndirOffset(indirOpnd, fPostRegAlloc ? SCRATCH_REG : RegNOREG); LegalizeMD::LegalizeInstr(addInstr, fPostRegAlloc); } void LegalizeMD::LegalizeSymOffset( IR::Instr * instr, IR::SymOpnd * symOpnd, LegalForms forms, bool fPostRegAlloc) { AssertMsg(fPostRegAlloc, "LegalizeMD::LegalizeSymOffset can (and will) be called as part of register allocation. Can't call it as part of lowerer, as final argument area is not available yet."); RegNum baseReg; int32 offset; if (!symOpnd->m_sym->IsStackSym()) { return; } EncoderMD::BaseAndOffsetFromSym(symOpnd, &baseReg, &offset, instr->m_func->GetTopFunc()); if (forms & (L_SymU12I8 | L_SymU12)) { if (IS_CONST_UINT12(offset)) { return; } } if (forms & L_SymU12I8) { if (IS_CONST_INT8(offset)) { return; } } if (forms & (L_VSymI11)) { if (IS_CONST_UINT10((offset < 0? -offset: offset))) { return; } IR::RegOpnd *baseOpnd = IR::RegOpnd::New(NULL, baseReg, TyMachPtr, instr->m_func); IR::Instr* instrAdd = instr->HoistSymOffsetAsAdd(symOpnd, baseOpnd, offset, fPostRegAlloc ? SCRATCH_REG : RegNOREG); LegalizeMD::LegalizeInstr(instrAdd, fPostRegAlloc); return; } IR::Instr * newInstr; if (instr->m_opcode == Js::OpCode::LEA) { instr->m_opcode = Js::OpCode::ADD; instr->ReplaceSrc1(IR::RegOpnd::New(NULL, baseReg, TyMachPtr, instr->m_func)); instr->SetSrc2(IR::IntConstOpnd::New(offset, TyMachReg, instr->m_func)); newInstr = instr->HoistSrc2(Js::OpCode::LDIMM, fPostRegAlloc ? SCRATCH_REG : RegNOREG); LegalizeMD::LegalizeInstr(newInstr, fPostRegAlloc); LegalizeMD::LegalizeInstr(instr, fPostRegAlloc); } else { newInstr = instr->HoistSymOffset(symOpnd, baseReg, offset, fPostRegAlloc ? SCRATCH_REG : RegNOREG); LegalizeMD::LegalizeInstr(newInstr, fPostRegAlloc); } } void LegalizeMD::LegalizeImmed( IR::Instr * instr, IR::Opnd * opnd, uint opndNum, IntConstType immed, LegalForms forms, bool fPostRegAlloc) { if (!(((forms & L_ImmModC12) && EncoderMD::CanEncodeModConst12(immed)) || ((forms & L_ImmU5) && IS_CONST_UINT5(immed)) || ((forms & L_ImmU12) && IS_CONST_UINT12(immed)) || ((forms & L_ImmU16) && IS_CONST_UINT16(immed)))) { if (instr->m_opcode != Js::OpCode::LDIMM) { instr = LegalizeMD::GenerateLDIMM(instr, opndNum, fPostRegAlloc ? SCRATCH_REG : RegNOREG); } if (fPostRegAlloc) { LegalizeMD::LegalizeLDIMM(instr, immed); } } } void LegalizeMD::LegalizeLabelOpnd( IR::Instr * instr, IR::Opnd * opnd, uint opndNum, bool fPostRegAlloc) { if (instr->m_opcode != Js::OpCode::LDIMM) { instr = LegalizeMD::GenerateLDIMM(instr, opndNum, fPostRegAlloc ? SCRATCH_REG : RegNOREG); } if (fPostRegAlloc) { LegalizeMD::LegalizeLdLabel(instr, opnd); } } IR::Instr * LegalizeMD::GenerateLDIMM(IR::Instr * instr, uint opndNum, RegNum scratchReg) { if (LowererMD::IsAssign(instr) && instr->GetDst()->IsRegOpnd()) { instr->m_opcode = Js::OpCode::LDIMM; } else { if (opndNum == 1) { instr = instr->HoistSrc1(Js::OpCode::LDIMM, scratchReg); } else { instr = instr->HoistSrc2(Js::OpCode::LDIMM, scratchReg); } } return instr; } void LegalizeMD::LegalizeLDIMM(IR::Instr * instr, IntConstType immed) { // In case of inlined entry instruction, we don't know the offset till the encoding phase if (!instr->isInlineeEntryInstr) { if (IS_CONST_UINT16(immed) || EncoderMD::CanEncodeModConst12(immed)) { instr->m_opcode = Js::OpCode::MOV; return; } bool fDontEncode = Security::DontEncode(instr->GetSrc1()); IR::IntConstOpnd *src1 = IR::IntConstOpnd::New(immed & 0x0000FFFF, TyInt16, instr->m_func); IR::Instr * instrMov = IR::Instr::New(Js::OpCode::MOV, instr->GetDst(), src1, instr->m_func); instr->InsertBefore(instrMov); src1 = IR::IntConstOpnd::New((immed & 0xFFFF0000)>>16, TyInt16, instr->m_func); instr->ReplaceSrc1(src1); instr->m_opcode = Js::OpCode::MOVT; if (!fDontEncode) { LegalizeMD::ObfuscateLDIMM(instrMov, instr); } } else { Assert(Security::DontEncode(instr->GetSrc1())); IR::LabelInstr *label = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, false); instr->InsertBefore(label); Assert((immed & 0x0000000F) == immed); label->SetOffset(immed); IR::LabelOpnd *target = IR::LabelOpnd::New(label, instr->m_func); IR::Instr * instrMov = IR::Instr::New(Js::OpCode::MOVW, instr->GetDst(), target, instr->m_func); instr->InsertBefore(instrMov); instr->ReplaceSrc1(target); instr->m_opcode = Js::OpCode::MOVT; label->isInlineeEntryInstr = true; instr->isInlineeEntryInstr = false; } } void LegalizeMD::ObfuscateLDIMM(IR::Instr * instrMov, IR::Instr * instrMovt) { // Are security measures disabled? if (CONFIG_ISENABLED(Js::DebugFlag) || CONFIG_ISENABLED(Js::BenchmarkFlag) || PHASE_OFF(Js::EncodeConstantsPhase, instrMov->m_func->GetTopFunc()) ) { return; } UINT_PTR rand = Math::Rand(); // Use this random value as follows: // bits 0-3: reg to use in pre-LDIMM instr // bits 4: do/don't emit pre-LDIMM instr // bits 5-6: emit and/or/add/mov as pre-LDIMM instr // Similarly for bits 7-13 (mid-LDIMM) and 14-20 (post-LDIMM) RegNum targetReg = instrMov->GetDst()->AsRegOpnd()->GetReg(); LegalizeMD::EmitRandomNopBefore(instrMov, rand, targetReg); LegalizeMD::EmitRandomNopBefore(instrMovt, rand >> 7, targetReg); LegalizeMD::EmitRandomNopBefore(instrMovt->m_next, rand >> 14, targetReg); } void LegalizeMD::EmitRandomNopBefore(IR::Instr *insertInstr, UINT_PTR rand, RegNum targetReg) { // bits 0-3: reg to use in pre-LDIMM instr // bits 4: do/don't emit pre-LDIMM instr // bits 5-6: emit and/or/add/mov as pre-LDIMM instr if (!(rand & (1 << 4)) && !PHASE_FORCE(Js::EncodeConstantsPhase, insertInstr->m_func->GetTopFunc())) { return; } IR::Instr * instr; IR::RegOpnd * opnd1; IR::Opnd * opnd2 = NULL; Js::OpCode op = Js::OpCode::InvalidOpCode; RegNum regNum = (RegNum)((rand & ((1 << 4) - 1)) + RegR0); opnd1 = IR::RegOpnd::New(NULL, regNum, TyMachReg, insertInstr->m_func); if (regNum == RegSP || regNum == RegPC || regNum == targetReg) //skip sp & pc & the target reg { // ORR pc,pc,0 has unpredicted behavior. // AND sp,sp,sp has unpredicted behavior. // We avoid target reg to avoid pipeline stalls. // Less likely target reg will be RegR12 as we insert nops only for user defined constants and // RegR12 is mostly used for temporary data such as legalizer post regalloc. opnd1->SetReg(RegR12); } switch ((rand >> 5) & 3) { case 0: op = Js::OpCode::AND; opnd2 = opnd1; break; case 1: op = Js::OpCode::ORR; opnd2 = IR::IntConstOpnd::New(0, TyMachReg, insertInstr->m_func); break; case 2: op = Js::OpCode::ADD; opnd2 = IR::IntConstOpnd::New(0, TyMachReg, insertInstr->m_func); break; case 3: op = Js::OpCode::MOV; break; } instr = IR::Instr::New(op, opnd1, opnd1, insertInstr->m_func); if (opnd2) { instr->SetSrc2(opnd2); } insertInstr->InsertBefore(instr); } void LegalizeMD::LegalizeLdLabel(IR::Instr * instr, IR::Opnd * opnd) { Assert(instr->m_opcode == Js::OpCode::LDIMM); Assert(opnd->IsLabelOpnd()); IR::Instr * instrMov = IR::Instr::New(Js::OpCode::MOVW, instr->GetDst(), opnd, instr->m_func); instr->InsertBefore(instrMov); instr->m_opcode = Js::OpCode::MOVT; } bool LegalizeMD::LegalizeDirectBranch(IR::BranchInstr *branchInstr, uint32 branchOffset) { Assert(branchInstr->IsBranchInstr()); uint32 labelOffset = branchInstr->GetTarget()->GetOffset(); Assert(labelOffset); //Label offset must be set. int32 offset = labelOffset - branchOffset; //We should never run out of 24 bits which corresponds to +-16MB of code size. AssertMsg(IS_CONST_INT24(offset >> 1), "Cannot encode more that 16 MB offset"); if (LowererMD::IsUnconditionalBranch(branchInstr)) { return false; } if (IS_CONST_INT21(offset)) { return false; } // Convert a conditional branch which can only be +-1MB to unconditional branch which is +-16MB // Convert beq Label (where Label is long jump) to something like this // bne Fallback // b Label // Fallback: IR::LabelInstr *doneLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, branchInstr->m_func, false); IR::BranchInstr *newBranchInstr = IR::BranchInstr::New(branchInstr->m_opcode, doneLabelInstr, branchInstr->m_func); LowererMD::InvertBranch(newBranchInstr); branchInstr->InsertBefore(newBranchInstr); branchInstr->InsertAfter(doneLabelInstr); branchInstr->m_opcode = Js::OpCode::B; return true; } void LegalizeMD::LegalizeIndirOpndForVFP(IR::Instr* insertInstr, IR::IndirOpnd *indirOpnd, bool fPostRegAlloc) { IR::RegOpnd *baseOpnd = indirOpnd->GetBaseOpnd(); int32 offset = indirOpnd->GetOffset(); IR::RegOpnd *indexOpnd = indirOpnd->UnlinkIndexOpnd(); //Clears index operand byte scale = indirOpnd->GetScale(); IR::Instr *instr = NULL; if (indexOpnd) { if (scale > 0) { // There is no support for ADD instruction with barrel shifter in encoder, hence add an explicit instruction to left shift the index operand // Reason is this requires 4 operand in IR and there is no support for this yet. // If we encounter more such scenarios, its better to solve the root cause. // Also VSTR & VLDR don't take index operand as parameter IR::RegOpnd* newIndexOpnd = IR::RegOpnd::New(indexOpnd->GetType(), insertInstr->m_func); instr = IR::Instr::New(Js::OpCode::LSL, newIndexOpnd, indexOpnd, IR::IntConstOpnd::New(scale, TyMachReg, insertInstr->m_func), insertInstr->m_func); insertInstr->InsertBefore(instr); indirOpnd->SetScale(0); //Clears scale indexOpnd = newIndexOpnd; } insertInstr->HoistIndirIndexOpndAsAdd(indirOpnd, baseOpnd, indexOpnd, fPostRegAlloc? SCRATCH_REG : RegNOREG); } if (IS_CONST_UINT10((offset < 0? -offset: offset))) { return; } IR::Instr* instrAdd = insertInstr->HoistIndirOffsetAsAdd(indirOpnd, indirOpnd->GetBaseOpnd(), offset, fPostRegAlloc ? SCRATCH_REG : RegNOREG); LegalizeMD::LegalizeInstr(instrAdd, fPostRegAlloc); } #ifdef DBG void LegalizeMD::IllegalInstr(IR::Instr * instr, const char16 * msg, ...) { va_list argptr; va_start(argptr, msg); Output::Print(_u("Illegal instruction: ")); instr->Dump(); Output::Print(msg, argptr); Assert(UNREACHED); } #endif