CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/MIPS/IR/IRFrontend.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "Common/Log.h"18#include "Common/Serialize/Serializer.h"19#include "Common/Serialize/SerializeFuncs.h"20#include "Core/Debugger/Breakpoints.h"21#include "Core/Debugger/SymbolMap.h"22#include "Core/Reporting.h"23#include "Core/HLE/ReplaceTables.h"24#include "Core/MemMap.h"25#include "Core/MIPS/MIPSTables.h"26#include "Core/MIPS/IR/IRFrontend.h"27#include "Core/MIPS/IR/IRRegCache.h"28#include "Core/MIPS/IR/IRPassSimplify.h"29#include "Core/MIPS/IR/IRInterpreter.h"30#include "Core/MIPS/MIPSTracer.h"3132#include <iterator>3334namespace MIPSComp {3536IRFrontend::IRFrontend(bool startDefaultPrefix) {37js.startDefaultPrefix = startDefaultPrefix;38js.hasSetRounding = false;3940// The debugger sets this so that "go" on a breakpoint will actually... go.41// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.42CBreakPoints::SetSkipFirst(0);43}4445void IRFrontend::DoState(PointerWrap &p) {46auto s = p.Section("Jit", 1, 2);47if (!s)48return;4950Do(p, js.startDefaultPrefix);51if (s >= 2) {52Do(p, js.hasSetRounding);53js.lastSetRounding = 0;54} else {55js.hasSetRounding = 1;56}5758// The debugger sets this so that "go" on a breakpoint will actually... go.59// But if they reset, we can end up hitting it by mistake, since it's based on PC and ticks.60CBreakPoints::SetSkipFirst(0);61}6263void IRFrontend::FlushAll() {64FlushPrefixV();65}6667void IRFrontend::FlushPrefixV() {68if (js.startDefaultPrefix && !js.blockWrotePrefixes && js.HasNoPrefix()) {69// They started default, we never modified in memory, and they're default now.70// No reason to modify memory. This is common at end of blocks. Just clear dirty.71js.prefixSFlag = (JitState::PrefixState)(js.prefixSFlag & ~JitState::PREFIX_DIRTY);72js.prefixTFlag = (JitState::PrefixState)(js.prefixTFlag & ~JitState::PREFIX_DIRTY);73js.prefixDFlag = (JitState::PrefixState)(js.prefixDFlag & ~JitState::PREFIX_DIRTY);74return;75}7677if ((js.prefixSFlag & JitState::PREFIX_DIRTY) != 0) {78ir.Write(IROp::SetCtrlVFPU, VFPU_CTRL_SPREFIX, ir.AddConstant(js.prefixS));79js.prefixSFlag = (JitState::PrefixState) (js.prefixSFlag & ~JitState::PREFIX_DIRTY);80}8182if ((js.prefixTFlag & JitState::PREFIX_DIRTY) != 0) {83ir.Write(IROp::SetCtrlVFPU, VFPU_CTRL_TPREFIX, ir.AddConstant(js.prefixT));84js.prefixTFlag = (JitState::PrefixState) (js.prefixTFlag & ~JitState::PREFIX_DIRTY);85}8687if ((js.prefixDFlag & JitState::PREFIX_DIRTY) != 0) {88ir.Write(IROp::SetCtrlVFPU, VFPU_CTRL_DPREFIX, ir.AddConstant(js.prefixD));89js.prefixDFlag = (JitState::PrefixState) (js.prefixDFlag & ~JitState::PREFIX_DIRTY);90}9192// If we got here, we must've written prefixes to memory in this block.93js.blockWrotePrefixes = true;94}9596void IRFrontend::EatInstruction(MIPSOpcode op) {97MIPSInfo info = MIPSGetInfo(op);98if (info & DELAYSLOT) {99ERROR_LOG_REPORT_ONCE(ateDelaySlot, Log::JIT, "Ate a branch op.");100}101if (js.inDelaySlot) {102ERROR_LOG_REPORT_ONCE(ateInDelaySlot, Log::JIT, "Ate an instruction inside a delay slot.");103}104105CheckBreakpoint(GetCompilerPC() + 4);106js.numInstructions++;107js.compilerPC += 4;108js.downcountAmount += MIPSGetInstructionCycleEstimate(op);109}110111void IRFrontend::CompileDelaySlot() {112js.inDelaySlot = true;113CheckBreakpoint(GetCompilerPC() + 4);114MIPSOpcode op = GetOffsetInstruction(1);115MIPSCompileOp(op, this);116js.inDelaySlot = false;117}118119bool IRFrontend::CheckRounding(u32 blockAddress) {120bool cleanSlate = false;121if (js.hasSetRounding && !js.lastSetRounding) {122WARN_LOG(Log::JIT, "Detected rounding mode usage, rebuilding jit with checks");123// Won't loop, since hasSetRounding is only ever set to 1.124js.lastSetRounding = js.hasSetRounding;125cleanSlate = true;126}127128// Drat. The VFPU hit an uneaten prefix at the end of a block.129if (js.startDefaultPrefix && js.MayHavePrefix()) {130WARN_LOG_REPORT(Log::JIT, "An uneaten prefix at end of block for %08x", blockAddress);131logBlocks = 1;132js.LogPrefix();133134// Let's try that one more time. We won't get back here because we toggled the value.135js.startDefaultPrefix = false;136cleanSlate = true;137}138return cleanSlate;139}140141void IRFrontend::Comp_ReplacementFunc(MIPSOpcode op) {142int index = op.encoding & MIPS_EMUHACK_VALUE_MASK;143144const ReplacementTableEntry *entry = GetReplacementFunc(index);145if (!entry) {146ERROR_LOG(Log::HLE, "Invalid replacement op %08x", op.encoding);147return;148}149150u32 funcSize = g_symbolMap->GetFunctionSize(GetCompilerPC());151bool disabled = (entry->flags & REPFLAG_DISABLED) != 0;152if (!disabled && funcSize != SymbolMap::INVALID_ADDRESS && funcSize > sizeof(u32)) {153// We don't need to disable hooks, the code will still run.154if ((entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) == 0) {155// Any breakpoint at the func entry was already tripped, so we can still run the replacement.156// That's a common case - just to see how often the replacement hits.157disabled = CBreakPoints::RangeContainsBreakPoint(GetCompilerPC() + sizeof(u32), funcSize - sizeof(u32));158}159}160161if (disabled) {162MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);163} else if (entry->replaceFunc) {164FlushAll();165RestoreRoundingMode();166ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));167ir.Write(IROp::CallReplacement, IRTEMP_0, ir.AddConstant(index));168169if (entry->flags & (REPFLAG_HOOKENTER | REPFLAG_HOOKEXIT)) {170// Compile the original instruction at this address. We ignore cycles for hooks.171ApplyRoundingMode();172MIPSCompileOp(Memory::Read_Instruction(GetCompilerPC(), true), this);173} else {174ApplyRoundingMode();175// If IRTEMP_0 was set to 1, it means the replacement needs to run again (sliced.)176// This is necessary for replacements that take a lot of cycles.177ir.Write(IROp::Downcount, 0, ir.AddConstant(js.downcountAmount));178ir.Write(IROp::ExitToConstIfNeq, ir.AddConstant(GetCompilerPC()), IRTEMP_0, MIPS_REG_ZERO);179ir.Write(IROp::ExitToReg, 0, MIPS_REG_RA, 0);180js.compiling = false;181}182} else {183ERROR_LOG(Log::HLE, "Replacement function %s has neither jit nor regular impl", entry->name);184}185}186187void IRFrontend::Comp_Generic(MIPSOpcode op) {188FlushAll();189ir.Write(IROp::Interpret, 0, ir.AddConstant(op.encoding));190const MIPSInfo info = MIPSGetInfo(op);191if ((info & IS_VFPU) != 0 && (info & VFPU_NO_PREFIX) == 0) {192// If it does eat them, it'll happen in MIPSCompileOp().193if ((info & OUT_EAT_PREFIX) == 0)194js.PrefixUnknown();195196// Even if DISABLE'd, we want to set this flag so we overwrite.197if ((info & OUT_VFPU_PREFIX) != 0)198js.blockWrotePrefixes = true;199}200}201202// Destroys SCRATCH2203void IRFrontend::RestoreRoundingMode(bool force) {204// If the game has never set an interesting rounding mode, we can safely skip this.205if (force || js.hasSetRounding) {206ir.Write(IROp::RestoreRoundingMode);207}208}209210// Destroys SCRATCH1 and SCRATCH2211void IRFrontend::ApplyRoundingMode(bool force) {212// If the game has never set an interesting rounding mode, we can safely skip this.213if (force || js.hasSetRounding) {214ir.Write(IROp::ApplyRoundingMode);215}216}217218// Destroys SCRATCH1 and SCRATCH2219void IRFrontend::UpdateRoundingMode() {220// We must set js.hasSetRounding at compile time, or this block will use the wrong rounding mode.221js.hasSetRounding = true;222ir.Write(IROp::UpdateRoundingMode);223}224225void IRFrontend::Comp_DoNothing(MIPSOpcode op) {226}227228int IRFrontend::Replace_fabsf() {229Crash();230return 0;231}232233u32 IRFrontend::GetCompilerPC() {234return js.compilerPC;235}236237MIPSOpcode IRFrontend::GetOffsetInstruction(int offset) {238return Memory::Read_Instruction(GetCompilerPC() + 4 * offset);239}240241void IRFrontend::DoJit(u32 em_address, std::vector<IRInst> &instructions, u32 &mipsBytes, bool preload) {242js.cancel = false;243js.preloading = preload;244js.blockStart = em_address;245js.compilerPC = em_address;246js.lastContinuedPC = 0;247js.initialBlockSize = 0;248js.nextExit = 0;249js.downcountAmount = 0;250js.curBlock = nullptr;251js.compiling = true;252js.hadBreakpoints = false;253js.blockWrotePrefixes = false;254js.inDelaySlot = false;255js.PrefixStart();256ir.Clear();257258js.numInstructions = 0;259while (js.compiling) {260// Jit breakpoints are quite fast, so let's do them in release too.261CheckBreakpoint(GetCompilerPC());262263MIPSOpcode inst = Memory::Read_Opcode_JIT(GetCompilerPC());264js.downcountAmount += MIPSGetInstructionCycleEstimate(inst);265MIPSCompileOp(inst, this);266js.compilerPC += 4;267js.numInstructions++;268}269270if (js.cancel) {271// Clear the instructions to signal this was not compiled.272ir.Clear();273}274275mipsBytes = js.compilerPC - em_address;276277IRWriter simplified;278IRWriter *code = &ir;279if (!js.hadBreakpoints) {280std::vector<IRPassFunc> passes{281&ApplyMemoryValidation,282&RemoveLoadStoreLeftRight,283&OptimizeFPMoves,284&PropagateConstants,285&PurgeTemps,286&ReduceVec4Flush,287&OptimizeLoadsAfterStores,288// &ReorderLoadStore,289// &MergeLoadStore,290// &ThreeOpToTwoOp,291};292293if (opts.optimizeForInterpreter) {294// Add special passes here.295passes.push_back(&OptimizeForInterpreter);296}297if (IRApplyPasses(passes.data(), passes.size(), ir, simplified, opts))298logBlocks = 1;299code = &simplified;300//if (ir.GetInstructions().size() >= 24)301// logBlocks = 1;302}303304if (!mipsTracer.tracing_enabled) {305instructions = code->GetInstructions();306}307else {308std::vector<IRInst> block_instructions = code->GetInstructions();309instructions.reserve(block_instructions.capacity());310// The first instruction is "Downcount"311instructions.push_back(block_instructions.front());312instructions.push_back({ IROp::LogIRBlock, 0, 0, 0, 0 });313std::copy(block_instructions.begin() + 1, block_instructions.end(), std::back_inserter(instructions));314}315316if (logBlocks > 0 && dontLogBlocks == 0) {317char temp2[256];318NOTICE_LOG(Log::JIT, "=============== mips %08x ===============", em_address);319for (u32 cpc = em_address; cpc != GetCompilerPC(); cpc += 4) {320temp2[0] = 0;321MIPSDisAsm(Memory::Read_Opcode_JIT(cpc), cpc, temp2, sizeof(temp2), true);322NOTICE_LOG(Log::JIT, "M: %08x %s", cpc, temp2);323}324}325326if (logBlocks > 0 && dontLogBlocks == 0) {327NOTICE_LOG(Log::JIT, "=============== Original IR (%d instructions) ===============", (int)ir.GetInstructions().size());328for (size_t i = 0; i < ir.GetInstructions().size(); i++) {329char buf[256];330DisassembleIR(buf, sizeof(buf), ir.GetInstructions()[i]);331NOTICE_LOG(Log::JIT, "%s", buf);332}333NOTICE_LOG(Log::JIT, "=============== end =================");334}335336if (logBlocks > 0 && dontLogBlocks == 0) {337NOTICE_LOG(Log::JIT, "=============== IR (%d instructions) ===============", (int)code->GetInstructions().size());338for (size_t i = 0; i < code->GetInstructions().size(); i++) {339char buf[256];340DisassembleIR(buf, sizeof(buf), code->GetInstructions()[i]);341NOTICE_LOG(Log::JIT, "%s", buf);342}343NOTICE_LOG(Log::JIT, "=============== end =================");344}345346if (logBlocks > 0)347logBlocks--;348if (dontLogBlocks > 0)349dontLogBlocks--;350}351352void IRFrontend::Comp_RunBlock(MIPSOpcode op) {353// This shouldn't be necessary, the dispatcher should catch us before we get here.354ERROR_LOG(Log::JIT, "Comp_RunBlock should never be reached!");355}356357void IRFrontend::CheckBreakpoint(u32 addr) {358if (CBreakPoints::IsAddressBreakPoint(addr)) {359FlushAll();360361// Can't skip this even at the start of a block, might impact block linking.362ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));363364RestoreRoundingMode();365// At this point, downcount HAS the delay slot, but not the instruction itself.366int downcountOffset = 0;367if (js.inDelaySlot) {368MIPSOpcode branchOp = Memory::Read_Opcode_JIT(GetCompilerPC());369MIPSOpcode delayOp = Memory::Read_Opcode_JIT(addr);370downcountOffset = -MIPSGetInstructionCycleEstimate(delayOp);371if ((MIPSGetInfo(branchOp) & LIKELY) != 0) {372// Okay, we're in a likely branch. Also negate the branch cycles.373downcountOffset += -MIPSGetInstructionCycleEstimate(branchOp);374}375}376int downcountAmount = js.downcountAmount + downcountOffset;377if (downcountAmount != 0)378ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount));379// Note that this means downcount can't be metadata on the block.380js.downcountAmount = -downcountOffset;381ir.Write(IROp::Breakpoint, 0, ir.AddConstant(addr));382ApplyRoundingMode();383384js.hadBreakpoints = true;385}386}387388void IRFrontend::CheckMemoryBreakpoint(int rs, int offset) {389if (CBreakPoints::HasMemChecks()) {390FlushAll();391392// Can't skip this even at the start of a block, might impact block linking.393ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC()));394395RestoreRoundingMode();396// At this point, downcount HAS the delay slot, but not the instruction itself.397int downcountOffset = 0;398if (js.inDelaySlot) {399// We assume delay slot in compilerPC + 4.400MIPSOpcode branchOp = Memory::Read_Opcode_JIT(GetCompilerPC());401MIPSOpcode delayOp = Memory::Read_Opcode_JIT(GetCompilerPC() + 4);402downcountOffset = -MIPSGetInstructionCycleEstimate(delayOp);403if ((MIPSGetInfo(branchOp) & LIKELY) != 0) {404// Okay, we're in a likely branch. Also negate the branch cycles.405downcountOffset += -MIPSGetInstructionCycleEstimate(branchOp);406}407}408int downcountAmount = js.downcountAmount + downcountOffset;409if (downcountAmount != 0)410ir.Write(IROp::Downcount, 0, ir.AddConstant(downcountAmount));411// Note that this means downcount can't be metadata on the block.412js.downcountAmount = -downcountOffset;413ir.Write(IROp::MemoryCheck, js.inDelaySlot ? 4 : 0, rs, ir.AddConstant(offset));414ApplyRoundingMode();415416js.hadBreakpoints = true;417}418}419420} // namespace421422423