| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft Corporation and contributors. All rights reserved. |
| // Copyright (c) ChakraCore Project Contributors. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| |
| #include "Backend.h" |
| #include "Language/JavascriptFunctionArgIndex.h" |
| |
| const Js::OpCode LowererMD::MDUncondBranchOpcode = Js::OpCode::B; |
| const Js::OpCode LowererMD::MDMultiBranchOpcode = Js::OpCode::BR; |
| const Js::OpCode LowererMD::MDTestOpcode = Js::OpCode::TST; |
| const Js::OpCode LowererMD::MDOrOpcode = Js::OpCode::ORR; |
| const Js::OpCode LowererMD::MDXorOpcode = Js::OpCode::EOR; |
| const Js::OpCode LowererMD::MDOverflowBranchOpcode = Js::OpCode::BVS; |
| const Js::OpCode LowererMD::MDNotOverflowBranchOpcode = Js::OpCode::BVC; |
| const Js::OpCode LowererMD::MDConvertFloat32ToFloat64Opcode = Js::OpCode::FCVT; |
| const Js::OpCode LowererMD::MDConvertFloat64ToFloat32Opcode = Js::OpCode::FCVT; |
| const Js::OpCode LowererMD::MDCallOpcode = Js::OpCode::Call; |
| const Js::OpCode LowererMD::MDImulOpcode = Js::OpCode::MUL; |
| const Js::OpCode LowererMD::MDLea = Js::OpCode::LEA; |
| const Js::OpCode LowererMD::MDSpecBlockNEOpcode = Js::OpCode::CSELNE; |
| const Js::OpCode LowererMD::MDSpecBlockFNEOpcode = Js::OpCode::FCSELNE; |
| |
| template<typename T> |
| inline void Swap(T& x, T& y) |
| { |
| T temp = x; |
| x = y; |
| y = temp; |
| } |
| |
| // Static utility fn() |
| // |
| bool |
| LowererMD::IsAssign(const IR::Instr *instr) |
| { |
| return (instr->m_opcode == Js::OpCode::MOV || |
| instr->m_opcode == Js::OpCode::FMOV || |
| instr->m_opcode == Js::OpCode::LDIMM || |
| instr->m_opcode == Js::OpCode::LDR || |
| instr->m_opcode == Js::OpCode::LDRS || |
| instr->m_opcode == Js::OpCode::FLDR || |
| instr->m_opcode == Js::OpCode::STR || |
| instr->m_opcode == Js::OpCode::FSTR); |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::IsCall |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::IsCall(const IR::Instr *instr) |
| { |
| return (instr->m_opcode == Js::OpCode::BL || |
| instr->m_opcode == Js::OpCode::BLR); |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::IsIndirectBranch |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::IsIndirectBranch(const IR::Instr *instr) |
| { |
| return (instr->m_opcode == Js::OpCode::BR); |
| } |
| |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::IsUnconditionalBranch |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::IsUnconditionalBranch(const IR::Instr *instr) |
| { |
| return (instr->m_opcode == Js::OpCode::B || |
| instr->m_opcode == Js::OpCode::BR); |
| } |
| |
| bool |
| LowererMD::IsReturnInstr(const IR::Instr *instr) |
| { |
| return instr->m_opcode == Js::OpCode::RET; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::InvertBranch |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| void |
| LowererMD::InvertBranch(IR::BranchInstr *branchInstr) |
| { |
| switch (branchInstr->m_opcode) |
| { |
| case Js::OpCode::BEQ: |
| branchInstr->m_opcode = Js::OpCode::BNE; |
| break; |
| case Js::OpCode::BNE: |
| branchInstr->m_opcode = Js::OpCode::BEQ; |
| break; |
| case Js::OpCode::BGE: |
| branchInstr->m_opcode = Js::OpCode::BLT; |
| break; |
| case Js::OpCode::BGT: |
| branchInstr->m_opcode = Js::OpCode::BLE; |
| break; |
| case Js::OpCode::BLT: |
| branchInstr->m_opcode = Js::OpCode::BGE; |
| break; |
| case Js::OpCode::BLE: |
| branchInstr->m_opcode = Js::OpCode::BGT; |
| break; |
| case Js::OpCode::BCS: |
| branchInstr->m_opcode = Js::OpCode::BCC; |
| break; |
| case Js::OpCode::BCC: |
| branchInstr->m_opcode = Js::OpCode::BCS; |
| break; |
| case Js::OpCode::BMI: |
| branchInstr->m_opcode = Js::OpCode::BPL; |
| break; |
| case Js::OpCode::BPL: |
| branchInstr->m_opcode = Js::OpCode::BMI; |
| break; |
| case Js::OpCode::BVS: |
| branchInstr->m_opcode = Js::OpCode::BVC; |
| break; |
| case Js::OpCode::BVC: |
| branchInstr->m_opcode = Js::OpCode::BVS; |
| break; |
| case Js::OpCode::BLS: |
| branchInstr->m_opcode = Js::OpCode::BHI; |
| break; |
| case Js::OpCode::BHI: |
| branchInstr->m_opcode = Js::OpCode::BLS; |
| break; |
| case Js::OpCode::CBZ: |
| branchInstr->m_opcode = Js::OpCode::CBNZ; |
| break; |
| case Js::OpCode::CBNZ: |
| branchInstr->m_opcode = Js::OpCode::CBZ; |
| break; |
| case Js::OpCode::TBZ: |
| branchInstr->m_opcode = Js::OpCode::TBNZ; |
| break; |
| case Js::OpCode::TBNZ: |
| branchInstr->m_opcode = Js::OpCode::TBZ; |
| break; |
| |
| default: |
| AssertMsg(UNREACHED, "B missing in InvertBranch()"); |
| } |
| |
| } |
| |
| Js::OpCode |
| LowererMD::MDConvertFloat64ToInt32Opcode(const RoundMode roundMode) |
| { |
| switch (roundMode) |
| { |
| case RoundModeTowardZero: |
| return Js::OpCode::FCVTZ; |
| case RoundModeTowardInteger: |
| return Js::OpCode::Nop; |
| case RoundModeHalfToEven: |
| return Js::OpCode::FCVTN; |
| default: |
| AssertMsg(0, "RoundMode has no MD mapping."); |
| return Js::OpCode::Nop; |
| } |
| } |
| |
| // GenerateMemRef: Return an opnd that can be used to access the given address. |
| // ARM can't encode direct accesses to physical addresses, so put the address in a register |
| // and return an indir. (This facilitates re-use of the loaded address without having to re-load it.) |
| IR::Opnd * |
| LowererMD::GenerateMemRef(intptr_t addr, IRType type, IR::Instr *instr, bool dontEncode) |
| { |
| IR::RegOpnd *baseOpnd = IR::RegOpnd::New(TyMachReg, this->m_func); |
| IR::AddrOpnd *addrOpnd = IR::AddrOpnd::New(addr, IR::AddrOpndKindDynamicMisc, this->m_func, dontEncode); |
| Lowerer::InsertMove(baseOpnd, addrOpnd, instr); |
| |
| return IR::IndirOpnd::New(baseOpnd, 0, type, this->m_func); |
| } |
| |
| void |
| LowererMD::FlipHelperCallArgsOrder() |
| { |
| int left = 0; |
| int right = helperCallArgsCount - 1; |
| while (left < right) |
| { |
| IR::Opnd *tempOpnd = helperCallArgs[left]; |
| helperCallArgs[left] = helperCallArgs[right]; |
| helperCallArgs[right] = tempOpnd; |
| |
| left++; |
| right--; |
| } |
| } |
| |
| IR::Instr * |
| LowererMD::LowerCallHelper(IR::Instr *instrCall) |
| { |
| IR::Opnd *argOpnd = instrCall->UnlinkSrc2(); |
| IR::Instr *prevInstr = instrCall; |
| IR::JnHelperMethod helperMethod = instrCall->GetSrc1()->AsHelperCallOpnd()->m_fnHelper; |
| instrCall->FreeSrc1(); |
| |
| while (argOpnd) |
| { |
| Assert(argOpnd->IsRegOpnd()); |
| IR::RegOpnd *regArg = argOpnd->AsRegOpnd(); |
| |
| Assert(regArg->m_sym->m_isSingleDef); |
| IR::Instr *instrArg = regArg->m_sym->m_instrDef; |
| |
| Assert(instrArg->m_opcode == Js::OpCode::ArgOut_A || instrArg->m_opcode == Js::OpCode::ExtendArg_A && |
| ( |
| helperMethod == IR::JnHelperMethod::HelperOP_InitCachedScope || |
| helperMethod == IR::JnHelperMethod::HelperScrFunc_OP_NewScFuncHomeObj || |
| helperMethod == IR::JnHelperMethod::HelperScrFunc_OP_NewScGenFuncHomeObj || |
| helperMethod == IR::JnHelperMethod::HelperRestify || |
| helperMethod == IR::JnHelperMethod::HelperStPropIdArrFromVar |
| )); |
| prevInstr = this->LoadHelperArgument(prevInstr, instrArg->GetSrc1()); |
| |
| argOpnd = instrArg->GetSrc2(); |
| |
| if (instrArg->m_opcode == Js::OpCode::ArgOut_A) |
| { |
| instrArg->UnlinkSrc1(); |
| if (argOpnd) |
| { |
| instrArg->UnlinkSrc2(); |
| } |
| regArg->Free(this->m_func); |
| instrArg->Remove(); |
| } |
| else if (instrArg->m_opcode == Js::OpCode::ExtendArg_A) |
| { |
| if (instrArg->GetSrc1()->IsRegOpnd()) |
| { |
| m_lowerer->addToLiveOnBackEdgeSyms->Set(instrArg->GetSrc1()->AsRegOpnd()->GetStackSym()->m_id); |
| } |
| } |
| } |
| |
| switch (helperMethod) |
| { |
| case IR::JnHelperMethod::HelperScrFunc_OP_NewScFuncHomeObj: |
| case IR::JnHelperMethod::HelperScrFunc_OP_NewScGenFuncHomeObj: |
| break; |
| default: |
| prevInstr = m_lowerer->LoadScriptContext(prevInstr); |
| break; |
| } |
| this->FlipHelperCallArgsOrder(); |
| return this->ChangeToHelperCall(instrCall, helperMethod); |
| } |
| |
| // Lower a call: May be either helper or native JS call. Just set the opcode, and |
| // put the result into the return register. (No stack adjustment required.) |
| IR::Instr * |
| LowererMD::LowerCall(IR::Instr * callInstr, Js::ArgSlot argCount) |
| { |
| IR::Instr *retInstr = callInstr; |
| IR::Opnd *targetOpnd = callInstr->GetSrc1(); |
| AssertMsg(targetOpnd, "Call without a target?"); |
| |
| // This is required here due to calls created during lowering |
| callInstr->m_func->SetHasCallsOnSelfAndParents(); |
| |
| if (targetOpnd->IsRegOpnd()) |
| { |
| // Indirect call |
| callInstr->m_opcode = Js::OpCode::BLR; |
| } |
| else |
| { |
| AssertMsg(targetOpnd->IsHelperCallOpnd(), "Why haven't we loaded the call target?"); |
| // Direct call |
| // |
| // load the address into a register because we cannot directly access more than 24 bit constants |
| // in BL instruction. Non helper call methods will already be accessed indirectly. |
| // |
| // Skip this for bailout calls. The register allocator will lower that as appropriate, without affecting spill choices. |
| |
| if (!callInstr->HasBailOutInfo()) |
| { |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(nullptr, RegLR, TyMachPtr, this->m_func); |
| IR::Instr *movInstr = IR::Instr::New(Js::OpCode::LDIMM, regOpnd, callInstr->UnlinkSrc1(), this->m_func); |
| regOpnd->m_isCallArg = true; |
| callInstr->SetSrc1(regOpnd); |
| callInstr->InsertBefore(movInstr); |
| } |
| callInstr->m_opcode = Js::OpCode::BLR; |
| } |
| |
| IR::Opnd *dstOpnd = callInstr->GetDst(); |
| if (dstOpnd) |
| { |
| Js::OpCode assignOp; |
| RegNum returnReg; |
| |
| if(dstOpnd->IsFloat64()) |
| { |
| assignOp = Js::OpCode::FMOV; |
| returnReg = RETURN_DBL_REG; |
| } |
| else |
| { |
| assignOp = Js::OpCode::MOV; |
| returnReg = RETURN_REG; |
| |
| if (callInstr->GetSrc1()->IsHelperCallOpnd()) |
| { |
| // Truncate the result of a conversion to 32-bit int, because the C++ code doesn't. |
| IR::HelperCallOpnd *helperOpnd = callInstr->GetSrc1()->AsHelperCallOpnd(); |
| if (helperOpnd->m_fnHelper == IR::HelperConv_ToInt32 || |
| helperOpnd->m_fnHelper == IR::HelperConv_ToInt32_Full || |
| helperOpnd->m_fnHelper == IR::HelperConv_ToInt32Core || |
| helperOpnd->m_fnHelper == IR::HelperConv_ToUInt32 || |
| helperOpnd->m_fnHelper == IR::HelperConv_ToUInt32_Full || |
| helperOpnd->m_fnHelper == IR::HelperConv_ToUInt32Core) |
| { |
| assignOp = Js::OpCode::MOV_TRUNC; |
| } |
| } |
| } |
| |
| IR::Instr * movInstr = callInstr->SinkDst(assignOp); |
| |
| callInstr->GetDst()->AsRegOpnd()->SetReg(returnReg); |
| movInstr->GetSrc1()->AsRegOpnd()->SetReg(returnReg); |
| |
| retInstr = movInstr; |
| |
| Legalize(retInstr); |
| } |
| |
| // |
| // assign the arguments to appropriate positions |
| // |
| |
| AssertMsg(this->helperCallArgsCount >= 0, "Fatal. helper call arguments ought to be positive"); |
| AssertMsg(this->helperCallArgsCount <= MaxArgumentsToHelper, "Too many helper call arguments"); |
| |
| uint16 argsLeft = this->helperCallArgsCount; |
| uint16 doubleArgsLeft = this->helperCallDoubleArgsCount; |
| uint16 intArgsLeft = argsLeft - doubleArgsLeft; |
| |
| while(argsLeft > 0) |
| { |
| IR::Opnd *helperArgOpnd = this->helperCallArgs[this->helperCallArgsCount - argsLeft]; |
| IR::Opnd * opndParam = nullptr; |
| |
| if (helperArgOpnd->IsFloat()) |
| { |
| opndParam = this->GetOpndForArgSlot(doubleArgsLeft - 1, helperArgOpnd); |
| AssertMsg(opndParam->IsRegOpnd(), "NYI for other kind of operands"); |
| --doubleArgsLeft; |
| } |
| else |
| { |
| opndParam = this->GetOpndForArgSlot(intArgsLeft - 1, helperArgOpnd); |
| --intArgsLeft; |
| } |
| Lowerer::InsertMove(opndParam, helperArgOpnd, callInstr); |
| --argsLeft; |
| } |
| Assert(doubleArgsLeft == 0 && intArgsLeft == 0 && argsLeft == 0); |
| |
| // We're done with the args (if any) now, so clear the param location state. |
| this->FinishArgLowering(); |
| |
| return retInstr; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadDynamicArgument(IR::Instr *instr, uint argNumber) |
| { |
| Assert(instr->m_opcode == Js::OpCode::ArgOut_A_Dynamic); |
| Assert(instr->GetSrc2() == nullptr); |
| |
| IR::Opnd* dst = GetOpndForArgSlot((Js::ArgSlot) (argNumber - 1)); |
| instr->SetDst(dst); |
| instr->m_opcode = Js::OpCode::MOV; |
| LegalizeMD::LegalizeInstr(instr); |
| |
| return instr; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadDynamicArgumentUsingLength(IR::Instr *instr) |
| { |
| Assert(instr->m_opcode == Js::OpCode::ArgOut_A_Dynamic); |
| IR::RegOpnd* src2 = instr->UnlinkSrc2()->AsRegOpnd(); |
| |
| // We register store the first INT_ARG_REG_COUNT - 3 parameters, since the first 3 register parameters are taken by function object, callinfo, and this pointer |
| IR::Instr *add = IR::Instr::New(Js::OpCode::SUB, IR::RegOpnd::New(src2->GetType(), this->m_func), src2, IR::IntConstOpnd::New(INT_ARG_REG_COUNT - 3, TyInt8, this->m_func), this->m_func); |
| instr->InsertBefore(add); |
| LegalizeMD::LegalizeInstr(add); |
| //We need store nth actuals, so stack location is after function object, callinfo & this pointer |
| IR::RegOpnd *stackPointer = IR::RegOpnd::New(nullptr, GetRegStackPointer(), TyMachReg, this->m_func); |
| IR::IndirOpnd *actualsLocation = IR::IndirOpnd::New(stackPointer, add->GetDst()->AsRegOpnd(), GetDefaultIndirScale(), TyMachReg, this->m_func); |
| instr->SetDst(actualsLocation); |
| instr->m_opcode = Js::OpCode::LDR; |
| LegalizeMD::LegalizeInstr(instr); |
| |
| return instr; |
| } |
| |
| void |
| LowererMD::SetMaxArgSlots(Js::ArgSlot actualCount /*including this*/) |
| { |
| Js::ArgSlot offset = 3;//For function object & callInfo & this |
| if (this->m_func->m_argSlotsForFunctionsCalled < (uint32) (actualCount + offset)) |
| { |
| this->m_func->m_argSlotsForFunctionsCalled = (uint32)(actualCount + offset); |
| } |
| return; |
| } |
| |
| void |
| LowererMD::GenerateMemInit(IR::RegOpnd * opnd, int32 offset, size_t value, IR::Instr * insertBeforeInstr, bool isZeroed) |
| { |
| m_lowerer->GenerateMemInit(opnd, offset, (uint32)value, insertBeforeInstr, isZeroed); |
| } |
| |
| IR::Instr * |
| LowererMD::LowerCallIDynamic(IR::Instr *callInstr, IR::Instr*saveThisArgOutInstr, IR::Opnd *argsLength, ushort callFlags, IR::Instr * insertBeforeInstrForCFG) |
| { |
| callInstr->InsertBefore(saveThisArgOutInstr); //Move this Argout next to call; |
| this->LoadDynamicArgument(saveThisArgOutInstr, 3); //this pointer is the 3rd argument |
| |
| //callInfo |
| if (callInstr->m_func->IsInlinee()) |
| { |
| Assert(argsLength->AsIntConstOpnd()->GetValue() == callInstr->m_func->actualCount); |
| this->SetMaxArgSlots((Js::ArgSlot)callInstr->m_func->actualCount); |
| } |
| else |
| { |
| callInstr->InsertBefore(IR::Instr::New(Js::OpCode::ADD, argsLength, argsLength, IR::IntConstOpnd::New(1, TyInt8, this->m_func), this->m_func)); |
| this->SetMaxArgSlots(Js::InlineeCallInfo::MaxInlineeArgoutCount); |
| } |
| Lowerer::InsertMove( this->GetOpndForArgSlot(1), argsLength, callInstr); |
| |
| IR::RegOpnd *funcObjOpnd = callInstr->UnlinkSrc1()->AsRegOpnd(); |
| GeneratePreCall(callInstr, funcObjOpnd, insertBeforeInstrForCFG); |
| |
| // functionOpnd is the first argument. |
| IR::Opnd * opndParam = this->GetOpndForArgSlot(0); |
| Lowerer::InsertMove(opndParam, funcObjOpnd, callInstr); |
| return this->LowerCall(callInstr, 0); |
| } |
| |
| void |
| LowererMD::GenerateFunctionObjectTest(IR::Instr * callInstr, IR::RegOpnd *functionObjOpnd, bool isHelper, IR::LabelInstr* continueAfterExLabel /* = nullptr */) |
| { |
| AssertMsg(!m_func->IsJitInDebugMode() || continueAfterExLabel, "When jit is in debug mode, continueAfterExLabel must be provided otherwise continue after exception may cause AV."); |
| |
| // Need check and error if we are calling a tagged int. |
| if (!functionObjOpnd->IsNotTaggedValue()) |
| { |
| IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| if (this->GenerateObjectTest(functionObjOpnd, callInstr, helperLabel)) |
| { |
| |
| IR::LabelInstr * callLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, isHelper); |
| IR::Instr * instr = IR::BranchInstr::New(Js::OpCode::B, callLabel, this->m_func); |
| callInstr->InsertBefore(instr); |
| |
| callInstr->InsertBefore(helperLabel); |
| callInstr->InsertBefore(callLabel); |
| |
| this->m_lowerer->GenerateRuntimeError(callLabel, JSERR_NeedFunction); |
| |
| if (continueAfterExLabel) |
| { |
| // Under debugger the RuntimeError (exception) can be ignored, generate branch to jmp to safe place |
| // (which would normally be debugger bailout check). |
| IR::BranchInstr* continueAfterEx = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, continueAfterExLabel, this->m_func); |
| callLabel->InsertBefore(continueAfterEx); |
| } |
| } |
| } |
| } |
| |
| IR::Instr* |
| LowererMD::GeneratePreCall(IR::Instr * callInstr, IR::Opnd *functionObjOpnd, IR::Instr * insertBeforeInstrForCFGCheck) |
| { |
| if (insertBeforeInstrForCFGCheck == nullptr) |
| { |
| insertBeforeInstrForCFGCheck = callInstr; |
| } |
| |
| IR::RegOpnd * functionTypeRegOpnd = nullptr; |
| IR::IndirOpnd * entryPointIndirOpnd = nullptr; |
| |
| // For calls to fixed functions we load the function's type directly from the known (hard-coded) function object address. |
| // For other calls, we need to load it from the function object stored in a register operand. |
| if (functionObjOpnd->IsAddrOpnd() && functionObjOpnd->AsAddrOpnd()->m_isFunction) |
| { |
| functionTypeRegOpnd = this->m_lowerer->GenerateFunctionTypeFromFixedFunctionObject(insertBeforeInstrForCFGCheck, functionObjOpnd); |
| } |
| else if (functionObjOpnd->IsRegOpnd()) |
| { |
| AssertMsg(functionObjOpnd->AsRegOpnd()->m_sym->IsStackSym(), "Expected call target to be stackSym"); |
| |
| functionTypeRegOpnd = IR::RegOpnd::New(TyMachReg, this->m_func); |
| |
| IR::IndirOpnd* functionTypeIndirOpnd = IR::IndirOpnd::New(functionObjOpnd->AsRegOpnd(), |
| Js::RecyclableObject::GetOffsetOfType(), TyMachReg, this->m_func); |
| Lowerer::InsertMove(functionTypeRegOpnd, functionTypeIndirOpnd, insertBeforeInstrForCFGCheck); |
| } |
| else |
| { |
| AnalysisAssertMsg(false, "Unexpected call target operand type."); |
| } |
| entryPointIndirOpnd = IR::IndirOpnd::New(functionTypeRegOpnd, Js::Type::GetOffsetOfEntryPoint(), TyMachPtr, m_func); |
| |
| IR::RegOpnd *entryPointRegOpnd = functionTypeRegOpnd; |
| entryPointRegOpnd->m_isCallArg = true; |
| |
| IR::Instr * stackParamInsert = Lowerer::InsertMove(entryPointRegOpnd, entryPointIndirOpnd, insertBeforeInstrForCFGCheck); |
| |
| // targetAddrOpnd is the address we'll call. |
| callInstr->SetSrc1(entryPointRegOpnd); |
| |
| #if defined(_CONTROL_FLOW_GUARD) |
| // verify that the call target is valid (CFG Check) |
| if (!PHASE_OFF(Js::CFGInJitPhase, this->m_func)) |
| { |
| this->GenerateCFGCheck(entryPointRegOpnd, insertBeforeInstrForCFGCheck); |
| } |
| #endif |
| |
| return stackParamInsert; |
| } |
| |
| IR::Instr * |
| LowererMD::LowerCallI(IR::Instr * callInstr, ushort callFlags, bool isHelper, IR::Instr * insertBeforeInstrForCFG) |
| { |
| // Indirect call using JS calling convention: |
| // R0 = callee func object |
| // R1 = callinfo |
| // R2 = arg0 ("this") |
| // R3 = arg1 |
| // [sp] = arg2 |
| // etc. |
| |
| // First load the target address. Note that we want to wind up with this: |
| // ... |
| // [sp+4] = arg3 |
| // [sp] = arg2 |
| // load target addr from func obj |
| // R3 = arg1 |
| // ... |
| // R0 = func obj |
| // BLX target addr |
| // This way the register containing the target addr interferes with the param regs |
| // only, not the regs we use to store params to the stack. |
| |
| // We're sinking the stores of stack params so that the call sequence is contiguous. |
| // This is required by nested calls, since each call will re-use the same stack slots. |
| // But if there is no nesting, stack params can be stored as soon as they're computed. |
| |
| IR::Opnd * functionObjOpnd = callInstr->UnlinkSrc1(); |
| IR::Instr * insertBeforeInstrForCFGCheck = callInstr; |
| |
| // If this is a call for new, we already pass the function operand through NewScObject, |
| // which checks if the function operand is a real function or not, don't need to add a check again. |
| // If this is a call to a fixed function, we've already verified that the target is, indeed, a function. |
| if (callInstr->m_opcode != Js::OpCode::CallIFixed && !(callFlags & Js::CallFlags_New)) |
| { |
| Assert(functionObjOpnd->IsRegOpnd()); |
| IR::LabelInstr* continueAfterExLabel = Lowerer::InsertContinueAfterExceptionLabelForDebugger(m_func, callInstr, isHelper); |
| GenerateFunctionObjectTest(callInstr, functionObjOpnd->AsRegOpnd(), isHelper, continueAfterExLabel); |
| } |
| else if (insertBeforeInstrForCFG != nullptr) |
| { |
| // RegNum dstReg = insertBeforeInstrForCFG->GetDst()->AsRegOpnd()->GetReg(); |
| // AssertMsg(dstReg == RegArg2 || dstReg == RegArg3, "NewScObject should insert the first Argument in RegArg2/RegArg3 only based on Spread call or not."); |
| insertBeforeInstrForCFGCheck = insertBeforeInstrForCFG; |
| } |
| |
| IR::Instr * stackParamInsert = GeneratePreCall(callInstr, functionObjOpnd, insertBeforeInstrForCFGCheck); |
| |
| // We need to get the calculated CallInfo in SimpleJit because that doesn't include any changes for stack alignment |
| IR::IntConstOpnd *callInfo; |
| int32 argCount = this->LowerCallArgs(callInstr, stackParamInsert, callFlags, 1, &callInfo); |
| |
| // functionObjOpnd is the first argument. |
| IR::Opnd * opndParam = this->GetOpndForArgSlot(0); |
| Lowerer::InsertMove(opndParam, functionObjOpnd, callInstr); |
| |
| IR::Opnd *const finalDst = callInstr->GetDst(); |
| |
| // Finally, lower the call instruction itself. |
| IR::Instr* ret = this->LowerCall(callInstr, (Js::ArgSlot)argCount); |
| |
| IR::AutoReuseOpnd autoReuseSavedFunctionObjOpnd; |
| if (callInstr->IsJitProfilingInstr()) |
| { |
| Assert(callInstr->m_func->IsSimpleJit()); |
| Assert(!CONFIG_FLAG(NewSimpleJit)); |
| |
| if(finalDst && |
| finalDst->IsRegOpnd() && |
| functionObjOpnd->IsRegOpnd() && |
| finalDst->AsRegOpnd()->m_sym == functionObjOpnd->AsRegOpnd()->m_sym) |
| { |
| // The function object sym is going to be overwritten, so save it in a temp for profiling |
| IR::RegOpnd *const savedFunctionObjOpnd = IR::RegOpnd::New(functionObjOpnd->GetType(), callInstr->m_func); |
| autoReuseSavedFunctionObjOpnd.Initialize(savedFunctionObjOpnd, callInstr->m_func); |
| Lowerer::InsertMove(savedFunctionObjOpnd, functionObjOpnd, callInstr->m_next); |
| functionObjOpnd = savedFunctionObjOpnd; |
| } |
| |
| auto instr = callInstr->AsJitProfilingInstr(); |
| ret = this->m_lowerer->GenerateCallProfiling( |
| instr->profileId, |
| instr->inlineCacheIndex, |
| instr->GetDst(), |
| functionObjOpnd, |
| callInfo, |
| instr->isProfiledReturnCall, |
| callInstr, |
| ret); |
| } |
| return ret; |
| } |
| |
| int32 |
| LowererMD::LowerCallArgs(IR::Instr *callInstr, IR::Instr *stackParamInsert, ushort callFlags, Js::ArgSlot extraParams, IR::IntConstOpnd **callInfoOpndRef) |
| { |
| AssertMsg(this->helperCallArgsCount == 0, "We don't support nested helper calls yet"); |
| |
| uint32 argCount = 0; |
| |
| IR::Opnd* opndParam; |
| // Now walk the user arguments and remember the arg count. |
| |
| IR::Instr * argInstr = callInstr; |
| IR::Opnd *src2Opnd = callInstr->UnlinkSrc2(); |
| while (src2Opnd->IsSymOpnd()) |
| { |
| // Get the arg instr |
| IR::SymOpnd * argLinkOpnd = src2Opnd->AsSymOpnd(); |
| StackSym * argLinkSym = argLinkOpnd->m_sym->AsStackSym(); |
| AssertMsg(argLinkSym->IsArgSlotSym() && argLinkSym->m_isSingleDef, "Arg tree not single def..."); |
| argLinkOpnd->Free(this->m_func); |
| |
| argInstr = argLinkSym->m_instrDef; |
| |
| // The arg sym isn't assigned a constant directly anymore |
| argLinkSym->m_isConst = false; |
| argLinkSym->m_isIntConst = false; |
| argLinkSym->m_isTaggableIntConst = false; |
| |
| // The arg slot nums are 1-based, so subtract 1. Then add 1 for the non-user args (callinfo). |
| auto argSlotNum = argLinkSym->GetArgSlotNum(); |
| if(argSlotNum + extraParams < argSlotNum) |
| { |
| Js::Throw::OutOfMemory(); |
| } |
| opndParam = this->GetOpndForArgSlot(argSlotNum + extraParams); |
| |
| src2Opnd = argInstr->UnlinkSrc2(); |
| argInstr->ReplaceDst(opndParam); |
| argInstr->Unlink(); |
| if (opndParam->IsRegOpnd()) |
| { |
| callInstr->InsertBefore(argInstr); |
| } |
| else |
| { |
| stackParamInsert->InsertBefore(argInstr); |
| } |
| this->ChangeToAssign(argInstr); |
| argCount++; |
| } |
| |
| IR::RegOpnd * argLinkOpnd = src2Opnd->AsRegOpnd(); |
| StackSym *argLinkSym = argLinkOpnd->m_sym->AsStackSym(); |
| AssertMsg(!argLinkSym->IsArgSlotSym() && argLinkSym->m_isSingleDef, "Arg tree not single def..."); |
| |
| IR::Instr *startCallInstr = argLinkSym->m_instrDef; |
| |
| AssertMsg(startCallInstr->m_opcode == Js::OpCode::StartCall || startCallInstr->m_opcode == Js::OpCode::LoweredStartCall, "Problem with arg chain."); |
| AssertMsg(startCallInstr->GetArgOutCount(/*getInterpreterArgOutCount*/ false) == argCount, |
| "ArgCount doesn't match StartCall count"); |
| |
| // Deal with the SC. |
| this->LowerStartCall(startCallInstr); |
| |
| // Second argument is the callinfo. |
| IR::IntConstOpnd *opndCallInfo = Lowerer::MakeCallInfoConst(callFlags, argCount, m_func); |
| if(callInfoOpndRef) |
| { |
| opndCallInfo->Use(m_func); |
| *callInfoOpndRef = opndCallInfo; |
| } |
| opndParam = this->GetOpndForArgSlot(extraParams); |
| Lowerer::InsertMove(opndParam, opndCallInfo, callInstr); |
| |
| return argCount + 1 + extraParams; // + 1 for call flags |
| } |
| |
| IR::Instr * |
| LowererMD::LowerStartCall(IR::Instr * instr) |
| { |
| // StartCall doesn't need to generate a stack adjustment. Just delete it. |
| instr->m_opcode = Js::OpCode::LoweredStartCall; |
| return instr; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadHelperArgument(IR::Instr * instr, IR::Opnd * opndArgValue) |
| { |
| // Load the given parameter into the appropriate location. |
| // We update the current param state so we can do this work without making the caller |
| // do the work. |
| Assert(this->helperCallArgsCount < LowererMD::MaxArgumentsToHelper); |
| |
| __analysis_assume(this->helperCallArgsCount < MaxArgumentsToHelper); |
| |
| helperCallArgs[helperCallArgsCount++] = opndArgValue; |
| |
| if (opndArgValue->GetType() == TyMachDouble) |
| { |
| this->helperCallDoubleArgsCount++; |
| } |
| |
| return instr; |
| } |
| |
| void |
| LowererMD::FinishArgLowering() |
| { |
| this->helperCallArgsCount = 0; |
| this->helperCallDoubleArgsCount = 0; |
| } |
| |
| IR::Opnd * |
| LowererMD::GetOpndForArgSlot(Js::ArgSlot argSlot, IR::Opnd * argOpnd) |
| { |
| IR::Opnd * opndParam = nullptr; |
| |
| IRType type = argOpnd ? argOpnd->GetType() : TyMachReg; |
| if (argOpnd == nullptr || !argOpnd->IsFloat()) |
| { |
| if (argSlot < NUM_INT_ARG_REGS) |
| { |
| // Return an instance of the next arg register. |
| IR::RegOpnd *regOpnd; |
| regOpnd = IR::RegOpnd::New(nullptr, (RegNum)(argSlot + FIRST_INT_ARG_REG), type, this->m_func); |
| |
| regOpnd->m_isCallArg = true; |
| |
| opndParam = regOpnd; |
| } |
| else |
| { |
| // Create a stack slot reference and bump up the size of this function's outgoing param area, |
| // if necessary. |
| argSlot = argSlot - NUM_INT_ARG_REGS; |
| IntConstType offset = argSlot * MachRegInt; |
| IR::RegOpnd * spBase = IR::RegOpnd::New(nullptr, this->GetRegStackPointer(), TyMachReg, this->m_func); |
| opndParam = IR::IndirOpnd::New(spBase, int32(offset), type, this->m_func); |
| |
| if (this->m_func->m_argSlotsForFunctionsCalled < (uint32)(argSlot + 1)) |
| { |
| this->m_func->m_argSlotsForFunctionsCalled = argSlot + 1; |
| } |
| } |
| } |
| else |
| { |
| if (argSlot < MaxDoubleArgumentsToHelper) |
| { |
| // Return an instance of the next arg register. |
| IR::RegOpnd *regOpnd; |
| regOpnd = IR::RegOpnd::New(nullptr, (RegNum)(argSlot + FIRST_DOUBLE_ARG_REG), type, this->m_func); |
| regOpnd->m_isCallArg = true; |
| opndParam = regOpnd; |
| } |
| else |
| { |
| AssertMsg(false,"More than 8 double parameter passing disallowed"); |
| } |
| } |
| return opndParam; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadDoubleHelperArgument(IR::Instr * instr, IR::Opnd * opndArg) |
| { |
| // Load the given parameter into the appropriate location. |
| // We update the current param state so we can do this work without making the caller |
| // do the work. |
| Assert(opndArg->GetType() == TyMachDouble); |
| return this->LoadHelperArgument(instr, opndArg); |
| } |
| |
| void |
| LowererMD::GenerateStackProbe(IR::Instr *insertInstr, bool afterProlog) |
| { |
| // |
| // Generate a stack overflow check. This can be as simple as a cmp esp, const |
| // because this function is guaranteed to be called on its base thread only. |
| // If the check fails call ThreadContext::ProbeCurrentStack which will check again and must throw. |
| // |
| // LDIMM r17, ThreadContext::scriptStackLimit + frameSize //Load to register first, as this can be more than 12 bit supported in CMP |
| // CMP sp, r17 |
| // BHI done |
| // begin: |
| // LDIMM r0, frameSize |
| // LDIMM r1, scriptContext |
| // LDIMM r2, ThreadContext::ProbeCurrentStack //MUST THROW |
| // BLX r2 //BX r2 if the stackprobe is before prolog |
| // done: |
| // |
| |
| // For thread context with script interrupt enabled: |
| // LDIMM r17, &ThreadContext::scriptStackLimitForCurrentThread |
| // LDR r17, [r17] |
| // MOV r15, frameSize |
| // ADDS r17, r17, r15 |
| // BVS $helper |
| // CMP sp, r17 |
| // BHI done |
| // $helper: |
| // LDIMM r0, frameSize |
| // LDIMM r1, scriptContext |
| // LDIMM r2, ThreadContext::ProbeCurrentStack //MUST THROW |
| // BLX r2 //BX r2 if the stackprobe is before prolog |
| // done: |
| // |
| |
| //m_localStackHeight for ARM contains (m_argSlotsForFunctionsCalled * MachPtr) |
| uint32 frameSize = this->m_func->m_localStackHeight + Js::Constants::MinStackJIT; |
| |
| IR::RegOpnd *scratchOpnd = IR::RegOpnd::New(nullptr, SCRATCH_REG, TyMachReg, this->m_func); |
| IR::LabelInstr *helperLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, afterProlog); |
| IR::Instr *instr; |
| bool doInterruptProbe = m_func->GetJITFunctionBody()->DoInterruptProbe(); |
| |
| if (doInterruptProbe || !m_func->GetThreadContextInfo()->IsThreadBound()) |
| { |
| // LDIMM r17, &ThreadContext::scriptStackLimitForCurrentThread |
| intptr_t pLimit = m_func->GetThreadContextInfo()->GetThreadStackLimitAddr(); |
| Lowerer::InsertMove(scratchOpnd, IR::AddrOpnd::New(pLimit, IR::AddrOpndKindDynamicMisc, this->m_func), insertInstr); |
| |
| // LDR r17, [r17, #0] |
| Lowerer::InsertMove(scratchOpnd, IR::IndirOpnd::New(scratchOpnd, 0, TyMachReg, this->m_func), insertInstr); |
| |
| AssertMsg(!IS_CONST_00000FFF(frameSize), "For small size we can just add frameSize to r17"); |
| |
| // MOV r15, frameSize |
| IR::Opnd* spAllocRegOpnd = IR::RegOpnd::New(nullptr, SP_ALLOC_SCRATCH_REG, TyMachReg, this->m_func); |
| Lowerer::InsertMove(spAllocRegOpnd, IR::IntConstOpnd::New(frameSize, TyMachReg, this->m_func), insertInstr); |
| |
| // ADDS r17, r17, r15 |
| instr = IR::Instr::New(Js::OpCode::ADDS, scratchOpnd, scratchOpnd, spAllocRegOpnd, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| // If this add overflows, we have to call the helper. |
| instr = IR::BranchInstr::New(Js::OpCode::BVS, helperLabel, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| else |
| { |
| // MOV r17, frameSize + scriptStackLimit |
| uint64 scriptStackLimit = m_func->GetThreadContextInfo()->GetScriptStackLimit(); |
| IR::Opnd *stackLimitOpnd = IR::IntConstOpnd::New(frameSize + scriptStackLimit, TyMachReg, this->m_func); |
| Lowerer::InsertMove(scratchOpnd, stackLimitOpnd, insertInstr); |
| } |
| |
| IR::LabelInstr *doneLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false); |
| if (!IS_FAULTINJECT_STACK_PROBE_ON) // Do stack check fastpath only if not doing StackProbe fault injection |
| { |
| // CMP sp, r17 |
| instr = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| instr->SetSrc1(IR::RegOpnd::New(nullptr, GetRegStackPointer(), TyMachReg, this->m_func)); |
| instr->SetSrc2(scratchOpnd); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| |
| // BHI done |
| instr = IR::BranchInstr::New(Js::OpCode::BHI, doneLabelInstr, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| |
| insertInstr->InsertBefore(helperLabel); |
| |
| // ToDo (SaAgarwa): Make sure all SP offsets are correct |
| // Zero out the pointer to the list of stack nested funcs, since the functions won't be initialized on this path. |
| /* |
| scratchOpnd = IR::RegOpnd::New(nullptr, RegR0, TyMachReg, m_func); |
| IR::RegOpnd *frameReg = IR::RegOpnd::New(nullptr, GetRegFramePointer(), TyMachReg, m_func); |
| Lowerer::InsertMove(scratchOpnd, IR::IntConstOpnd::New(0, TyMachReg, m_func), insertInstr); |
| IR::Opnd *indirOpnd = IR::IndirOpnd::New( |
| frameReg, -(int32)(Js::Constants::StackNestedFuncList * sizeof(Js::Var)), TyMachReg, m_func); |
| Lowerer::InsertMove(indirOpnd, scratchOpnd, insertInstr); |
| */ |
| IR::RegOpnd *r0Opnd = IR::RegOpnd::New(nullptr, RegR0, TyMachReg, this->m_func); |
| Lowerer::InsertMove(r0Opnd, IR::IntConstOpnd::New(frameSize, TyMachReg, this->m_func, true), insertInstr); |
| |
| IR::RegOpnd *r1Opnd = IR::RegOpnd::New(nullptr, RegR1, TyMachReg, this->m_func); |
| Lowerer::InsertMove(r1Opnd, this->m_lowerer->LoadScriptContextOpnd(insertInstr), insertInstr); |
| |
| IR::RegOpnd *r2Opnd = IR::RegOpnd::New(nullptr, RegR2, TyMachReg, m_func); |
| Lowerer::InsertMove(r2Opnd, IR::HelperCallOpnd::New(IR::HelperProbeCurrentStack, this->m_func), insertInstr); |
| |
| instr = IR::Instr::New(afterProlog? Js::OpCode::BLR : Js::OpCode::BR, this->m_func); |
| instr->SetSrc1(r2Opnd); |
| insertInstr->InsertBefore(instr); |
| |
| insertInstr->InsertBefore(doneLabelInstr); |
| } |
| |
| // |
| // Emits the code to allocate 'size' amount of space on stack. for values smaller than PAGE_SIZE |
| // this will just emit sub SP,size otherwise calls _chkstk. |
| // |
| bool |
| LowererMD::GenerateStackAllocation(IR::Instr *instr, uint32 allocSize, uint32 probeSize) |
| { |
| IR::RegOpnd* spOpnd = IR::RegOpnd::New(nullptr, GetRegStackPointer(), TyMachReg, this->m_func); |
| |
| if (IsSmallStack(probeSize)) |
| { |
| AssertMsg(!(allocSize & 0xFFFFF000), "Must fit in 12 bits"); |
| AssertMsg(allocSize % MachStackAlignment == 0, "Must be aligned"); |
| // Generate SUB SP, SP, stackSize |
| IR::IntConstOpnd * stackSizeOpnd = IR::IntConstOpnd::New(allocSize, TyMachReg, this->m_func, true); |
| IR::Instr * subInstr = IR::Instr::New(Js::OpCode::SUB, spOpnd, spOpnd, stackSizeOpnd, this->m_func); |
| instr->InsertBefore(subInstr); |
| return false; |
| } |
| |
| //__chkStk is a leaf function and hence alignment is not required. |
| |
| // Generate _chkstk call |
| // LDIMM RegR15, stackSize/16 |
| // LDIMM RegR17, HelperCRT_chkstk |
| // BLX RegR17 |
| // SUB SP, SP, x15, lsl #4 |
| |
| //chkstk expects the stacksize argument in R15 register |
| IR::RegOpnd *spAllocOpnd = IR::RegOpnd::New(nullptr, SP_ALLOC_SCRATCH_REG, TyMachReg, this->m_func); |
| IR::RegOpnd *targetOpnd = IR::RegOpnd::New(nullptr, SCRATCH_REG, TyMachReg, this->m_func); |
| |
| IR::IntConstOpnd * stackSizeOpnd = IR::IntConstOpnd::New((allocSize / MachStackAlignment), TyMachReg, this->m_func, true); |
| |
| IR::Instr *movHelperAddrInstr = IR::Instr::New(Js::OpCode::LDIMM, targetOpnd, IR::HelperCallOpnd::New(IR::HelperCRT_chkstk, this->m_func), this->m_func); |
| instr->InsertBefore(movHelperAddrInstr); |
| |
| IR::Instr *movInstr = IR::Instr::New(Js::OpCode::LDIMM, spAllocOpnd, stackSizeOpnd, this->m_func); |
| instr->InsertBefore(movInstr); |
| |
| IR::Instr * callInstr = IR::Instr::New(Js::OpCode::BLR, spAllocOpnd, targetOpnd, this->m_func); |
| instr->InsertBefore(callInstr); |
| |
| // _chkstk succeeded adjust SP by allocSize. r15 contains allocSize/16 so left shift r15 by 4 to get allocSize |
| // Generate SUB SP, SP, x15, lsl #4 |
| IR::Instr * subInstr = IR::Instr::New(Js::OpCode::SUB_LSL4, spOpnd, spOpnd, spAllocOpnd, this->m_func); |
| instr->InsertBefore(subInstr); |
| |
| // return true to imply scratch register is trashed |
| return true; |
| } |
| |
| void |
| LowererMD::GenerateStackDeallocation(IR::Instr *instr, uint32 allocSize) |
| { |
| IR::RegOpnd * spOpnd = IR::RegOpnd::New(nullptr, this->GetRegStackPointer(), TyMachReg, this->m_func); |
| |
| IR::Instr * spAdjustInstr = IR::Instr::New(Js::OpCode::ADD, |
| spOpnd, |
| spOpnd, |
| IR::IntConstOpnd::New(allocSize, TyMachReg, this->m_func, true), this->m_func); |
| instr->InsertBefore(spAdjustInstr); |
| LegalizeMD::LegalizeInstr(spAdjustInstr); |
| } |
| |
| |
| |
| class ARM64StackLayout |
| { |
| // |
| // Canonical ARM64 prolog/epilog stack layout (stack grows downward): |
| // |
| // +-------------------------------------+ |
| // | caller-allocated parameters | |
| // +=====================================+-----> SP at time of call |
| // | callee-saved parameters (x0-x7) | |
| // +-------------------------------------+ |
| // | frame pointer + link register | |
| // +-------------------------------------+-----> updated FP points here |
| // | arguments slot + StackFunctionList | |
| // +-------------------------------------+ |
| // | callee-saved registers (x19-x28) | |
| // +-------------------------------------+ |
| // | callee-saved FP regs (d8-d15) | |
| // +-------------------------------------+-----> == regOffset |
| // | locals area | |
| // +-------------------------------------+-----> locals pointer if not SP |
| // | caller-allocated parameters | |
| // +=====================================+-----> SP points here when done |
| // |
| |
| public: |
| ARM64StackLayout(Func* func); |
| |
| // Getters |
| bool HasCalls() const { return m_hasCalls; } |
| bool HasTry() const { return m_hasTry; } |
| ULONG ArgSlotCount() const { return m_argSlotCount; } |
| BitVector HomedParams() const { return m_homedParams; } |
| BitVector SavedRegisters() const { return m_savedRegisters; } |
| BitVector SavedDoubles() const { return m_savedDoubles; } |
| |
| // Locals area sits right after space allocated for arguments |
| ULONG LocalsOffset() const { return this->m_argSlotCount * MachRegInt; } |
| ULONG LocalsSize() const { return this->m_localsArea; } |
| |
| // Saved non-volatile double registers sit past the locals area |
| ULONG SavedDoublesOffset() const { return this->LocalsOffset() + this->LocalsSize(); } |
| ULONG SavedDoublesSize() const { return this->m_savedDoubles.Count() * MachRegDouble; } |
| |
| // Saved non-volatile integer registers sit after the saved doubles |
| ULONG SavedRegistersOffset() const { return this->SavedDoublesOffset() + this->SavedDoublesSize(); } |
| ULONG SavedRegistersSize() const { return this->m_savedRegisters.Count() * MachRegInt; } |
| |
| // The argument slot and StackFunctionList entry come after the saved integer registers |
| ULONG ArgSlotOffset() const { return this->SavedRegistersOffset() + this->SavedRegistersSize(); } |
| ULONG ArgSlotSize() const { return this->m_hasCalls ? (2 * MachRegInt) : 0; } |
| |
| // Next comes the frame chain |
| ULONG FpLrOffset() const { return this->ArgSlotOffset() + this->ArgSlotSize(); } |
| ULONG FpLrSize() const { return this->m_hasCalls ? (2 * MachRegInt) : 0; } |
| |
| // Followed by any homed parameters |
| ULONG HomedParamsOffset() const { return this->FpLrOffset() + this->FpLrSize(); } |
| ULONG HomedParamsSize() const { return this->m_homedParams.Count() * MachRegInt; } |
| |
| // And that's the total stack allocation |
| ULONG TotalStackSize() const { return this->HomedParamsOffset() + this->HomedParamsSize(); } |
| |
| // The register area is the area at the far end that doesn't include locals or arg slots |
| ULONG RegisterAreaOffset() const { return this->SavedDoublesOffset(); } |
| ULONG RegisterAreaSize() const { return this->TotalStackSize() - this->RegisterAreaOffset(); } |
| |
| private: |
| bool m_hasCalls; |
| bool m_hasTry; |
| ULONG m_argSlotCount; |
| ULONG m_localsArea; |
| BitVector m_homedParams; |
| BitVector m_savedRegisters; |
| BitVector m_savedDoubles; |
| }; |
| |
| ARM64StackLayout::ARM64StackLayout(Func* func) |
| : m_hasCalls(false), |
| m_hasTry(func->HasTry()), |
| m_argSlotCount(func->m_argSlotsForFunctionsCalled), |
| m_localsArea(func->m_localStackHeight) |
| { |
| Assert(m_localsArea % 16 == 0); |
| Assert(m_argSlotCount % 2 == 0); |
| |
| // If there is a try, behave specially because the try/catch/finally helpers assume a |
| // fully-populated stack layout. |
| if (this->m_hasTry) |
| { |
| this->m_hasCalls = true; |
| this->m_savedRegisters.SetRange(FIRST_CALLEE_SAVED_GP_REG, CALLEE_SAVED_GP_REG_COUNT); |
| this->m_savedDoubles.SetRange(FIRST_CALLEE_SAVED_DBL_REG, CALLEE_SAVED_DOUBLE_REG_COUNT); |
| this->m_homedParams.SetRange(0, NUM_INT_ARG_REGS); |
| } |
| |
| // Otherwise, be more selective |
| else |
| { |
| // Determine integer register saves. Since registers are always saved in pairs, mark both registers |
| // in each pair as being saved even if only one is actually used. |
| for (RegNum curReg = FIRST_CALLEE_SAVED_GP_REG; curReg <= LAST_CALLEE_SAVED_GP_REG; curReg = RegNum(curReg + 2)) |
| { |
| Assert(LinearScan::IsCalleeSaved(curReg)); |
| RegNum nextReg = RegNum(curReg + 1); |
| Assert(LinearScan::IsCalleeSaved(nextReg)); |
| if (func->m_regsUsed.Test(curReg) || func->m_regsUsed.Test(nextReg)) |
| { |
| this->m_savedRegisters.SetRange(curReg, 2); |
| } |
| } |
| |
| // Determine double register saves. Since registers are always saved in pairs, mark both registers |
| // in each pair as being saved even if only one is actually used. |
| for (RegNum curReg = FIRST_CALLEE_SAVED_DBL_REG; curReg <= LAST_CALLEE_SAVED_DBL_REG; curReg = RegNum(curReg + 2)) |
| { |
| Assert(LinearScan::IsCalleeSaved(curReg)); |
| RegNum nextReg = RegNum(curReg + 1); |
| Assert(LinearScan::IsCalleeSaved(nextReg)); |
| if (func->m_regsUsed.Test(curReg) || func->m_regsUsed.Test(nextReg)) |
| { |
| this->m_savedDoubles.SetRange(curReg, 2); |
| } |
| } |
| |
| // Determine if there are nested calls. |
| // |
| // If the function has a try, we need to have the same register saves in the prolog as the |
| // arm64_CallEhFrame helper, so that we can use the same epilog. So always allocate a slot |
| // for the stack nested func here whether we actually do have any stack nested func or not |
| // TODO-STACK-NESTED-FUNC: May be use a different arm64_CallEhFrame for when we have |
| // stack nested func? |
| // |
| // Note that this->TotalStackSize() will not include the homed parameters yet, so we add in |
| // the worst case assumption (homing all NUM_INT_ARG_REGS). |
| this->m_hasCalls = func->GetHasCalls() || |
| func->HasAnyStackNestedFunc() || |
| !LowererMD::IsSmallStack(this->TotalStackSize() + NUM_INT_ARG_REGS * MachRegInt) || |
| Lowerer::IsArgSaveRequired(func); |
| |
| // Home the params. This is done to enable on-the-fly creation of the arguments object, |
| // Dyno bailout code, etc. For non-global functions, that means homing all the param registers |
| // (since we have to assume they all have valid parameters). For the global function, |
| // just home x0 (function object) and x1 (callinfo), which the runtime can't get by any other means. |
| int homedParams = MIN_HOMED_PARAM_REGS; |
| if (func->IsLoopBody()) |
| { |
| // Jitted loop body takes only one "user" param: the pointer to the local slots. |
| homedParams += 1; |
| } |
| else if (!this->m_hasCalls) |
| { |
| // A leaf function (no calls of any kind, including helpers) may still need its params, or, if it |
| // has none, may still need the function object and call info. |
| homedParams += func->GetInParamsCount(); |
| } |
| else |
| { |
| homedParams = NUM_INT_ARG_REGS; |
| } |
| |
| // Round up to an even number to keep stack alignment |
| if (homedParams % 2 != 0) |
| { |
| homedParams += 1; |
| } |
| this->m_homedParams.SetRange(0, (homedParams < NUM_INT_ARG_REGS) ? homedParams : NUM_INT_ARG_REGS); |
| } |
| } |
| |
| IR::Instr * |
| LowererMD::LowerEntryInstr(IR::EntryInstr * entryInstr) |
| { |
| IR::Instr *insertInstr = entryInstr->m_next; |
| |
| // Begin recording info for later pdata/xdata emission. |
| this->m_func->m_unwindInfo.Init(this->m_func); |
| |
| // Ensure there are an even number of slots for called functions |
| if (this->m_func->m_argSlotsForFunctionsCalled % 2 != 0) |
| { |
| this->m_func->m_argSlotsForFunctionsCalled += 1; |
| } |
| |
| if (this->m_func->HasInlinee()) |
| { |
| // Allocate the inlined arg out stack in the locals. Allocate an additional slot so that |
| // we can unconditionally clear the first slot past the current frame. |
| this->m_func->m_localStackHeight += this->m_func->GetInlineeArgumentStackSize(); |
| } |
| |
| // Ensure the locals area is 16 byte aligned. |
| this->m_func->m_localStackHeight = Math::Align<int32>(this->m_func->m_localStackHeight, MachStackAlignment); |
| |
| // Now that the localStackHeight has been adjusted, compute the final layout |
| ARM64StackLayout layout(this->m_func); |
| Assert(layout.TotalStackSize() % 16 == 0); |
| |
| // Set the arguments offset relative to the end of the locals area |
| this->m_func->m_ArgumentsOffset = layout.HomedParamsOffset() - (layout.LocalsOffset() + layout.LocalsSize()); |
| |
| // Set the frame height if inlinee arguments are needed |
| if (m_func->GetMaxInlineeArgOutSize() != 0) |
| { |
| // subtracting 2 for frame pointer & return address |
| this->m_func->GetJITOutput()->SetFrameHeight(this->m_func->m_localStackHeight + this->m_func->m_ArgumentsOffset - 2 * MachRegInt); |
| } |
| |
| // Two situations to handle: |
| // |
| // 1. If total stack allocation < 512, we can do a single allocation up front |
| // 2. Otherwise, we allocate the register area first, save regs, then allocate locals |
| // |
| // Breaking this down, there are two stack allocations |
| // |
| // Allocation 1 = situation1 ? TotalStackSize : RegisterAreaSize |
| // Allocation 2 = TotalStackSize - Allocation 1 |
| // |
| // <probe> |
| // prologStart: |
| // sub sp, sp, #allocation1 |
| // stp d8-d15, [sp, #savedDoublesOffset - allocation2] |
| // stp x19-x28, [sp, #savedRegistersOffset - allocation2] |
| // stp fp, lr, [sp, #fpLrOffset - allocation2] |
| // add fp, sp, #fpLrOffset - allocation2 |
| // sub sp, sp, #allocation2 (might be call to _chkstk) |
| // prologEnd: |
| // stp zr, zr, [fp, #argSlotOffset - fpLrOffset] |
| // stp x0-x7, [fp, #paramSaveOffset - fpLrOffset] |
| // add localsptr, sp, #localsOffset |
| // sub ehsave, fp, #fpLrOffset - registerAreaOffset |
| // |
| |
| // Determine the 1 or 2 stack allocation sizes |
| ULONG stackAllocation1 = (layout.TotalStackSize() < 512) ? layout.TotalStackSize() : layout.RegisterAreaSize(); |
| ULONG stackAllocation2 = layout.TotalStackSize() - stackAllocation1; |
| |
| // this->GenerateDebugBreak(insertInstr); |
| |
| // Generate a stack probe for large stacks first even before register push |
| bool fStackProbeAfterProlog = IsSmallStack(layout.TotalStackSize()); |
| if (!fStackProbeAfterProlog) |
| { |
| GenerateStackProbe(insertInstr, false); |
| } |
| |
| // Create the prologStart label |
| IR::LabelInstr *prologStartLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| insertInstr->InsertBefore(prologStartLabel); |
| this->m_func->m_unwindInfo.SetFunctionOffsetLabel(UnwindPrologStart, prologStartLabel); |
| |
| // Perform the initial stack allocation (guaranteed to be small) |
| IR::RegOpnd *spOpnd = IR::RegOpnd::New(nullptr, RegSP, TyMachReg, this->m_func); |
| if (stackAllocation1 > 0) |
| { |
| IR::Instr * instrSub = IR::Instr::New(Js::OpCode::SUB, spOpnd, spOpnd, IR::IntConstOpnd::New(stackAllocation1, TyMachReg, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instrSub); |
| } |
| |
| // Save doubles in pairs |
| if (!layout.SavedDoubles().IsEmpty()) |
| { |
| ULONG curOffset = layout.SavedDoublesOffset() - stackAllocation2; |
| for (RegNum curReg = FIRST_CALLEE_SAVED_DBL_REG; curReg <= LAST_CALLEE_SAVED_DBL_REG; curReg = RegNum(curReg + 2)) |
| { |
| if (layout.SavedDoubles().Test(curReg)) |
| { |
| RegNum nextReg = RegNum(curReg + 1); |
| IR::Instr * instrStp = IR::Instr::New(Js::OpCode::FSTP, |
| IR::IndirOpnd::New(spOpnd, curOffset, TyMachReg, this->m_func), |
| IR::RegOpnd::New(curReg, TyMachDouble, this->m_func), |
| IR::RegOpnd::New(nextReg, TyMachDouble, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instrStp); |
| curOffset += 2 * MachRegDouble; |
| } |
| } |
| } |
| |
| // Save integer registers in pairs |
| if (!layout.SavedRegisters().IsEmpty()) |
| { |
| ULONG curOffset = layout.SavedRegistersOffset() - stackAllocation2; |
| for (RegNum curReg = FIRST_CALLEE_SAVED_GP_REG; curReg <= LAST_CALLEE_SAVED_GP_REG; curReg = RegNum(curReg + 2)) |
| { |
| if (layout.SavedRegisters().Test(curReg)) |
| { |
| RegNum nextReg = RegNum(curReg + 1); |
| IR::Instr * instrStp = IR::Instr::New(Js::OpCode::STP, |
| IR::IndirOpnd::New(spOpnd, curOffset, TyMachReg, this->m_func), |
| IR::RegOpnd::New(curReg, TyMachReg, this->m_func), |
| IR::RegOpnd::New(nextReg, TyMachReg, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instrStp); |
| curOffset += 2 * MachRegInt; |
| } |
| } |
| } |
| |
| // Save FP/LR and compute FP |
| IR::RegOpnd *fpOpnd = fpOpnd = IR::RegOpnd::New(nullptr, RegFP, TyMachReg, this->m_func); |
| if (layout.HasCalls()) |
| { |
| // STP fp, lr, [sp, #offs] |
| ULONG fpOffset = layout.FpLrOffset() - stackAllocation2; |
| IR::Instr * instrStp = IR::Instr::New(Js::OpCode::STP, |
| IR::IndirOpnd::New(spOpnd, fpOffset, TyMachReg, this->m_func), |
| fpOpnd, IR::RegOpnd::New(RegLR, TyMachReg, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instrStp); |
| |
| // ADD fp, sp, #offs |
| // For exception handling, do this part AFTER the prolog to allow for proper unwinding |
| if (!layout.HasTry()) |
| { |
| Lowerer::InsertAdd(false, fpOpnd, spOpnd, IR::IntConstOpnd::New(fpOffset, TyMachReg, this->m_func), insertInstr); |
| } |
| } |
| |
| // Perform the second (potentially large) stack allocation |
| if (stackAllocation2 > 0) |
| { |
| // TODO: is the probeSize parameter correct here? |
| this->GenerateStackAllocation(insertInstr, stackAllocation2, stackAllocation1 + stackAllocation2); |
| } |
| |
| // Future work in the register area should be done FP-relative if it is set up |
| IR::RegOpnd *regAreaBaseOpnd = layout.HasCalls() ? fpOpnd : spOpnd; |
| ULONG regAreaBaseOffset = layout.HasCalls() ? layout.FpLrOffset() : 0; |
| |
| // This marks the end of the formal prolog (for EH purposes); create and register a label |
| IR::LabelInstr *prologEndLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| insertInstr->InsertBefore(prologEndLabel); |
| this->m_func->m_unwindInfo.SetFunctionOffsetLabel(UnwindPrologEnd, prologEndLabel); |
| |
| // Compute the FP now if there is a try present |
| if (layout.HasTry()) |
| { |
| Lowerer::InsertAdd(false, fpOpnd, spOpnd, IR::IntConstOpnd::New(layout.FpLrOffset(), TyMachReg, this->m_func), insertInstr); |
| } |
| |
| // Zero the argument slot if present |
| IR::RegOpnd *zrOpnd = IR::RegOpnd::New(nullptr, RegZR, TyMachReg, this->m_func); |
| if (layout.ArgSlotSize() > 0) |
| { |
| IR::Instr * instrStp = IR::Instr::New(Js::OpCode::STP, |
| IR::IndirOpnd::New(regAreaBaseOpnd, layout.ArgSlotOffset() - regAreaBaseOffset, TyMachReg, this->m_func), |
| zrOpnd, zrOpnd, this->m_func); |
| insertInstr->InsertBefore(instrStp); |
| } |
| |
| // Home parameter registers in pairs |
| if (!layout.HomedParams().IsEmpty()) |
| { |
| ULONG curOffset = layout.HomedParamsOffset() - regAreaBaseOffset; |
| for (RegNum curReg = FIRST_INT_ARG_REG; curReg <= LAST_INT_ARG_REG; curReg = RegNum(curReg + 2)) |
| { |
| if (layout.HomedParams().Test(curReg)) |
| { |
| RegNum nextReg = RegNum(curReg + 1); |
| IR::Instr * instrStp = IR::Instr::New(Js::OpCode::STP, |
| IR::IndirOpnd::New(regAreaBaseOpnd, curOffset, TyMachReg, this->m_func), |
| IR::RegOpnd::New(curReg, TyMachReg, this->m_func), |
| IR::RegOpnd::New(nextReg, TyMachReg, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instrStp); |
| curOffset += 2 * MachRegInt; |
| } |
| } |
| } |
| |
| // Compute the locals pointer if needed |
| RegNum localsReg = this->m_func->GetLocalsPointer(); |
| if (localsReg != RegSP) |
| { |
| IR::RegOpnd* localsOpnd = IR::RegOpnd::New(nullptr, localsReg, TyMachReg, this->m_func); |
| Lowerer::InsertAdd(false, localsOpnd, spOpnd, IR::IntConstOpnd::New(layout.LocalsOffset(), TyMachReg, this->m_func), insertInstr); |
| } |
| |
| // Zero initialize the first inlinee frames argc. |
| if (this->m_func->GetMaxInlineeArgOutSize() != 0) |
| { |
| // STR argc, zr |
| StackSym *sym = this->m_func->m_symTable->GetArgSlotSym((Js::ArgSlot) - 1); |
| sym->m_isInlinedArgSlot = true; |
| sym->m_offset = 0; |
| IR::Instr * instrStr = IR::Instr::New(Js::OpCode::STR, IR::SymOpnd::New(sym, 0, TyMachReg, this->m_func), zrOpnd, this->m_func); |
| insertInstr->InsertBefore(instrStr); |
| } |
| |
| // Now do the stack probe for small stacks |
| // hasCalls catches the recursion case |
| if (layout.HasCalls() && fStackProbeAfterProlog) |
| { |
| GenerateStackProbe(insertInstr, true); //stack is already aligned in this case |
| } |
| |
| return entryInstr; |
| } |
| |
| IR::Instr * |
| LowererMD::LowerExitInstr(IR::ExitInstr * exitInstr) |
| { |
| // Compute the final layout (should match the prolog) |
| ARM64StackLayout layout(this->m_func); |
| Assert(layout.TotalStackSize() % 16 == 0); |
| |
| // Determine the 1 or 2 stack allocation sizes |
| // Note that on exit, if there is a try, we always do a 2-step deallocation because the |
| // epilog is re-used by the try/catch/finally code |
| ULONG stackAllocation1 = (layout.TotalStackSize() < 512 && !layout.HasTry()) ? layout.TotalStackSize() : layout.RegisterAreaSize(); |
| ULONG stackAllocation2 = layout.TotalStackSize() - stackAllocation1; |
| |
| // Mark the start of the epilog |
| IR::LabelInstr *epilogStartLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| exitInstr->InsertBefore(epilogStartLabel); |
| this->m_func->m_unwindInfo.SetFunctionOffsetLabel(UnwindEpilogStart, epilogStartLabel); |
| |
| IR::RegOpnd *spOpnd = IR::RegOpnd::New(nullptr, RegSP, TyMachReg, this->m_func); |
| IR::RegOpnd *fpOpnd = IR::RegOpnd::New(nullptr, RegFP, TyMachReg, this->m_func); |
| |
| // Exception handling regions exit via the same epilog |
| IR::LabelInstr* ehEpilogLabel = this->m_func->m_epilogLabel; |
| if (ehEpilogLabel != nullptr) |
| { |
| ehEpilogLabel->Unlink(); |
| exitInstr->InsertBefore(ehEpilogLabel); |
| } |
| |
| // Undo the last stack allocation |
| if (stackAllocation2 > 0) |
| { |
| GenerateStackDeallocation(exitInstr, stackAllocation2); |
| } |
| |
| // Recover FP and LR |
| if (layout.HasCalls()) |
| { |
| // LDP fp, lr, [sp, #offs] |
| ULONG fpOffset = layout.FpLrOffset() - stackAllocation2; |
| IR::Instr * instrLdp = IR::Instr::New(Js::OpCode::LDP, fpOpnd, |
| IR::IndirOpnd::New(spOpnd, fpOffset, TyMachReg, this->m_func), |
| IR::RegOpnd::New(RegLR, TyMachReg, this->m_func), this->m_func); |
| exitInstr->InsertBefore(instrLdp); |
| } |
| |
| // Recover integer registers in pairs |
| if (!layout.SavedRegisters().IsEmpty()) |
| { |
| ULONG curOffset = layout.SavedRegistersOffset() - stackAllocation2 + layout.SavedRegistersSize(); |
| for (RegNum curReg = RegNum(LAST_CALLEE_SAVED_GP_REG - 1); curReg >= FIRST_CALLEE_SAVED_GP_REG; curReg = RegNum(curReg - 2)) |
| { |
| if (layout.SavedRegisters().Test(curReg)) |
| { |
| curOffset -= 2 * MachRegInt; |
| RegNum nextReg = RegNum(curReg + 1); |
| IR::Instr * instrLdp = IR::Instr::New(Js::OpCode::LDP, |
| IR::RegOpnd::New(curReg, TyMachReg, this->m_func), |
| IR::IndirOpnd::New(spOpnd, curOffset, TyMachReg, this->m_func), |
| IR::RegOpnd::New(nextReg, TyMachReg, this->m_func), this->m_func); |
| exitInstr->InsertBefore(instrLdp); |
| } |
| } |
| } |
| |
| // Recover doubles in pairs |
| if (!layout.SavedDoubles().IsEmpty()) |
| { |
| ULONG curOffset = layout.SavedDoublesOffset() - stackAllocation2 + layout.SavedDoublesSize(); |
| for (RegNum curReg = RegNum(LAST_CALLEE_SAVED_DBL_REG - 1); curReg >= FIRST_CALLEE_SAVED_DBL_REG; curReg = RegNum(curReg - 2)) |
| { |
| if (layout.SavedDoubles().Test(curReg)) |
| { |
| curOffset -= 2 * MachRegDouble; |
| RegNum nextReg = RegNum(curReg + 1); |
| IR::Instr * instrLdp = IR::Instr::New(Js::OpCode::FLDP, |
| IR::RegOpnd::New(curReg, TyMachDouble, this->m_func), |
| IR::IndirOpnd::New(spOpnd, curOffset, TyMachReg, this->m_func), |
| IR::RegOpnd::New(nextReg, TyMachDouble, this->m_func), this->m_func); |
| exitInstr->InsertBefore(instrLdp); |
| } |
| } |
| } |
| |
| // Final stack deallocation |
| if (stackAllocation1 > 0) |
| { |
| GenerateStackDeallocation(exitInstr, stackAllocation1); |
| } |
| |
| // Return |
| IR::Instr * instrRet = IR::Instr::New(Js::OpCode::RET, nullptr, IR::RegOpnd::New(nullptr, RegLR, TyMachReg, this->m_func), this->m_func); |
| exitInstr->InsertBefore(instrRet); |
| |
| // Label the end |
| IR::LabelInstr *epilogEndLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| exitInstr->InsertBefore(epilogEndLabel); |
| this->m_func->m_unwindInfo.SetFunctionOffsetLabel(UnwindEpilogEnd, epilogEndLabel); |
| |
| return exitInstr; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadNewScObjFirstArg(IR::Instr * instr, IR::Opnd * argSrc, ushort extraArgs) |
| { |
| // Spread moves down the argument slot by one. |
| // LowerCallArgs will handle the extraArgs. We only need to specify the argument number |
| // i.e 1 and not + extraArgs as done in AMD64 |
| IR::SymOpnd *argOpnd = IR::SymOpnd::New(this->m_func->m_symTable->GetArgSlotSym(1), TyVar, this->m_func); |
| IR::Instr *argInstr = IR::Instr::New(Js::OpCode::ArgOut_A, argOpnd, argSrc, this->m_func); |
| instr->InsertBefore(argInstr); |
| |
| // Insert the argument into the arg chain. |
| if (m_lowerer->IsSpreadCall(instr)) |
| { |
| // Spread calls need LdSpreadIndices as the last arg in the arg chain. |
| instr = m_lowerer->GetLdSpreadIndicesInstr(instr); |
| } |
| IR::Opnd *linkOpnd = instr->UnlinkSrc2(); |
| argInstr->SetSrc2(linkOpnd); |
| instr->SetSrc2(argOpnd); |
| |
| return argInstr; |
| } |
| |
| IR::Instr * |
| LowererMD::LowerTry(IR::Instr * tryInstr, IR::JnHelperMethod helperMethod) |
| { |
| // Mark the entry to the try |
| IR::Instr * instr = tryInstr->GetNextRealInstrOrLabel(); |
| AssertMsg(instr->IsLabelInstr(), "No label at the entry to a try?"); |
| IR::LabelInstr * tryAddr = instr->AsLabelInstr(); |
| |
| // Arg 7: ScriptContext |
| this->m_lowerer->LoadScriptContext(tryAddr); |
| |
| if (tryInstr->m_opcode == Js::OpCode::TryCatch || this->m_func->DoOptimizeTry() || (this->m_func->IsSimpleJit() && this->m_func->hasBailout)) |
| { |
| // Arg 6 : hasBailedOutOffset |
| IR::Opnd * hasBailedOutOffset = IR::IntConstOpnd::New(this->m_func->GetHasBailedOutSym()->m_offset + tryInstr->m_func->GetInlineeArgumentStackSize(), TyInt32, this->m_func); |
| this->LoadHelperArgument(tryAddr, hasBailedOutOffset); |
| } |
| |
| // Arg 5: arg out size |
| IR::RegOpnd * argOutSize = IR::RegOpnd::New(TyMachReg, this->m_func); |
| instr = IR::Instr::New(Js::OpCode::LDARGOUTSZ, argOutSize, this->m_func); |
| tryAddr->InsertBefore(instr); |
| this->LoadHelperArgument(tryAddr, argOutSize); |
| |
| // Arg 4: locals pointer |
| IR::RegOpnd * localsPtr = IR::RegOpnd::New(nullptr, this->m_func->GetLocalsPointer(), TyMachReg, this->m_func); |
| this->LoadHelperArgument(tryAddr, localsPtr); |
| |
| // Arg 3: frame pointer |
| IR::RegOpnd * framePtr = IR::RegOpnd::New(nullptr, FRAME_REG, TyMachReg, this->m_func); |
| this->LoadHelperArgument(tryAddr, framePtr); |
| |
| // Arg 2: helper address |
| IR::LabelInstr * helperAddr = tryInstr->AsBranchInstr()->GetTarget(); |
| this->LoadHelperArgument(tryAddr, IR::LabelOpnd::New(helperAddr, this->m_func)); |
| |
| // Arg 1: try address |
| this->LoadHelperArgument(tryAddr, IR::LabelOpnd::New(tryAddr, this->m_func)); |
| |
| // Call the helper |
| IR::RegOpnd *continuationAddr = |
| IR::RegOpnd::New(StackSym::New(TyMachReg,this->m_func), RETURN_REG, TyMachReg, this->m_func); |
| IR::Instr * callInstr = IR::Instr::New( |
| Js::OpCode::Call, continuationAddr, IR::HelperCallOpnd::New(helperMethod, this->m_func), this->m_func); |
| tryAddr->InsertBefore(callInstr); |
| this->LowerCall(callInstr, 0); |
| |
| // Jump to the continuation address supplied by the helper |
| IR::BranchInstr *branchInstr = IR::MultiBranchInstr::New(Js::OpCode::BR, continuationAddr, this->m_func); |
| tryAddr->InsertBefore(branchInstr); |
| |
| return tryInstr->m_prev; |
| } |
| |
| IR::Instr * |
| LowererMD::LowerLeaveNull(IR::Instr * leaveInstr) |
| { |
| IR::Instr * instrPrev = leaveInstr->m_prev; |
| |
| // Return a NULL continuation address to the caller to indicate that the finally did not seize the flow. |
| this->LowerEHRegionReturn(leaveInstr, IR::IntConstOpnd::New(0, TyMachReg, this->m_func)); |
| |
| leaveInstr->Remove(); |
| return instrPrev; |
| } |
| |
| IR::Instr * |
| LowererMD::LowerEHRegionReturn(IR::Instr * insertBeforeInstr, IR::Opnd * targetOpnd) |
| { |
| IR::RegOpnd *retReg = IR::RegOpnd::New(nullptr, RETURN_REG, TyMachReg, this->m_func); |
| |
| // Load the continuation address into the return register. |
| Lowerer::InsertMove(retReg, targetOpnd, insertBeforeInstr); |
| |
| IR::LabelInstr *epilogLabel = this->EnsureEHEpilogLabel(); |
| IR::BranchInstr *jmpInstr = IR::BranchInstr::New(Js::OpCode::B, epilogLabel, this->m_func); |
| insertBeforeInstr->InsertBefore(jmpInstr); |
| |
| // return the last instruction inserted |
| return jmpInstr; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::Init |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| void |
| LowererMD::Init(Lowerer *lowerer) |
| { |
| m_lowerer = lowerer; |
| // The arg slot count computed by an earlier phase (e.g., IRBuilder) doesn't work for |
| // ARM if it accounts for nesting. Clear it here and let Lower compute its own value. |
| this->m_func->m_argSlotsForFunctionsCalled = 0; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::LoadInputParamPtr |
| /// |
| /// Load the address of the start of the passed-in parameters not including |
| /// the this parameter. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::LoadInputParamPtr(IR::Instr * instrInsert, IR::RegOpnd * optionalDstOpnd /* = nullptr */) |
| { |
| if (this->m_func->GetJITFunctionBody()->IsCoroutine()) |
| { |
| IR::RegOpnd * argPtrRegOpnd = Lowerer::LoadGeneratorArgsPtr(instrInsert); |
| IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(argPtrRegOpnd, 1 * MachPtr, TyMachPtr, this->m_func); |
| IR::RegOpnd * dstOpnd = optionalDstOpnd != nullptr ? optionalDstOpnd : IR::RegOpnd::New(TyMachPtr, this->m_func); |
| |
| return Lowerer::InsertLea(dstOpnd, indirOpnd, instrInsert); |
| } |
| else |
| { |
| StackSym * paramSym = GetImplicitParamSlotSym(3); |
| return this->m_lowerer->InsertLoadStackAddress(paramSym, instrInsert); |
| } |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::LoadInputParamCount |
| /// |
| /// Load the passed-in parameter count from the appropriate slot. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::LoadInputParamCount(IR::Instr * instrInsert, int adjust, bool needFlags) |
| { |
| // LDR Rz, CallInfo |
| // UBFX Rx, Rz, 27, #1 // Get CallEval bit. |
| // UBFX Rz, Rz, 0, #24 // Extract call count |
| // SUB Rz, Rz, Rx // Now Rz has the right number of parameters |
| |
| IR::SymOpnd * srcOpnd = Lowerer::LoadCallInfo(instrInsert); |
| IR::RegOpnd * dstOpnd = IR::RegOpnd::New(TyMachReg, this->m_func); |
| |
| IR::Instr *instr = IR::Instr::New(Js::OpCode::LDR, dstOpnd, srcOpnd, this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| // Get the actual call count. On ARM64 top 32 bits are unused |
| instr = IR::Instr::New(Js::OpCode::UBFX, dstOpnd, dstOpnd, IR::IntConstOpnd::New(BITFIELD(0, Js::CallInfo::ksizeofCount), TyMachReg, this->m_func), this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| return Lowerer::InsertSub(needFlags, dstOpnd, dstOpnd, IR::IntConstOpnd::New(-adjust, TyUint32, this->m_func), instrInsert); |
| } |
| |
| IR::Instr * |
| LowererMD::LoadStackArgPtr(IR::Instr * instr) |
| { |
| if (this->m_func->IsLoopBody()) |
| { |
| // Get the first user param from the interpreter frame instance that was passed in. |
| // These args don't include the func object and callinfo; we just need to advance past "this". |
| |
| // t1 = LDR [prm1 + m_inParams] |
| // dst = ADD t1, sizeof(var) |
| |
| Assert(this->m_func->GetLoopParamSym()); |
| IR::RegOpnd *baseOpnd = IR::RegOpnd::New(this->m_func->GetLoopParamSym(), TyMachReg, this->m_func); |
| size_t offset = Js::InterpreterStackFrame::GetOffsetOfInParams(); |
| IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(baseOpnd, (int32)offset, TyMachReg, this->m_func); |
| IR::RegOpnd *tmpOpnd = IR::RegOpnd::New(TyMachReg, this->m_func); |
| Lowerer::InsertMove(tmpOpnd, indirOpnd, instr); |
| |
| instr->SetSrc1(tmpOpnd); |
| instr->SetSrc2(IR::IntConstOpnd::New(sizeof(Js::Var), TyMachReg, this->m_func)); |
| } |
| else if (this->m_func->GetJITFunctionBody()->IsCoroutine()) |
| { |
| IR::Instr *instr2 = LoadInputParamPtr(instr, instr->UnlinkDst()->AsRegOpnd()); |
| instr->Remove(); |
| instr = instr2; |
| } |
| else |
| { |
| // Get the args pointer relative to fp. We assume that fp is set up, since we'll only be looking |
| // for the stack arg pointer in a non-leaf. |
| |
| // dst = ADD r11, "this" offset + sizeof(var) |
| |
| instr->SetSrc1(IR::RegOpnd::New(nullptr, FRAME_REG, TyMachReg, this->m_func)); |
| instr->SetSrc2(IR::IntConstOpnd::New((ArgOffsetFromFramePtr + Js::JavascriptFunctionArgIndex_SecondScriptArg) * sizeof(Js::Var), TyMachReg, this->m_func)); |
| } |
| |
| instr->m_opcode = Js::OpCode::ADD; |
| Legalize(instr); |
| |
| return instr->m_prev; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadArgumentsFromFrame(IR::Instr * instr) |
| { |
| IR::RegOpnd *baseOpnd; |
| int32 offset; |
| |
| if (this->m_func->IsLoopBody()) |
| { |
| // Get the arguments ptr from the interpreter frame instance that was passed in. |
| Assert(this->m_func->GetLoopParamSym()); |
| baseOpnd = IR::RegOpnd::New(this->m_func->GetLoopParamSym(), TyMachReg, this->m_func); |
| offset = Js::InterpreterStackFrame::GetOffsetOfArguments(); |
| } |
| else |
| { |
| // Get the arguments relative to the frame pointer. |
| baseOpnd = IR::RegOpnd::New(nullptr, FRAME_REG, TyMachReg, this->m_func); |
| offset = -MachArgsSlotOffset; |
| } |
| |
| instr->SetSrc1(IR::IndirOpnd::New(baseOpnd, offset, TyMachReg, this->m_func)); |
| this->ChangeToAssign(instr); |
| |
| return instr->m_prev; |
| } |
| |
| // load argument count as I4 |
| IR::Instr * |
| LowererMD::LoadArgumentCount(IR::Instr * instr) |
| { |
| IR::RegOpnd *baseOpnd; |
| int32 offset; |
| |
| if (this->m_func->IsLoopBody()) |
| { |
| // Pull the arg count from the interpreter frame instance that was passed in. |
| // (The callinfo in the loop body's frame just shows the single parameter, the interpreter frame.) |
| Assert(this->m_func->GetLoopParamSym()); |
| baseOpnd = IR::RegOpnd::New(this->m_func->GetLoopParamSym(), TyMachReg, this->m_func); |
| offset = Js::InterpreterStackFrame::GetOffsetOfInSlotsCount(); |
| } |
| else |
| { |
| baseOpnd = IR::RegOpnd::New(nullptr, FRAME_REG, TyMachReg, this->m_func); |
| offset = (ArgOffsetFromFramePtr + Js::JavascriptFunctionArgIndex_CallInfo) * sizeof(Js::Var); |
| } |
| |
| instr->SetSrc1(IR::IndirOpnd::New(baseOpnd, offset, TyInt32, this->m_func)); |
| this->ChangeToAssign(instr); |
| |
| return instr->m_prev; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::LoadHeapArguments |
| /// |
| /// Load the arguments object |
| /// NOTE: The same caveat regarding arguments passed on the stack applies here |
| /// as in LoadInputParamCount above. |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::LoadHeapArguments(IR::Instr * instrArgs) |
| { |
| ASSERT_INLINEE_FUNC(instrArgs); |
| Func *func = instrArgs->m_func; |
| IR::Instr * instrPrev = instrArgs->m_prev; |
| |
| if (func->IsStackArgsEnabled()) |
| { |
| // The initial args slot value is zero. |
| instrArgs->m_opcode = Js::OpCode::LDIMM; |
| instrArgs->ReplaceSrc1(IR::AddrOpnd::NewNull(func)); |
| if (PHASE_TRACE1(Js::StackArgFormalsOptPhase) && func->GetJITFunctionBody()->GetInParamsCount() > 1) |
| { |
| Output::Print(_u("StackArgFormals : %s (%d) :Removing Heap Arguments object creation in Lowerer. \n"), instrArgs->m_func->GetJITFunctionBody()->GetDisplayName(), instrArgs->m_func->GetFunctionNumber()); |
| Output::Flush(); |
| } |
| } |
| else |
| { |
| // s7 = formals are let decls |
| // s6 = memory context |
| // s5 = array of property ID's |
| // s4 = local frame instance |
| // s3 = address of first actual argument (after "this") |
| // s2 = actual argument count |
| // s1 = current function |
| // dst = JavascriptOperators::LoadHeapArguments(s1, s2, s3, s4, s5, s6, s7) |
| |
| // s7 = formals are let decls |
| this->LoadHelperArgument(instrArgs, IR::IntConstOpnd::New(instrArgs->m_opcode == Js::OpCode::LdLetHeapArguments ? TRUE : FALSE, TyUint8, func)); |
| |
| // s6 = memory context |
| this->m_lowerer->LoadScriptContext(instrArgs); |
| |
| // s5 = array of property ID's |
| |
| intptr_t formalsPropIdArray = instrArgs->m_func->GetJITFunctionBody()->GetFormalsPropIdArrayAddr(); |
| if (!formalsPropIdArray) |
| { |
| formalsPropIdArray = instrArgs->m_func->GetScriptContextInfo()->GetNullAddr(); |
| } |
| |
| IR::Opnd * argArray = IR::AddrOpnd::New(formalsPropIdArray, IR::AddrOpndKindDynamicMisc, m_func); |
| this->LoadHelperArgument(instrArgs, argArray); |
| |
| // s4 = local frame instance |
| IR::Opnd * frameObj = instrArgs->UnlinkSrc1(); |
| this->LoadHelperArgument(instrArgs, frameObj); |
| |
| if (func->IsInlinee()) |
| { |
| // s3 = address of first actual argument (after "this"). |
| StackSym *firstRealArgSlotSym = func->GetInlineeArgvSlotOpnd()->m_sym->AsStackSym(); |
| this->m_func->SetArgOffset(firstRealArgSlotSym, firstRealArgSlotSym->m_offset + MachPtr); |
| IR::Instr *instr = this->m_lowerer->InsertLoadStackAddress(firstRealArgSlotSym, instrArgs); |
| this->LoadHelperArgument(instrArgs, instr->GetDst()); |
| |
| // s2 = actual argument count (without counting "this"). |
| this->LoadHelperArgument(instrArgs, IR::IntConstOpnd::New(func->actualCount - 1, TyUint32, func)); |
| |
| // s1 = current function. |
| this->LoadHelperArgument(instrArgs, func->GetInlineeFunctionObjectSlotOpnd()); |
| |
| // Save the newly-created args object to its dedicated stack slot. |
| IR::SymOpnd *argObjSlotOpnd = func->GetInlineeArgumentsObjectSlotOpnd(); |
| Lowerer::InsertMove(argObjSlotOpnd,instrArgs->GetDst(), instrArgs->m_next); |
| } |
| else |
| { |
| // s3 = address of first actual argument (after "this") |
| // Stack looks like (function object)+0, (arg count)+4, (this)+8, actual args |
| IR::Instr * instr = this->LoadInputParamPtr(instrArgs); |
| this->LoadHelperArgument(instrArgs, instr->GetDst()); |
| |
| // s2 = actual argument count (without counting "this") |
| instr = this->LoadInputParamCount(instrArgs, -1); |
| IR::Opnd * opndInputParamCount = instr->GetDst(); |
| |
| this->LoadHelperArgument(instrArgs, opndInputParamCount); |
| |
| // s1 = current function |
| StackSym * paramSym = GetImplicitParamSlotSym(0); |
| IR::Opnd * srcOpnd = IR::SymOpnd::New(paramSym, TyMachReg, func); |
| this->LoadHelperArgument(instrArgs, srcOpnd); |
| |
| // Save the newly-created args object to its dedicated stack slot. |
| Lowerer::InsertMove(LowererMD::CreateStackArgumentsSlotOpnd(func), instrArgs->GetDst(), instrArgs->m_next); |
| } |
| this->ChangeToHelperCall(instrArgs, IR::HelperOp_LoadHeapArguments); |
| } |
| return instrPrev; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::LoadHeapArgsCached |
| /// |
| /// Load the heap-based arguments object using a cached scope |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::LoadHeapArgsCached(IR::Instr * instrArgs) |
| { |
| Assert(!this->m_func->GetJITFunctionBody()->IsGenerator()); |
| ASSERT_INLINEE_FUNC(instrArgs); |
| Func *func = instrArgs->m_func; |
| IR::Instr * instrPrev = instrArgs->m_prev; |
| |
| if (instrArgs->m_func->IsStackArgsEnabled()) |
| { |
| instrArgs->m_opcode = Js::OpCode::LDIMM; |
| instrArgs->ReplaceSrc1(IR::AddrOpnd::NewNull(func)); |
| |
| if (PHASE_TRACE1(Js::StackArgFormalsOptPhase) && func->GetJITFunctionBody()->GetInParamsCount() > 1) |
| { |
| Output::Print(_u("StackArgFormals : %s (%d) :Removing Heap Arguments object creation in Lowerer. \n"), instrArgs->m_func->GetJITFunctionBody()->GetDisplayName(), instrArgs->m_func->GetFunctionNumber()); |
| Output::Flush(); |
| } |
| } |
| else |
| { |
| // s7 = formals are let decls |
| // s6 = memory context |
| // s5 = local frame instance |
| // s4 = address of first actual argument (after "this") |
| // s3 = formal argument count |
| // s2 = actual argument count |
| // s1 = current function |
| // dst = JavascriptOperators::LoadHeapArgsCached(s1, s2, s3, s4, s5, s6, s7) |
| |
| |
| // s7 = formals are let decls |
| IR::Opnd * formalsAreLetDecls = IR::IntConstOpnd::New((IntConstType)(instrArgs->m_opcode == Js::OpCode::LdLetHeapArgsCached), TyUint8, func); |
| this->LoadHelperArgument(instrArgs, formalsAreLetDecls); |
| |
| // s6 = memory context |
| this->m_lowerer->LoadScriptContext(instrArgs); |
| |
| // s5 = local frame instance |
| IR::Opnd * frameObj = instrArgs->UnlinkSrc1(); |
| this->LoadHelperArgument(instrArgs, frameObj); |
| |
| if (func->IsInlinee()) |
| { |
| // s4 = address of first actual argument (after "this"). |
| StackSym *firstRealArgSlotSym = func->GetInlineeArgvSlotOpnd()->m_sym->AsStackSym(); |
| this->m_func->SetArgOffset(firstRealArgSlotSym, firstRealArgSlotSym->m_offset + MachPtr); |
| |
| IR::Instr *instr = this->m_lowerer->InsertLoadStackAddress(firstRealArgSlotSym, instrArgs); |
| this->LoadHelperArgument(instrArgs, instr->GetDst()); |
| |
| // s3 = formal argument count (without counting "this"). |
| uint32 formalsCount = func->GetJITFunctionBody()->GetInParamsCount() - 1; |
| this->LoadHelperArgument(instrArgs, IR::IntConstOpnd::New(formalsCount, TyUint32, func)); |
| |
| // s2 = actual argument count (without counting "this"). |
| this->LoadHelperArgument(instrArgs, IR::IntConstOpnd::New(func->actualCount - 1, TyUint32, func)); |
| |
| // s1 = current function. |
| this->LoadHelperArgument(instrArgs, func->GetInlineeFunctionObjectSlotOpnd()); |
| |
| // Save the newly-created args object to its dedicated stack slot. |
| IR::SymOpnd *argObjSlotOpnd = func->GetInlineeArgumentsObjectSlotOpnd(); |
| Lowerer::InsertMove(argObjSlotOpnd, instrArgs->GetDst(), instrArgs->m_next); |
| } |
| else |
| { |
| // s4 = address of first actual argument (after "this") |
| IR::Instr * instr = this->LoadInputParamPtr(instrArgs); |
| this->LoadHelperArgument(instrArgs, instr->GetDst()); |
| |
| // s3 = formal argument count (without counting "this") |
| uint32 formalsCount = func->GetInParamsCount() - 1; |
| this->LoadHelperArgument(instrArgs, IR::IntConstOpnd::New(formalsCount, TyMachReg, func)); |
| |
| // s2 = actual argument count (without counting "this") |
| instr = this->LoadInputParamCount(instrArgs, -1); |
| this->LoadHelperArgument(instrArgs, instr->GetDst()); |
| |
| // s1 = current function |
| StackSym * paramSym = GetImplicitParamSlotSym(0); |
| IR::Opnd * srcOpnd = IR::SymOpnd::New(paramSym, TyMachReg, func); |
| this->LoadHelperArgument(instrArgs, srcOpnd); |
| |
| |
| // Save the newly-created args object to its dedicated stack slot. |
| Lowerer::InsertMove(LowererMD::CreateStackArgumentsSlotOpnd(func), instrArgs->GetDst(), instrArgs->m_next); |
| |
| } |
| |
| this->ChangeToHelperCall(instrArgs, IR::HelperOp_LoadHeapArgsCached); |
| } |
| return instrPrev; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::ChangeToHelperCall |
| /// |
| /// Change the current instruction to a call to the given helper. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::ChangeToHelperCall(IR::Instr * callInstr, IR::JnHelperMethod helperMethod, IR::LabelInstr *labelBailOut, |
| IR::Opnd *opndInstance, IR::PropertySymOpnd *propSymOpnd, bool isHelperContinuation) |
| { |
| #if DBG |
| this->m_lowerer->ReconcileWithLowererStateOnHelperCall(callInstr, helperMethod); |
| #endif |
| IR::Instr * bailOutInstr = callInstr; |
| if (callInstr->HasBailOutInfo()) |
| { |
| const IR::BailOutKind bailOutKind = callInstr->GetBailOutKind(); |
| if (bailOutKind == IR::BailOutOnNotPrimitive) |
| { |
| callInstr = IR::Instr::New(callInstr->m_opcode, callInstr->m_func); |
| bailOutInstr->TransferTo(callInstr); |
| bailOutInstr->InsertBefore(callInstr); |
| |
| bailOutInstr->m_opcode = Js::OpCode::BailOnNotPrimitive; |
| bailOutInstr->SetSrc1(opndInstance); |
| } |
| else if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) |
| { |
| bailOutInstr = this->m_lowerer->SplitBailOnImplicitCall(callInstr); |
| } |
| else |
| { |
| AssertMsg(false, "Unexpected BailOutKind, are we adding new BailOutKind on instructions?"); |
| } |
| } |
| |
| IR::HelperCallOpnd *helperCallOpnd = Lowerer::CreateHelperCallOpnd(helperMethod, this->GetHelperArgsCount(), m_func); |
| if (helperCallOpnd->IsDiagHelperCallOpnd()) |
| { |
| // Load arguments for the wrapper. |
| this->LoadHelperArgument(callInstr, IR::AddrOpnd::New((Js::Var)IR::GetMethodOriginalAddress(m_func->GetThreadContextInfo(), helperMethod), IR::AddrOpndKindDynamicMisc, m_func)); |
| this->m_lowerer->LoadScriptContext(callInstr); |
| } |
| callInstr->SetSrc1(helperCallOpnd); |
| |
| IR::Instr * instrRet = this->LowerCall(callInstr, 0); |
| |
| if (bailOutInstr != callInstr) |
| { |
| // The bailout needs to be lowered after we lower the helper call because the helper argument |
| // has already been loaded. We need to drain them on AMD64 before starting another helper call |
| if (bailOutInstr->m_opcode == Js::OpCode::BailOnNotPrimitive) |
| { |
| this->m_lowerer->LowerBailOnTrue(bailOutInstr, labelBailOut); |
| } |
| else if (bailOutInstr->m_opcode == Js::OpCode::BailOnNotEqual) |
| { |
| // `SplitBailOnImplicitCall` above changes the opcode to BailOnNotEqual |
| Assert(BailOutInfo::IsBailOutOnImplicitCalls(bailOutInstr->GetBailOutKind())); |
| this->m_lowerer->LowerBailOnEqualOrNotEqual(bailOutInstr, nullptr, labelBailOut, propSymOpnd, isHelperContinuation); |
| } |
| else |
| { |
| AssertMsg(false, "Unexpected OpCode for BailOutInstruction"); |
| } |
| } |
| |
| return instrRet; |
| } |
| |
| IR::Instr* LowererMD::ChangeToHelperCallMem(IR::Instr * instr, IR::JnHelperMethod helperMethod) |
| { |
| this->m_lowerer->LoadScriptContext(instr); |
| |
| return this->ChangeToHelperCall(instr, helperMethod); |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::ChangeToAssign |
| /// |
| /// Change to a copy. Handle riscification of operands. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| // ToDo (SaAgarwa) Copied from ARM32 to compile. Validate is this correct |
| IR::Instr * |
| LowererMD::ChangeToAssignNoBarrierCheck(IR::Instr * instr) |
| { |
| return ChangeToAssign(instr, instr->GetDst()->GetType()); |
| } |
| |
| IR::Instr * |
| LowererMD::ChangeToAssign(IR::Instr * instr) |
| { |
| return ChangeToAssign(instr, instr->GetDst()->GetType()); |
| } |
| |
| IR::Instr * |
| LowererMD::ChangeToAssign(IR::Instr * instr, IRType destType) |
| { |
| Assert(!instr->HasBailOutInfo() || instr->GetBailOutKind() == IR::BailOutExpectingInteger |
| || instr->GetBailOutKind() == IR::BailOutExpectingString); |
| |
| IR::Opnd *src = instr->GetSrc1(); |
| IRType srcType = src->GetType(); |
| if (src->IsImmediateOpnd() || src->IsLabelOpnd()) |
| { |
| instr->m_opcode = Js::OpCode::LDIMM; |
| } |
| else if(destType == TyFloat32 && instr->GetDst()->IsRegOpnd()) |
| { |
| Assert(instr->GetSrc1()->IsFloat32()); |
| instr->m_opcode = Js::OpCode::FLDR; |
| |
| // Note that we allocate double register for single precision floats as well, as the register allocator currently |
| // does not support 32-bit float registers |
| instr->ReplaceDst(instr->GetDst()->UseWithNewType(TyFloat64, instr->m_func)); |
| if(instr->GetSrc1()->IsRegOpnd()) |
| { |
| instr->ReplaceSrc1(instr->GetSrc1()->UseWithNewType(TyFloat64, instr->m_func)); |
| } |
| } |
| else if (!src->IsIndirOpnd() && TySize[destType] > TySize[srcType] && (IRType_IsSignedInt(destType) || IRType_IsUnsignedInt(destType))) |
| { |
| // If we're moving between different lengths of registers, we need to use the |
| // right operator - sign extend if the source is int, zero extend if uint. |
| if (IRType_IsSignedInt(srcType)) |
| { |
| instr->ReplaceSrc1(src->UseWithNewType(IRType_EnsureSigned(destType), instr->m_func)); |
| instr->SetSrc2(IR::IntConstOpnd::New(BITFIELD(0, TySize[srcType] * MachBits), TyMachReg, instr->m_func, true)); |
| instr->m_opcode = Js::OpCode::SBFX; |
| } |
| else if (IRType_IsUnsignedInt(srcType)) |
| { |
| instr->ReplaceSrc1(src->UseWithNewType(IRType_EnsureUnsigned(destType), instr->m_func)); |
| instr->SetSrc2(IR::IntConstOpnd::New(BITFIELD(0, TySize[srcType] * MachBits), TyMachReg, instr->m_func, true)); |
| instr->m_opcode = Js::OpCode::UBFX; |
| } |
| else |
| { |
| AssertMsg(false, "argument size mismatch for mov instruction, with non int/uint types!"); |
| } |
| } |
| else |
| { |
| instr->m_opcode = IRType_IsFloat(destType) ? Js::OpCode::FMOV : Js::OpCode::MOV; |
| } |
| |
| AutoRestoreLegalize restore(instr->m_func, false); |
| LegalizeMD::LegalizeInstr(instr); |
| |
| return instr; |
| } |
| |
| IR::Instr * |
| LowererMD::ChangeToWriteBarrierAssign(IR::Instr * assignInstr, const Func* func) |
| { |
| #ifdef RECYCLER_WRITE_BARRIER_JIT |
| // WriteBarrier-TODO- Implement ARM JIT |
| #endif |
| return ChangeToAssignNoBarrierCheck(assignInstr); |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::LowerRet |
| /// |
| /// Lower Ret to "MOV EAX, src" |
| /// The real RET is inserted at the exit of the function when emitting the |
| /// epilog. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::LowerRet(IR::Instr * retInstr) |
| { |
| IR::RegOpnd *retReg = IR::RegOpnd::New(TyMachReg, m_func); |
| retReg->SetReg(RETURN_REG); |
| Lowerer::InsertMove(retReg, retInstr->UnlinkSrc1(), retInstr); |
| |
| retInstr->SetSrc1(retReg); |
| |
| return retInstr; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::MDBranchOpcode |
| /// |
| /// Map HIR branch opcode to machine-dependent equivalent. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| Js::OpCode |
| LowererMD::MDBranchOpcode(Js::OpCode opcode) |
| { |
| switch (opcode) |
| { |
| case Js::OpCode::BrEq_A: |
| case Js::OpCode::BrSrEq_A: |
| case Js::OpCode::BrNotNeq_A: |
| case Js::OpCode::BrSrNotNeq_A: |
| case Js::OpCode::BrAddr_A: |
| return Js::OpCode::BEQ; |
| |
| case Js::OpCode::BrNeq_A: |
| case Js::OpCode::BrSrNeq_A: |
| case Js::OpCode::BrNotEq_A: |
| case Js::OpCode::BrSrNotEq_A: |
| case Js::OpCode::BrNotAddr_A: |
| return Js::OpCode::BNE; |
| |
| case Js::OpCode::BrLt_A: |
| case Js::OpCode::BrNotGe_A: |
| return Js::OpCode::BLT; |
| |
| case Js::OpCode::BrLe_A: |
| case Js::OpCode::BrNotGt_A: |
| return Js::OpCode::BLE; |
| |
| case Js::OpCode::BrGt_A: |
| case Js::OpCode::BrNotLe_A: |
| return Js::OpCode::BGT; |
| |
| case Js::OpCode::BrGe_A: |
| case Js::OpCode::BrNotLt_A: |
| return Js::OpCode::BGE; |
| |
| case Js::OpCode::BrUnGt_A: |
| return Js::OpCode::BHI; |
| |
| case Js::OpCode::BrUnGe_A: |
| return Js::OpCode::BCS; |
| |
| case Js::OpCode::BrUnLt_A: |
| return Js::OpCode::BCC; |
| |
| case Js::OpCode::BrUnLe_A: |
| return Js::OpCode::BLS; |
| |
| default: |
| AssertMsg(0, "NYI"); |
| return opcode; |
| } |
| } |
| |
| Js::OpCode |
| LowererMD::MDUnsignedBranchOpcode(Js::OpCode opcode) |
| { |
| switch (opcode) |
| { |
| case Js::OpCode::BrEq_A: |
| case Js::OpCode::BrSrEq_A: |
| case Js::OpCode::BrSrNotNeq_A: |
| case Js::OpCode::BrNotNeq_A: |
| case Js::OpCode::BrAddr_A: |
| return Js::OpCode::BEQ; |
| |
| case Js::OpCode::BrNeq_A: |
| case Js::OpCode::BrSrNeq_A: |
| case Js::OpCode::BrSrNotEq_A: |
| case Js::OpCode::BrNotEq_A: |
| case Js::OpCode::BrNotAddr_A: |
| return Js::OpCode::BNE; |
| |
| case Js::OpCode::BrLt_A: |
| case Js::OpCode::BrNotGe_A: |
| return Js::OpCode::BCC; |
| |
| case Js::OpCode::BrLe_A: |
| case Js::OpCode::BrNotGt_A: |
| return Js::OpCode::BLS; |
| |
| case Js::OpCode::BrGt_A: |
| case Js::OpCode::BrNotLe_A: |
| return Js::OpCode::BHI; |
| |
| case Js::OpCode::BrGe_A: |
| case Js::OpCode::BrNotLt_A: |
| return Js::OpCode::BCS; |
| |
| default: |
| AssertMsg(0, "NYI"); |
| return opcode; |
| } |
| } |
| |
| Js::OpCode LowererMD::MDCompareWithZeroBranchOpcode(Js::OpCode opcode) |
| { |
| Assert(opcode == Js::OpCode::BrLt_A || opcode == Js::OpCode::BrGe_A); |
| return opcode == Js::OpCode::BrLt_A ? Js::OpCode::BMI : Js::OpCode::BPL; |
| } |
| |
| void LowererMD::ChangeToAdd(IR::Instr *const instr, const bool needFlags) |
| { |
| Assert(instr); |
| Assert(instr->GetDst()); |
| Assert(instr->GetSrc1()); |
| Assert(instr->GetSrc2()); |
| |
| if(instr->GetDst()->IsFloat64()) |
| { |
| Assert(instr->GetSrc1()->IsFloat64()); |
| Assert(instr->GetSrc2()->IsFloat64()); |
| Assert(!needFlags); |
| instr->m_opcode = Js::OpCode::FADD; |
| return; |
| } |
| |
| instr->m_opcode = needFlags ? Js::OpCode::ADDS : Js::OpCode::ADD; |
| } |
| |
| void LowererMD::ChangeToSub(IR::Instr *const instr, const bool needFlags) |
| { |
| Assert(instr); |
| Assert(instr->GetDst()); |
| Assert(instr->GetSrc1()); |
| Assert(instr->GetSrc2()); |
| |
| if(instr->GetDst()->IsFloat64()) |
| { |
| Assert(instr->GetSrc1()->IsFloat64()); |
| Assert(instr->GetSrc2()->IsFloat64()); |
| Assert(!needFlags); |
| instr->m_opcode = Js::OpCode::FSUB; |
| return; |
| } |
| |
| instr->m_opcode = needFlags ? Js::OpCode::SUBS : Js::OpCode::SUB; |
| } |
| |
| void LowererMD::ChangeToShift(IR::Instr *const instr, const bool needFlags) |
| { |
| Assert(instr); |
| Assert(instr->GetDst()); |
| Assert(instr->GetSrc1()); |
| Assert(instr->GetSrc2()); |
| |
| Func *const func = instr->m_func; |
| |
| switch(instr->m_opcode) |
| { |
| case Js::OpCode::Shl_A: |
| case Js::OpCode::Shl_I4: |
| Assert(!needFlags); // not implemented |
| instr->m_opcode = Js::OpCode::LSL; |
| break; |
| |
| case Js::OpCode::Shr_A: |
| case Js::OpCode::Shr_I4: |
| Assert(!needFlags); // not implemented |
| instr->m_opcode = Js::OpCode::ASR; |
| break; |
| |
| case Js::OpCode::ShrU_A: |
| case Js::OpCode::ShrU_I4: |
| Assert(!needFlags); // not implemented |
| instr->m_opcode = Js::OpCode::LSR; |
| break; |
| |
| default: |
| Assert(false); |
| __assume(false); |
| } |
| |
| // Javascript requires the ShiftCount is masked to the bottom 5 bits. |
| uint8 mask = TySize[instr->GetDst()->GetType()] == 8 ? 63 : 31; |
| if (instr->GetSrc2()->IsIntConstOpnd()) |
| { |
| // In the constant case, do the mask manually. |
| IntConstType immed = instr->GetSrc2()->AsIntConstOpnd()->GetValue() & mask; |
| if (immed == 0) |
| { |
| // Shift by zero is just a move, and the shift-right instructions |
| // don't permit encoding of a zero shift amount. |
| instr->m_opcode = Js::OpCode::MOV; |
| instr->FreeSrc2(); |
| } |
| else |
| { |
| instr->GetSrc2()->AsIntConstOpnd()->SetValue(immed); |
| } |
| } |
| else |
| { |
| // In the variable case, generate code to do the mask. |
| IR::Opnd *const src2 = instr->UnlinkSrc2(); |
| instr->SetSrc2(IR::RegOpnd::New(src2->GetType(), func)); |
| IR::Instr *const newInstr = IR::Instr::New( |
| Js::OpCode::AND, instr->GetSrc2(), src2, IR::IntConstOpnd::New(mask, TyInt8, func), func); |
| instr->InsertBefore(newInstr); |
| } |
| } |
| |
| const uint16 |
| LowererMD::GetFormalParamOffset() |
| { |
| //In ARM formal params are offset into the param area. |
| //So we only count the non-user params (Function object & CallInfo and let the encoder account for the saved R11 and LR |
| return 2; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::LowerCondBranch |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::LowerCondBranch(IR::Instr * instr) |
| { |
| AssertMsg(instr->GetSrc1() != nullptr, "Expected src opnds on conditional branch"); |
| |
| IR::Opnd * opndSrc1 = instr->UnlinkSrc1(); |
| IR::Instr * instrPrev = nullptr; |
| |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::BrTrue_A: |
| case Js::OpCode::BrOnNotEmpty: |
| case Js::OpCode::BrNotNull_A: |
| case Js::OpCode::BrOnObject_A: |
| case Js::OpCode::BrOnObjectOrNull_A: |
| case Js::OpCode::BrOnConstructor_A: |
| case Js::OpCode::BrOnClassConstructor: |
| case Js::OpCode::BrOnBaseConstructorKind: |
| Assert(!opndSrc1->IsFloat64()); |
| AssertMsg(opndSrc1->IsRegOpnd(),"NYI for other operands"); |
| AssertMsg(instr->GetSrc2() == nullptr, "Expected 1 src on boolean branch"); |
| instrPrev = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| instrPrev->SetSrc1(opndSrc1); |
| instrPrev->SetSrc2(IR::IntConstOpnd::New(0, TyInt32, m_func)); |
| instr->InsertBefore(instrPrev); |
| LegalizeMD::LegalizeInstr(instrPrev); |
| |
| instr->m_opcode = Js::OpCode::BNE; |
| |
| break; |
| case Js::OpCode::BrFalse_A: |
| case Js::OpCode::BrOnEmpty: |
| Assert(!opndSrc1->IsFloat64()); |
| AssertMsg(opndSrc1->IsRegOpnd(),"NYI for other operands"); |
| AssertMsg(instr->GetSrc2() == nullptr, "Expected 1 src on boolean branch"); |
| instrPrev = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| instrPrev->SetSrc1(opndSrc1); |
| instrPrev->SetSrc2(IR::IntConstOpnd::New(0, TyInt32, m_func)); |
| instr->InsertBefore(instrPrev); |
| LegalizeMD::LegalizeInstr(instrPrev); |
| |
| instr->m_opcode = Js::OpCode::BEQ; |
| |
| break; |
| |
| default: |
| IR::Opnd * opndSrc2 = instr->UnlinkSrc2(); |
| AssertMsg(opndSrc2 != nullptr, "Expected 2 src's on non-boolean branch"); |
| |
| if (opndSrc1->IsFloat64()) |
| { |
| AssertMsg(opndSrc1->IsRegOpnd(),"NYI for other operands"); |
| Assert(opndSrc2->IsFloat64()); |
| Assert(opndSrc2->IsRegOpnd() && opndSrc1->IsRegOpnd()); |
| //This comparison updates the FPSCR - floating point status control register |
| instrPrev = IR::Instr::New(Js::OpCode::FCMP, this->m_func); |
| instrPrev->SetSrc1(opndSrc1); |
| instrPrev->SetSrc2(opndSrc2); |
| instr->InsertBefore(instrPrev); |
| LegalizeMD::LegalizeInstr(instrPrev); |
| |
| instr->m_opcode = LowererMD::MDBranchOpcode(instr->m_opcode); |
| } |
| else |
| { |
| AssertMsg(opndSrc2->IsRegOpnd() || opndSrc2->IsIntConstOpnd() || (opndSrc2->IsAddrOpnd()), "NYI for other operands"); |
| |
| instrPrev = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| instrPrev->SetSrc1(opndSrc1); |
| instrPrev->SetSrc2(opndSrc2); |
| instr->InsertBefore(instrPrev); |
| |
| LegalizeMD::LegalizeInstr(instrPrev); |
| |
| instr->m_opcode = MDBranchOpcode(instr->m_opcode); |
| } |
| break; |
| } |
| return instr; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::ForceDstToReg |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr* |
| LowererMD::ForceDstToReg(IR::Instr *instr) |
| { |
| IR::Opnd * dst = instr->GetDst(); |
| |
| if (dst->IsRegOpnd()) |
| { |
| return instr; |
| } |
| |
| IR::Instr * newInstr = instr->SinkDst(Js::OpCode::Ld_A); |
| LowererMD::ChangeToAssign(newInstr); |
| return newInstr; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadFunctionObjectOpnd(IR::Instr *instr, IR::Opnd *&functionObjOpnd) |
| { |
| IR::Opnd * src1 = instr->GetSrc1(); |
| IR::Instr * instrPrev = instr->m_prev; |
| if (src1 == nullptr) |
| { |
| IR::RegOpnd * regOpnd = IR::RegOpnd::New(TyMachPtr, m_func); |
| //function object is first argument and mark it as IsParamSlotSym. |
| StackSym *paramSym = GetImplicitParamSlotSym(0); |
| IR::SymOpnd *paramOpnd = IR::SymOpnd::New(paramSym, TyMachPtr, m_func); |
| |
| instrPrev = Lowerer::InsertMove(regOpnd, paramOpnd, instr); |
| functionObjOpnd = instrPrev->GetDst(); |
| } |
| else |
| { |
| // Inlinee LdHomeObj, use the function object opnd on the instruction |
| functionObjOpnd = instr->UnlinkSrc1(); |
| if (!functionObjOpnd->IsRegOpnd()) |
| { |
| Assert(functionObjOpnd->IsAddrOpnd()); |
| } |
| } |
| |
| return instrPrev; |
| } |
| bool |
| LowererMD::GenerateFastDivAndRem(IR::Instr *instrDiv, IR::LabelInstr* bailOutLabel) |
| { |
| return false; |
| } |
| void |
| LowererMD::GenerateFastDivByPow2(IR::Instr *instrDiv) |
| { |
| //// Given: |
| //// dst = Div_A src1, src2 |
| //// where src2 == power of 2 |
| //// |
| //// Generate: |
| //// (observation: positive q divides by p equally, where p = power of 2, if q's binary representation |
| //// has all zeroes to the right of p's power 2 bit, try to see if that is the case) |
| //// s1 = AND src1, 0x80000001 | ((src2Value - 1) << 1) |
| //// CMP s1, 1 |
| //// BNE $doesntDivideEqually |
| //// s1 = ASR src1, log2(src2Value) -- do the equal divide |
| //// dst = EOR s1, 1 -- restore tagged int bit |
| //// B $done |
| //// $doesntDivideEqually: |
| //// (now check if it divides with the remainder of 1, for which we can do integer divide and accommodate with +0.5 |
| //// note that we need only the part that is to the left of p's power 2 bit) |
| //// s1 = AND s1, 0x80000001 | (src2Value - 1) |
| //// CMP s1, 1 |
| //// BNE $helper |
| //// s1 = ASR src1, log2(src2Value) + 1 -- do the integer divide and also shift out the tagged int bit |
| //// PUSH 0xXXXXXXXX (ScriptContext) |
| //// PUSH s1 |
| //// dst = CALL Op_FinishOddDivByPow2 -- input: actual value, scriptContext; output: JavascriptNumber with 0.5 added to the input |
| //// JMP $done |
| //// $helper: |
| //// ... |
| //// $done: |
| |
| //if (instrDiv->GetSrc1()->IsRegOpnd() && instrDiv->GetSrc1()->AsRegOpnd()->m_sym->m_isNotInt) |
| //{ |
| // return; |
| //} |
| |
| //IR::Opnd *dst = instrDiv->GetDst(); |
| //IR::Opnd *src1 = instrDiv->GetSrc1(); |
| //IR::AddrOpnd *src2 = instrDiv->GetSrc2()->IsAddrOpnd() ? instrDiv->GetSrc2()->AsAddrOpnd() : nullptr; |
| //IR::LabelInstr *doesntDivideEqually = IR::LabelInstr::New(Js::OpCode::Label, m_func); |
| //IR::LabelInstr *helper = IR::LabelInstr::New(Js::OpCode::Label, m_func, true); |
| //IR::LabelInstr *done = IR::LabelInstr::New(Js::OpCode::Label, m_func); |
| //IR::RegOpnd *s1 = IR::RegOpnd::New(TyVar, m_func); |
| //IR::Instr *instr; |
| |
| //Assert(src2 && src2->IsVar() && Js::TaggedInt::Is(src2->m_address) && (Math::IsPow2(Js::TaggedInt::ToInt32(src2->m_address)))); |
| //int32 src2Value = Js::TaggedInt::ToInt32(src2->m_address); |
| |
| //// s1 = AND src1, 0x80000001 | ((src2Value - 1) << 1) |
| //instr = IR::Instr::New(Js::OpCode::AND, s1, src1, IR::IntConstOpnd::New((0x80000001 | ((src2Value - 1) << 1)), TyInt32, m_func), m_func); |
| //instrDiv->InsertBefore(instr); |
| //LegalizeMD::LegalizeInstr(instr); |
| |
| //// CMP s1, 1 |
| //instr = IR::Instr::New(Js::OpCode::CMP, m_func); |
| //instr->SetSrc1(s1); |
| //instr->SetSrc2(IR::IntConstOpnd::New(1, TyInt32, m_func)); |
| //instrDiv->InsertBefore(instr); |
| |
| //// BNE $doesntDivideEqually |
| //instr = IR::BranchInstr::New(Js::OpCode::BNE, doesntDivideEqually, m_func); |
| //instrDiv->InsertBefore(instr); |
| |
| //// s1 = ASR src1, log2(src2Value) -- do the equal divide |
| //instr = IR::Instr::New(Js::OpCode::ASR, s1, src1, IR::IntConstOpnd::New(Math::Log2(src2Value), TyInt32, m_func), m_func); |
| //instrDiv->InsertBefore(instr); |
| //LegalizeMD::LegalizeInstr(instr); |
| |
| //// dst = ORR s1, 1 -- restore tagged int bit |
| //instr = IR::Instr::New(Js::OpCode::ORR, dst, s1, IR::IntConstOpnd::New(1, TyInt32, m_func), m_func); |
| //instrDiv->InsertBefore(instr); |
| //LegalizeMD::LegalizeInstr(instr); |
| // |
| //// B $done |
| //instr = IR::BranchInstr::New(Js::OpCode::B, done, m_func); |
| //instrDiv->InsertBefore(instr); |
| |
| //// $doesntDivideEqually: |
| //instrDiv->InsertBefore(doesntDivideEqually); |
| |
| //// s1 = AND s1, 0x80000001 | (src2Value - 1) |
| //instr = IR::Instr::New(Js::OpCode::AND, s1, s1, IR::IntConstOpnd::New((0x80000001 | (src2Value - 1)), TyInt32, m_func), m_func); |
| //instrDiv->InsertBefore(instr); |
| |
| //// CMP s1, 1 |
| //instr = IR::Instr::New(Js::OpCode::CMP, m_func); |
| //instr->SetSrc1(s1); |
| //instr->SetSrc2(IR::IntConstOpnd::New(1, TyInt32, m_func)); |
| //instrDiv->InsertBefore(instr); |
| |
| //// BNE $helper |
| //instrDiv->InsertBefore(IR::BranchInstr::New(Js::OpCode::BNE, helper, m_func)); |
| |
| //// s1 = ASR src1, log2(src2Value) + 1 -- do the integer divide and also shift out the tagged int bit |
| //instr = IR::Instr::New(Js::OpCode::ASR, s1, src1, IR::IntConstOpnd::New(Math::Log2(src2Value) + 1, TyInt32, m_func), m_func); |
| //instrDiv->InsertBefore(instr); |
| //LegalizeMD::LegalizeInstr(instr); |
| |
| //// Arg2: scriptContext |
| //IR::JnHelperMethod helperMethod; |
| //if (instrDiv->dstIsTempNumber) |
| //{ |
| // // Var JavascriptMath::FinishOddDivByPow2_InPlace(uint32 value, ScriptContext *scriptContext, _Out_ JavascriptNumber* result) |
| // helperMethod = IR::HelperOp_FinishOddDivByPow2InPlace; |
| // Assert(dst->IsRegOpnd()); |
| // StackSym * tempNumberSym = this->m_lowerer->GetTempNumberSym(dst, instr->dstIsTempNumberTransferred); |
| |
| // instr = this->m_lowerer->InsertLoadStackAddress(tempNumberSym, instrDiv); |
| // LegalizeMD::LegalizeInstr(instr); |
| |
| // this->LoadHelperArgument(instrDiv, instr->GetDst()); |
| //} |
| //else |
| //{ |
| // // Var JavascriptMath::FinishOddDivByPow2(uint32 value, ScriptContext *scriptContext) |
| // helperMethod = IR::HelperOp_FinishOddDivByPow2; |
| //} |
| //this->m_lowerer->LoadScriptContext(instrDiv); |
| |
| //// Arg1: value |
| //this->LoadHelperArgument(instrDiv, s1); |
| |
| //// dst = CALL Op_FinishOddDivByPow2 -- input: actual value, output: JavascriptNumber with 0.5 added to the input |
| //instr = IR::Instr::New(Js::OpCode::Call, dst, IR::HelperCallOpnd::New(helperMethod, m_func), m_func); |
| //instrDiv->InsertBefore(instr); |
| //this->LowerCall(instr, 0); |
| |
| //// JMP $done |
| //instrDiv->InsertBefore(IR::BranchInstr::New(Js::OpCode::B, done, m_func)); |
| |
| //// $helper: |
| //instrDiv->InsertBefore(helper); |
| |
| //// $done: |
| //instrDiv->InsertAfter(done); |
| |
| return; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastCmSrEqConst |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastCmSrXxConst(IR::Instr *instr) |
| { |
| // |
| // Given: |
| // s1 = CmSrXX_A s2, s3 |
| // where either s2 or s3 is 'null', 'true' or 'false' |
| // |
| // Generate: |
| // |
| // CMP s2, s3 |
| // JEQ $mov_res |
| // MOV s1, eq ? Library.GetFalse() : Library.GetTrue() |
| // JMP $done |
| // $mov_res: |
| // MOV s1, eq ? Library.GetTrue() : Library.GetFalse() |
| // $done: |
| // |
| |
| Assert(m_lowerer->IsConstRegOpnd(instr->GetSrc2()->AsRegOpnd())); |
| return false; |
| } |
| |
| void LowererMD::GenerateFastCmXxI4(IR::Instr *instr) |
| { |
| this->GenerateFastCmXx(instr); |
| } |
| |
| void LowererMD::GenerateFastCmXxR8(IR::Instr *instr) |
| { |
| this->GenerateFastCmXx(instr); |
| } |
| |
| void LowererMD::GenerateFastCmXx(IR::Instr *instr) |
| { |
| // For float src: |
| // LDIMM dst, trueResult |
| // FCMP src1, src2 |
| // - BVS $done (NaN check iff B.cond is BNE) |
| // B.cond $done |
| // LDIMM dst, falseResult |
| // $done |
| |
| // For Int src: |
| // LDIMM dst, trueResult |
| // CMP src1, src2 |
| // B.cond $done |
| // LDIMM dst, falseResult |
| // $done: |
| |
| IR::Opnd * src1 = instr->UnlinkSrc1(); |
| IR::Opnd * src2 = instr->UnlinkSrc2(); |
| IR::Opnd * dst = instr->UnlinkDst(); |
| bool isIntDst = dst->AsRegOpnd()->m_sym->IsInt32(); |
| bool isFloatSrc = src1->IsFloat(); |
| Assert(!isFloatSrc || src2->IsFloat()); |
| Assert(!src1->IsInt64() || src2->IsInt64()); |
| Assert(!isFloatSrc || AutoSystemInfo::Data.SSE2Available()); |
| Assert(src1->IsRegOpnd()); |
| IR::Opnd * opndTrue; |
| IR::Opnd * opndFalse; |
| IR::Instr * newInstr; |
| IR::LabelInstr * done = IR::LabelInstr::New(Js::OpCode::Label, m_func); |
| |
| if (dst->IsEqual(src1)) |
| { |
| IR::RegOpnd *newSrc1 = IR::RegOpnd::New(src1->GetType(), m_func); |
| Lowerer::InsertMove(newSrc1, src1, instr); |
| src1 = newSrc1; |
| } |
| |
| if (dst->IsEqual(src2)) |
| { |
| IR::RegOpnd *newSrc2 = IR::RegOpnd::New(src1->GetType(), m_func); |
| Lowerer::InsertMove(newSrc2, src2, instr); |
| src2 = newSrc2; |
| } |
| |
| if (isIntDst) |
| { |
| opndTrue = IR::IntConstOpnd::New(1, TyInt32, this->m_func); |
| opndFalse = IR::IntConstOpnd::New(0, TyInt32, this->m_func); |
| } |
| else |
| { |
| opndTrue = this->m_lowerer->LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue); |
| opndFalse = this->m_lowerer->LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse); |
| } |
| |
| Lowerer::InsertMove(dst, opndTrue, instr); |
| |
| // CMP src1, src2 |
| newInstr = IR::Instr::New(isFloatSrc ? Js::OpCode::FCMP : Js::OpCode::CMP, this->m_func); |
| newInstr->SetSrc1(src1); |
| newInstr->SetSrc2(src2); |
| instr->InsertBefore(newInstr); |
| LowererMD::Legalize(newInstr); |
| |
| bool addNaNCheck = false; |
| Js::OpCode opcode = Js::OpCode::InvalidOpCode; |
| |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::CmEq_A: |
| case Js::OpCode::CmSrEq_A: |
| case Js::OpCode::CmEq_I4: |
| opcode = Js::OpCode::BEQ; |
| break; |
| |
| case Js::OpCode::CmNeq_A: |
| case Js::OpCode::CmSrNeq_A: |
| case Js::OpCode::CmNeq_I4: |
| opcode = Js::OpCode::BNE; |
| addNaNCheck = isFloatSrc; |
| break; |
| |
| case Js::OpCode::CmGt_A: |
| case Js::OpCode::CmGt_I4: |
| opcode = Js::OpCode::BGT; |
| break; |
| |
| case Js::OpCode::CmGe_A: |
| case Js::OpCode::CmGe_I4: |
| opcode = Js::OpCode::BGE; |
| break; |
| |
| case Js::OpCode::CmLt_A: |
| case Js::OpCode::CmLt_I4: |
| //Can't use BLT as is set when the operands are unordered (NaN). |
| opcode = isFloatSrc ? Js::OpCode::BCC : Js::OpCode::BLT; |
| break; |
| |
| case Js::OpCode::CmLe_A: |
| case Js::OpCode::CmLe_I4: |
| //Can't use BLE as it is set when the operands are unordered (NaN). |
| opcode = isFloatSrc ? Js::OpCode::BLS : Js::OpCode::BLE; |
| break; |
| |
| case Js::OpCode::CmUnGt_A: |
| case Js::OpCode::CmUnGt_I4: |
| opcode = Js::OpCode::BHI; |
| break; |
| |
| case Js::OpCode::CmUnGe_A: |
| case Js::OpCode::CmUnGe_I4: |
| opcode = Js::OpCode::BCS; |
| break; |
| |
| case Js::OpCode::CmUnLt_A: |
| case Js::OpCode::CmUnLt_I4: |
| opcode = Js::OpCode::BCC; |
| break; |
| |
| case Js::OpCode::CmUnLe_A: |
| case Js::OpCode::CmUnLe_I4: |
| opcode = Js::OpCode::BLS; |
| break; |
| |
| default: Assert(false); |
| } |
| |
| if (addNaNCheck) |
| { |
| newInstr = IR::BranchInstr::New(Js::OpCode::BVS, done, m_func); |
| instr->InsertBefore(newInstr); |
| } |
| |
| newInstr = IR::BranchInstr::New(opcode, done, m_func); |
| instr->InsertBefore(newInstr); |
| |
| Lowerer::InsertMove(dst, opndFalse, instr); |
| instr->InsertBefore(done); |
| instr->Remove(); |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastCmXxTaggedInt |
| /// |
| ///---------------------------------------------------------------------------- |
| bool LowererMD::GenerateFastCmXxTaggedInt(IR::Instr *instr, bool isInHelper /* = false */) |
| { |
| // The idea is to do an inline compare if we can prove that both sources |
| // are tagged ints (i.e., are vars with the low bit set). |
| // |
| // Given: |
| // |
| // Cmxx_A dst, src1, src2 |
| // |
| // Generate: |
| // |
| // (If not Int31's, goto $helper) |
| // LDIMM dst, trueResult |
| // CMP src1, src2 |
| // BEQ $fallthru |
| // LDIMM dst, falseResult |
| // B $fallthru |
| // $helper: |
| // (caller will generate normal helper call sequence) |
| // $fallthru: |
| IR::Opnd * src1 = instr->GetSrc1(); |
| IR::Opnd * src2 = instr->GetSrc2(); |
| IR::Opnd * dst = instr->GetDst(); |
| IR::LabelInstr * helper = IR::LabelInstr::New(Js::OpCode::Label, m_func, true); |
| IR::LabelInstr * fallthru = IR::LabelInstr::New(Js::OpCode::Label, m_func, isInHelper); |
| |
| Assert(src1 && src2 && dst); |
| |
| // Not tagged ints? |
| if (src1->IsRegOpnd() && src1->AsRegOpnd()->m_sym->m_isNotNumber) |
| { |
| return false; |
| } |
| if (src2->IsRegOpnd() && src2->AsRegOpnd()->m_sym->m_isNotNumber) |
| { |
| return false; |
| } |
| |
| Js::OpCode opcode = Js::OpCode::InvalidOpCode; |
| switch ( instr->m_opcode) |
| { |
| case Js::OpCode::CmEq_A: |
| case Js::OpCode::CmSrEq_A: |
| case Js::OpCode::CmEq_I4: |
| opcode = Js::OpCode::BEQ; |
| break; |
| |
| case Js::OpCode::CmNeq_A: |
| case Js::OpCode::CmSrNeq_A: |
| case Js::OpCode::CmNeq_I4: |
| opcode = Js::OpCode::BNE; |
| break; |
| |
| case Js::OpCode::CmGt_A: |
| case Js::OpCode::CmGt_I4: |
| opcode = Js::OpCode::BGT; |
| break; |
| |
| case Js::OpCode::CmGe_A: |
| case Js::OpCode::CmGe_I4: |
| opcode = Js::OpCode::BGE; |
| break; |
| |
| case Js::OpCode::CmLt_A: |
| case Js::OpCode::CmLt_I4: |
| opcode = Js::OpCode::BLT; |
| break; |
| |
| case Js::OpCode::CmLe_A: |
| case Js::OpCode::CmLe_I4: |
| opcode = Js::OpCode::BLE; |
| break; |
| |
| case Js::OpCode::CmUnGt_A: |
| case Js::OpCode::CmUnGt_I4: |
| opcode = Js::OpCode::BHI; |
| break; |
| |
| case Js::OpCode::CmUnGe_A: |
| case Js::OpCode::CmUnGe_I4: |
| opcode = Js::OpCode::BCS; |
| break; |
| |
| case Js::OpCode::CmUnLt_A: |
| case Js::OpCode::CmUnLt_I4: |
| opcode = Js::OpCode::BCC; |
| break; |
| |
| case Js::OpCode::CmUnLe_A: |
| case Js::OpCode::CmUnLe_I4: |
| opcode = Js::OpCode::BLS; |
| break; |
| |
| default: Assert(false); |
| } |
| |
| // Tagged ints? |
| bool isTaggedInts = false; |
| if (src1->IsTaggedInt() || src1->IsInt32()) |
| { |
| if (src2->IsTaggedInt() || src2->IsInt32()) |
| { |
| isTaggedInts = true; |
| } |
| } |
| |
| if (!isTaggedInts) |
| { |
| this->GenerateSmIntPairTest(instr, src1, src2, helper); |
| } |
| |
| if (dst->IsEqual(src1)) |
| { |
| IR::RegOpnd *newSrc1 = IR::RegOpnd::New(TyMachReg, m_func); |
| Lowerer::InsertMove(newSrc1, src1, instr); |
| src1 = newSrc1; |
| } |
| |
| if (dst->IsEqual(src2)) |
| { |
| IR::RegOpnd *newSrc2 = IR::RegOpnd::New(TyMachReg, m_func); |
| Lowerer::InsertMove(newSrc2, src2, instr); |
| src2 = newSrc2; |
| } |
| |
| IR::Opnd *opndTrue, *opndFalse; |
| |
| if (dst->IsInt32()) |
| { |
| opndTrue = IR::IntConstOpnd::New(1, TyMachReg, this->m_func); |
| opndFalse = IR::IntConstOpnd::New(0, TyMachReg, this->m_func); |
| } |
| else |
| { |
| opndTrue = m_lowerer->LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue); |
| opndFalse = m_lowerer->LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse); |
| } |
| |
| // LDIMM dst, trueResult |
| // CMP src1, src2 |
| // BEQ $fallthru |
| // LDIMM dst, falseResult |
| // B $fallthru |
| |
| src1 = src1->UseWithNewType(TyInt32, m_func); |
| src2 = src2->UseWithNewType(TyInt32, m_func); |
| |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::LDIMM, dst, opndTrue, m_func)); |
| IR::Instr *instrCmp = IR::Instr::New(Js::OpCode::CMP, m_func); |
| instrCmp->SetSrc1(src1); |
| instrCmp->SetSrc2(src2); |
| instr->InsertBefore(instrCmp); |
| LegalizeMD::LegalizeInstr(instrCmp); |
| |
| instr->InsertBefore(IR::BranchInstr::New(opcode, fallthru, m_func)); |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::LDIMM, dst, opndFalse, m_func)); |
| |
| if (isTaggedInts) |
| { |
| instr->InsertAfter(fallthru); |
| instr->Remove(); |
| return true; |
| } |
| |
| // B $fallthru |
| instr->InsertBefore(IR::BranchInstr::New(Js::OpCode::B, fallthru, m_func)); |
| |
| instr->InsertBefore(helper); |
| instr->InsertAfter(fallthru); |
| return false; |
| } |
| |
| IR::Instr * LowererMD::GenerateConvBool(IR::Instr *instr) |
| { |
| // dst = LDIMM true |
| // TST src1, src2 |
| // BNE fallthrough |
| // dst = LDIMM false |
| // fallthrough: |
| |
| IR::RegOpnd *dst = instr->GetDst()->AsRegOpnd(); |
| IR::RegOpnd *src1 = instr->GetSrc1()->AsRegOpnd(); |
| IR::Opnd *opndTrue = m_lowerer->LoadLibraryValueOpnd(instr, LibraryValue::ValueTrue); |
| IR::Opnd *opndFalse = m_lowerer->LoadLibraryValueOpnd(instr, LibraryValue::ValueFalse); |
| IR::LabelInstr *fallthru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| |
| // dst = LDIMM true |
| IR::Instr *instrFirst = IR::Instr::New(Js::OpCode::LDIMM, dst, opndTrue, m_func); |
| instr->InsertBefore(instrFirst); |
| |
| // TST src1, src2 |
| IR::Instr *instrTst = IR::Instr::New(Js::OpCode::TST, m_func); |
| instrTst->SetSrc1(src1); |
| instrTst->SetSrc2(src1); |
| instr->InsertBefore(instrTst); |
| LegalizeMD::LegalizeInstr(instrTst); |
| |
| // BNE fallthrough |
| instr->InsertBefore(IR::BranchInstr::New(Js::OpCode::BNE, fallthru, m_func)); |
| |
| // dst = LDIMM false |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::LDIMM, dst, opndFalse, m_func)); |
| |
| // fallthrough: |
| instr->InsertAfter(fallthru); |
| instr->Remove(); |
| |
| return instrFirst; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastAdd |
| /// |
| /// NOTE: We assume that only the sum of two Int31's will have 0x2 set. This |
| /// is only true until we have a var type with tag == 0x2. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastAdd(IR::Instr * instrAdd) |
| { |
| // Given: |
| // |
| // dst = Add src1, src2 |
| // |
| // Generate: |
| // |
| // (If not 2 Int31's, jump to $helper.) |
| // s1 = MOV src1 |
| // s1 = ADDS s1, src2 -- try an inline add |
| // BVS $helper -- bail if the add overflowed |
| // s1 = ORR s1, AtomTag_IntPtr |
| // dst = MOV s1 |
| // B $fallthru |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| IR::Instr * instr; |
| IR::LabelInstr * labelHelper; |
| IR::LabelInstr * labelFallThru; |
| IR::Opnd * opndReg; |
| IR::Opnd * opndSrc1; |
| IR::Opnd * opndSrc2; |
| |
| opndSrc1 = instrAdd->GetSrc1(); |
| opndSrc2 = instrAdd->GetSrc2(); |
| AssertMsg(opndSrc1 && opndSrc2, "Expected 2 src opnd's on Add instruction"); |
| |
| // Generate fastpath for Incr_A anyway - |
| // Incrementing strings representing integers can be inter-mixed with integers e.g. "1"++ -> converts 1 to an int and thereafter, integer increment is expected. |
| if (opndSrc1->IsRegOpnd() && (opndSrc1->AsRegOpnd()->IsNotInt() || opndSrc1->GetValueType().IsString() |
| || (instrAdd->m_opcode != Js::OpCode::Incr_A && opndSrc1->GetValueType().IsLikelyString()))) |
| { |
| return false; |
| } |
| |
| if (opndSrc2->IsRegOpnd() && (opndSrc2->AsRegOpnd()->IsNotInt() || |
| opndSrc2->GetValueType().IsLikelyString())) |
| { |
| return false; |
| } |
| |
| // Tagged ints? |
| bool isTaggedInts = false; |
| if (opndSrc1->IsTaggedInt()) |
| { |
| if (opndSrc2->IsTaggedInt()) |
| { |
| isTaggedInts = true; |
| } |
| } |
| |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| |
| if (!isTaggedInts) |
| { |
| // (If not 2 Int31's, jump to $helper.) |
| |
| this->GenerateSmIntPairTest(instrAdd, opndSrc1, opndSrc2, labelHelper); |
| } |
| |
| if (opndSrc1->IsImmediateOpnd()) |
| { |
| // If opnd1 is a constant, just swap them. |
| IR::Opnd *opndTmp = opndSrc1; |
| opndSrc1 = opndSrc2; |
| opndSrc2 = opndTmp; |
| } |
| |
| // |
| // For 32 bit arithmetic we copy them and set the size of operands to be 32 bits. This is |
| // relevant only on ARM64. |
| // |
| |
| opndSrc1 = opndSrc1->UseWithNewType(TyInt32, this->m_func); |
| opndSrc2 = opndSrc2->UseWithNewType(TyInt32, this->m_func); |
| |
| // s1 = MOV src1 |
| |
| opndReg = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(opndReg, opndSrc1, instrAdd); |
| |
| // s1 = ADDS s1, src2 |
| |
| instr = IR::Instr::New(Js::OpCode::ADDS, opndReg, opndReg, opndSrc2, this->m_func); |
| instrAdd->InsertBefore(instr); |
| Legalize(instr); |
| |
| // BVS $helper |
| |
| instr = IR::BranchInstr::New(Js::OpCode::BVS, labelHelper, this->m_func); |
| instrAdd->InsertBefore(instr); |
| |
| // |
| // Convert TyInt32 operand, back to TyMachPtr type. |
| // |
| |
| if(TyMachReg != opndReg->GetType()) |
| { |
| opndReg = opndReg->UseWithNewType(TyMachPtr, this->m_func); |
| } |
| |
| // s1 = ORR s1, AtomTag_IntPtr |
| GenerateInt32ToVarConversion(opndReg, instrAdd); |
| |
| // dst = MOV s1 |
| |
| instr = IR::Instr::New(Js::OpCode::MOV, instrAdd->GetDst(), opndReg, this->m_func); |
| instrAdd->InsertBefore(instr); |
| |
| // B $fallthru |
| |
| labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelFallThru, this->m_func); |
| instrAdd->InsertBefore(instr); |
| |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| instrAdd->InsertBefore(labelHelper); |
| instrAdd->InsertAfter(labelFallThru); |
| |
| return true; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastSub |
| /// |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastSub(IR::Instr * instrSub) |
| { |
| // Given: |
| // |
| // dst = Sub src1, src2 |
| // |
| // Generate: |
| // |
| // (If not 2 Int31's, jump to $helper.) |
| // s1 = MOV src1 |
| // s1 = SUBS s1, src2 -- try an inline sub |
| // BVS $helper -- bail if the subtract overflowed |
| // BNE $helper |
| // s1 = ORR s1, AtomTag_IntPtr |
| // dst = MOV s1 |
| // B $fallthru |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| IR::Instr * instr; |
| IR::LabelInstr * labelHelper; |
| IR::LabelInstr * labelFallThru; |
| IR::Opnd * opndReg; |
| IR::Opnd * opndSrc1; |
| IR::Opnd * opndSrc2; |
| |
| opndSrc1 = instrSub->GetSrc1(); |
| opndSrc2 = instrSub->GetSrc2(); |
| AssertMsg(opndSrc1 && opndSrc2, "Expected 2 src opnd's on Sub instruction"); |
| |
| // Not tagged ints? |
| if (opndSrc1->IsRegOpnd() && opndSrc1->AsRegOpnd()->IsNotInt()) |
| { |
| return false; |
| } |
| if (opndSrc2->IsRegOpnd() && opndSrc2->AsRegOpnd()->IsNotInt()) |
| { |
| return false; |
| } |
| |
| // Tagged ints? |
| bool isTaggedInts = false; |
| if (opndSrc1->IsTaggedInt()) |
| { |
| if (opndSrc2->IsTaggedInt()) |
| { |
| isTaggedInts = true; |
| } |
| } |
| |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| |
| if (!isTaggedInts) |
| { |
| // (If not 2 Int31's, jump to $helper.) |
| |
| this->GenerateSmIntPairTest(instrSub, opndSrc1, opndSrc2, labelHelper); |
| } |
| |
| // |
| // For 32 bit arithmetic we copy them and set the size of operands to be 32 bits. This is |
| // relevant only on ARM64. |
| // |
| |
| opndSrc1 = opndSrc1->UseWithNewType(TyInt32, this->m_func); |
| opndSrc2 = opndSrc2->UseWithNewType(TyInt32, this->m_func); |
| |
| // s1 = MOV src1 |
| |
| opndReg = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(opndReg, opndSrc1, instrSub); |
| |
| // s1 = SUBS s1, src2 |
| |
| instr = IR::Instr::New(Js::OpCode::SUBS, opndReg, opndReg, opndSrc2, this->m_func); |
| instrSub->InsertBefore(instr); |
| Legalize(instr); |
| |
| // BVS $helper |
| |
| instr = IR::BranchInstr::New(Js::OpCode::BVS, labelHelper, this->m_func); |
| instrSub->InsertBefore(instr); |
| |
| // |
| // Convert TyInt32 operand, back to TyMachPtr type. |
| // |
| |
| if(TyMachReg != opndReg->GetType()) |
| { |
| opndReg = opndReg->UseWithNewType(TyMachPtr, this->m_func); |
| } |
| |
| // s1 = ORR s1, AtomTag_IntPtr |
| GenerateInt32ToVarConversion(opndReg, instrSub); |
| |
| // dst = MOV s1 |
| |
| instr = IR::Instr::New(Js::OpCode::MOV, instrSub->GetDst(), opndReg, this->m_func); |
| instrSub->InsertBefore(instr); |
| |
| // B $fallthru |
| |
| labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelFallThru, this->m_func); |
| instrSub->InsertBefore(instr); |
| |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| instrSub->InsertBefore(labelHelper); |
| instrSub->InsertAfter(labelFallThru); |
| |
| return true; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastMul |
| /// |
| ///---------------------------------------------------------------------------- |
| bool |
| LowererMD::GenerateFastMul(IR::Instr * instrMul) |
| { |
| // Given: |
| // |
| // dst = Mul src1, src2 |
| // |
| // Generate: |
| // |
| // (If not 2 Int31's, jump to $helper.) |
| // s1 = MOV src1 |
| // s2 = MOV src2 |
| // s3 = SMULL s1, s2 -- do the signed mul |
| // CMP s3, s3 SXTW |
| // BNE $helper -- bail if the result overflowed |
| // CBZ s3, $zero -- Check result is 0. might be -0. Result is -0 when a negative number is multiplied with 0. |
| // B $nonzero |
| // $zero: -- result of mul was 0. try to check for -0 |
| // s2 = ADDS s2, src1 --check for same sign |
| // BGE $nonzero - positive 0 if signs are equal |
| // dst = ToVar(-0.0) -- load negative 0 |
| // B $fallthru |
| // $nonzero: |
| // s3 = ORR s3, AtomTag_IntPtr |
| // dst= MOV s3 |
| // B $fallthru |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| IR::LabelInstr * labelHelper; |
| IR::LabelInstr * labelFallThru; |
| IR::LabelInstr * labelNonZero; |
| IR::Instr * instr; |
| IR::RegOpnd * opndReg1; |
| IR::RegOpnd * opndReg2; |
| IR::RegOpnd * s3; |
| IR::Opnd * opndSrc1; |
| IR::Opnd * opndSrc2; |
| |
| opndSrc1 = instrMul->GetSrc1(); |
| opndSrc2 = instrMul->GetSrc2(); |
| AssertMsg(opndSrc1 && opndSrc2, "Expected 2 src opnd's on mul instruction"); |
| |
| if (opndSrc1->IsRegOpnd() && opndSrc1->AsRegOpnd()->IsNotInt()) |
| { |
| return true; |
| } |
| if (opndSrc2->IsRegOpnd() && opndSrc2->AsRegOpnd()->IsNotInt()) |
| { |
| return true; |
| } |
| // (If not 2 Int31's, jump to $helper.) |
| |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| labelNonZero = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| |
| this->GenerateSmIntPairTest(instrMul, opndSrc1, opndSrc2, labelHelper); |
| |
| // |
| // For 32 bit arithmetic we copy them and set the size of operands to be 32 bits. This is |
| // relevant only on ARM64. |
| // |
| |
| opndSrc1 = opndSrc1->UseWithNewType(TyInt32, this->m_func); |
| opndSrc2 = opndSrc2->UseWithNewType(TyInt32, this->m_func); |
| |
| if (opndSrc1->IsImmediateOpnd()) |
| { |
| IR::Opnd * temp = opndSrc1; |
| opndSrc1 = opndSrc2; |
| opndSrc2 = temp; |
| } |
| |
| // s1 = MOV src1 |
| |
| opndReg1 = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(opndReg1, opndSrc1, instrMul); |
| |
| // s2 = MOV src2 |
| |
| opndReg2 = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(opndReg2, opndSrc2, instrMul); |
| |
| // s3 = SMULL s1, s2 |
| |
| s3 = IR::RegOpnd::New(TyInt64, this->m_func); |
| instr = IR::Instr::New(Js::OpCode::SMULL, s3, opndReg1, opndReg2, this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // CMP s3, s3 SXTW s3 |
| |
| instr = IR::Instr::New(Js::OpCode::CMP_SXTW, this->m_func); |
| instr->SetSrc1(s3); |
| instr->SetSrc2(s3); |
| instrMul->InsertBefore(instr); |
| |
| // BNE $helper |
| |
| instr = IR::BranchInstr::New(Js::OpCode::BNE, labelHelper, this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // CBZ s3, $zero -- Check result is 0. might be -0. Result is -0 when a negative number is multiplied with 0. |
| |
| IR::LabelInstr *labelZero = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| instr = IR::BranchInstr::New(Js::OpCode::CBZ, labelZero, this->m_func); |
| instr->SetSrc1(s3); |
| instrMul->InsertBefore(instr); |
| |
| // B $nonzero |
| |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelNonZero, this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // $zero: |
| |
| instrMul->InsertBefore(labelZero); |
| |
| // s2 = ADDS s2, s1 |
| |
| instr = IR::Instr::New(Js::OpCode::ADDS, opndReg2, opndReg2, opndReg1, this->m_func); |
| instrMul->InsertBefore(instr); |
| Legalize(instr); |
| |
| // BGE $nonzero |
| instr = IR::BranchInstr::New(Js::OpCode::BGE, labelNonZero, this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // dst = ToVar(-0.0) -- load negative 0 |
| |
| instr = IR::Instr::New(Js::OpCode::LDIMM, instrMul->GetDst(), m_lowerer->LoadLibraryValueOpnd(instrMul, LibraryValue::ValueNegativeZero), this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // B $fallthru |
| |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelFallThru, this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // $nonzero: |
| |
| instrMul->InsertBefore(labelNonZero); |
| |
| // dst = MOV_TRUNC s3 |
| |
| instr = IR::Instr::New(Js::OpCode::MOV_TRUNC, instrMul->GetDst()->UseWithNewType(TyInt32,this->m_func), s3->UseWithNewType(TyInt32, this->m_func), this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // dst = OR dst, AtomTag_IntPtr |
| |
| GenerateInt32ToVarConversion(instrMul->GetDst(), instrMul); |
| |
| |
| // B $fallthru |
| |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelFallThru, this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| instrMul->InsertBefore(labelHelper); |
| instrMul->InsertAfter(labelFallThru); |
| |
| return true; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastAnd |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastAnd(IR::Instr * instrAnd) |
| { |
| // Left empty to match AMD64; assuming this is not performance critical |
| return true; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastOr |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastOr(IR::Instr * instrOr) |
| { |
| // Left empty to match AMD64; assuming this is not performance critical |
| return true; |
| } |
| |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastXor |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastXor(IR::Instr * instrXor) |
| { |
| // Left empty to match AMD64; assuming this is not performance critical |
| return true; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // |
| // LowererMD::GenerateFastNot |
| // |
| //---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastNot(IR::Instr * instrNot) |
| { |
| // Left empty to match AMD64; assuming this is not performance critical |
| return true; |
| } |
| |
| // |
| // If value is zero in tagged int representation, jump to $labelHelper. |
| // |
| void |
| LowererMD::GenerateTaggedZeroTest( IR::Opnd * opndSrc, IR::Instr * insertInstr, IR::LabelInstr * labelHelper ) |
| { |
| // Cast the var to 32 bit integer. |
| if(opndSrc->GetSize() != 4) |
| { |
| opndSrc = opndSrc->UseWithNewType(TyUint32, this->m_func); |
| } |
| AssertMsg(TySize[opndSrc->GetType()] == 4, "This technique works only on the 32-bit version"); |
| |
| if(labelHelper != nullptr) |
| { |
| // CBZ src1, $labelHelper |
| IR::Instr* instr = IR::BranchInstr::New(Js::OpCode::CBZ, labelHelper, this->m_func); |
| instr->SetSrc1(opndSrc); |
| insertInstr->InsertBefore(instr); |
| } |
| else |
| { |
| // TST src1, src1 |
| IR::Instr* instr = IR::Instr::New(Js::OpCode::TST, this->m_func); |
| instr->SetSrc1(opndSrc); |
| instr->SetSrc2(opndSrc); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| } |
| |
| bool |
| LowererMD::GenerateFastNeg(IR::Instr * instrNeg) |
| { |
| // Given: |
| // |
| // dst = Not src |
| // |
| // Generate: |
| // |
| // if not int, jump $helper |
| // if src == 0 -- test for zero (must be handled by the runtime to preserve |
| // BEQ $helper -- Difference between +0 and -0) |
| // dst = SUB dst, 0, src -- do an inline NEG |
| // BVS $helper -- bail if the subtract overflowed |
| // dst = OR dst, tag -- restore the var tag on the result |
| // BVS $helper |
| // B $fallthru |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| IR::Instr * instr; |
| IR::LabelInstr * labelHelper = nullptr; |
| IR::LabelInstr * labelFallThru = nullptr; |
| IR::Opnd * opndSrc1; |
| IR::Opnd * opndDst; |
| bool usingNewDst = false; |
| opndSrc1 = instrNeg->GetSrc1(); |
| AssertMsg(opndSrc1, "Expected src opnd on Neg instruction"); |
| |
| |
| if (opndSrc1->IsRegOpnd() && opndSrc1->AsRegOpnd()->m_sym->IsIntConst()) |
| { |
| IR::Opnd *newOpnd; |
| IntConstType value = opndSrc1->AsRegOpnd()->m_sym->GetIntConstValue(); |
| |
| if (value == 0) |
| { |
| // If the negate operand is zero, the result is -0.0, which is a Number rather than an Int31. |
| newOpnd = m_lowerer->LoadLibraryValueOpnd(instrNeg, LibraryValue::ValueNegativeZero); |
| } |
| else |
| { |
| // negation below can overflow because max negative int32 value > max positive value by 1. |
| newOpnd = IR::AddrOpnd::NewFromNumber(-(int64)value, m_func); |
| } |
| |
| instrNeg->ClearBailOutInfo(); |
| instrNeg->FreeSrc1(); |
| instrNeg->SetSrc1(newOpnd); |
| instrNeg = this->ChangeToAssign(instrNeg); |
| |
| // Skip lowering call to helper |
| return false; |
| } |
| |
| bool isInt = (opndSrc1->IsTaggedInt()); |
| |
| if (opndSrc1->IsRegOpnd() && opndSrc1->AsRegOpnd()->IsNotInt()) |
| { |
| return true; |
| } |
| |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| if (!isInt) |
| { |
| GenerateSmIntTest(opndSrc1, instrNeg, labelHelper); |
| } |
| |
| // For 32 bit arithmetic we copy them and set the size of operands to be 32 bits. |
| opndSrc1 = opndSrc1->UseWithNewType(TyInt32, this->m_func); |
| GenerateTaggedZeroTest(opndSrc1, instrNeg, labelHelper); |
| |
| if (opndSrc1->IsEqual(instrNeg->GetDst())) |
| { |
| usingNewDst = true; |
| opndDst = IR::RegOpnd::New(TyInt32, this->m_func); |
| } |
| else |
| { |
| opndDst = instrNeg->GetDst()->UseWithNewType(TyInt32, this->m_func); |
| } |
| |
| // dst = SUBS zr, src |
| |
| instr = IR::Instr::New(Js::OpCode::SUBS, opndDst, IR::RegOpnd::New(nullptr, RegZR, TyInt32, this->m_func), opndSrc1, this->m_func); |
| instrNeg->InsertBefore(instr); |
| |
| // BVS $helper |
| |
| instr = IR::BranchInstr::New(Js::OpCode::BVS, labelHelper, this->m_func); |
| instrNeg->InsertBefore(instr); |
| |
| // |
| // Convert TyInt32 operand, back to TyMachPtr type. |
| // |
| if (TyMachReg != opndDst->GetType()) |
| { |
| opndDst = opndDst->UseWithNewType(TyMachPtr, this->m_func); |
| } |
| |
| GenerateInt32ToVarConversion(opndDst, instrNeg); |
| |
| if (usingNewDst) |
| { |
| Lowerer::InsertMove(instrNeg->GetDst(), opndDst, instrNeg); |
| } |
| |
| // B $fallthru |
| |
| labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelFallThru, this->m_func); |
| instrNeg->InsertBefore(instr); |
| |
| // $helper: |
| // (caller generates helper sequence) |
| // $fallthru: |
| |
| AssertMsg(labelHelper, "Should not be NULL"); |
| instrNeg->InsertBefore(labelHelper); |
| instrNeg->InsertAfter(labelFallThru); |
| |
| return true; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastShiftLeft |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastShiftLeft(IR::Instr * instrShift) |
| { |
| // Left empty to match AMD64; assuming this is not performance critical |
| return true; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateFastShiftRight |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| bool |
| LowererMD::GenerateFastShiftRight(IR::Instr * instrShift) |
| { |
| // Given: |
| // |
| // dst = Shr/ShrU src1, src2 |
| // |
| // Generate: |
| // |
| // (If not 2 Int31's, jump to $helper.) |
| // s1 = MOV src1 |
| // s2 = MOV src2 |
| // AND s2, 0x1F [unsigned only] // Bail if unsigned and not shifting, |
| // BEQ $helper [unsigned only] // as we may not end up with a taggable int |
| // s1 = ASR/LSR s1, s2 |
| // ORR s1, 1 << VarTag_Shift |
| //dst = MOV s1 |
| // B $fallthru |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| IR::Instr * instr; |
| IR::LabelInstr * labelHelper; |
| IR::LabelInstr * labelFallThru; |
| IR::Opnd * opndReg; |
| IR::Opnd * opndSrc1; |
| IR::Opnd * opndSrc2; |
| Assert(instrShift->m_opcode == Js::OpCode::ShrU_A || instrShift->m_opcode == Js::OpCode::Shr_A); |
| bool isUnsigned = (instrShift->m_opcode == Js::OpCode::ShrU_A); |
| |
| opndSrc1 = instrShift->GetSrc1(); |
| opndSrc2 = instrShift->GetSrc2(); |
| AssertMsg(opndSrc1 && opndSrc2, "Expected 2 src opnd's on Add instruction"); |
| |
| // Not int? |
| if (opndSrc1->IsRegOpnd() && opndSrc1->AsRegOpnd()->IsNotInt()) |
| { |
| return true; |
| } |
| if (opndSrc2->IsRegOpnd() && opndSrc2->AsRegOpnd()->IsNotInt()) |
| { |
| return true; |
| } |
| |
| // Tagged ints? |
| bool isTaggedInts = false; |
| if (opndSrc1->IsTaggedInt()) |
| { |
| if (opndSrc2->IsTaggedInt()) |
| { |
| isTaggedInts = true; |
| } |
| } |
| |
| IntConstType s2Value = 0; |
| bool src2IsIntConst = false; |
| |
| if (isUnsigned) |
| { |
| if (opndSrc2->IsRegOpnd()) |
| { |
| src2IsIntConst = opndSrc2->AsRegOpnd()->m_sym->IsTaggableIntConst(); |
| if (src2IsIntConst) |
| { |
| s2Value = opndSrc2->AsRegOpnd()->m_sym->GetIntConstValue(); |
| } |
| } |
| else |
| { |
| AssertMsg(opndSrc2->IsAddrOpnd() && Js::TaggedInt::Is(opndSrc2->AsAddrOpnd()->m_address), |
| "Expect src2 of shift right to be reg or Var."); |
| src2IsIntConst = true; |
| s2Value = Js::TaggedInt::ToInt32(opndSrc2->AsAddrOpnd()->m_address); |
| } |
| |
| // 32-bit Shifts only uses the bottom 5 bits. |
| s2Value &= 0x1F; |
| |
| // Unsigned shift by 0 could yield a value not encodable as a tagged int. |
| if (isUnsigned && src2IsIntConst && s2Value == 0) |
| { |
| return true; |
| } |
| } |
| |
| |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| |
| if (!isTaggedInts) |
| { |
| // (If not 2 Int31's, jump to $helper.) |
| |
| this->GenerateSmIntPairTest(instrShift, opndSrc1, opndSrc2, labelHelper); |
| } |
| |
| opndSrc1 = opndSrc1->UseWithNewType(TyInt32, this->m_func); |
| if (src2IsIntConst) |
| { |
| opndSrc2 = IR::IntConstOpnd::New(s2Value, TyInt32, this->m_func); |
| } |
| else |
| { |
| // s2 = MOV src2 |
| opndReg = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(opndReg, opndSrc2, instrShift); |
| opndSrc2 = opndReg; |
| } |
| |
| if (!src2IsIntConst && isUnsigned) |
| { |
| // s2 = AND s2, 0x1F [unsigned only] // Bail if unsigned and not shifting, |
| instr = IR::Instr::New(Js::OpCode::AND, opndSrc2, opndSrc2, IR::IntConstOpnd::New(0x1F, TyInt32, this->m_func), this->m_func); |
| instrShift->InsertBefore(instr); |
| |
| // CBZ s2, $helper [unsigned only] // as we may not end up with a taggable int |
| instr = IR::BranchInstr::New(Js::OpCode::CBZ, labelHelper, this->m_func); |
| instr->SetSrc1(opndSrc2); |
| instrShift->InsertBefore(instr); |
| } |
| |
| // s1 = MOV src1 |
| opndReg = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(opndReg, opndSrc1, instrShift); |
| |
| // s1 = ASR/LSR s1, RCX |
| instr = IR::Instr::New(isUnsigned ? Js::OpCode::LSR : Js::OpCode::ASR, opndReg, opndReg, opndSrc2, this->m_func); |
| instrShift->InsertBefore(instr); |
| |
| // |
| // Convert TyInt32 operand, back to TyMachPtr type. |
| // |
| |
| if(TyMachReg != opndReg->GetType()) |
| { |
| opndReg = opndReg->UseWithNewType(TyMachPtr, this->m_func); |
| } |
| |
| // ORR s1, 1 << VarTag_Shift |
| this->GenerateInt32ToVarConversion(opndReg, instrShift); |
| |
| // dst = MOV s1 |
| |
| instr = IR::Instr::New(Js::OpCode::MOV, instrShift->GetDst(), opndReg, this->m_func); |
| instrShift->InsertBefore(instr); |
| |
| // B $fallthru |
| |
| labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelFallThru, this->m_func); |
| instrShift->InsertBefore(instr); |
| |
| // $helper: |
| // (caller generates helper call) |
| // $fallthru: |
| |
| instrShift->InsertBefore(labelHelper); |
| instrShift->InsertAfter(labelFallThru); |
| |
| return true; |
| } |
| |
| void |
| LowererMD::GenerateFastBrS(IR::BranchInstr *brInstr) |
| { |
| IR::Opnd *src1 = brInstr->UnlinkSrc1(); |
| |
| Assert(src1->IsIntConstOpnd() || src1->IsAddrOpnd() || src1->IsRegOpnd()); |
| |
| m_lowerer->InsertTest( |
| m_lowerer->LoadOptimizationOverridesValueOpnd( |
| brInstr, OptimizationOverridesValue::OptimizationOverridesSideEffects), |
| src1, |
| brInstr); |
| |
| Js::OpCode opcode; |
| |
| switch(brInstr->m_opcode) |
| { |
| case Js::OpCode::BrHasSideEffects: |
| opcode = Js::OpCode::BNE; |
| break; |
| |
| case Js::OpCode::BrNotHasSideEffects: |
| opcode = Js::OpCode::BEQ; |
| break; |
| |
| default: |
| Assert(UNREACHED); |
| __assume(false); |
| } |
| |
| brInstr->m_opcode = opcode; |
| } |
| |
| ///---------------------------------------------------------------------------- |
| /// |
| /// LowererMD::GenerateSmIntPairTest |
| /// |
| /// Generate code to test whether the given operands are both Int31 vars |
| /// and branch to the given label if not. |
| /// |
| ///---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::GenerateSmIntPairTest( |
| IR::Instr * instrInsert, |
| IR::Opnd * opndSrc1, |
| IR::Opnd * opndSrc2, |
| IR::LabelInstr * labelFail) |
| { |
| IR::Opnd * opndReg; |
| IR::Instr * instrPrev = instrInsert->m_prev; |
| IR::Instr * instr; |
| |
| Assert(opndSrc1->GetType() == TyVar); |
| Assert(opndSrc2->GetType() == TyVar); |
| |
| if (opndSrc1->IsTaggedInt()) |
| { |
| IR::Opnd *tempOpnd = opndSrc1; |
| opndSrc1 = opndSrc2; |
| opndSrc2 = tempOpnd; |
| } |
| |
| if (opndSrc2->IsTaggedInt()) |
| { |
| if (opndSrc1->IsTaggedInt()) |
| { |
| return instrPrev; |
| } |
| |
| GenerateSmIntTest(opndSrc1, instrInsert, labelFail); |
| return instrPrev; |
| } |
| |
| opndReg = IR::RegOpnd::New(TyMachReg, this->m_func); |
| |
| // s1 = MOV src1 |
| // s1 = UBFX s1, VarTagShift - 16, 64 - (VarTag_Shift - 16) |
| // s2 = MOV src2 |
| // s1 = BFXIL s2, VarTagShift, 64 - VarTag_Shift |
| // s1 = EOR s1, AtomTag_Pair ------ compare the tags together to the expected tag pair |
| // CBNZ s1, $fail |
| |
| // s1 = MOV src1 |
| |
| instr = IR::Instr::New(Js::OpCode::MOV, opndReg, opndSrc1, this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| // s1 = UBFX s1, VarTagShift - 16, 64 - (VarTag_Shift - 16) |
| |
| instr = IR::Instr::New(Js::OpCode::UBFX, opndReg, opndReg, IR::IntConstOpnd::New(BITFIELD(Js::VarTag_Shift - 16, 64 - (Js::VarTag_Shift - 16)), TyMachReg, this->m_func), this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| // s2 = MOV src2 |
| |
| IR::Opnd * opndReg1 = IR::RegOpnd::New(TyMachReg, this->m_func); |
| instr = IR::Instr::New(Js::OpCode::MOV, opndReg1, opndSrc2, this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| // s1 = BFXIL s2, VarTagShift, 64 - VarTag_Shift |
| |
| instr = IR::Instr::New(Js::OpCode::BFXIL, opndReg, opndReg1, IR::IntConstOpnd::New(BITFIELD(Js::VarTag_Shift, 64 - Js::VarTag_Shift), TyMachReg, this->m_func), this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| opndReg = opndReg->UseWithNewType(TyInt32, this->m_func)->AsRegOpnd(); |
| |
| // s1 = EOR s1, AtomTag_Pair |
| instr = IR::Instr::New(Js::OpCode::EOR, opndReg, opndReg, IR::IntConstOpnd::New(Js::AtomTag_Pair, TyInt32, this->m_func, true), this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| // CBNZ s1, $fail |
| instr = IR::BranchInstr::New(Js::OpCode::CBNZ, labelFail, this->m_func); |
| instr->SetSrc1(opndReg); |
| instrInsert->InsertBefore(instr); |
| |
| return instrPrev; |
| } |
| |
| bool LowererMD::GenerateObjectTest(IR::Opnd * opndSrc, IR::Instr * insertInstr, IR::LabelInstr * labelTarget, bool fContinueLabel) |
| { |
| AssertMsg(opndSrc->GetSize() == MachPtr, "64-bit register required"); |
| |
| if (opndSrc->IsTaggedValue() && fContinueLabel) |
| { |
| // Insert delete branch opcode to tell the dbChecks not to assert on the helper label we may fall through into |
| IR::Instr *fakeBr = IR::PragmaInstr::New(Js::OpCode::DeletedNonHelperBranch, 0, this->m_func); |
| insertInstr->InsertBefore(fakeBr); |
| |
| return false; |
| } |
| else if (opndSrc->IsNotTaggedValue() && !fContinueLabel) |
| { |
| return false; |
| } |
| |
| IR::Opnd * opndReg = IR::RegOpnd::New(TyMachReg, this->m_func); |
| |
| // s1 = MOV src1 - Move to a temporary |
| IR::Instr * instr = IR::Instr::New(Js::OpCode::MOV, opndReg, opndSrc, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| // s1 = UBFX s1, s1, #VarTag_Shift, #64 - VarTag_Shift |
| instr = IR::Instr::New(Js::OpCode::UBFX, opndReg, opndReg, IR::IntConstOpnd::New(BITFIELD(Js::VarTag_Shift, 64 - Js::VarTag_Shift), TyMachReg, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| if (fContinueLabel) |
| { |
| // CBZ s1, $labelHelper |
| instr = IR::BranchInstr::New(Js::OpCode::CBZ, labelTarget, this->m_func); |
| instr->SetSrc1(opndReg); |
| insertInstr->InsertBefore(instr); |
| IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| insertInstr->InsertBefore(labelHelper); |
| } |
| else |
| { |
| // CBNZ s1, $labelHelper |
| instr = IR::BranchInstr::New(Js::OpCode::CBNZ, labelTarget, this->m_func); |
| instr->SetSrc1(opndReg); |
| insertInstr->InsertBefore(instr); |
| } |
| return true; |
| } |
| |
| void |
| LowererMD::GenerateLoadTaggedType(IR::Instr * instrLdSt, IR::RegOpnd * opndType, IR::RegOpnd * opndTaggedType) |
| { |
| // taggedType = OR type, InlineCacheAuxSlotTypeTag |
| IR::IntConstOpnd * opndAuxSlotTag = IR::IntConstOpnd::New(InlineCacheAuxSlotTypeTag, TyInt8, instrLdSt->m_func); |
| IR::Instr * instr = IR::Instr::New(Js::OpCode::ORR, opndTaggedType, opndType, opndAuxSlotTag, instrLdSt->m_func); |
| instrLdSt->InsertBefore(instr); |
| } |
| |
| void |
| LowererMD::GenerateLoadPolymorphicInlineCacheSlot(IR::Instr * instrLdSt, IR::RegOpnd * opndInlineCache, IR::RegOpnd * opndType, uint polymorphicInlineCacheSize) |
| { |
| // Generate |
| // |
| // LDR r1, type |
| // LSR r1, r1, #PolymorphicInlineCacheShift |
| // AND r1, r1, #(size - 1) |
| // LSL r1, r1, #log2(sizeof(Js::InlineCache)) |
| // ADD inlineCache, inlineCache, r1 |
| |
| // MOV r1, type |
| IR::RegOpnd * opndOffset = IR::RegOpnd::New(TyMachPtr, instrLdSt->m_func); |
| IR::Instr * instr = IR::Instr::New(Js::OpCode::MOV, opndOffset, opndType, instrLdSt->m_func); |
| instrLdSt->InsertBefore(instr); |
| |
| IntConstType rightShiftAmount = PolymorphicInlineCacheShift; |
| IntConstType leftShiftAmount = Math::Log2(sizeof(Js::InlineCache)); |
| // instead of generating |
| // LSR r1, r1, #PolymorphicInlineCacheShift |
| // AND r1, r1, #(size - 1) |
| // LSL r1, r1, #log2(sizeof(Js::InlineCache)) |
| // |
| // we can generate: |
| // LSR r1, r1, #(PolymorphicInlineCacheShift - log2(sizeof(Js::InlineCache)) |
| // AND r1, r1, #(size - 1) << log2(sizeof(Js::InlineCache)) |
| Assert(rightShiftAmount > leftShiftAmount); |
| instr = IR::Instr::New(Js::OpCode::LSR, opndOffset, opndOffset, IR::IntConstOpnd::New(rightShiftAmount - leftShiftAmount, TyUint8, instrLdSt->m_func, true), instrLdSt->m_func); |
| instrLdSt->InsertBefore(instr); |
| Lowerer::InsertAnd(opndOffset, opndOffset, IR::IntConstOpnd::New(((IntConstType)(polymorphicInlineCacheSize - 1)) << leftShiftAmount, TyMachPtr, instrLdSt->m_func, true), instrLdSt); |
| |
| // ADD inlineCache, inlineCache, r1 |
| Lowerer::InsertAdd(false, opndInlineCache, opndInlineCache, opndOffset, instrLdSt); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // |
| // LowererMD::GenerateFastScopedFldLookup |
| // |
| // This is a helper call which generates asm for both |
| // ScopedLdFld & ScopedStFld |
| // |
| //---------------------------------------------------------------------------- |
| |
| IR::Instr * |
| LowererMD::GenerateFastScopedFld(IR::Instr * instrScopedFld, bool isLoad) |
| { |
| // LDR s1, [base, offset(length)] |
| // CMP s1, 1 -- get the length on array and test if it is 1. |
| // BNE $helper |
| // LDR s2, [base, offset(scopes)] -- load the first scope |
| // LDR s3, [s2, offset(type)] |
| // LDIMM s4, inlineCache |
| // LDR s5, [s4, offset(u.local.type)] |
| // CMP s3, s5 -- check type |
| // BNE $helper |
| // LDR s6, [s2, offset(slots)] -- load the slots array |
| // LDR s7 , [s4, offset(u.local.slotIndex)] -- load the cached slot index |
| // |
| // if (load) { |
| // LDR dst, [s6, s7, LSL #2] -- load the value from the slot |
| // } |
| // else { |
| // STR src, [s6, s7, LSL #2] |
| // } |
| // B $done |
| //$helper: |
| // dst = BLX PatchGetPropertyScoped(inlineCache, base, field, defaultInstance, scriptContext) |
| //$done: |
| |
| IR::Instr * instr; |
| IR::Instr * instrPrev = instrScopedFld->m_prev; |
| |
| IR::RegOpnd * opndBase; |
| IR::RegOpnd * opndReg1; //s1 |
| IR::RegOpnd * opndReg2; //s2 |
| IR::RegOpnd * opndInlineCache; //s4 |
| IR::IndirOpnd * indirOpnd; |
| IR::Opnd * propertyBase; |
| |
| IR::LabelInstr * labelHelper; |
| IR::LabelInstr * labelFallThru; |
| |
| if (isLoad) |
| { |
| propertyBase = instrScopedFld->GetSrc1(); |
| } |
| else |
| { |
| propertyBase = instrScopedFld->GetDst(); |
| } |
| |
| AssertMsg(propertyBase->IsSymOpnd() && propertyBase->AsSymOpnd()->IsPropertySymOpnd() && propertyBase->AsSymOpnd()->m_sym->IsPropertySym(), |
| "Expected property sym operand of ScopedLdFld or ScopedStFld"); |
| |
| IR::PropertySymOpnd * propertySymOpnd = propertyBase->AsPropertySymOpnd(); |
| |
| opndBase = propertySymOpnd->CreatePropertyOwnerOpnd(m_func); |
| const IR::AutoReuseOpnd holdAfterLegalization(opndBase, m_func); |
| AssertMsg(opndBase->m_sym->m_isSingleDef, "We assume this isn't redefined"); |
| |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| |
| // LDR s1, [base, offset(length)] -- get the length on array and test if it is 1. |
| indirOpnd = IR::IndirOpnd::New(opndBase, Js::FrameDisplay::GetOffsetOfLength(), TyInt16, this->m_func); |
| opndReg1 = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(opndReg1, indirOpnd, instrScopedFld); |
| |
| // CMP s1, 1 -- get the length on array and test if it is 1. |
| instr = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| instr->SetSrc1(opndReg1); |
| instr->SetSrc2(IR::IntConstOpnd::New(0x1, TyInt8, this->m_func)); |
| instrScopedFld->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| |
| // BNE $helper |
| instr = IR::BranchInstr::New(Js::OpCode::BNE, labelHelper, this->m_func); |
| instrScopedFld->InsertBefore(instr); |
| |
| // LDR s2, [base, offset(scopes)] -- load the first scope |
| indirOpnd = IR::IndirOpnd::New(opndBase, Js::FrameDisplay::GetOffsetOfScopes(), TyMachReg,this->m_func); |
| opndReg2 = IR::RegOpnd::New(TyMachReg, this->m_func); |
| Lowerer::InsertMove(opndReg2, indirOpnd, instrScopedFld); |
| |
| // LDR s3, [s2, offset(type)] |
| // LDIMM s4, inlineCache |
| // LDR s5, [s4, offset(u.local.type)] |
| // CMP s3, s5 -- check type |
| // BNE $helper |
| |
| opndInlineCache = IR::RegOpnd::New(TyMachReg, this->m_func); |
| opndReg2->m_sym->m_isNotNumber = true; |
| |
| IR::RegOpnd * opndType = IR::RegOpnd::New(TyMachReg, this->m_func); |
| this->m_lowerer->GenerateObjectTestAndTypeLoad(instrScopedFld, opndReg2, opndType, labelHelper); |
| Lowerer::InsertMove(opndInlineCache, m_lowerer->LoadRuntimeInlineCacheOpnd(instrScopedFld, propertySymOpnd), instrScopedFld); |
| |
| labelFallThru = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| |
| // Check the local cache with the tagged type |
| IR::RegOpnd * opndTaggedType = IR::RegOpnd::New(TyMachReg, this->m_func); |
| GenerateLoadTaggedType(instrScopedFld, opndType, opndTaggedType); |
| Lowerer::GenerateLocalInlineCacheCheck(instrScopedFld, opndTaggedType, opndInlineCache, labelHelper); |
| if (isLoad) |
| { |
| IR::Opnd *opndDst = instrScopedFld->GetDst(); |
| Lowerer::GenerateLdFldFromLocalInlineCache(instrScopedFld, opndReg2, opndDst, opndInlineCache, labelFallThru, false); |
| } |
| else |
| { |
| IR::Opnd *opndSrc = instrScopedFld->GetSrc1(); |
| GenerateStFldFromLocalInlineCache(instrScopedFld, opndReg2, opndSrc, opndInlineCache, labelFallThru, false); |
| } |
| |
| // $helper: |
| // if (isLoad) { |
| // dst = BLX PatchGetPropertyScoped(inlineCache, opndBase, propertyId, srcBase, scriptContext) |
| // } |
| // else { |
| // BLX PatchSetPropertyScoped(inlineCache, base, field, value, defaultInstance, scriptContext) |
| // } |
| // $fallthru: |
| instrScopedFld->InsertBefore(labelHelper); |
| instrScopedFld->InsertAfter(labelFallThru); |
| |
| return instrPrev; |
| } |
| |
| //---------------------------------------------------------------------------- |
| // |
| // LowererMD::GenerateFastScopedLdFld |
| // |
| // Make use of the helper to cache the type and slot index used to do a ScopedLdFld |
| // when the scope is an array of length 1. |
| // Extract the only element from array and do an inline load from the appropriate slot |
| // if the type hasn't changed since the last time this ScopedLdFld was executed. |
| // |
| //---------------------------------------------------------------------------- |
| IR::Instr * |
| LowererMD::GenerateFastScopedLdFld(IR::Instr * instrLdScopedFld) |
| { |
| //Helper GenerateFastScopedFldLookup generates following: |
| // |
| // LDR s1, [base, offset(length)] |
| // CMP s1, 1 -- get the length on array and test if it is 1. |
| // BNE $helper |
| // LDR s2, [base, offset(scopes)] -- load the first scope |
| // LDR s3, [s2, offset(type)] |
| // LDIMM s4, inlineCache |
| // LDR s5, [s4, offset(u.local.type)] |
| // CMP s3, s5 -- check type |
| // BNE $helper |
| // LDR s6, [s2, offset(slots)] -- load the slots array |
| // LDR s7 , [s4, offset(u.local.slotIndex)] -- load the cached slot index |
| // LDR dst, [s6, s7, LSL #2] -- load the value from the slot |
| // B $done |
| //$helper: |
| // dst = BLX PatchGetPropertyScoped(inlineCache, base, field, defaultInstance, scriptContext) |
| //$done: |
| |
| return GenerateFastScopedFld(instrLdScopedFld, true); |
| } |
| |
| //---------------------------------------------------------------------------- |
| // |
| // LowererMD::GenerateFastScopedStFld |
| // |
| // Make use of the helper to cache the type and slot index used to do a ScopedStFld |
| // when the scope is an array of length 1. |
| // Extract the only element from array and do an inline load from the appropriate slot |
| // if the type hasn't changed since the last time this ScopedStFld was executed. |
| // |
| //---------------------------------------------------------------------------- |
| IR::Instr * |
| LowererMD::GenerateFastScopedStFld(IR::Instr * instrStScopedFld) |
| { |
| // LDR s1, [base, offset(length)] |
| // CMP s1, 1 -- get the length on array and test if it is 1. |
| // BNE $helper |
| // LDR s2, [base, offset(scopes)] -- load the first scope |
| // LDR s3, [s2, offset(type)] |
| // LDIMM s4, inlineCache |
| // LDR s5, [s4, offset(u.local.type)] |
| // CMP s3, s5 -- check type |
| // BNE $helper |
| // LDR s6, [s2, offset(slots)] -- load the slots array |
| // LDR s7 , [s4, offset(u.local.slotIndex)] -- load the cached slot index |
| // STR src, [s6, s7, LSL #2] -- store the value directly at the slot |
| // B $done |
| //$helper: |
| // BLX PatchSetPropertyScoped(inlineCache, base, field, value, defaultInstance, scriptContext) |
| //$done: |
| |
| return GenerateFastScopedFld(instrStScopedFld, false); |
| } |
| |
| void |
| LowererMD::GenerateStFldFromLocalInlineCache( |
| IR::Instr * instrStFld, |
| IR::RegOpnd * opndBase, |
| IR::Opnd * opndSrc, |
| IR::RegOpnd * opndInlineCache, |
| IR::LabelInstr * labelFallThru, |
| bool isInlineSlot) |
| { |
| IR::RegOpnd * opndSlotArray = nullptr; |
| IR::IndirOpnd * opndIndir; |
| IR::Instr * instr; |
| |
| if (!isInlineSlot) |
| { |
| // s2 = MOV base->slots -- load the slot array |
| opndSlotArray = IR::RegOpnd::New(TyMachReg, instrStFld->m_func); |
| opndIndir = IR::IndirOpnd::New(opndBase, Js::DynamicObject::GetOffsetOfAuxSlots(), TyMachReg, instrStFld->m_func); |
| Lowerer::InsertMove(opndSlotArray, opndIndir, instrStFld); |
| } |
| |
| // LDR s5, [s2, offset(u.local.slotIndex)] -- load the cached slot index |
| IR::RegOpnd *opndSlotIndex = IR::RegOpnd::New(TyUint16, instrStFld->m_func); |
| opndIndir = IR::IndirOpnd::New(opndInlineCache, offsetof(Js::InlineCache, u.local.slotIndex), TyUint16, instrStFld->m_func); |
| Lowerer::InsertMove(opndSlotIndex, opndIndir, instrStFld); |
| |
| if (isInlineSlot) |
| { |
| // STR src, [base, s5, LSL #2] -- store the value directly to the slot [s4 + s5 * 4] = src |
| opndIndir = IR::IndirOpnd::New(opndBase, opndSlotIndex, LowererMD::GetDefaultIndirScale(), TyMachReg, instrStFld->m_func); |
| instr = IR::Instr::New(Js::OpCode::STR, opndIndir, opndSrc, instrStFld->m_func); |
| instrStFld->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| else |
| { |
| // STR src, [s4, s5, LSL #2] -- store the value directly to the slot [s4 + s5 * 4] = src |
| opndIndir = IR::IndirOpnd::New(opndSlotArray, opndSlotIndex, LowererMD::GetDefaultIndirScale(), TyMachReg, instrStFld->m_func); |
| instr = IR::Instr::New(Js::OpCode::STR, opndIndir, opndSrc, instrStFld->m_func); |
| instrStFld->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| |
| // B $done |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelFallThru, instrStFld->m_func); |
| instrStFld->InsertBefore(instr); |
| } |
| |
| IR::Opnd * |
| LowererMD::CreateStackArgumentsSlotOpnd(Func *func) |
| { |
| // Save the newly-created args object to its dedicated stack slot. |
| IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(nullptr, FRAME_REG , TyMachReg, func), |
| -MachArgsSlotOffset, TyMachPtr, func); |
| |
| return indirOpnd; |
| } |
| |
| // |
| // jump to $labelHelper, based on the result of CMP |
| // |
| void LowererMD::GenerateSmIntTest(IR::Opnd *opndSrc, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::Instr **instrFirst, bool fContinueLabel /* = false */) |
| { |
| AssertMsg(opndSrc->GetSize() == MachPtr, "64-bit register required"); |
| |
| IR::Opnd * opndReg = IR::RegOpnd::New(TyMachReg, this->m_func); |
| |
| // s1 = MOV src1 - Move to a temporary |
| IR::Instr * instr = IR::Instr::New(Js::OpCode::MOV, opndReg, opndSrc, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| if (instrFirst) |
| { |
| *instrFirst = instr; |
| } |
| |
| // s1 = UBFX s1, VarTag_Shift, 64 - VarTag_Shift |
| instr = IR::Instr::New(Js::OpCode::UBFX, opndReg, opndReg, IR::IntConstOpnd::New(BITFIELD(Js::VarTag_Shift, 64 - Js::VarTag_Shift), TyMachReg, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instr); |
| Legalize(instr); |
| |
| // s1 = EOR s1, AtomTag |
| instr = IR::Instr::New(Js::OpCode::EOR, opndReg, opndReg, IR::IntConstOpnd::New(Js::AtomTag, TyInt32, this->m_func, /* dontEncode = */ true), this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| if(fContinueLabel) |
| { |
| // CBZ s1, $labelHelper |
| instr = IR::BranchInstr::New(Js::OpCode::CBZ, labelHelper, this->m_func); |
| } |
| else |
| { |
| // CBNZ s1, $labelHelper |
| instr = IR::BranchInstr::New(Js::OpCode::CBNZ, labelHelper, this->m_func); |
| } |
| instr->SetSrc1(opndReg); |
| insertInstr->InsertBefore(instr); |
| } |
| |
| void LowererMD::GenerateInt32ToVarConversion(IR::Opnd * opndSrc, IR::Instr * insertInstr ) |
| { |
| AssertMsg(opndSrc->IsRegOpnd(), "NYI for other types"); |
| |
| IR::Instr* instr = IR::Instr::New(Js::OpCode::ORR, opndSrc, opndSrc, IR::IntConstOpnd::New(Js::AtomTag_IntPtr, TyMachReg, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| |
| IR::RegOpnd * |
| LowererMD::GenerateUntagVar(IR::RegOpnd * src, IR::LabelInstr * labelFail, IR::Instr * assignInstr, bool generateTagCheck) |
| { |
| Assert(src->IsVar()); |
| |
| // MOV valueOpnd, index |
| IR::RegOpnd *valueOpnd = IR::RegOpnd::New(TyInt32, this->m_func); |
| |
| // |
| // Convert Index to 32 bits. |
| // |
| if (generateTagCheck) |
| { |
| IR::Opnd * opnd = src->UseWithNewType(TyMachReg, this->m_func); |
| Assert(!opnd->IsTaggedInt()); |
| this->GenerateSmIntTest(opnd, assignInstr, labelFail); |
| } |
| |
| // Doing a 32-bit MOV clears the tag bits on ARM64. Use MOV_TRUNC so it doesn't get peeped away. |
| IR::Instr * instr = IR::Instr::New(Js::OpCode::MOV_TRUNC, valueOpnd, src->UseWithNewType(TyInt32, this->m_func), this->m_func); |
| assignInstr->InsertBefore(instr); |
| return valueOpnd; |
| } |
| |
| IR::RegOpnd *LowererMD::LoadNonnegativeIndex( |
| IR::RegOpnd *indexOpnd, |
| const bool skipNegativeCheck, |
| IR::LabelInstr *const notTaggedIntLabel, |
| IR::LabelInstr *const negativeLabel, |
| IR::Instr *const insertBeforeInstr) |
| { |
| Assert(indexOpnd); |
| Assert(indexOpnd->IsVar() || indexOpnd->GetType() == TyInt32 || indexOpnd->GetType() == TyUint32); |
| Assert(indexOpnd->GetType() != TyUint32 || skipNegativeCheck); |
| Assert(!indexOpnd->IsVar() || notTaggedIntLabel); |
| Assert(skipNegativeCheck || negativeLabel); |
| Assert(insertBeforeInstr); |
| |
| if(indexOpnd->IsVar()) |
| { |
| if (indexOpnd->GetValueType().IsLikelyFloat()) |
| { |
| return m_lowerer->LoadIndexFromLikelyFloat(indexOpnd, skipNegativeCheck, notTaggedIntLabel, negativeLabel, insertBeforeInstr); |
| } |
| |
| indexOpnd = GenerateUntagVar(indexOpnd, notTaggedIntLabel, insertBeforeInstr, !indexOpnd->IsTaggedInt()); |
| } |
| |
| if(!skipNegativeCheck) |
| { |
| // TBNZ index, #31, $notTaggedIntOrNegative |
| IR::Instr *instr = IR::BranchInstr::New(Js::OpCode::TBNZ, negativeLabel, this->m_func); |
| instr->SetSrc1(indexOpnd); |
| instr->SetSrc2(IR::IntConstOpnd::New(31, TyVar, this->m_func)); |
| insertBeforeInstr->InsertBefore(instr); |
| } |
| return indexOpnd; |
| } |
| |
| // Inlines fast-path for int Mul/Add or int Mul/Sub. If not int, call MulAdd/MulSub helper |
| bool LowererMD::TryGenerateFastMulAdd(IR::Instr * instrAdd, IR::Instr ** pInstrPrev) |
| { |
| IR::Instr *instrMul = instrAdd->GetPrevRealInstrOrLabel(); |
| IR::Opnd *addSrc; |
| IR::RegOpnd *addCommonSrcOpnd; |
| |
| Assert(instrAdd->m_opcode == Js::OpCode::Add_A || instrAdd->m_opcode == Js::OpCode::Sub_A); |
| |
| bool isSub = (instrAdd->m_opcode == Js::OpCode::Sub_A) ? true : false; |
| |
| // Mul needs to be a single def reg |
| if (instrMul->m_opcode != Js::OpCode::Mul_A || instrMul->GetDst()->IsRegOpnd() == false) |
| { |
| // Cannot generate MulAdd |
| return false; |
| } |
| |
| if (instrMul->HasBailOutInfo()) |
| { |
| // Bailout will be generated for the Add, but not the Mul. |
| // We could handle this, but this path isn't used that much anymore. |
| return false; |
| } |
| |
| IR::RegOpnd *regMulDst = instrMul->GetDst()->AsRegOpnd(); |
| |
| if (regMulDst->m_sym->m_isSingleDef == false) |
| { |
| // Cannot generate MulAdd |
| return false; |
| } |
| |
| // Only handle a * b + c, so dst of Mul needs to match left source of Add |
| if (instrMul->GetDst()->IsEqual(instrAdd->GetSrc1())) |
| { |
| addCommonSrcOpnd = instrAdd->GetSrc1()->AsRegOpnd(); |
| addSrc = instrAdd->GetSrc2(); |
| } |
| else if (instrMul->GetDst()->IsEqual(instrAdd->GetSrc2())) |
| { |
| addSrc = instrAdd->GetSrc1(); |
| addCommonSrcOpnd = instrAdd->GetSrc2()->AsRegOpnd(); |
| } |
| else |
| { |
| return false; |
| } |
| |
| // Only handle a * b + c where c != a * b |
| if (instrAdd->GetSrc1()->IsEqual(instrAdd->GetSrc2())) |
| { |
| return false; |
| } |
| |
| if (addCommonSrcOpnd->m_isTempLastUse == false) |
| { |
| return false; |
| } |
| |
| IR::Opnd *mulSrc1 = instrMul->GetSrc1(); |
| IR::Opnd *mulSrc2 = instrMul->GetSrc2(); |
| |
| if (mulSrc1->IsRegOpnd() && mulSrc1->AsRegOpnd()->IsTaggedInt() |
| && mulSrc2->IsRegOpnd() && mulSrc2->AsRegOpnd()->IsTaggedInt()) |
| { |
| return false; |
| } |
| |
| // Save prevInstr for the main lower loop |
| *pInstrPrev = instrMul->m_prev; |
| |
| // Generate int31 fast-path for Mul, go to MulAdd helper if it fails, or one of the source is marked notInt |
| if (!(addSrc->IsRegOpnd() && addSrc->AsRegOpnd()->IsNotInt()) |
| && !(mulSrc1->IsRegOpnd() && mulSrc1->AsRegOpnd()->IsNotInt()) |
| && !(mulSrc2->IsRegOpnd() && mulSrc2->AsRegOpnd()->IsNotInt())) |
| { |
| this->GenerateFastMul(instrMul); |
| |
| IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| IR::Instr *instr = IR::BranchInstr::New(Js::OpCode::B, labelHelper, this->m_func); |
| instrMul->InsertBefore(instr); |
| |
| // Generate int31 fast-path for Add |
| bool success; |
| if (isSub) |
| { |
| success = this->GenerateFastSub(instrAdd); |
| } |
| else |
| { |
| success = this->GenerateFastAdd(instrAdd); |
| } |
| |
| if (!success) |
| { |
| labelHelper->isOpHelper = false; |
| } |
| // Generate MulAdd helper call |
| instrAdd->InsertBefore(labelHelper); |
| } |
| |
| if (instrAdd->dstIsTempNumber) |
| { |
| m_lowerer->LoadHelperTemp(instrAdd, instrAdd); |
| } |
| else |
| { |
| IR::Opnd *tempOpnd = IR::IntConstOpnd::New(0, TyInt32, this->m_func); |
| this->LoadHelperArgument(instrAdd, tempOpnd); |
| } |
| this->m_lowerer->LoadScriptContext(instrAdd); |
| |
| IR::JnHelperMethod helper; |
| |
| if (addSrc == instrAdd->GetSrc2()) |
| { |
| instrAdd->FreeSrc1(); |
| IR::Opnd *addOpnd = instrAdd->UnlinkSrc2(); |
| this->LoadHelperArgument(instrAdd, addOpnd); |
| helper = isSub ? IR::HelperOp_MulSubRight : IR::HelperOp_MulAddRight; |
| } |
| else |
| { |
| instrAdd->FreeSrc2(); |
| IR::Opnd *addOpnd = instrAdd->UnlinkSrc1(); |
| this->LoadHelperArgument(instrAdd, addOpnd); |
| helper = isSub ? IR::HelperOp_MulSubLeft : IR::HelperOp_MulAddLeft; |
| } |
| |
| IR::Opnd *src2 = instrMul->UnlinkSrc2(); |
| this->LoadHelperArgument(instrAdd, src2); |
| |
| IR::Opnd *src1 = instrMul->UnlinkSrc1(); |
| this->LoadHelperArgument(instrAdd, src1); |
| |
| this->ChangeToHelperCall(instrAdd, helper); |
| |
| instrMul->Remove(); |
| |
| return true; |
| } |
| |
| IR::Instr * |
| LowererMD::LoadCheckedFloat( |
| IR::RegOpnd *opndOrig, |
| IR::RegOpnd *opndFloat, |
| IR::LabelInstr *labelInline, |
| IR::LabelInstr *labelHelper, |
| IR::Instr *instrInsert, |
| const bool checkForNullInLoopBody) |
| { |
| // |
| // if (TaggedInt::Is(opndOrig)) |
| // opndFloat = FCVT opndOrig_32 |
| // B $labelInline |
| // else |
| // B $labelOpndIsNotInt |
| // |
| // $labelOpndIsNotInt: |
| // if (TaggedFloat::Is(opndOrig)) |
| // s2 = MOV opndOrig |
| // s2 = EOR FloatTag_Value |
| // opndFloat = FCVT s2 |
| // else |
| // B $labelHelper |
| // |
| // $labelInline: |
| // |
| |
| IR::Instr *instrFirst = nullptr; |
| |
| IR::LabelInstr *labelOpndIsNotInt = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| GenerateSmIntTest(opndOrig, instrInsert, labelOpndIsNotInt, &instrFirst); |
| |
| if (opndOrig->GetValueType().IsLikelyFloat()) |
| { |
| // Make this path helper if value is likely a float |
| instrInsert->InsertBefore(IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true)); |
| } |
| |
| IR::Opnd *opndOrig_32 = opndOrig->UseWithNewType(TyInt32, this->m_func); |
| EmitIntToFloat(opndFloat, opndOrig_32, instrInsert); |
| |
| IR::Instr *jmpInline = IR::BranchInstr::New(Js::OpCode::B, labelInline, this->m_func); |
| instrInsert->InsertBefore(jmpInline); |
| |
| instrInsert->InsertBefore(labelOpndIsNotInt); |
| |
| GenerateFloatTest(opndOrig, instrInsert, labelHelper, checkForNullInLoopBody); |
| |
| IR::RegOpnd *s2 = IR::RegOpnd::New(TyMachReg, this->m_func); |
| IR::Instr *mov = IR::Instr::New(Js::OpCode::MOV, s2, opndOrig, this->m_func); |
| instrInsert->InsertBefore(mov); |
| |
| IR::Instr *eorTag = IR::Instr::New(Js::OpCode::EOR, |
| s2, |
| s2, |
| IR::IntConstOpnd::New(Js::FloatTag_Value, |
| TyMachReg, |
| this->m_func, |
| /* dontEncode = */ true), |
| this->m_func); |
| instrInsert->InsertBefore(eorTag); |
| |
| IR::Instr *movFloat = IR::Instr::New(Js::OpCode::FMOV_GEN, opndFloat, s2, this->m_func); |
| instrInsert->InsertBefore(movFloat); |
| |
| return instrFirst; |
| } |
| |
| void |
| LowererMD::EmitLoadFloatFromNumber(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr) |
| { |
| IR::LabelInstr *labelDone; |
| IR::Instr *instr; |
| labelDone = EmitLoadFloatCommon(dst, src, insertInstr, insertInstr->HasBailOutInfo()); |
| |
| if (labelDone == nullptr) |
| { |
| // We're done |
| insertInstr->Remove(); |
| return; |
| } |
| |
| // $Done note: insertAfter |
| insertInstr->InsertAfter(labelDone); |
| |
| if (!insertInstr->HasBailOutInfo()) |
| { |
| // $Done |
| insertInstr->Remove(); |
| return; |
| } |
| |
| IR::LabelInstr *labelNoBailOut = nullptr; |
| IR::SymOpnd *tempSymOpnd = nullptr; |
| |
| if (insertInstr->GetBailOutKind() == IR::BailOutPrimitiveButString) |
| { |
| if (!this->m_func->tempSymDouble) |
| { |
| this->m_func->tempSymDouble = StackSym::New(TyFloat64, this->m_func); |
| this->m_func->StackAllocate(this->m_func->tempSymDouble, MachDouble); |
| } |
| |
| // LEA r3, tempSymDouble |
| IR::RegOpnd *reg3Opnd = IR::RegOpnd::New(TyMachReg, this->m_func); |
| tempSymOpnd = IR::SymOpnd::New(this->m_func->tempSymDouble, TyFloat64, this->m_func); |
| Lowerer::InsertLea(reg3Opnd, tempSymOpnd, insertInstr); |
| |
| // regBoolResult = to_number_fromPrimitive(value, &dst, allowUndef, scriptContext); |
| |
| this->m_lowerer->LoadScriptContext(insertInstr); |
| IR::IntConstOpnd *allowUndefOpnd; |
| if (insertInstr->GetBailOutKind() == IR::BailOutPrimitiveButString) |
| { |
| allowUndefOpnd = IR::IntConstOpnd::New(true, TyInt32, this->m_func); |
| } |
| else |
| { |
| Assert(insertInstr->GetBailOutKind() == IR::BailOutNumberOnly); |
| allowUndefOpnd = IR::IntConstOpnd::New(false, TyInt32, this->m_func); |
| } |
| this->LoadHelperArgument(insertInstr, allowUndefOpnd); |
| this->LoadHelperArgument(insertInstr, reg3Opnd); |
| this->LoadHelperArgument(insertInstr, src); |
| |
| IR::RegOpnd *regBoolResult = IR::RegOpnd::New(TyInt32, this->m_func); |
| instr = IR::Instr::New(Js::OpCode::Call, regBoolResult, IR::HelperCallOpnd::New(IR::HelperOp_ConvNumber_FromPrimitive, this->m_func), this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| this->LowerCall(instr, 0); |
| |
| // TEST regBoolResult, regBoolResult |
| instr = IR::Instr::New(Js::OpCode::TST, this->m_func); |
| instr->SetSrc1(regBoolResult); |
| instr->SetSrc2(regBoolResult); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| |
| // BNE $noBailOut |
| labelNoBailOut = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| instr = IR::BranchInstr::New(Js::OpCode::BNE, labelNoBailOut, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| |
| // Bailout code |
| Assert(insertInstr->m_opcode == Js::OpCode::FromVar); |
| insertInstr->UnlinkDst(); |
| insertInstr->FreeSrc1(); |
| IR::Instr *bailoutInstr = insertInstr; |
| insertInstr = bailoutInstr->m_next; |
| this->m_lowerer->GenerateBailOut(bailoutInstr); |
| |
| // $noBailOut |
| if (labelNoBailOut) |
| { |
| insertInstr->InsertBefore(labelNoBailOut); |
| |
| Assert(dst->IsRegOpnd()); |
| |
| // VLDR dst, [pResult].f64 |
| instr = IR::Instr::New(Js::OpCode::FLDR, dst, tempSymOpnd, this->m_func); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| } |
| |
| IR::LabelInstr* |
| LowererMD::EmitLoadFloatCommon(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, bool needHelperLabel) |
| { |
| IR::Instr *instr; |
| |
| Assert(src->GetType() == TyVar); |
| Assert(dst->GetType() == TyFloat64 || TyFloat32); |
| bool isFloatConst = false; |
| IR::RegOpnd *regFloatOpnd = nullptr; |
| |
| if (src->IsRegOpnd() && src->AsRegOpnd()->m_sym->m_isFltConst) |
| { |
| IR::RegOpnd *regOpnd = src->AsRegOpnd(); |
| Assert(regOpnd->m_sym->m_isSingleDef); |
| |
| Js::Var value = regOpnd->m_sym->GetFloatConstValueAsVar_PostGlobOpt(); |
| IR::MemRefOpnd *memRef = IR::MemRefOpnd::New((BYTE*)value + Js::JavascriptNumber::GetValueOffset(), TyFloat64, this->m_func, IR::AddrOpndKindDynamicDoubleRef); |
| regFloatOpnd = IR::RegOpnd::New(TyFloat64, this->m_func); |
| instr = IR::Instr::New(Js::OpCode::FLDR, regFloatOpnd, memRef, this->m_func); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| isFloatConst = true; |
| } |
| // Src is constant? |
| if (src->IsImmediateOpnd() || src->IsFloatConstOpnd()) |
| { |
| regFloatOpnd = IR::RegOpnd::New(TyFloat64, this->m_func); |
| m_lowerer->LoadFloatFromNonReg(src, regFloatOpnd, insertInstr); |
| isFloatConst = true; |
| } |
| if (isFloatConst) |
| { |
| if (dst->GetType() == TyFloat32) |
| { |
| // FCVT.F32.F64 regOpnd32.f32, regOpnd.f64 -- Convert regOpnd from f64 to f32 |
| IR::RegOpnd *regOpnd32 = regFloatOpnd->UseWithNewType(TyFloat32, this->m_func)->AsRegOpnd(); |
| instr = IR::Instr::New(Js::OpCode::FCVT, regOpnd32, regFloatOpnd, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| // FMOV dst, regOpnd32 |
| instr = IR::Instr::New(Js::OpCode::FMOV, dst, regOpnd32, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| else |
| { |
| instr = IR::Instr::New(Js::OpCode::FMOV, dst, regFloatOpnd, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| LegalizeMD::LegalizeInstr(instr); |
| return nullptr; |
| } |
| Assert(src->IsRegOpnd()); |
| |
| IR::LabelInstr *labelStore = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| IR::LabelInstr *labelHelper; |
| IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| if (needHelperLabel) |
| { |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| } |
| else |
| { |
| labelHelper = labelDone; |
| } |
| IR::RegOpnd *reg2 = IR::RegOpnd::New(TyMachDouble, this->m_func); |
| // Load the float value in reg2 |
| this->LoadCheckedFloat(src->AsRegOpnd(), reg2, labelStore, labelHelper, insertInstr, needHelperLabel); |
| |
| // $Store |
| insertInstr->InsertBefore(labelStore); |
| if (dst->GetType() == TyFloat32) |
| { |
| IR::RegOpnd *reg2_32 = reg2->UseWithNewType(TyFloat32, this->m_func)->AsRegOpnd(); |
| // FCVT.F32.F64 r2_32.f32, r2.f64 -- Convert regOpnd from f64 to f32 |
| instr = IR::Instr::New(Js::OpCode::FCVT, reg2_32, reg2, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| // FMOV dst, r2_32 |
| instr = IR::Instr::New(Js::OpCode::FMOV, dst, reg2_32, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| else |
| { |
| // FMOV dst, r2 |
| instr = IR::Instr::New(Js::OpCode::FMOV, dst, reg2, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| LegalizeMD::LegalizeInstr(instr); |
| |
| // B $Done |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelDone, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| if (needHelperLabel) |
| { |
| // $Helper |
| insertInstr->InsertBefore(labelHelper); |
| } |
| |
| return labelDone; |
| } |
| |
| void |
| LowererMD::EmitLoadFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *insertInstr, IR::Instr * instrBailOut, IR::LabelInstr * labelBailOut) |
| { |
| IR::LabelInstr *labelDone; |
| IR::Instr *instr; |
| |
| Assert(src->GetType() == TyVar); |
| Assert(dst->GetType() == TyFloat64 || TyFloat32); |
| Assert(src->IsRegOpnd()); |
| |
| labelDone = EmitLoadFloatCommon(dst, src, insertInstr, true); |
| |
| if (labelDone == nullptr) |
| { |
| // We're done |
| return; |
| } |
| |
| IR::BailOutKind bailOutKind = instrBailOut && instrBailOut->HasBailOutInfo() ? instrBailOut->GetBailOutKind() : IR::BailOutInvalid; |
| |
| if (bailOutKind & IR::BailOutOnArrayAccessHelperCall) |
| { |
| // Bail out instead of making the helper call. |
| Assert(labelBailOut); |
| m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, insertInstr); |
| insertInstr->InsertBefore(labelDone); |
| return; |
| } |
| |
| IR::Opnd *memAddress = dst; |
| if (dst->IsRegOpnd()) |
| { |
| IR::SymOpnd *symOpnd = nullptr; |
| if (dst->GetType() == TyFloat32) |
| { |
| symOpnd = IR::SymOpnd::New(StackSym::New(TyFloat32, this->m_func), TyFloat32, this->m_func); |
| this->m_func->StackAllocate(symOpnd->m_sym->AsStackSym(), sizeof(float)); |
| } |
| else |
| { |
| symOpnd = IR::SymOpnd::New(StackSym::New(TyFloat64,this->m_func), TyMachDouble, this->m_func); |
| this->m_func->StackAllocate(symOpnd->m_sym->AsStackSym(), sizeof(double)); |
| } |
| memAddress = symOpnd; |
| } |
| |
| // LEA r3, dst |
| IR::RegOpnd *reg3Opnd = IR::RegOpnd::New(TyMachReg, this->m_func); |
| Lowerer::InsertLea(reg3Opnd, memAddress, insertInstr); |
| |
| // to_number_full(value, &dst, scriptContext); |
| // Create dummy binary op to convert into helper |
| |
| instr = IR::Instr::New(Js::OpCode::Add_A, this->m_func); |
| instr->SetSrc1(src); |
| instr->SetSrc2(reg3Opnd); |
| insertInstr->InsertBefore(instr); |
| |
| if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) |
| { |
| _Analysis_assume_(instrBailOut != nullptr); |
| instr = instr->ConvertToBailOutInstr(instrBailOut->GetBailOutInfo(), bailOutKind); |
| if (instrBailOut->GetBailOutInfo()->bailOutInstr == instrBailOut) |
| { |
| IR::Instr * instrShare = instrBailOut->ShareBailOut(); |
| m_lowerer->LowerBailTarget(instrShare); |
| } |
| } |
| |
| IR::JnHelperMethod helper; |
| if (dst->GetType() == TyFloat32) |
| { |
| helper = IR::HelperOp_ConvFloat_Helper; |
| } |
| else |
| { |
| helper = IR::HelperOp_ConvNumber_Helper; |
| } |
| |
| this->m_lowerer->LowerBinaryHelperMem(instr, helper); |
| |
| if (dst->IsRegOpnd()) |
| { |
| instr = IR::Instr::New(Js::OpCode::FLDR, dst , memAddress, this->m_func); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| |
| // $Done |
| insertInstr->InsertBefore(labelDone); |
| } |
| |
| void |
| LowererMD::GenerateFastRecyclerAlloc(size_t allocSize, IR::RegOpnd* newObjDst, IR::Instr* insertionPointInstr, IR::LabelInstr* allocHelperLabel, IR::LabelInstr* allocDoneLabel) |
| { |
| ScriptContextInfo* scriptContext = this->m_func->GetScriptContextInfo(); |
| void* allocatorAddress; |
| uint32 endAddressOffset; |
| uint32 freeListOffset; |
| size_t alignedSize = HeapInfo::GetAlignedSizeNoCheck(allocSize); |
| |
| bool allowNativeCodeBumpAllocation = scriptContext->GetRecyclerAllowNativeCodeBumpAllocation(); |
| Recycler::GetNormalHeapBlockAllocatorInfoForNativeAllocation((void*)scriptContext->GetRecyclerAddr(), alignedSize, |
| allocatorAddress, endAddressOffset, freeListOffset, |
| allowNativeCodeBumpAllocation, this->m_func->IsOOPJIT()); |
| |
| IR::RegOpnd * allocatorAddressRegOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); |
| |
| // LDIMM allocatorAddressRegOpnd, allocator |
| IR::AddrOpnd* allocatorAddressOpnd = IR::AddrOpnd::New(allocatorAddress, IR::AddrOpndKindDynamicMisc, this->m_func); |
| IR::Instr * loadAllocatorAddressInstr = IR::Instr::New(Js::OpCode::LDIMM, allocatorAddressRegOpnd, allocatorAddressOpnd, this->m_func); |
| insertionPointInstr->InsertBefore(loadAllocatorAddressInstr); |
| |
| IR::IndirOpnd * endAddressOpnd = IR::IndirOpnd::New(allocatorAddressRegOpnd, endAddressOffset, TyMachPtr, this->m_func); |
| IR::IndirOpnd * freeObjectListOpnd = IR::IndirOpnd::New(allocatorAddressRegOpnd, freeListOffset, TyMachPtr, this->m_func); |
| |
| // LDR newObjDst, allocator->freeObjectList |
| IR::Instr * loadMemBlockInstr = IR::Instr::New(Js::OpCode::LDR, newObjDst, freeObjectListOpnd, this->m_func); |
| insertionPointInstr->InsertBefore(loadMemBlockInstr); |
| LegalizeMD::LegalizeInstr(loadMemBlockInstr); |
| |
| // nextMemBlock = ADD newObjDst, allocSize |
| IR::RegOpnd * nextMemBlockOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func); |
| IR::IntConstOpnd* allocSizeOpnd = IR::IntConstOpnd::New((int32)allocSize, TyInt32, this->m_func); |
| IR::Instr * loadNextMemBlockInstr = IR::Instr::New(Js::OpCode::ADD, nextMemBlockOpnd, newObjDst, allocSizeOpnd, this->m_func); |
| insertionPointInstr->InsertBefore(loadNextMemBlockInstr); |
| LegalizeMD::LegalizeInstr(loadNextMemBlockInstr); |
| |
| // CMP nextMemBlock, allocator->endAddress |
| IR::Instr * checkInstr = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| checkInstr->SetSrc1(nextMemBlockOpnd); |
| checkInstr->SetSrc2(endAddressOpnd); |
| insertionPointInstr->InsertBefore(checkInstr); |
| LegalizeMD::LegalizeInstr(checkInstr); |
| |
| // BHI $allocHelper |
| IR::BranchInstr * branchToAllocHelperInstr = IR::BranchInstr::New(Js::OpCode::BHI, allocHelperLabel, this->m_func); |
| insertionPointInstr->InsertBefore(branchToAllocHelperInstr); |
| |
| // LDR allocator->freeObjectList, nextMemBlock |
| IR::Instr * setFreeObjectListInstr = IR::Instr::New(Js::OpCode::LDR, freeObjectListOpnd, nextMemBlockOpnd, this->m_func); |
| insertionPointInstr->InsertBefore(setFreeObjectListInstr); |
| LegalizeMD::LegalizeInstr(setFreeObjectListInstr); |
| |
| // B $allocDone |
| IR::BranchInstr * branchToAllocDoneInstr = IR::BranchInstr::New(Js::OpCode::B, allocDoneLabel, this->m_func); |
| insertionPointInstr->InsertBefore(branchToAllocDoneInstr); |
| } |
| |
| void |
| LowererMD::GenerateClz(IR::Instr * instr) |
| { |
| Assert(instr->GetSrc1()->IsIntegral32()); |
| Assert(IRType_IsNativeInt(instr->GetDst()->GetType())); |
| instr->m_opcode = Js::OpCode::CLZ; |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| |
| void |
| LowererMD::SaveDoubleToVar(IR::RegOpnd * dstOpnd, IR::RegOpnd *opndFloat, IR::Instr *instrOrig, IR::Instr *instrInsert, bool isHelper) |
| { |
| Assert(opndFloat->GetType() == TyFloat64); |
| |
| // Call JSNumber::ToVar to save the float operand to the result of the original (var) instruction |
| |
| // s1 = MOV opndFloat |
| IR::RegOpnd *s1 = IR::RegOpnd::New(TyMachReg, m_func); |
| IR::Instr *mov = IR::Instr::New(Js::OpCode::FMOV_GEN, s1, opndFloat, m_func); |
| instrInsert->InsertBefore(mov); |
| |
| if (m_func->GetJITFunctionBody()->IsAsmJsMode()) |
| { |
| // s1 = FMOV_GEN src |
| // tmp = UBFX s1, #52, #11 ; extract exponent, bits 52-62 |
| // cmp = tmp, 0x7FF |
| // beq tmp, helper |
| // b done |
| // helper: |
| // tmp2 = tmp2 = UBFX s1, #0, #52 ; extract mantissa, bits 0-51 |
| // cbz tmp2, done |
| // s1 = JavascriptNumber::k_Nan |
| // done: |
| |
| IR::RegOpnd* tmp = IR::RegOpnd::New(TyMachReg, m_func); |
| |
| IR::Instr* newInstr = IR::Instr::New(Js::OpCode::UBFX, tmp, s1, IR::IntConstOpnd::New(BITFIELD(52, 11), TyMachReg, m_func, true), m_func); |
| instrInsert->InsertBefore(newInstr); |
| LowererMD::Legalize(newInstr); |
| |
| newInstr = IR::Instr::New(Js::OpCode::CMP, tmp, IR::IntConstOpnd::New(0x7FF, TyMachReg, m_func, true), m_func); |
| instrInsert->InsertBefore(newInstr); |
| LowererMD::Legalize(newInstr); |
| |
| IR::LabelInstr* helper = Lowerer::InsertLabel(true, instrInsert); |
| IR::Instr* branch = IR::BranchInstr::New(Js::OpCode::BEQ, helper, m_func); |
| helper->InsertBefore(branch); |
| |
| IR::LabelInstr* done = Lowerer::InsertLabel(isHelper, instrInsert); |
| |
| Lowerer::InsertBranch(Js::OpCode::Br, done, helper); |
| |
| IR::RegOpnd* tmp2 = IR::RegOpnd::New(TyMachReg, m_func); |
| |
| newInstr = IR::Instr::New(Js::OpCode::UBFX, tmp2, s1, IR::IntConstOpnd::New(BITFIELD(0, 52), TyMachReg, m_func, true), m_func); |
| done->InsertBefore(newInstr); |
| LowererMD::Legalize(newInstr); |
| |
| branch = IR::BranchInstr::New(Js::OpCode::CBZ, done, m_func); |
| branch->SetSrc1(tmp2); |
| done->InsertBefore(branch); |
| |
| IR::Opnd * opndNaN = IR::AddrOpnd::New((Js::Var)Js::JavascriptNumber::k_Nan, IR::AddrOpndKindConstantVar, m_func, true); |
| Lowerer::InsertMove(s1, opndNaN, done); |
| } |
| |
| // s1 = EOR s1, FloatTag_Value |
| // dst = s1 |
| |
| IR::Instr* setTag = IR::Instr::New(Js::OpCode::EOR, s1, s1, IR::AddrOpnd::New((Js::Var)Js::FloatTag_Value, IR::AddrOpndKindConstantVar, this->m_func, true), this->m_func); |
| IR::Instr* movDst = IR::Instr::New(Js::OpCode::MOV, dstOpnd, s1, this->m_func); |
| |
| instrInsert->InsertBefore(setTag); |
| instrInsert->InsertBefore(movDst); |
| LowererMD::Legalize(setTag); |
| } |
| |
| void |
| LowererMD::GenerateFastAbs(IR::Opnd *dst, IR::Opnd *src, IR::Instr *callInstr, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::LabelInstr *doneLabel) |
| { |
| // if isFloat goto $float |
| // s1 = MOV src |
| // CMP s1, #0 |
| // s1 = CSNEGPL s1, s1 |
| // TBNZ s1, #31, $labelHelper |
| // s1 = ORR s1, AtomTag_IntPtr |
| // JMP $done |
| // $float |
| // CMP [src], JavascriptNumber.vtable |
| // JNE $helper |
| // MOVSD r1, [src + offsetof(value)] |
| // ANDPD r1, absDoubleCst |
| // dst = DoubleToVar(r1) |
| |
| IR::Instr *instr = nullptr; |
| IR::LabelInstr *labelFloat = nullptr; |
| bool isInt = false; |
| bool isNotInt = false; |
| |
| if (src->IsRegOpnd()) |
| { |
| if (src->AsRegOpnd()->IsTaggedInt()) |
| { |
| isInt = true; |
| |
| } |
| else if (src->AsRegOpnd()->IsNotInt()) |
| { |
| isNotInt = true; |
| } |
| } |
| else if (src->IsAddrOpnd()) |
| { |
| IR::AddrOpnd *varOpnd = src->AsAddrOpnd(); |
| Assert(varOpnd->IsVar() && Js::TaggedInt::Is(varOpnd->m_address)); |
| |
| int64 absValue = ::_abs64(Js::TaggedInt::ToInt32(varOpnd->m_address)); |
| |
| if (!Js::TaggedInt::IsOverflow(absValue)) |
| { |
| varOpnd->SetAddress(Js::TaggedInt::ToVarUnchecked((int32)absValue), IR::AddrOpndKindConstantVar); |
| |
| instr = IR::Instr::New(Js::OpCode::MOV, dst, varOpnd, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| return; |
| } |
| } |
| |
| if (src->IsRegOpnd() == false) |
| { |
| IR::RegOpnd *regOpnd = IR::RegOpnd::New(TyVar, this->m_func); |
| instr = IR::Instr::New(Js::OpCode::MOV, regOpnd, src, this->m_func); |
| insertInstr->InsertBefore(instr); |
| src = regOpnd; |
| } |
| |
| bool emitFloatAbs = !isInt; |
| |
| if (!isNotInt) |
| { |
| if (!isInt) |
| { |
| IR::LabelInstr *label = labelHelper; |
| if (emitFloatAbs) |
| { |
| label = labelFloat = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| } |
| |
| GenerateSmIntTest(src, insertInstr, label); |
| } |
| |
| // s1 = MOV src |
| IR::RegOpnd *regSrc = IR::RegOpnd::New(TyInt32, this->m_func); |
| Lowerer::InsertMove(regSrc, src, insertInstr); |
| |
| // CMP s1, #0 |
| instr = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| instr->SetSrc1(regSrc); |
| instr->SetSrc2(IR::IntConstOpnd::New(0, IRType::TyInt32, this->m_func)); |
| insertInstr->InsertBefore(instr); |
| Legalize(instr); |
| |
| // s1 = CSNEGPL s1, s1 |
| instr = IR::Instr::New(Js::OpCode::CSNEGPL, regSrc, regSrc, regSrc, this->m_func); |
| insertInstr->InsertBefore(instr); |
| Legalize(instr); |
| |
| // TBNZ s1, #31, $labelHelper |
| instr = IR::BranchInstr::New(Js::OpCode::TBNZ, labelHelper, this->m_func); |
| instr->SetSrc1(regSrc); |
| instr->SetSrc2(IR::IntConstOpnd::New(31, IRType::TyInt32, this->m_func)); |
| insertInstr->InsertBefore(instr); |
| |
| // MOV dst, s1 |
| instr = IR::Instr::New(Js::OpCode::MOV, dst, regSrc, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| GenerateInt32ToVarConversion(dst, insertInstr); |
| } |
| |
| if (labelFloat) |
| { |
| // B $done |
| instr = IR::BranchInstr::New(Js::OpCode::B, doneLabel, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| // $float |
| insertInstr->InsertBefore(labelFloat); |
| } |
| |
| if (emitFloatAbs) |
| { |
| // if (typeof(src) == double) |
| IR::RegOpnd *src64 = src->AsRegOpnd(); |
| GenerateFloatTest(src64, insertInstr, labelHelper); |
| |
| // dst64 = MOV src64 |
| insertInstr->InsertBefore(IR::Instr::New(Js::OpCode::MOV, dst, src64, this->m_func)); |
| |
| // Unconditionally set the sign bit. This will get XORd away when we remove the tag. |
| // dst64 = ORR 0x8000000000000000 |
| insertInstr->InsertBefore(IR::Instr::New(Js::OpCode::ORR, dst, dst, IR::IntConstOpnd::New(MachSignBit, TyMachReg, this->m_func), this->m_func)); |
| } |
| else if(!isInt) |
| { |
| // The source is not known to be a tagged int, so either it's definitely not an int (isNotInt), or the int version of |
| // abs failed the tag check and jumped here. We can't emit the float version of abs (!emitFloatAbs) due to SSE2 not |
| // being available, so jump straight to the helper. |
| |
| // JMP $helper |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelHelper, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| } |
| |
| void |
| LowererMD::EmitInt4Instr(IR::Instr *instr) |
| { |
| IR::Instr * newInstr; |
| IR::Opnd * src1; |
| IR::Opnd * src2; |
| |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::Neg_I4: |
| instr->m_opcode = Js::OpCode::SUB; |
| instr->SetSrc2(instr->UnlinkSrc1()); |
| instr->SetSrc1(IR::RegOpnd::New(nullptr, RegZR, TyInt32, instr->m_func)); |
| break; |
| |
| case Js::OpCode::Not_I4: |
| instr->m_opcode = Js::OpCode::MVN; |
| break; |
| |
| case Js::OpCode::Add_I4: |
| instr->m_opcode = Js::OpCode::ADD; |
| break; |
| |
| case Js::OpCode::Sub_I4: |
| instr->m_opcode = Js::OpCode::SUB; |
| break; |
| |
| case Js::OpCode::Mul_I4: |
| instr->m_opcode = Js::OpCode::MUL; |
| break; |
| |
| case Js::OpCode::DivU_I4: |
| AssertMsg(UNREACHED, "Unsigned div NYI"); |
| case Js::OpCode::Div_I4: |
| instr->m_opcode = Js::OpCode::SDIV; |
| break; |
| |
| case Js::OpCode::RemU_I4: |
| AssertMsg(UNREACHED, "Unsigned rem NYI"); |
| case Js::OpCode::Rem_I4: |
| instr->m_opcode = Js::OpCode::REM; |
| break; |
| |
| case Js::OpCode::Or_I4: |
| instr->m_opcode = Js::OpCode::ORR; |
| break; |
| |
| case Js::OpCode::Xor_I4: |
| instr->m_opcode = Js::OpCode::EOR; |
| break; |
| |
| case Js::OpCode::And_I4: |
| instr->m_opcode = Js::OpCode::AND; |
| break; |
| |
| case Js::OpCode::Shl_I4: |
| case Js::OpCode::ShrU_I4: |
| case Js::OpCode::Shr_I4: |
| ChangeToShift(instr, false /* needFlags */); |
| break; |
| |
| case Js::OpCode::BrTrue_I4: |
| instr->m_opcode = Js::OpCode::CBNZ; |
| break; |
| |
| case Js::OpCode::BrFalse_I4: |
| instr->m_opcode = Js::OpCode::CBZ; |
| break; |
| |
| case Js::OpCode::BrEq_I4: |
| instr->m_opcode = Js::OpCode::BEQ; |
| goto br2_Common; |
| |
| case Js::OpCode::BrNeq_I4: |
| instr->m_opcode = Js::OpCode::BNE; |
| goto br2_Common; |
| |
| case Js::OpCode::BrGt_I4: |
| instr->m_opcode = Js::OpCode::BGT; |
| goto br2_Common; |
| |
| case Js::OpCode::BrGe_I4: |
| instr->m_opcode = Js::OpCode::BGE; |
| goto br2_Common; |
| |
| case Js::OpCode::BrLe_I4: |
| instr->m_opcode = Js::OpCode::BLE; |
| goto br2_Common; |
| |
| case Js::OpCode::BrLt_I4: |
| instr->m_opcode = Js::OpCode::BLT; |
| goto br2_Common; |
| |
| case Js::OpCode::BrUnGt_I4: |
| instr->m_opcode = Js::OpCode::BHI; |
| goto br2_Common; |
| |
| case Js::OpCode::BrUnGe_I4: |
| instr->m_opcode = Js::OpCode::BCS; |
| goto br2_Common; |
| |
| case Js::OpCode::BrUnLt_I4: |
| instr->m_opcode = Js::OpCode::BCC; |
| goto br2_Common; |
| |
| case Js::OpCode::BrUnLe_I4: |
| instr->m_opcode = Js::OpCode::BLS; |
| goto br2_Common; |
| |
| br2_Common: |
| src1 = instr->UnlinkSrc1(); |
| src2 = instr->UnlinkSrc2(); |
| newInstr = IR::Instr::New(Js::OpCode::CMP, instr->m_func); |
| instr->InsertBefore(newInstr); |
| newInstr->SetSrc1(src1); |
| newInstr->SetSrc2(src2); |
| // Let instr point to the CMP so we can legalize it. |
| instr = newInstr; |
| break; |
| |
| default: |
| AssertMsg(UNREACHED, "NYI I4 instr"); |
| break; |
| } |
| |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| |
| void |
| LowererMD::LowerInt4NegWithBailOut( |
| IR::Instr *const instr, |
| const IR::BailOutKind bailOutKind, |
| IR::LabelInstr *const bailOutLabel, |
| IR::LabelInstr *const skipBailOutLabel) |
| { |
| Assert(instr); |
| Assert(instr->m_opcode == Js::OpCode::Neg_I4); |
| Assert(!instr->HasBailOutInfo()); |
| Assert(bailOutKind & IR::BailOutOnResultConditions || bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck); |
| Assert(bailOutLabel); |
| Assert(instr->m_next == bailOutLabel); |
| Assert(skipBailOutLabel); |
| Assert(instr->GetDst()->IsInt32()); |
| Assert(instr->GetSrc1()->IsInt32()); |
| |
| // SUBS dst, zr, src1 |
| // BVS $bailOutLabel |
| // BEQ $bailOutLabel |
| // B $skipBailOut |
| // $bailOut: |
| // ... |
| // $skipBailOut: |
| |
| // Lower the instruction |
| instr->m_opcode = Js::OpCode::SUBS; |
| instr->ReplaceDst(instr->GetDst()->UseWithNewType(TyInt32, instr->m_func)); |
| instr->SetSrc2(instr->UnlinkSrc1()->UseWithNewType(TyInt32, instr->m_func)); |
| instr->SetSrc1(IR::RegOpnd::New(nullptr, RegZR, TyInt32, instr->m_func)); |
| Legalize(instr); |
| |
| if(bailOutKind & IR::BailOutOnOverflow) |
| { |
| bailOutLabel->InsertBefore(IR::BranchInstr::New(Js::OpCode::BVS, bailOutLabel, instr->m_func)); |
| } |
| |
| if(bailOutKind & IR::BailOutOnNegativeZero) |
| { |
| bailOutLabel->InsertBefore(IR::BranchInstr::New(Js::OpCode::BEQ, bailOutLabel, instr->m_func)); |
| } |
| |
| // Skip bailout |
| bailOutLabel->InsertBefore(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, skipBailOutLabel, instr->m_func)); |
| } |
| |
| void |
| LowererMD::LowerInt4AddWithBailOut( |
| IR::Instr *const instr, |
| const IR::BailOutKind bailOutKind, |
| IR::LabelInstr *const bailOutLabel, |
| IR::LabelInstr *const skipBailOutLabel) |
| { |
| Assert(instr); |
| Assert(instr->m_opcode == Js::OpCode::Add_I4); |
| Assert(!instr->HasBailOutInfo()); |
| Assert( |
| (bailOutKind & IR::BailOutOnResultConditions) == IR::BailOutOnOverflow || |
| bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck); |
| Assert(bailOutLabel); |
| Assert(instr->m_next == bailOutLabel); |
| Assert(skipBailOutLabel); |
| Assert(instr->GetDst()->IsInt32()); |
| Assert(instr->GetSrc1()->IsInt32()); |
| Assert(instr->GetSrc2()->IsInt32()); |
| |
| // ADDS dst, src1, src2 |
| // BVC skipBailOutLabel |
| // fallthrough to bailout |
| |
| const auto dst = instr->GetDst(), src1 = instr->GetSrc1(), src2 = instr->GetSrc2(); |
| Assert(dst->IsRegOpnd()); |
| |
| const bool dstEquSrc1 = dst->IsEqual(src1), dstEquSrc2 = dst->IsEqual(src2); |
| if(dstEquSrc1 || dstEquSrc2) |
| { |
| LowererMD::ChangeToAssign(instr->SinkDst(Js::OpCode::Ld_I4, RegNOREG, skipBailOutLabel)); |
| } |
| |
| // Lower the instruction |
| ChangeToAdd(instr, true /* needFlags */); |
| Legalize(instr); |
| |
| // Skip bailout on no overflow |
| bailOutLabel->InsertBefore(IR::BranchInstr::New(Js::OpCode::BVC, skipBailOutLabel, instr->m_func)); |
| |
| // Fall through to bailOutLabel |
| } |
| |
| void |
| LowererMD::LowerInt4SubWithBailOut( |
| IR::Instr *const instr, |
| const IR::BailOutKind bailOutKind, |
| IR::LabelInstr *const bailOutLabel, |
| IR::LabelInstr *const skipBailOutLabel) |
| { |
| Assert(instr); |
| Assert(instr->m_opcode == Js::OpCode::Sub_I4); |
| Assert(!instr->HasBailOutInfo()); |
| Assert( |
| (bailOutKind & IR::BailOutOnResultConditions) == IR::BailOutOnOverflow || |
| bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck); |
| Assert(bailOutLabel); |
| Assert(instr->m_next == bailOutLabel); |
| Assert(skipBailOutLabel); |
| Assert(instr->GetDst()->IsInt32()); |
| Assert(instr->GetSrc1()->IsInt32()); |
| Assert(instr->GetSrc2()->IsInt32()); |
| |
| // SUBS dst, src1, src2 |
| // BVC skipBailOutLabel |
| // fallthrough to bailout |
| |
| const auto dst = instr->GetDst(), src1 = instr->GetSrc1(), src2 = instr->GetSrc2(); |
| Assert(dst->IsRegOpnd()); |
| |
| const bool dstEquSrc1 = dst->IsEqual(src1), dstEquSrc2 = dst->IsEqual(src2); |
| if(dstEquSrc1 || dstEquSrc2) |
| { |
| LowererMD::ChangeToAssign(instr->SinkDst(Js::OpCode::Ld_I4, RegNOREG, skipBailOutLabel)); |
| } |
| |
| // Lower the instruction |
| ChangeToSub(instr, true /* needFlags */); |
| Legalize(instr); |
| |
| // Skip bailout on no overflow |
| bailOutLabel->InsertBefore(IR::BranchInstr::New(Js::OpCode::BVC, skipBailOutLabel, instr->m_func)); |
| |
| // Fall through to bailOutLabel |
| } |
| |
| void |
| LowererMD::LowerInt4MulWithBailOut( |
| IR::Instr *const instr, |
| const IR::BailOutKind bailOutKind, |
| IR::LabelInstr *const bailOutLabel, |
| IR::LabelInstr *const skipBailOutLabel) |
| { |
| Assert(instr); |
| Assert(instr->m_opcode == Js::OpCode::Mul_I4); |
| Assert(!instr->HasBailOutInfo()); |
| Assert(bailOutKind & IR::BailOutOnResultConditions || bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck); |
| Assert(bailOutLabel); |
| Assert(instr->m_next == bailOutLabel); |
| Assert(skipBailOutLabel); |
| |
| IR::Opnd *dst = instr->GetDst(); |
| IR::Opnd *src1 = instr->UnlinkSrc1(); |
| IR::Opnd *src2 = instr->UnlinkSrc2(); |
| IR::Instr *insertInstr; |
| |
| Assert(dst->IsInt32()); |
| Assert(src1->IsInt32()); |
| Assert(src2->IsInt32()); |
| |
| // s3 = SMULL src1, src2 // result is i64 |
| IR::Opnd* s3 = IR::RegOpnd::New(TyInt64, instr->m_func); |
| insertInstr = IR::Instr::New(Js::OpCode::SMULL, s3, src1, src2, instr->m_func); |
| instr->InsertBefore(insertInstr); |
| LegalizeMD::LegalizeInstr(insertInstr); |
| |
| // dst = MOV_TRUNC s3 |
| instr->m_opcode = Js::OpCode::MOV_TRUNC; |
| instr->SetSrc1(s3->UseWithNewType(TyInt32, instr->m_func)); |
| |
| // check negative zero |
| // |
| // If the result is zero, we need to check and only bail out if it would be -0. |
| // We know that if the result is 0/-0, at least operand should be zero. |
| // We should bailout if src1 + src2 < 0, as this proves that the other operand is negative |
| // |
| // CMN src1, src2 |
| // BPL $skipBailOutLabel |
| // |
| // $bailOutLabel |
| // GenerateBailout |
| // |
| // $skipBailOutLabel |
| IR::LabelInstr *checkForNegativeZeroLabel = nullptr; |
| if(bailOutKind & IR::BailOutOnNegativeZero) |
| { |
| checkForNegativeZeroLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, true); |
| bailOutLabel->InsertBefore(checkForNegativeZeroLabel); |
| |
| Assert(dst->IsRegOpnd()); |
| Assert(!src1->IsEqual(src2)); // cannot result in -0 if both operands are the same; GlobOpt should have figured that out |
| |
| // CMN src1, src2 |
| // BPL $skipBailOutLabel |
| insertInstr = IR::Instr::New(Js::OpCode::CMN, instr->m_func); |
| insertInstr->SetSrc1(src1); |
| insertInstr->SetSrc2(src2); |
| bailOutLabel->InsertBefore(insertInstr); |
| LegalizeMD::LegalizeInstr(insertInstr); |
| bailOutLabel->InsertBefore(IR::BranchInstr::New(Js::OpCode::BPL, skipBailOutLabel, instr->m_func)); |
| |
| // Fall through to bailOutLabel |
| } |
| |
| IR::LabelInstr* insertBeforeInstr = checkForNegativeZeroLabel ? checkForNegativeZeroLabel : bailOutLabel; |
| |
| //check overflow |
| if(bailOutKind & IR::BailOutOnMulOverflow || bailOutKind == IR::BailOutOnFailedHoistedLoopCountBasedBoundCheck) |
| { |
| insertInstr = IR::Instr::New(Js::OpCode::CMP_SXTW, instr->m_func); |
| insertInstr->SetSrc1(s3); |
| insertInstr->SetSrc2(s3); |
| instr->InsertBefore(insertInstr); |
| |
| // BNE $bailOutHelper |
| insertInstr = IR::BranchInstr::New(Js::OpCode::BNE, bailOutLabel, instr->m_func); |
| instr->InsertBefore(insertInstr); |
| } |
| |
| if(bailOutKind & IR::BailOutOnNegativeZero) |
| { |
| // TST dst, dst |
| // BEQ $checkForNegativeZeroLabel |
| insertInstr = IR::Instr::New(Js::OpCode::TST, instr->m_func); |
| insertInstr->SetSrc1(dst); |
| insertInstr->SetSrc2(dst); |
| insertBeforeInstr->InsertBefore(insertInstr); |
| LegalizeMD::LegalizeInstr(insertInstr); |
| |
| insertBeforeInstr->InsertBefore(IR::BranchInstr::New(Js::OpCode::BEQ, checkForNegativeZeroLabel, instr->m_func)); |
| } |
| |
| insertBeforeInstr->InsertBefore(IR::BranchInstr::New(Js::OpCode::B, skipBailOutLabel, instr->m_func)); |
| } |
| |
| void |
| LowererMD::LowerInt4RemWithBailOut( |
| IR::Instr *const instr, |
| const IR::BailOutKind bailOutKind, |
| IR::LabelInstr *const bailOutLabel, |
| IR::LabelInstr *const skipBailOutLabel) const |
| { |
| Assert(instr); |
| Assert(instr->m_opcode == Js::OpCode::Rem_I4); |
| Assert(!instr->HasBailOutInfo()); |
| Assert(bailOutKind & IR::BailOutOnResultConditions); |
| Assert(bailOutLabel); |
| Assert(instr->m_next == bailOutLabel); |
| Assert(skipBailOutLabel); |
| |
| IR::Opnd *dst = instr->GetDst(); |
| IR::Opnd *src1 = instr->GetSrc1(); |
| |
| Assert(dst->IsInt32()); |
| Assert(src1->IsInt32()); |
| Assert(instr->GetSrc2()->IsInt32()); |
| |
| //Lower the instruction |
| EmitInt4Instr(instr); |
| |
| //check for negative zero |
| //We have, dst = src1 % src2 |
| //We need to bailout if dst == 0 and src1 < 0 |
| // tst dst, dst |
| // bne $skipBailOutLabel |
| // tst src1,src1 |
| // bpl $skipBailOutLabel |
| // |
| //$bailOutLabel |
| // GenerateBailout(); |
| // |
| //$skipBailOutLabel |
| if(bailOutKind & IR::BailOutOnNegativeZero) |
| { |
| IR::LabelInstr *checkForNegativeZeroLabel = IR::LabelInstr::New(Js::OpCode::Label, instr->m_func, true); |
| bailOutLabel->InsertBefore(checkForNegativeZeroLabel); |
| |
| IR::Instr *insertInstr = IR::Instr::New(Js::OpCode::TST, instr->m_func); |
| insertInstr->SetSrc1(dst); |
| insertInstr->SetSrc2(dst); |
| bailOutLabel->InsertBefore(insertInstr); |
| LegalizeMD::LegalizeInstr(insertInstr); |
| |
| IR::Instr *branchInstr = IR::BranchInstr::New(Js::OpCode::BNE, skipBailOutLabel, instr->m_func); |
| bailOutLabel->InsertBefore(branchInstr); |
| |
| insertInstr = IR::Instr::New(Js::OpCode::TST, instr->m_func); |
| insertInstr->SetSrc1(src1); |
| insertInstr->SetSrc2(src1); |
| bailOutLabel->InsertBefore(insertInstr); |
| LegalizeMD::LegalizeInstr(insertInstr); |
| |
| branchInstr = IR::BranchInstr::New(Js::OpCode::BPL, skipBailOutLabel, instr->m_func); |
| bailOutLabel->InsertBefore(branchInstr); |
| } |
| // Fall through to bailOutLabel |
| } |
| |
| void |
| LowererMD::EmitLoadVar(IR::Instr *instrLoad, bool isFromUint32, bool isHelper) |
| { |
| // MOV.32 e1, e_src1 |
| // TBNZ e1, #31, $Helper [uint32] -- overflows? |
| // ORR r1, 1<<VarTag_Shift |
| // MOV r_dst, r1 |
| // JMP $done [uint32] |
| // $helper [uint32] |
| // EmitLoadVarNoCheck |
| // $done [uint32] |
| |
| |
| Assert(instrLoad->GetSrc1()->IsRegOpnd()); |
| Assert(instrLoad->GetDst()->GetType() == TyVar); |
| |
| bool isInt = false; |
| IR::Opnd *dst = instrLoad->GetDst(); |
| IR::RegOpnd *src1 = instrLoad->GetSrc1()->AsRegOpnd(); |
| IR::LabelInstr *labelHelper = nullptr; |
| |
| // TODO: Fix bad lowering. We shouldn't get TyVars here. |
| // Assert(instrLoad->GetSrc1()->GetType() == TyInt32); |
| src1->SetType(isFromUint32 ? TyUint32 : TyInt32); |
| |
| if (src1->IsTaggedInt()) |
| { |
| isInt = true; |
| } |
| else if (src1->IsNotInt()) |
| { |
| // ToVar() |
| this->EmitLoadVarNoCheck(dst->AsRegOpnd(), src1, instrLoad, isFromUint32, isHelper); |
| return; |
| } |
| |
| IR::RegOpnd *r1 = IR::RegOpnd::New(TyVar, m_func); |
| |
| // e1 = MOV_TRUNC src1 |
| // (Use 32-bit MOV_TRUNC here as we rely on the register copy to clear the upper 32 bits.) |
| IR::RegOpnd *e1 = r1->Copy(m_func)->AsRegOpnd(); |
| e1->SetType(TyInt32); |
| instrLoad->InsertBefore(IR::Instr::New(Js::OpCode::MOV_TRUNC, |
| e1, |
| src1, |
| m_func)); |
| |
| if (!isInt && isFromUint32) |
| { |
| Assert(!labelHelper); |
| labelHelper = IR::LabelInstr::New(Js::OpCode::Label, m_func, true); |
| |
| // TBNZ e1, #31, $helper |
| IR::Instr* instr = IR::BranchInstr::New(Js::OpCode::TBNZ, labelHelper, m_func); |
| instr->SetSrc1(e1); |
| instr->SetSrc2(IR::IntConstOpnd::New(31, TyInt32, m_func)); |
| instrLoad->InsertBefore(instr); |
| } |
| |
| // The previous operation clears the top 32 bits. |
| // ORR r1, 1<<VarTag_Shift |
| this->GenerateInt32ToVarConversion(r1, instrLoad); |
| |
| // REVIEW: We need r1 only if we could generate sn = Ld_A_I4 sn. i.e. the destination and |
| // source are the same. |
| // r_dst = MOV r1 |
| instrLoad->InsertBefore(IR::Instr::New(Js::OpCode::MOV, |
| dst, |
| r1, |
| m_func)); |
| |
| if (labelHelper) |
| { |
| Assert(isFromUint32); |
| |
| // B $done |
| IR::LabelInstr * labelDone = IR::LabelInstr::New(Js::OpCode::Label, m_func, isHelper); |
| instrLoad->InsertBefore(IR::BranchInstr::New(Js::OpCode::B, labelDone, m_func)); |
| |
| // $helper |
| instrLoad->InsertBefore(labelHelper); |
| |
| // ToVar() |
| this->EmitLoadVarNoCheck(dst->AsRegOpnd(), src1, instrLoad, isFromUint32, true); |
| |
| // $done |
| instrLoad->InsertBefore(labelDone); |
| } |
| instrLoad->Remove(); |
| } |
| |
| void |
| LowererMD::EmitLoadVarNoCheck(IR::RegOpnd * dst, IR::RegOpnd * src, IR::Instr *instrLoad, bool isFromUint32, bool isHelper) |
| { |
| IR::RegOpnd * floatReg = IR::RegOpnd::New(TyFloat64, this->m_func); |
| if (isFromUint32) |
| { |
| this->EmitUIntToFloat(floatReg, src, instrLoad); |
| } |
| else |
| { |
| this->EmitIntToFloat(floatReg, src, instrLoad); |
| } |
| this->SaveDoubleToVar(dst, floatReg, instrLoad, instrLoad, isHelper); |
| } |
| |
| bool |
| LowererMD::EmitLoadInt32(IR::Instr *instrLoad, bool conversionFromObjectAllowed, bool bailOutOnHelper, IR::LabelInstr * labelBailOut) |
| { |
| // |
| // r1 = MOV src1 |
| // rtest = UBFX src1, AtomTag_Shift, 64 - AtomTag_Shift |
| // EOR rtest, 1 |
| // CBNZ $helper or $float |
| // r_dst = MOV.32 e_src1 |
| // B $done |
| // $float: |
| // dst = ConvertToFloat(r1, $helper) |
| // $helper: |
| // r_dst = ToInt32() |
| // |
| |
| Assert(instrLoad->GetSrc1()->IsRegOpnd()); |
| Assert(instrLoad->GetSrc1()->GetType() == TyVar); |
| |
| // TODO: Fix bad lowering. We shouldn't see TyVars here. |
| // Assert(instrLoad->GetDst()->GetType() == TyInt32); |
| |
| bool isInt = false; |
| bool isNotInt = false; |
| IR::Opnd *dst = instrLoad->GetDst(); |
| IR::RegOpnd *src1 = instrLoad->GetSrc1()->AsRegOpnd(); |
| IR::LabelInstr *helper = nullptr; |
| IR::LabelInstr *labelFloat = nullptr; |
| IR::LabelInstr *done = nullptr; |
| |
| if (src1->IsTaggedInt()) |
| { |
| isInt = true; |
| } |
| else if (src1->IsNotInt()) |
| { |
| isNotInt = true; |
| } |
| |
| if (src1->IsEqual(instrLoad->GetDst()) == false) |
| { |
| // r1 = MOV src1 |
| IR::RegOpnd *r1 = IR::RegOpnd::New(TyVar, instrLoad->m_func); |
| r1->SetValueType(src1->GetValueType()); |
| instrLoad->InsertBefore(IR::Instr::New(Js::OpCode::MOV, r1, src1, instrLoad->m_func)); |
| src1 = r1; |
| } |
| |
| const ValueType src1ValueType(src1->GetValueType()); |
| const bool doFloatToIntFastPath = |
| (src1ValueType.IsLikelyFloat() || src1ValueType.IsLikelyUntaggedInt()) && |
| !(instrLoad->HasBailOutInfo() && (instrLoad->GetBailOutKind() == IR::BailOutIntOnly || instrLoad->GetBailOutKind() == IR::BailOutExpectingInteger)); |
| |
| if (isNotInt) |
| { |
| // Known to be non-integer. If we are required to bail out on helper call, just re-jit. |
| if (!doFloatToIntFastPath && bailOutOnHelper) |
| { |
| if(!GlobOpt::DoEliminateArrayAccessHelperCall(this->m_func)) |
| { |
| // Array access helper call removal is already off for some reason. Prevent trying to rejit again |
| // because it won't help and the same thing will happen again. Just abort jitting this function. |
| if(PHASE_TRACE(Js::BailOutPhase, this->m_func)) |
| { |
| Output::Print(_u(" Aborting JIT because EliminateArrayAccessHelperCall is already off\n")); |
| Output::Flush(); |
| } |
| throw Js::OperationAbortedException(); |
| } |
| |
| throw Js::RejitException(RejitReason::ArrayAccessHelperCallEliminationDisabled); |
| } |
| } |
| else |
| { |
| // It could be an integer in this case. |
| if (!isInt) |
| { |
| if(doFloatToIntFastPath) |
| { |
| labelFloat = IR::LabelInstr::New(Js::OpCode::Label, instrLoad->m_func, false); |
| } |
| else |
| { |
| helper = IR::LabelInstr::New(Js::OpCode::Label, instrLoad->m_func, true); |
| } |
| |
| this->GenerateSmIntTest(src1, instrLoad, labelFloat ? labelFloat : helper); |
| } |
| |
| instrLoad->InsertBefore(IR::Instr::New(Js::OpCode::MOV_TRUNC, |
| dst->UseWithNewType(TyInt32, instrLoad->m_func), |
| src1->UseWithNewType(TyInt32, instrLoad->m_func), |
| instrLoad->m_func)); |
| |
| if (!isInt) |
| { |
| // JMP $done |
| done = instrLoad->GetOrCreateContinueLabel(); |
| instrLoad->InsertBefore(IR::BranchInstr::New(Js::OpCode::B, done, m_func)); |
| } |
| } |
| |
| if (!isInt) |
| { |
| if(doFloatToIntFastPath) |
| { |
| if(labelFloat) |
| { |
| instrLoad->InsertBefore(labelFloat); |
| } |
| if(!helper) |
| { |
| helper = IR::LabelInstr::New(Js::OpCode::Label, instrLoad->m_func, true); |
| } |
| |
| if(!done) |
| { |
| done = instrLoad->GetOrCreateContinueLabel(); |
| } |
| IR::RegOpnd* floatOpnd = this->CheckFloatAndUntag(src1, instrLoad, helper); |
| this->ConvertFloatToInt32(instrLoad->GetDst(), floatOpnd, helper, done, instrLoad); |
| } |
| |
| // $helper: |
| if (helper) |
| { |
| instrLoad->InsertBefore(helper); |
| } |
| if(instrLoad->HasBailOutInfo() && (instrLoad->GetBailOutKind() == IR::BailOutIntOnly || instrLoad->GetBailOutKind() == IR::BailOutExpectingInteger)) |
| { |
| // Avoid bailout if we have a JavascriptNumber whose value is a signed 32-bit integer |
| m_lowerer->LoadInt32FromUntaggedVar(instrLoad); |
| |
| // Need to bail out instead of calling a helper |
| return true; |
| } |
| |
| if (bailOutOnHelper) |
| { |
| Assert(labelBailOut); |
| m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, instrLoad); |
| instrLoad->Remove(); |
| } |
| else if (conversionFromObjectAllowed) |
| { |
| m_lowerer->LowerUnaryHelperMem(instrLoad, IR::HelperConv_ToInt32); |
| } |
| else |
| { |
| m_lowerer->LowerUnaryHelperMemWithBoolReference(instrLoad, IR::HelperConv_ToInt32_NoObjects, true /*useBoolForBailout*/); |
| } |
| } |
| else |
| { |
| instrLoad->Remove(); |
| } |
| |
| return false; |
| } |
| |
| void |
| LowererMD::ImmedSrcToReg(IR::Instr * instr, IR::Opnd * newOpnd, int srcNum) |
| { |
| if (srcNum == 2) |
| { |
| instr->SetSrc2(newOpnd); |
| } |
| else |
| { |
| Assert(srcNum == 1); |
| instr->SetSrc1(newOpnd); |
| } |
| |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::LDIMM: |
| instr->m_opcode = Js::OpCode::MOV; |
| break; |
| default: |
| // Nothing to do (unless we have immed/reg variations for other instructions). |
| break; |
| } |
| } |
| |
| IR::LabelInstr * |
| LowererMD::GetBailOutStackRestoreLabel(BailOutInfo * bailOutInfo, IR::LabelInstr * exitTargetInstr) |
| { |
| return exitTargetInstr; |
| } |
| |
| StackSym * |
| LowererMD::GetImplicitParamSlotSym(Js::ArgSlot argSlot) |
| { |
| return GetImplicitParamSlotSym(argSlot, this->m_func); |
| } |
| |
| StackSym * |
| LowererMD::GetImplicitParamSlotSym(Js::ArgSlot argSlot, Func * func) |
| { |
| // For ARM, offset for implicit params always start at 0 |
| // TODO: Consider not to use the argSlot number for the param slot sym, which can |
| // be confused with arg slot number from javascript |
| StackSym * stackSym = StackSym::NewParamSlotSym(argSlot, func); |
| func->SetArgOffset(stackSym, argSlot * MachPtr); |
| func->SetHasImplicitParamLoad(); |
| return stackSym; |
| } |
| |
| IR::LabelInstr * |
| LowererMD::EnsureEHEpilogLabel() |
| { |
| if (this->m_func->m_epilogLabel) |
| { |
| return this->m_func->m_epilogLabel; |
| } |
| |
| IR::Instr *exitInstr = this->m_func->m_exitInstr; |
| IR::Instr *prevInstr = exitInstr->GetPrevRealInstrOrLabel(); |
| if (prevInstr->IsLabelInstr()) |
| { |
| this->m_func->m_epilogLabel = prevInstr->AsLabelInstr(); |
| return prevInstr->AsLabelInstr(); |
| } |
| IR::LabelInstr *labelInstr = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| exitInstr->InsertBefore(labelInstr); |
| this->m_func->m_epilogLabel = labelInstr; |
| return labelInstr; |
| } |
| |
| // Helper method: inserts legalized assign for given srcOpnd into RegD0 in front of given instr in the following way: |
| // dstReg = InsertMove srcOpnd |
| // Used to put args of inline built-in call into RegD0 and RegD1 before we call actual CRT function. |
| void LowererMD::GenerateAssignForBuiltinArg(RegNum dstReg, IR::Opnd* srcOpnd, IR::Instr* instr) |
| { |
| IR::RegOpnd* tempDst = IR::RegOpnd::New(nullptr, dstReg, TyMachDouble, this->m_func); |
| tempDst->m_isCallArg = true; // This is to make sure that lifetime of opnd is virtually extended until next CALL instr. |
| Lowerer::InsertMove(tempDst, srcOpnd, instr); |
| } |
| |
| // For given InlineMathXXX instr, generate the call to actual CRT function/CPU instr. |
| void LowererMD::GenerateFastInlineBuiltInCall(IR::Instr* instr, IR::JnHelperMethod helperMethod) |
| { |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::InlineMathSqrt: |
| // Sqrt maps directly to the VFP instruction. |
| // src and dst are already float, all we need is just change the opcode and legalize. |
| // Before: |
| // dst = InlineMathSqrt src1 |
| // After: |
| // <potential FSTR by legalizer if src1 is not a register> |
| // dst = FSQRT src1 |
| Assert(helperMethod == (IR::JnHelperMethod)0); |
| Assert(instr->GetSrc2() == nullptr); |
| instr->m_opcode = Js::OpCode::FSQRT; |
| LegalizeMD::LegalizeInstr(instr); |
| break; |
| |
| case Js::OpCode::InlineMathAbs: |
| Assert(helperMethod == (IR::JnHelperMethod)0); |
| return GenerateFastInlineBuiltInMathAbs(instr); |
| |
| case Js::OpCode::InlineMathFloor: |
| case Js::OpCode::InlineMathCeil: |
| Assert(helperMethod == (IR::JnHelperMethod)0); |
| return GenerateFastInlineBuiltInMathFloorCeil(instr); |
| |
| case Js::OpCode::InlineMathRound: |
| Assert(helperMethod == (IR::JnHelperMethod)0); |
| return GenerateFastInlineBuiltInMathRound(instr); |
| |
| case Js::OpCode::InlineMathMin: |
| case Js::OpCode::InlineMathMax: |
| Assert(helperMethod == (IR::JnHelperMethod)0); |
| return GenerateFastInlineBuiltInMathMinMax(instr); |
| |
| default: |
| // Before: |
| // dst = <Built-in call> src1, src2 |
| // After: |
| // d0 = InsertMove src1 |
| // lr = MOV helperAddr |
| // BLX lr |
| // dst = InsertMove call->dst (d0) |
| |
| // Src1 |
| AssertMsg(instr->GetDst()->IsFloat(), "Currently accepting only float args for math helpers -- dst."); |
| AssertMsg(instr->GetSrc1()->IsFloat(), "Currently accepting only float args for math helpers -- src1."); |
| AssertMsg(!instr->GetSrc2() || instr->GetSrc2()->IsFloat(), "Currently accepting only float args for math helpers -- src2."); |
| this->GenerateAssignForBuiltinArg((RegNum)FIRST_FLOAT_REG, instr->UnlinkSrc1(), instr); |
| |
| // Src2 |
| if (instr->GetSrc2() != nullptr) |
| { |
| this->GenerateAssignForBuiltinArg((RegNum)(FIRST_FLOAT_REG + 1), instr->UnlinkSrc2(), instr); |
| } |
| |
| // Call CRT. |
| IR::RegOpnd* floatCallDst = IR::RegOpnd::New(nullptr, (RegNum)(FIRST_FLOAT_REG), TyMachDouble, this->m_func); // Dst in d0. |
| IR::Instr* floatCall = IR::Instr::New(Js::OpCode::BLR, floatCallDst, this->m_func); |
| instr->InsertBefore(floatCall); |
| |
| // lr = MOV helperAddr |
| // BLX lr |
| IR::AddrOpnd* targetAddr = IR::AddrOpnd::New((Js::Var)IR::GetMethodOriginalAddress(m_func->GetThreadContextInfo(), helperMethod), IR::AddrOpndKind::AddrOpndKindDynamicMisc, this->m_func); |
| IR::RegOpnd *targetOpnd = IR::RegOpnd::New(nullptr, RegLR, TyMachPtr, this->m_func); |
| IR::Instr *movInstr = IR::Instr::New(Js::OpCode::LDIMM, targetOpnd, targetAddr, this->m_func); |
| targetOpnd->m_isCallArg = true; |
| floatCall->SetSrc1(targetOpnd); |
| floatCall->InsertBefore(movInstr); |
| |
| // Save the result. |
| Lowerer::InsertMove(instr->UnlinkDst(), floatCall->GetDst(), instr); |
| instr->Remove(); |
| break; |
| } |
| } |
| |
| void |
| LowererMD::GenerateFastInlineBuiltInMathAbs(IR::Instr *inlineInstr) |
| { |
| IR::Opnd* src = inlineInstr->GetSrc1()->Copy(this->m_func); |
| IR::Opnd* dst = inlineInstr->UnlinkDst(); |
| Assert(src); |
| IR::Instr* tmpInstr; |
| IRType srcType = src->GetType(); |
| |
| IR::Instr* nextInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func); |
| IR::Instr* continueInstr = m_lowerer->LowerBailOnIntMin(inlineInstr); |
| continueInstr->InsertAfter(nextInstr); |
| if (srcType == IRType::TyInt32) |
| { |
| // Note: if execution gets so far, we always get (untagged) int32 here. |
| Assert(src->IsRegOpnd()); |
| |
| // CMP src, #0 |
| tmpInstr = IR::Instr::New(Js::OpCode::CMP, this->m_func); |
| tmpInstr->SetSrc1(src); |
| tmpInstr->SetSrc2(IR::IntConstOpnd::New(0, IRType::TyInt32, this->m_func)); |
| nextInstr->InsertBefore(tmpInstr); |
| Legalize(tmpInstr); |
| |
| // dst = CSNEGPL dst, src, src |
| tmpInstr = IR::Instr::New(Js::OpCode::CSNEGPL, dst, src, src, this->m_func); |
| nextInstr->InsertBefore(tmpInstr); |
| Legalize(tmpInstr); |
| } |
| else if (srcType == IRType::TyFloat64) |
| { |
| // FABS dst, src |
| tmpInstr = IR::Instr::New(Js::OpCode::FABS, dst, src, this->m_func); |
| nextInstr->InsertBefore(tmpInstr); |
| } |
| else |
| { |
| AssertMsg(FALSE, "GenerateFastInlineBuiltInMathAbs: unexpected type of the src!"); |
| } |
| } |
| |
| |
| void |
| LowererMD::GenerateFastInlineMathFround(IR::Instr* instr) |
| { |
| // Note that this is fround, not round; this operation is to |
| // round a double to Float32 precision. |
| IR::Opnd* src1 = instr->GetSrc1(); |
| IR::Opnd* dst = instr->GetDst(); |
| |
| Assert(dst->IsFloat()); |
| Assert(src1->IsFloat()); |
| |
| IRType srcType = src1->GetType(); |
| IRType dstType = dst->GetType(); |
| |
| if (srcType == TyFloat32) |
| { |
| if (dstType == TyFloat32) |
| { |
| LowererMD::ChangeToAssign(instr); |
| } |
| else |
| { |
| Assert(dstType == TyFloat64); |
| instr->m_opcode = Js::OpCode::FCVT; |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| } |
| else |
| { |
| Assert(srcType == TyFloat64); |
| if (dstType == TyFloat32) |
| { |
| instr->m_opcode = Js::OpCode::FCVT; |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| else |
| { |
| Assert(dstType == TyFloat64); |
| IR::RegOpnd* tempOpnd = IR::RegOpnd::New(TyFloat32, instr->m_func); |
| IR::Instr* shortener = IR::Instr::New(Js::OpCode::FCVT, tempOpnd, instr->UnlinkSrc1(), instr->m_func); |
| instr->InsertBefore(shortener); |
| instr->SetSrc1(tempOpnd); |
| instr->m_opcode = Js::OpCode::FCVT; |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| } |
| } |
| |
| void |
| LowererMD::GenerateFastInlineBuiltInMathRound(IR::Instr* instr) |
| { |
| Assert(instr->GetDst()->IsInt32()); |
| |
| IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| |
| // Allocate an integer register for negative zero checks if needed |
| IR::Opnd * negZeroReg = nullptr; |
| if (instr->ShouldCheckForNegativeZero()) |
| { |
| negZeroReg = IR::RegOpnd::New(TyInt64, this->m_func); |
| } |
| |
| // FMOV floatOpnd, src |
| IR::Opnd * src = instr->UnlinkSrc1(); |
| IR::RegOpnd* floatOpnd = IR::RegOpnd::New(TyFloat64, this->m_func); |
| this->m_lowerer->InsertMove(floatOpnd, src, instr); |
| |
| IR::LabelInstr * bailoutLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);; |
| bool sharedBailout = (instr->GetBailOutInfo()->bailOutInstr != instr) ? true : false; |
| |
| // FMOV_GEN negZeroReg, floatOpnd (note this is done before the 0.5 add below) |
| if (negZeroReg) |
| { |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::FMOV_GEN, negZeroReg, floatOpnd, instr->m_func)); |
| } |
| |
| // Add 0.5 |
| IR::Opnd * pointFive = IR::MemRefOpnd::New(m_func->GetThreadContextInfo()->GetDoublePointFiveAddr(), IRType::TyFloat64, this->m_func, IR::AddrOpndKindDynamicDoubleRef); |
| this->m_lowerer->InsertAdd(false, floatOpnd, floatOpnd, pointFive, instr); |
| |
| // MSR FPSR, xzr |
| IR::Instr* setFPSRInstr = IR::Instr::New(Js::OpCode::MSR_FPSR, instr->m_func); |
| setFPSRInstr->SetSrc1(IR::RegOpnd::New(nullptr, RegZR, TyUint32, instr->m_func)); |
| instr->InsertBefore(setFPSRInstr); |
| |
| // FCVTM intOpnd, floatOpnd |
| IR::Opnd * intOpnd = IR::RegOpnd::New(TyInt32, this->m_func); |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::FCVTM, intOpnd, floatOpnd, instr->m_func)); |
| |
| // FCVTM would set FPSR.IOC (0th bit in FPSR) if the source cannot be represented within the destination register |
| |
| // MRS exceptReg, FPSR |
| IR::Opnd * exceptReg = IR::RegOpnd::New(TyUint32, this->m_func); |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::MRS_FPSR, exceptReg, instr->m_func)); |
| |
| IR::LabelInstr* checkOverflowLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| |
| // CBNZ intOpnd, done/checkOverflow |
| IR::BranchInstr * cbnzInstr = cbnzInstr = IR::BranchInstr::New(Js::OpCode::CBNZ, checkOverflowLabel, instr->m_func); |
| cbnzInstr->SetSrc1(intOpnd); |
| instr->InsertBefore(cbnzInstr); |
| |
| if (negZeroReg) |
| { |
| // TBZ negZeroReg, 63 |
| IR::BranchInstr * tbzInstr = IR::BranchInstr::New(Js::OpCode::TBZ, doneLabel, instr->m_func); |
| tbzInstr->SetSrc1(negZeroReg); |
| tbzInstr->SetSrc2(IR::IntConstOpnd::New(63, TyMachReg, instr->m_func)); |
| instr->InsertBefore(tbzInstr); |
| |
| Lowerer::InsertBranch(LowererMD::MDUncondBranchOpcode, bailoutLabel, instr); |
| } |
| |
| |
| instr->InsertBefore(checkOverflowLabel); |
| |
| // TBZ exceptReg, #0, done |
| IR::BranchInstr * tbzInstr = IR::BranchInstr::New(Js::OpCode::TBZ, doneLabel, instr->m_func); |
| tbzInstr->SetSrc1(exceptReg); |
| tbzInstr->SetSrc2(IR::IntConstOpnd::New(0, TyMachReg, instr->m_func)); |
| instr->InsertBefore(tbzInstr); |
| |
| IR::Opnd * dst = instr->UnlinkDst(); |
| instr->InsertAfter(doneLabel); |
| if (!sharedBailout) |
| { |
| instr->InsertBefore(bailoutLabel); |
| } |
| |
| // In case of a shared bailout, we should jump to the code that sets some data on the bailout record which is specific |
| // to this bailout. Pass the bailoutLabel to GenerateFunction so that it may use the label as the collectRuntimeStatsLabel. |
| this->m_lowerer->GenerateBailOut(instr, nullptr, nullptr, sharedBailout ? bailoutLabel : nullptr); |
| |
| // MOV dst, intOpnd |
| IR::Instr* movInstr = IR::Instr::New(Js::OpCode::MOV, dst, intOpnd, this->m_func); |
| doneLabel->InsertAfter(movInstr); |
| } |
| |
| void |
| LowererMD::GenerateFastInlineBuiltInMathFloorCeil(IR::Instr* instr) |
| { |
| Assert(instr->GetDst()->IsInt32()); |
| |
| IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| |
| // Allocate an integer register for negative zero checks if needed |
| IR::Opnd * negZeroReg = nullptr; |
| if (instr->ShouldCheckForNegativeZero()) |
| { |
| negZeroReg = IR::RegOpnd::New(TyInt64, this->m_func); |
| } |
| |
| // FMOV floatOpnd, src |
| IR::Opnd * src = instr->UnlinkSrc1(); |
| IR::RegOpnd* floatOpnd = IR::RegOpnd::New(TyFloat64, this->m_func); |
| this->m_lowerer->InsertMove(floatOpnd, src, instr); |
| |
| IR::LabelInstr * bailoutLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true);; |
| bool sharedBailout = (instr->GetBailOutInfo()->bailOutInstr != instr) ? true : false; |
| |
| // MSR FPSR, xzr |
| IR::Instr* setFPSRInstr = IR::Instr::New(Js::OpCode::MSR_FPSR, instr->m_func); |
| setFPSRInstr->SetSrc1(IR::RegOpnd::New(nullptr, RegZR, TyUint32, instr->m_func)); |
| instr->InsertBefore(setFPSRInstr); |
| |
| // FMOV_GEN negZeroReg, floatOpnd (note this is done before the 0.5 add below) |
| if (negZeroReg) |
| { |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::FMOV_GEN, negZeroReg, floatOpnd, instr->m_func)); |
| } |
| |
| // FCVTM/FCVTP intOpnd, floatOpnd |
| IR::Opnd * intOpnd = IR::RegOpnd::New(TyInt32, this->m_func); |
| instr->InsertBefore(IR::Instr::New((instr->m_opcode == Js::OpCode::InlineMathCeil) ? Js::OpCode::FCVTP : Js::OpCode::FCVTM, intOpnd, floatOpnd, instr->m_func)); |
| |
| // EOR negZeroReg, #0x8000000000000000 |
| if (negZeroReg) |
| { |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::EOR, negZeroReg, negZeroReg, IR::IntConstOpnd::New(0x8000000000000000ULL, IRType::TyInt64, this->m_func), instr->m_func)); |
| } |
| |
| // FCVTM would set FPSR.IOC (0th bit in FPSR) if the source cannot be represented within the destination register |
| |
| // MRS exceptReg, FPSR |
| IR::Opnd * exceptReg = IR::RegOpnd::New(TyUint32, this->m_func); |
| instr->InsertBefore(IR::Instr::New(Js::OpCode::MRS_FPSR, exceptReg, instr->m_func)); |
| |
| // CBZ negZeroReg, bailout |
| if (negZeroReg) |
| { |
| IR::BranchInstr * cbzInstr = IR::BranchInstr::New(Js::OpCode::CBZ, bailoutLabel, instr->m_func); |
| cbzInstr->SetSrc1(negZeroReg); |
| instr->InsertBefore(cbzInstr); |
| } |
| |
| // TBZ exceptReg, #0, done |
| IR::BranchInstr * tbzInstr = IR::BranchInstr::New(Js::OpCode::TBZ, doneLabel, instr->m_func); |
| tbzInstr->SetSrc1(exceptReg); |
| tbzInstr->SetSrc2(IR::IntConstOpnd::New(0, TyMachReg, instr->m_func)); |
| instr->InsertBefore(tbzInstr); |
| |
| IR::Opnd * dst = instr->UnlinkDst(); |
| instr->InsertAfter(doneLabel); |
| if(!sharedBailout) |
| { |
| instr->InsertBefore(bailoutLabel); |
| } |
| |
| // In case of a shared bailout, we should jump to the code that sets some data on the bailout record which is specific |
| // to this bailout. Pass the bailoutLabel to GenerateFunction so that it may use the label as the collectRuntimeStatsLabel. |
| this->m_lowerer->GenerateBailOut(instr, nullptr, nullptr, sharedBailout ? bailoutLabel : nullptr); |
| |
| // MOV dst, intOpnd |
| IR::Instr* movInstr = IR::Instr::New(Js::OpCode::MOV, dst, intOpnd, this->m_func); |
| doneLabel->InsertAfter(movInstr); |
| } |
| |
| void |
| LowererMD::GenerateFastInlineBuiltInMathMinMax(IR::Instr* instr) |
| { |
| IR::Opnd* dst = instr->GetDst(); |
| |
| if (dst->IsInt32()) |
| { |
| IR::Opnd* src1 = instr->GetSrc1(); |
| IR::Opnd* src2 = instr->GetSrc2(); |
| |
| // CMP src1, src2 |
| IR::Instr* cmpInstr = IR::Instr::New(Js::OpCode::CMP, instr->m_func); |
| cmpInstr->SetSrc1(src1); |
| cmpInstr->SetSrc2(src2); |
| instr->InsertBefore(cmpInstr); |
| Legalize(cmpInstr); |
| |
| // (min) CSELLT dst, src1, src2 |
| // (max) CSELLT dst, src2, src1 |
| IR::Opnd* op1 = (instr->m_opcode == Js::OpCode::InlineMathMin) ? src1 : src2; |
| IR::Opnd* op2 = (instr->m_opcode == Js::OpCode::InlineMathMin) ? src2 : src1; |
| IR::Instr * csellinstr = IR::Instr::New(Js::OpCode::CSELLT, dst, op1, op2, instr->m_func); |
| instr->InsertBefore(csellinstr); |
| Legalize(csellinstr); |
| |
| instr->Remove(); |
| } |
| |
| else if (dst->IsFloat64()) |
| { |
| // (min) FMIN dst, src1, src2 |
| // (max) FMAX dst, src1, src2 |
| instr->m_opcode = (instr->m_opcode == Js::OpCode::InlineMathMin) ? Js::OpCode::FMIN : Js::OpCode::FMAX; |
| } |
| } |
| |
| IR::Instr * |
| LowererMD::LowerToFloat(IR::Instr *instr) |
| { |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::Add_A: |
| instr->m_opcode = Js::OpCode::FADD; |
| break; |
| |
| case Js::OpCode::Sub_A: |
| instr->m_opcode = Js::OpCode::FSUB; |
| break; |
| |
| case Js::OpCode::Mul_A: |
| instr->m_opcode = Js::OpCode::FMUL; |
| break; |
| |
| case Js::OpCode::Div_A: |
| instr->m_opcode = Js::OpCode::FDIV; |
| break; |
| |
| case Js::OpCode::Neg_A: |
| instr->m_opcode = Js::OpCode::FNEG; |
| break; |
| |
| case Js::OpCode::BrEq_A: |
| case Js::OpCode::BrNeq_A: |
| case Js::OpCode::BrSrEq_A: |
| case Js::OpCode::BrSrNeq_A: |
| case Js::OpCode::BrGt_A: |
| case Js::OpCode::BrGe_A: |
| case Js::OpCode::BrLt_A: |
| case Js::OpCode::BrLe_A: |
| case Js::OpCode::BrNotEq_A: |
| case Js::OpCode::BrNotNeq_A: |
| case Js::OpCode::BrSrNotEq_A: |
| case Js::OpCode::BrSrNotNeq_A: |
| case Js::OpCode::BrNotGt_A: |
| case Js::OpCode::BrNotGe_A: |
| case Js::OpCode::BrNotLt_A: |
| case Js::OpCode::BrNotLe_A: |
| return this->LowerFloatCondBranch(instr->AsBranchInstr()); |
| |
| default: |
| Assume(UNREACHED); |
| } |
| |
| LegalizeMD::LegalizeInstr(instr); |
| return instr; |
| } |
| |
| IR::BranchInstr * |
| LowererMD::LowerFloatCondBranch(IR::BranchInstr *instrBranch, bool ignoreNaN) |
| { |
| IR::Instr *instr; |
| Js::OpCode brOpcode = Js::OpCode::InvalidOpCode; |
| bool addNaNCheck = false; |
| |
| Func * func = instrBranch->m_func; |
| IR::Opnd *src1 = instrBranch->UnlinkSrc1(); |
| IR::Opnd *src2 = instrBranch->UnlinkSrc2(); |
| |
| IR::Instr *instrCmp = IR::Instr::New(Js::OpCode::FCMP, func); |
| instrCmp->SetSrc1(src1); |
| instrCmp->SetSrc2(src2); |
| instrBranch->InsertBefore(instrCmp); |
| LegalizeMD::LegalizeInstr(instrCmp); |
| |
| switch (instrBranch->m_opcode) |
| { |
| case Js::OpCode::BrSrEq_A: |
| case Js::OpCode::BrEq_A: |
| case Js::OpCode::BrNotNeq_A: |
| case Js::OpCode::BrSrNotNeq_A: |
| brOpcode = Js::OpCode::BEQ; |
| break; |
| |
| case Js::OpCode::BrNeq_A: |
| case Js::OpCode::BrSrNeq_A: |
| case Js::OpCode::BrSrNotEq_A: |
| case Js::OpCode::BrNotEq_A: |
| brOpcode = Js::OpCode::BNE; |
| addNaNCheck = !ignoreNaN; //Special check for BNE as it is set when the operands are unordered (NaN). |
| break; |
| |
| case Js::OpCode::BrLe_A: |
| brOpcode = Js::OpCode::BLS; //Can't use BLE as it is set when the operands are unordered (NaN). |
| break; |
| |
| case Js::OpCode::BrLt_A: |
| brOpcode = Js::OpCode::BCC; //Can't use BLT as is set when the operands are unordered (NaN). |
| break; |
| |
| case Js::OpCode::BrGe_A: |
| brOpcode = Js::OpCode::BGE; |
| break; |
| |
| case Js::OpCode::BrGt_A: |
| brOpcode = Js::OpCode::BGT; |
| break; |
| |
| case Js::OpCode::BrNotLe_A: |
| brOpcode = Js::OpCode::BHI; |
| break; |
| |
| case Js::OpCode::BrNotLt_A: |
| brOpcode = Js::OpCode::BPL; |
| break; |
| |
| case Js::OpCode::BrNotGe_A: |
| brOpcode = Js::OpCode::BLT; |
| break; |
| |
| case Js::OpCode::BrNotGt_A: |
| brOpcode = Js::OpCode::BLE; |
| break; |
| |
| default: |
| Assert(false); |
| break; |
| } |
| |
| if (addNaNCheck) |
| { |
| instr = IR::BranchInstr::New(Js::OpCode::BVS, instrBranch->GetTarget(), func); |
| instrBranch->InsertBefore(instr); |
| } |
| |
| instr = IR::BranchInstr::New(brOpcode, instrBranch->GetTarget(), func); |
| instrBranch->InsertBefore(instr); |
| |
| instrBranch->Remove(); |
| |
| return instr->AsBranchInstr(); |
| } |
| |
| void |
| LowererMD::EmitIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) |
| { |
| IR::Instr *instr; |
| |
| Assert(dst->IsRegOpnd() && dst->IsFloat64()); |
| Assert(src->IsRegOpnd() && src->IsInt32()); |
| |
| // Convert to Float |
| instr = IR::Instr::New(Js::OpCode::FCVT, dst, src, this->m_func); |
| instrInsert->InsertBefore(instr); |
| } |
| |
| void |
| LowererMD::EmitUIntToFloat(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) |
| { |
| IR::Instr *instr; |
| |
| Assert(dst->IsRegOpnd() && dst->IsFloat64()); |
| Assert(src->IsRegOpnd() && src->IsUInt32()); |
| |
| // Convert to Float |
| instr = IR::Instr::New(Js::OpCode::FCVT, dst, src, this->m_func); |
| instrInsert->InsertBefore(instr); |
| } |
| |
| void LowererMD::ConvertFloatToInt32(IR::Opnd* intOpnd, IR::Opnd* floatOpnd, IR::LabelInstr * labelHelper, IR::LabelInstr * labelDone, IR::Instr * instrInsert) |
| { |
| Assert(floatOpnd->IsFloat64()); |
| Assert(intOpnd->IsInt32()); |
| |
| // VCVTS32F64 dst.i32, src.f64 |
| // Convert to int |
| // ARM64_WORKITEM: On ARM32 this used the current rounding mode; here we are explicitly rounding toward zero -- is that ok? |
| IR::Instr * instr = IR::Instr::New(Js::OpCode::FCVTZ, intOpnd, floatOpnd, this->m_func); |
| instrInsert->InsertBefore(instr); |
| Legalize(instr); |
| |
| this->CheckOverflowOnFloatToInt32(instrInsert, intOpnd, labelHelper, labelDone); |
| } |
| |
| void |
| LowererMD::EmitIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) |
| { |
| Assert(UNREACHED); |
| } |
| |
| void |
| LowererMD::EmitUIntToLong(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) |
| { |
| Assert(UNREACHED); |
| } |
| |
| void |
| LowererMD::EmitLongToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert) |
| { |
| Assert(UNREACHED); |
| } |
| |
| void |
| LowererMD::CheckOverflowOnFloatToInt32(IR::Instr* instrInsert, IR::Opnd* intOpnd, IR::LabelInstr * labelHelper, IR::LabelInstr * labelDone) |
| { |
| // Test for 0x80000000 or 0x7FFFFFFF |
| |
| // tmp = EOR src, 0x80000000; gives 0 or -1 for overflow values |
| // tmp = EOR_ASR31 tmp, tmp; tmp = tmp ^ ((int32)tmp >> 31) -- converts -1 or 0 to 0 |
| // CBZ tmp, helper; branch if tmp was -1 or 0 |
| // B done; |
| |
| IR::RegOpnd* tmp = IR::RegOpnd::New(TyInt32, this->m_func); |
| |
| IR::Instr* instr = IR::Instr::New(Js::OpCode::EOR, tmp, intOpnd, IR::IntConstOpnd::New(0x80000000, TyUint32, this->m_func, true), this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| instr = IR::Instr::New(Js::OpCode::EOR_ASR31, tmp, tmp, tmp, this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| // CBZ $helper |
| instr = IR::BranchInstr::New(Js::OpCode::CBZ, labelHelper, this->m_func); |
| instr->SetSrc1(tmp); |
| instrInsert->InsertBefore(instr); |
| |
| // B $done |
| instr = IR::BranchInstr::New(Js::OpCode::B, labelDone, this->m_func); |
| instrInsert->InsertBefore(instr); |
| } |
| |
| void |
| LowererMD::EmitFloatToInt(IR::Opnd *dst, IR::Opnd *src, IR::Instr *instrInsert, IR::Instr * instrBailOut, IR::LabelInstr * labelBailOut) |
| { |
| IR::BailOutKind bailOutKind = IR::BailOutInvalid; |
| if (instrBailOut && instrBailOut->HasBailOutInfo()) |
| { |
| bailOutKind = instrBailOut->GetBailOutKind(); |
| if (bailOutKind & IR::BailOutOnArrayAccessHelperCall) |
| { |
| // Bail out instead of calling helper. If this is happening unconditionally, the caller should instead throw a rejit exception. |
| Assert(labelBailOut); |
| m_lowerer->InsertBranch(Js::OpCode::Br, labelBailOut, instrInsert); |
| return; |
| } |
| } |
| |
| IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, this->m_func); |
| IR::LabelInstr *labelHelper = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, true); |
| IR::Instr *instr; |
| |
| ConvertFloatToInt32(dst, src, labelHelper, labelDone, instrInsert); |
| |
| // $Helper |
| instrInsert->InsertBefore(labelHelper); |
| |
| instr = IR::Instr::New(Js::OpCode::Call, dst, this->m_func); |
| instrInsert->InsertBefore(instr); |
| |
| if (BailOutInfo::IsBailOutOnImplicitCalls(bailOutKind)) |
| { |
| _Analysis_assume_(instrBailOut != nullptr); |
| instr = instr->ConvertToBailOutInstr(instrBailOut->GetBailOutInfo(), bailOutKind); |
| if (instrBailOut->GetBailOutInfo()->bailOutInstr == instrBailOut) |
| { |
| IR::Instr * instrShare = instrBailOut->ShareBailOut(); |
| m_lowerer->LowerBailTarget(instrShare); |
| } |
| } |
| |
| // dst = ToInt32Core(src); |
| LoadDoubleHelperArgument(instr, src); |
| |
| this->ChangeToHelperCall(instr, IR::HelperConv_ToInt32Core); |
| |
| // $Done |
| instrInsert->InsertBefore(labelDone); |
| } |
| |
| IR::Instr * |
| LowererMD::InsertConvertFloat64ToInt32(const RoundMode roundMode, IR::Opnd *const dst, IR::Opnd *const src, IR::Instr *const insertBeforeInstr) |
| { |
| Assert(dst); |
| Assert(dst->IsInt32()); |
| Assert(src); |
| Assert(src->IsFloat64()); |
| Assert(insertBeforeInstr); |
| |
| // The caller is expected to check for overflow. To have that work be done automatically, use LowererMD::EmitFloatToInt. |
| |
| Func *const func = insertBeforeInstr->m_func; |
| IR::AutoReuseOpnd autoReuseSrcPlusHalf; |
| IR::Instr *instr = nullptr; |
| |
| switch (roundMode) |
| { |
| case RoundModeTowardInteger: |
| case RoundModeHalfToEven: |
| { |
| // Conversion with rounding towards nearest integer is not supported by the architecture. Add 0.5 and do a |
| // round-toward-zero conversion instead. |
| IR::RegOpnd *const srcPlusHalf = IR::RegOpnd::New(TyFloat64, func); |
| autoReuseSrcPlusHalf.Initialize(srcPlusHalf, func); |
| Lowerer::InsertAdd( |
| false /* needFlags */, |
| srcPlusHalf, |
| src, |
| IR::MemRefOpnd::New(insertBeforeInstr->m_func->GetThreadContextInfo()->GetDoublePointFiveAddr(), TyFloat64, func, |
| IR::AddrOpndKindDynamicDoubleRef), |
| insertBeforeInstr); |
| |
| instr = IR::Instr::New(LowererMD::MDConvertFloat64ToInt32Opcode(RoundModeTowardZero), dst, srcPlusHalf, func); |
| |
| insertBeforeInstr->InsertBefore(instr); |
| LowererMD::Legalize(instr); |
| return instr; |
| } |
| default: |
| AssertMsg(0, "RoundMode not supported."); |
| return nullptr; |
| } |
| } |
| |
| IR::Instr * |
| LowererMD::LoadFloatZero(IR::Opnd * opndDst, IR::Instr * instrInsert) |
| { |
| Assert(opndDst->GetType() == TyFloat64); |
| IR::Opnd * zero = IR::MemRefOpnd::New(instrInsert->m_func->GetThreadContextInfo()->GetDoubleZeroAddr(), TyFloat64, instrInsert->m_func, IR::AddrOpndKindDynamicDoubleRef); |
| |
| // Todo(magardn): Make sure the correct opcode is used for moving between float and non-float regs (FMOV_GEN) |
| return Lowerer::InsertMove(opndDst, zero, instrInsert); |
| } |
| |
| IR::Instr * |
| LowererMD::LoadFloatValue(IR::Opnd * opndDst, double value, IR::Instr * instrInsert) |
| { |
| // Floating point zero is a common value to load. Let's use a single memory location instead of allocating new memory for each. |
| const bool isFloatZero = value == 0.0 && !Js::JavascriptNumber::IsNegZero(value); // (-0.0 == 0.0) yields true |
| if (isFloatZero) |
| { |
| return LowererMD::LoadFloatZero(opndDst, instrInsert); |
| } |
| void * pValue = NativeCodeDataNewNoFixup(instrInsert->m_func->GetNativeCodeDataAllocator(), DoubleType<DataDesc_LowererMD_LoadFloatValue_Double>, value); |
| IR::Opnd * opnd; |
| if (instrInsert->m_func->IsOOPJIT()) |
| { |
| int offset = NativeCodeData::GetDataTotalOffset(pValue); |
| auto addressRegOpnd = IR::RegOpnd::New(TyMachPtr, instrInsert->m_func); |
| |
| Lowerer::InsertMove( |
| addressRegOpnd, |
| IR::MemRefOpnd::New(instrInsert->m_func->GetWorkItem()->GetWorkItemData()->nativeDataAddr, TyMachPtr, instrInsert->m_func, IR::AddrOpndKindDynamicNativeCodeDataRef), |
| instrInsert); |
| |
| opnd = IR::IndirOpnd::New(addressRegOpnd, offset, TyMachDouble, |
| #if DBG |
| NativeCodeData::GetDataDescription(pValue, instrInsert->m_func->m_alloc), |
| #endif |
| instrInsert->m_func, true); |
| } |
| else |
| { |
| opnd = IR::MemRefOpnd::New((void*)pValue, TyMachDouble, instrInsert->m_func); |
| } |
| IR::Instr * instr = IR::Instr::New(Js::OpCode::FLDR, opndDst, opnd, instrInsert->m_func); |
| instrInsert->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| return instr; |
| } |
| |
| void LowererMD::GenerateFloatTest(IR::RegOpnd * opndSrc, IR::Instr * insertInstr, IR::LabelInstr* labelHelper, const bool checkForNullInLoopBody) |
| { |
| if (opndSrc->GetValueType().IsFloat()) |
| { |
| return; |
| } |
| |
| // TST s1, floatTagReg |
| IR::Opnd* floatTag = IR::IntConstOpnd::New(Js::FloatTag_Value, TyMachReg, this->m_func, /* dontEncode = */ true); |
| IR::Instr* instr = IR::Instr::New(Js::OpCode::TST, this->m_func); |
| instr->SetSrc1(opndSrc); |
| instr->SetSrc2(floatTag); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| |
| // BZ $helper |
| instr = IR::BranchInstr::New(Js::OpCode::BEQ /* BZ */, labelHelper, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| |
| IR::RegOpnd* LowererMD::CheckFloatAndUntag(IR::RegOpnd * opndSrc, IR::Instr * insertInstr, IR::LabelInstr* labelHelper) |
| { |
| IR::Opnd* floatTag = IR::IntConstOpnd::New(Js::FloatTag_Value, TyMachReg, this->m_func, /* dontEncode = */ true); |
| |
| // MOV floatTagReg, FloatTag_Value |
| if (!opndSrc->GetValueType().IsFloat()) |
| { |
| // TST s1, floatTagReg |
| IR::Instr* instr = IR::Instr::New(Js::OpCode::TST, this->m_func); |
| instr->SetSrc1(opndSrc); |
| instr->SetSrc2(floatTag); |
| insertInstr->InsertBefore(instr); |
| LegalizeMD::LegalizeInstr(instr); |
| |
| // BZ $helper |
| instr = IR::BranchInstr::New(Js::OpCode::BEQ /* BZ */, labelHelper, this->m_func); |
| insertInstr->InsertBefore(instr); |
| } |
| |
| IR::RegOpnd* untaggedFloat = IR::RegOpnd::New(TyMachReg, this->m_func); |
| IR::Instr* instr = IR::Instr::New(Js::OpCode::EOR, untaggedFloat, opndSrc, floatTag, this->m_func); |
| insertInstr->InsertBefore(instr); |
| |
| IR::RegOpnd *floatReg = IR::RegOpnd::New(TyMachDouble, this->m_func); |
| instr = IR::Instr::New(Js::OpCode::FMOV_GEN, floatReg, untaggedFloat, this->m_func); |
| insertInstr->InsertBefore(instr); |
| return floatReg; |
| } |
| |
| template <bool verify> |
| void |
| LowererMD::Legalize(IR::Instr *const instr, bool fPostRegAlloc) |
| { |
| if (verify) |
| { |
| // NYI for the rest of legalization |
| return; |
| } |
| LegalizeMD::LegalizeInstr(instr); |
| } |
| |
| template void LowererMD::Legalize<false>(IR::Instr *const instr, bool fPostRegalloc); |
| #if DBG |
| template void LowererMD::Legalize<true>(IR::Instr *const instr, bool fPostRegalloc); |
| #endif |
| |
| void |
| LowererMD::FinalLower() |
| { |
| NoRecoverMemoryArenaAllocator tempAlloc(_u("BE-ARMFinalLower"), m_func->m_alloc->GetPageAllocator(), Js::Throw::OutOfMemory); |
| EncodeReloc *pRelocList = nullptr; |
| |
| size_t totalJmpTableSizeInBytes = 0; |
| |
| uintptr_t instrOffset = 0; |
| FOREACH_INSTR_BACKWARD_EDITING_IN_RANGE(instr, instrPrev, this->m_func->m_tailInstr, this->m_func->m_headInstr) |
| { |
| if (instr->IsLowered() == false) |
| { |
| if (instr->IsLabelInstr()) |
| { |
| //This is not the real set, Real offset gets set in encoder. |
| IR::LabelInstr *labelInstr = instr->AsLabelInstr(); |
| labelInstr->SetOffset(instrOffset); |
| } |
| |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::Ret: |
| instr->Remove(); |
| break; |
| case Js::OpCode::Leave: |
| Assert(this->m_func->DoOptimizeTry() && !this->m_func->IsLoopBodyInTry()); |
| instrPrev = m_lowerer->LowerLeave(instr, instr->AsBranchInstr()->GetTarget(), true /*fromFinalLower*/); |
| break; |
| } |
| } |
| else |
| { |
| instrOffset = instrOffset + MachMaxInstrSize; |
| |
| if (instr->IsBranchInstr()) |
| { |
| IR::BranchInstr *branchInstr = instr->AsBranchInstr(); |
| |
| if (branchInstr->IsMultiBranch()) |
| { |
| Assert(instr->GetSrc1() && instr->GetSrc1()->IsRegOpnd()); |
| IR::MultiBranchInstr * multiBranchInstr = instr->AsBranchInstr()->AsMultiBrInstr(); |
| |
| if (multiBranchInstr->m_isSwitchBr && |
| (multiBranchInstr->m_kind == IR::MultiBranchInstr::IntJumpTable || multiBranchInstr->m_kind == IR::MultiBranchInstr::SingleCharStrJumpTable)) |
| { |
| BranchJumpTableWrapper * branchJumpTableWrapper = multiBranchInstr->GetBranchJumpTable(); |
| totalJmpTableSizeInBytes += (branchJumpTableWrapper->tableSize * sizeof(void*)); |
| |
| // instrOffset is relative to the end of the function. Jump tables come after the function and so would result in negative offsets. label offsets |
| // are unsigned so instead give jump table lables offsets relative to the end of the jump table section. |
| branchJumpTableWrapper->labelInstr->SetOffset(totalJmpTableSizeInBytes); |
| } |
| } |
| else if (!LowererMD::IsUnconditionalBranch(branchInstr)) //Ignore other direct branches |
| { |
| uintptr_t targetOffset = branchInstr->GetTarget()->GetOffset(); |
| |
| if (targetOffset != 0) |
| { |
| // this is forward reference |
| if (LegalizeMD::LegalizeDirectBranch(branchInstr, instrOffset)) |
| { |
| //There might be an instruction inserted for legalizing conditional branch |
| instrOffset = instrOffset + MachMaxInstrSize; |
| } |
| } |
| else |
| { |
| EncodeReloc::New(&pRelocList, RelocTypeBranch19, (BYTE*)instrOffset, branchInstr, &tempAlloc); |
| //Assume this is a backward long branch, we shall fix up after complete pass, be conservative here |
| instrOffset = instrOffset + MachMaxInstrSize; |
| } |
| } |
| } |
| else if (LowererMD::IsAssign(instr) || instr->m_opcode == Js::OpCode::LEA || instr->m_opcode == Js::OpCode::LDARGOUTSZ || instr->m_opcode == Js::OpCode::REM) |
| { |
| // Cleanup spill code |
| // INSTR_BACKWARD_EDITING_IN_RANGE implies that next loop iteration will use instrPrev (instr->m_prev computed before entering current loop iteration). |
| IR::Instr* instrNext = instr->m_next; |
| bool canExpand = this->FinalLowerAssign(instr); |
| |
| if (canExpand) |
| { |
| uint32 expandedInstrCount = 0; // The number of instrs the LDIMM expands into. |
| FOREACH_INSTR_IN_RANGE(instrCount, instrPrev->m_next, instrNext) |
| { |
| ++expandedInstrCount; |
| } |
| NEXT_INSTR_IN_RANGE; |
| Assert(expandedInstrCount > 0); |
| |
| // Adjust the offset for expanded instrs. |
| instrOffset += (expandedInstrCount - 1) * MachMaxInstrSize; // We already accounted for one MachMaxInstrSize. |
| } |
| } |
| |
| if (instr->m_opcode == Js::OpCode::ADR) |
| { |
| IR::LabelInstr* label = instr->GetSrc1()->AsLabelOpnd()->GetLabel(); |
| if (label->GetOffset() != 0 && !label->m_isDataLabel) |
| { |
| // this is forward reference |
| if (LegalizeMD::LegalizeAdrOffset(instr, instrOffset)) |
| { |
| //Additional instructions were inserted. |
| instrOffset = instrOffset + MachMaxInstrSize * 2; |
| } |
| } |
| else |
| { |
| EncodeReloc::New(&pRelocList, RelocTypeLabelAdr, (BYTE*)instrOffset, instr, &tempAlloc); |
| //Assume this is a backward long branch, we shall fix up after complete pass, be conservative here |
| instrOffset = instrOffset + MachMaxInstrSize * 2; |
| } |
| } |
| } |
| } NEXT_INSTR_BACKWARD_EDITING_IN_RANGE; |
| |
| //Fixup all the backward branches |
| for (EncodeReloc *reloc = pRelocList; reloc; reloc = reloc->m_next) |
| { |
| uintptr_t relocAddress = (uintptr_t)reloc->m_consumerOffset; |
| |
| switch (reloc->m_relocType) |
| { |
| case RelocTypeBranch19: |
| AssertMsg(relocAddress < reloc->m_relocInstr->AsBranchInstr()->GetTarget()->GetOffset(), "Only backward branches require fixup"); |
| LegalizeMD::LegalizeDirectBranch(reloc->m_relocInstr->AsBranchInstr(), relocAddress); |
| break; |
| |
| case RelocTypeLabelAdr: |
| { |
| IR::LabelInstr* label = reloc->m_relocInstr->GetSrc1()->AsLabelOpnd()->GetLabel(); |
| if (label->m_isDataLabel) |
| { |
| uintptr_t dataOffset; |
| if (label == m_func->GetFuncStartLabel()) |
| { |
| dataOffset = instrOffset - relocAddress; |
| } |
| else if (label == m_func->GetFuncEndLabel()) |
| { |
| dataOffset = relocAddress; |
| } |
| else |
| { |
| Assert(label->GetOffset() != 0); |
| |
| // jump table label offsets are relative to the end of the jump table area. |
| dataOffset = relocAddress + totalJmpTableSizeInBytes - label->GetOffset(); |
| |
| // PC is a union with offset. Encoder expects this to be nullptr for jump table labels. |
| label->SetPC(nullptr); |
| } |
| |
| LegalizeMD::LegalizeDataAdr(reloc->m_relocInstr, dataOffset); |
| break; |
| } |
| |
| AssertMsg(relocAddress < label->GetOffset(), "Only backward branches require fixup"); |
| LegalizeMD::LegalizeAdrOffset(reloc->m_relocInstr, relocAddress); |
| break; |
| } |
| default: |
| Assert(false); |
| } |
| } |
| } |
| |
| // Returns true, if and only if the assign may expand into multiple instrs. |
| bool |
| LowererMD::FinalLowerAssign(IR::Instr * instr) |
| { |
| if (instr->m_opcode == Js::OpCode::LDIMM) |
| { |
| LegalizeMD::LegalizeInstr(instr); |
| |
| // LDIMM can expand into up to 4 instructions when the immediate is more than 16 bytes, |
| // it can also expand into multiple different no-op (normally MOV) instrs when we obfuscate it, which is randomly. |
| return true; |
| } |
| else if (EncoderMD::IsLoad(instr) || instr->m_opcode == Js::OpCode::LEA) |
| { |
| Assert(instr->GetDst()->IsRegOpnd()); |
| if (!instr->GetSrc1()->IsRegOpnd()) |
| { |
| LegalizeMD::LegalizeSrc(instr, instr->GetSrc1(), 1); |
| return true; |
| } |
| instr->m_opcode = instr->GetSrc1()->IsFloat() ? Js::OpCode::FMOV : Js::OpCode::MOV; |
| } |
| else if (EncoderMD::IsStore(instr)) |
| { |
| Assert(instr->GetSrc1()->IsRegOpnd()); |
| if (!instr->GetDst()->IsRegOpnd()) |
| { |
| LegalizeMD::LegalizeDst(instr); |
| return true; |
| } |
| instr->m_opcode = instr->GetDst()->IsFloat() ? Js::OpCode::FMOV : Js::OpCode::MOV; |
| } |
| else if (instr->m_opcode == Js::OpCode::LDARGOUTSZ) |
| { |
| Assert(instr->GetDst()->IsRegOpnd()); |
| Assert((instr->GetSrc1() == nullptr) && (instr->GetSrc2() == nullptr)); |
| // dst = LDARGOUTSZ |
| // This loads the function's arg out area size into the dst operand. We need a pseudo-op, |
| // because we generate the instruction during Lower but don't yet know the value of the constant it needs |
| // to load. Change it to the appropriate LDIMM here. |
| uint32 argOutSize = UInt32Math::Mul(this->m_func->m_argSlotsForFunctionsCalled, MachRegInt, Js::Throw::OutOfMemory); |
| instr->SetSrc1(IR::IntConstOpnd::New(argOutSize, TyMachReg, this->m_func)); |
| instr->m_opcode = Js::OpCode::LDIMM; |
| LegalizeMD::LegalizeInstr(instr); |
| return true; |
| } |
| else if (instr->m_opcode == Js::OpCode::REM) |
| { |
| IR::Opnd* dst = instr->GetDst(); |
| IR::Opnd* src1 = instr->GetSrc1(); |
| IR::Opnd* src2 = instr->GetSrc2(); |
| |
| Assert(src1->IsRegOpnd()); |
| Assert(src2->IsRegOpnd()); |
| |
| RegNum dstReg = dst->AsRegOpnd()->GetReg(); |
| |
| if (dstReg == src1->AsRegOpnd()->GetReg() || dstReg == src2->AsRegOpnd()->GetReg()) |
| { |
| Assert(src1->AsRegOpnd()->GetReg() != SCRATCH_REG); |
| Assert(src2->AsRegOpnd()->GetReg() != SCRATCH_REG); |
| Assert(src1->GetType() == src2->GetType()); |
| |
| // r17 = SDIV src1, src2 |
| IR::RegOpnd *regScratch = IR::RegOpnd::New(nullptr, SCRATCH_REG, src1->GetType(), instr->m_func); |
| IR::Instr *insertInstr = IR::Instr::New(Js::OpCode::SDIV, regScratch, src1, src2, instr->m_func); |
| instr->InsertBefore(insertInstr); |
| |
| // r17 = MSUB src1, src2, r17 (r17 = src1 - src2 * r17) |
| insertInstr = IR::Instr::New(Js::OpCode::MSUB, regScratch, src1, src2, instr->m_func); |
| instr->InsertBefore(insertInstr); |
| |
| // mov dst, r17 |
| insertInstr = IR::Instr::New(dst->IsFloat() ? Js::OpCode::FMOV : Js::OpCode::MOV, dst, regScratch, instr->m_func); |
| instr->InsertBefore(insertInstr); |
| instr->Remove(); |
| } |
| else |
| { |
| // dst = SDIV src1, src2 |
| IR::Instr *divInstr = IR::Instr::New(Js::OpCode::SDIV, dst, src1, src2, instr->m_func); |
| instr->InsertBefore(divInstr); |
| |
| // dst = MSUB src1, src2, dst (dst = src1 - src2 * dst) |
| instr->m_opcode = Js::OpCode::MSUB; |
| } |
| return true; |
| } |
| |
| return false; |
| } |
| IR::Opnd * |
| LowererMD::GenerateArgOutForStackArgs(IR::Instr* callInstr, IR::Instr* stackArgsInstr) |
| { |
| return this->m_lowerer->GenerateArgOutForStackArgs(callInstr, stackArgsInstr); |
| } |
| |
| IR::Instr * |
| LowererMD::LowerDivI4AndBailOnReminder(IR::Instr * instr, IR::LabelInstr * bailOutLabel) |
| { |
| // result = SDIV numerator, denominator |
| // mulResult = MUL result, denominator |
| // CMP mulResult, numerator |
| // BNE bailout |
| // <Caller insert more checks here> |
| // dst = MOV result <-- insertBeforeInstr |
| |
| |
| instr->m_opcode = Js::OpCode::SDIV; |
| |
| // delay assigning to the final dst. |
| IR::Instr * sinkedInstr = instr->SinkDst(Js::OpCode::MOV); |
| LegalizeMD::LegalizeInstr(instr); |
| LegalizeMD::LegalizeInstr(sinkedInstr); |
| |
| IR::Opnd * resultOpnd = instr->GetDst(); |
| IR::Opnd * numerator = instr->GetSrc1(); |
| IR::Opnd * denominatorOpnd = instr->GetSrc2(); |
| |
| // Insert all check before the assignment to the actual |
| IR::Instr * insertBeforeInstr = instr->m_next; |
| |
| // Jump to bailout if the reminder is not 0 (or the divResult * denominator is not same as the numerator) |
| IR::RegOpnd * mulResult = IR::RegOpnd::New(TyInt32, m_func); |
| IR::Instr * mulInstr = IR::Instr::New(Js::OpCode::MUL, mulResult, resultOpnd, denominatorOpnd, m_func); |
| insertBeforeInstr->InsertBefore(mulInstr); |
| LegalizeMD::LegalizeInstr(mulInstr); |
| |
| this->m_lowerer->InsertCompareBranch(mulResult, numerator, Js::OpCode::BrNeq_A, bailOutLabel, insertBeforeInstr); |
| return insertBeforeInstr; |
| } |
| |
| void |
| LowererMD::LowerInlineSpreadArgOutLoop(IR::Instr *callInstr, IR::RegOpnd *indexOpnd, IR::RegOpnd *arrayElementsStartOpnd) |
| { |
| this->m_lowerer->LowerInlineSpreadArgOutLoopUsingRegisters(callInstr, indexOpnd, arrayElementsStartOpnd); |
| } |
| |
| void |
| LowererMD::LowerTypeof(IR::Instr* typeOfInstr) |
| { |
| Func * func = typeOfInstr->m_func; |
| IR::Opnd * src1 = typeOfInstr->GetSrc1(); |
| IR::Opnd * dst = typeOfInstr->GetDst(); |
| Assert(src1->IsRegOpnd() && dst->IsRegOpnd()); |
| IR::LabelInstr * helperLabel = IR::LabelInstr::New(Js::OpCode::Label, func, true); |
| IR::LabelInstr * taggedIntLabel = IR::LabelInstr::New(Js::OpCode::Label, func); |
| IR::LabelInstr * doneLabel = IR::LabelInstr::New(Js::OpCode::Label, func); |
| |
| // MOV typeDisplayStringsArray, &javascriptLibrary->typeDisplayStrings |
| IR::RegOpnd * typeDisplayStringsArrayOpnd = IR::RegOpnd::New(TyMachPtr, func); |
| m_lowerer->InsertMove(typeDisplayStringsArrayOpnd, IR::AddrOpnd::New((BYTE*)m_func->GetScriptContextInfo()->GetLibraryAddr() + Js::JavascriptLibrary::GetTypeDisplayStringsOffset(), IR::AddrOpndKindConstantAddress, this->m_func), typeOfInstr); |
| |
| GenerateObjectTest(src1, typeOfInstr, taggedIntLabel); |
| |
| // MOV typeRegOpnd, [src1 + offset(Type)] |
| IR::RegOpnd * typeRegOpnd = IR::RegOpnd::New(TyMachReg, func); |
| m_lowerer->InsertMove(typeRegOpnd, |
| IR::IndirOpnd::New(src1->AsRegOpnd(), Js::RecyclableObject::GetOffsetOfType(), TyMachReg, func), |
| typeOfInstr); |
| |
| IR::LabelInstr * falsyLabel = IR::LabelInstr::New(Js::OpCode::Label, func); |
| m_lowerer->GenerateFalsyObjectTest(typeOfInstr, typeRegOpnd, falsyLabel); |
| |
| // <$not falsy> |
| // MOV typeId, TypeIds_Object |
| // MOV objTypeId, [typeRegOpnd + offsetof(typeId)] |
| // CMP objTypeId, TypeIds_Limit /*external object test*/ |
| // BCS $externalObjectLabel |
| // MOV typeId, objTypeId |
| // $loadTypeDisplayStringLabel: |
| // MOV dst, typeDisplayStrings[typeId] |
| // TEST dst, dst |
| // BEQ $helper |
| // B $done |
| IR::RegOpnd * typeIdOpnd = IR::RegOpnd::New(TyUint32, func); |
| m_lowerer->InsertMove(typeIdOpnd, IR::IntConstOpnd::New(Js::TypeIds_Object, TyUint32, func), typeOfInstr); |
| |
| IR::RegOpnd * objTypeIdOpnd = IR::RegOpnd::New(TyUint32, func); |
| m_lowerer->InsertMove(objTypeIdOpnd, IR::IndirOpnd::New(typeRegOpnd, Js::Type::GetOffsetOfTypeId(), TyInt32, func), typeOfInstr); |
| |
| IR::LabelInstr * loadTypeDisplayStringLabel = IR::LabelInstr::New(Js::OpCode::Label, func); |
| m_lowerer->InsertCompareBranch(objTypeIdOpnd, IR::IntConstOpnd::New(Js::TypeIds_Limit, TyUint32, func), Js::OpCode::BrGe_A, true /*unsigned*/, loadTypeDisplayStringLabel, typeOfInstr); |
| |
| m_lowerer->InsertMove(typeIdOpnd, objTypeIdOpnd, typeOfInstr); |
| typeOfInstr->InsertBefore(loadTypeDisplayStringLabel); |
| |
| if (dst->IsEqual(src1)) |
| { |
| ChangeToAssign(typeOfInstr->HoistSrc1(Js::OpCode::Ld_A)); |
| } |
| m_lowerer->InsertMove(dst, IR::IndirOpnd::New(typeDisplayStringsArrayOpnd, typeIdOpnd, this->GetDefaultIndirScale(), TyMachPtr, func), typeOfInstr); |
| m_lowerer->InsertTestBranch(dst, dst, Js::OpCode::BrEq_A, helperLabel, typeOfInstr); |
| |
| m_lowerer->InsertBranch(Js::OpCode::Br, doneLabel, typeOfInstr); |
| |
| // $taggedInt: |
| // MOV dst, typeDisplayStrings[TypeIds_Number] |
| // B $done |
| typeOfInstr->InsertBefore(taggedIntLabel); |
| m_lowerer->InsertMove(dst, IR::IndirOpnd::New(typeDisplayStringsArrayOpnd, Js::TypeIds_Number * sizeof(Js::Var), TyMachPtr, func), typeOfInstr); |
| m_lowerer->InsertBranch(Js::OpCode::Br, doneLabel, typeOfInstr); |
| |
| // $falsy: |
| // MOV dst, "undefined" |
| // B $done |
| typeOfInstr->InsertBefore(falsyLabel); |
| IR::Opnd * undefinedDisplayStringOpnd = IR::IndirOpnd::New(typeDisplayStringsArrayOpnd, Js::TypeIds_Undefined, TyMachPtr, func); |
| m_lowerer->InsertMove(dst, undefinedDisplayStringOpnd, typeOfInstr); |
| m_lowerer->InsertBranch(Js::OpCode::Br, doneLabel, typeOfInstr); |
| |
| // $helper |
| // CALL OP_TypeOf |
| // $done |
| typeOfInstr->InsertBefore(helperLabel); |
| typeOfInstr->InsertAfter(doneLabel); |
| m_lowerer->LowerUnaryHelperMem(typeOfInstr, IR::HelperOp_Typeof); |
| } |
| |
| void |
| LowererMD::InsertObjectPoison(IR::Opnd* poisonedOpnd, IR::BranchInstr* branchInstr, IR::Instr* insertInstr, bool isForStore) |
| { |
| if ((isForStore && CONFIG_FLAG_RELEASE(PoisonObjectsForStores)) || (!isForStore && CONFIG_FLAG_RELEASE(PoisonObjectsForLoads))) |
| { |
| Js::OpCode opcode; |
| if (branchInstr->m_opcode == Js::OpCode::BNE) |
| { |
| opcode = Js::OpCode::CSELEQ; |
| } |
| else |
| { |
| AssertOrFailFastMsg(branchInstr->m_opcode == Js::OpCode::BEQ, "Unexpected branch type in InsertObjectPoison preceeding instruction"); |
| opcode = Js::OpCode::CSELNE; |
| } |
| AssertOrFailFast(branchInstr->m_prev->m_opcode == Js::OpCode::SUBS || branchInstr->m_prev->m_opcode == Js::OpCode::ANDS); |
| |
| IR::RegOpnd* regZero = IR::RegOpnd::New(nullptr, RegZR, TyMachPtr, insertInstr->m_func); |
| IR::Instr* csel = IR::Instr::New(opcode, poisonedOpnd, poisonedOpnd, regZero, insertInstr->m_func); |
| insertInstr->InsertBefore(csel); |
| } |
| } |
| |
| IR::BranchInstr* |
| LowererMD::InsertMissingItemCompareBranch(IR::Opnd* compareSrc, IR::Opnd* missingItemOpnd, Js::OpCode opcode, IR::LabelInstr* target, IR::Instr* insertBeforeInstr) |
| { |
| Assert(compareSrc->IsFloat64() && missingItemOpnd->IsUint64()); |
| |
| IR::Opnd * compareSrcUint64Opnd = IR::RegOpnd::New(TyUint64, m_func); |
| if (compareSrc->IsRegOpnd()) |
| { |
| IR::Instr * movDoubleToUint64Instr = IR::Instr::New(Js::OpCode::FMOV_GEN, compareSrcUint64Opnd, compareSrc, insertBeforeInstr->m_func); |
| insertBeforeInstr->InsertBefore(movDoubleToUint64Instr); |
| } |
| else if (compareSrc->IsIndirOpnd()) |
| { |
| compareSrcUint64Opnd = compareSrc->UseWithNewType(TyUint64, m_func); |
| } |
| |
| return m_lowerer->InsertCompareBranch(compareSrcUint64Opnd, missingItemOpnd, opcode, target, insertBeforeInstr); |
| } |
| |
| #if DBG |
| // |
| // Helps in debugging of fast paths. |
| // |
| void LowererMD::GenerateDebugBreak( IR::Instr * insertInstr ) |
| { |
| IR::Instr *int3 = IR::Instr::New(Js::OpCode::DEBUGBREAK, insertInstr->m_func); |
| insertInstr->InsertBefore(int3); |
| } |
| #endif |
| |
| #ifdef _CONTROL_FLOW_GUARD |
| void |
| LowererMD::GenerateCFGCheck(IR::Opnd * entryPointOpnd, IR::Instr * insertBeforeInstr) |
| { |
| bool useJITTrampoline = CONFIG_FLAG(UseJITTrampoline); |
| IR::LabelInstr * callLabelInstr = nullptr; |
| uintptr_t jitThunkStartAddress = NULL; |
| |
| if (useJITTrampoline) |
| { |
| #if ENABLE_OOP_NATIVE_CODEGEN |
| if (m_func->IsOOPJIT()) |
| { |
| OOPJITThunkEmitter * jitThunkEmitter = m_func->GetOOPThreadContext()->GetJITThunkEmitter(); |
| jitThunkStartAddress = jitThunkEmitter->EnsureInitialized(); |
| } |
| else |
| #endif |
| { |
| InProcJITThunkEmitter * jitThunkEmitter = m_func->GetInProcThreadContext()->GetJITThunkEmitter(); |
| jitThunkStartAddress = jitThunkEmitter->EnsureInitialized(); |
| } |
| if (jitThunkStartAddress) |
| { |
| uintptr_t endAddressOfSegment = jitThunkStartAddress + InProcJITThunkEmitter::TotalThunkSize; |
| Assert(endAddressOfSegment > jitThunkStartAddress); |
| // Generate instructions for local Pre-Reserved Segment Range check |
| |
| IR::AddrOpnd * endAddressOfSegmentConstOpnd = IR::AddrOpnd::New(endAddressOfSegment, IR::AddrOpndKindDynamicMisc, m_func); |
| IR::RegOpnd *resultOpnd = IR::RegOpnd::New(TyMachReg, this->m_func); |
| |
| callLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func); |
| IR::LabelInstr * cfgLabelInstr = IR::LabelInstr::New(Js::OpCode::Label, m_func, true); |
| |
| // resultOpnd = SUB endAddressOfSegmentConstOpnd, entryPointOpnd |
| // CMP resultOpnd, TotalThunkSize |
| // BHS $cfgLabel |
| // AND entryPointOpnd, ~(ThunkSize-1) |
| // JMP $callLabel |
| m_lowerer->InsertSub(false, resultOpnd, endAddressOfSegmentConstOpnd, entryPointOpnd, insertBeforeInstr); |
| m_lowerer->InsertCompareBranch(resultOpnd, IR::IntConstOpnd::New(InProcJITThunkEmitter::TotalThunkSize, TyMachReg, m_func, true), Js::OpCode::BrGe_A, true, cfgLabelInstr, insertBeforeInstr); |
| m_lowerer->InsertAnd(entryPointOpnd, entryPointOpnd, IR::IntConstOpnd::New(InProcJITThunkEmitter::ThunkAlignmentMask, TyMachReg, m_func, true), insertBeforeInstr); |
| m_lowerer->InsertBranch(Js::OpCode::Br, callLabelInstr, insertBeforeInstr); |
| |
| insertBeforeInstr->InsertBefore(cfgLabelInstr); |
| } |
| } |
| //MOV x15, entryPoint |
| IR::RegOpnd * entryPointRegOpnd = IR::RegOpnd::New(nullptr, RegR15, TyMachReg, this->m_func); |
| entryPointRegOpnd->m_isCallArg = true; |
| IR::Instr *movInstrEntryPointToRegister = Lowerer::InsertMove(entryPointRegOpnd, entryPointOpnd, insertBeforeInstr); |
| |
| //Generate CheckCFG CALL here |
| IR::HelperCallOpnd *cfgCallOpnd = IR::HelperCallOpnd::New(IR::HelperGuardCheckCall, this->m_func); |
| IR::Instr* cfgCallInstr = IR::Instr::New(Js::OpCode::BLR, this->m_func); |
| this->m_func->SetHasCallsOnSelfAndParents(); |
| |
| //mov x16, __guard_check_icall_fptr |
| IR::RegOpnd *targetOpnd = IR::RegOpnd::New(nullptr, RegR16, TyMachPtr, this->m_func); |
| IR::Instr *movInstr = Lowerer::InsertMove(targetOpnd, cfgCallOpnd, insertBeforeInstr); |
| Legalize(movInstr); |
| |
| //call x16 |
| cfgCallInstr->SetSrc1(targetOpnd); |
| |
| //CALL cfg(x15) |
| insertBeforeInstr->InsertBefore(cfgCallInstr); |
| |
| if (jitThunkStartAddress) |
| { |
| Assert(callLabelInstr); |
| if (CONFIG_FLAG(ForceJITCFGCheck)) |
| { |
| // Always generate CFG check to make sure that the address is still valid |
| movInstrEntryPointToRegister->InsertBefore(callLabelInstr); |
| } |
| else |
| { |
| insertBeforeInstr->InsertBefore(callLabelInstr); |
| } |
| } |
| } |
| #endif |