/*
This file is part of jpcsp.
Jpcsp is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Jpcsp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Jpcsp. If not, see <http://www.gnu.org/licenses/>.
*/
package jpcsp.Allegrex.compiler;
import static jpcsp.util.Utilities.sleep;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import jpcsp.Emulator;
import jpcsp.Memory;
import jpcsp.MemoryMap;
import jpcsp.Processor;
import jpcsp.State;
import jpcsp.Allegrex.Common;
import jpcsp.Allegrex.CpuState;
import jpcsp.Allegrex.Decoder;
import jpcsp.Allegrex.Instructions;
import jpcsp.Allegrex.Common.Instruction;
import jpcsp.HLE.Modules;
import jpcsp.HLE.PspString;
import jpcsp.HLE.SyscallHandler;
import jpcsp.HLE.kernel.managers.IntrManager;
import jpcsp.HLE.kernel.types.SceKernelThreadInfo;
import jpcsp.HLE.modules.ThreadManForUser;
import jpcsp.HLE.modules.sceDisplay;
import jpcsp.hardware.Interrupts;
import jpcsp.memory.DebuggerMemory;
import jpcsp.memory.FastMemory;
import jpcsp.scheduler.Scheduler;
import jpcsp.settings.AbstractBoolSettingsListener;
import jpcsp.settings.Settings;
import jpcsp.util.CpuDurationStatistics;
import jpcsp.util.DurationStatistics;
import jpcsp.util.Utilities;
import org.apache.log4j.Logger;
/**
* @author gid15
*
*/
public class RuntimeContext {
public static Logger log = Logger.getLogger("runtime");
private static boolean compilerEnabled = true;
public static float[] fpr;
public static float[] vprFloat;
public static int[] vprInt;
public static int[] memoryInt;
public static Processor processor;
public static CpuState cpu;
public static Memory memory;
public static boolean enableDebugger = true;
public static final String debuggerName = "syncDebugger";
public static final boolean debugCodeBlockCalls = false;
public static final String debugCodeBlockStart = "debugCodeBlockStart";
public static final String debugCodeBlockEnd = "debugCodeBlockEnd";
private static final Map<Integer, Integer> debugCodeBlocks = new HashMap<Integer, Integer>();
public static final boolean debugCodeInstruction = false;
public static final String debugCodeInstructionName = "debugCodeInstruction";
public static final boolean debugMemoryRead = false;
public static final boolean debugMemoryWrite = false;
public static final boolean debugMemoryReadWriteNoSP = true;
public static final boolean enableInstructionTypeCounting = false;
public static final String instructionTypeCount = "instructionTypeCount";
public static final String logInfo = "logInfo";
public static final String pauseEmuWithStatus = "pauseEmuWithStatus";
public static final boolean enableLineNumbers = true;
public static final boolean checkCodeModification = false;
private static final boolean invalidateAllCodeBlocks = false;
private static final int idleSleepMicros = 1000;
private static final Map<Integer, CodeBlock> codeBlocks = Collections.synchronizedMap(new HashMap<Integer, CodeBlock>());
private static int codeBlocksLowestAddress = Integer.MAX_VALUE;
private static int codeBlocksHighestAddress = Integer.MIN_VALUE;
// A fast lookup array for executables (to improve the performance of the Allegrex instruction jalr)
private static IExecutable[] fastExecutableLookup;
// A fast lookup for the Allegrex instruction ICACHE HIT INVALIDATE
private static CodeBlockList[] fastCodeBlockLookup;
private static final int fastCodeBlockLookupShift = 8;
private static final int fastCodeBlockSize = 64; // Matching the size used by the Allegrex instruction ICACHE HIT INVALIDATE
private static final Map<SceKernelThreadInfo, RuntimeThread> threads = Collections.synchronizedMap(new HashMap<SceKernelThreadInfo, RuntimeThread>());
private static final Map<SceKernelThreadInfo, RuntimeThread> toBeStoppedThreads = Collections.synchronizedMap(new HashMap<SceKernelThreadInfo, RuntimeThread>());
private static final Map<SceKernelThreadInfo, RuntimeThread> alreadyStoppedThreads = Collections.synchronizedMap(new HashMap<SceKernelThreadInfo, RuntimeThread>());
private static final List<Thread> alreadySwitchedStoppedThreads = Collections.synchronizedList(new ArrayList<Thread>());
private static final Map<SceKernelThreadInfo, RuntimeThread> toBeDeletedThreads = Collections.synchronizedMap(new HashMap<SceKernelThreadInfo, RuntimeThread>());
public static volatile SceKernelThreadInfo currentThread = null;
private static volatile RuntimeThread currentRuntimeThread = null;
private static final Object waitForEnd = new Object();
private static volatile Emulator emulator;
private static volatile boolean isIdle = false;
private static volatile boolean reset = false;
public static CpuDurationStatistics idleDuration = new CpuDurationStatistics("Idle Time");
private static Map<Instruction, Integer> instructionTypeCounts = Collections.synchronizedMap(new HashMap<Instruction, Integer>());
public static boolean enableDaemonThreadSync = true;
public static final String syncName = "sync";
public static volatile boolean wantSync = false;
private static RuntimeSyncThread runtimeSyncThread = null;
private static RuntimeThread syscallRuntimeThread;
private static sceDisplay sceDisplayModule;
private static final Object idleSyncObject = new Object();
public static int firmwareVersion;
private static boolean isHomebrew = false;
private static class CompilerEnabledSettingsListerner extends AbstractBoolSettingsListener {
@Override
protected void settingsValueChanged(boolean value) {
setCompilerEnabled(value);
}
}
private static class CodeBlockList extends LinkedList<CodeBlock> {
private static final long serialVersionUID = 7370950118403866860L;
}
private static void setCompilerEnabled(boolean enabled) {
compilerEnabled = enabled;
}
public static boolean isCompilerEnabled() {
return compilerEnabled;
}
public static void execute(Instruction insn, int opcode) {
insn.interpret(processor, opcode);
}
private static int jumpCall(int address) throws Exception {
IExecutable executable = getExecutable(address);
if (executable == null) {
// TODO Return to interpreter
log.error("RuntimeContext.jumpCall - Cannot find executable");
throw new RuntimeException("Cannot find executable");
}
int returnValue;
int sp = cpu._sp;
RuntimeThread stackThread = currentRuntimeThread;
if (stackThread != null && stackThread.isStackMaxSize()) {
if (log.isDebugEnabled()) {
log.debug(String.format("jumpCall stack already reached maxSize, returning 0x%08X", address));
}
throw new StackPopException(address);
}
try {
if (stackThread != null) {
stackThread.increaseStackSize();
}
returnValue = executable.exec();
} catch (StackOverflowError e) {
log.error(String.format("StackOverflowError stackSize=%d", stackThread.getStackSize()));
throw e;
} finally {
if (stackThread != null) {
stackThread.decreaseStackSize();
}
}
if (stackThread != null && stackThread.isStackMaxSize() && cpu._sp > sp) {
if (log.isDebugEnabled()) {
log.debug(String.format("jumpCall returning 0x%08X with $sp=0x%08X, start $sp=0x%08X", returnValue, cpu._sp, sp));
}
throw new StackPopException(returnValue);
}
if (debugCodeBlockCalls && log.isDebugEnabled()) {
log.debug(String.format("RuntimeContext.jumpCall returning 0x%08X", returnValue));
}
return returnValue;
}
public static void jump(int address, int returnAddress) throws Exception {
if (debugCodeBlockCalls && log.isDebugEnabled()) {
log.debug(String.format("RuntimeContext.jump starting address=0x%08X, returnAddress=0x%08X, $sp=0x%08X", address, returnAddress, cpu._sp));
}
int sp = cpu._sp;
while (address != returnAddress) {
try {
address = jumpCall(address);
} catch (StackPopException e) {
if (log.isDebugEnabled()) {
log.debug(String.format("jumpCall catching StackPopException 0x%08X with $sp=0x%08X, start $sp=0x%08X", e.getRa(), cpu._sp, sp));
}
if (e.getRa() != returnAddress) {
throw e;
}
break;
}
}
if (debugCodeBlockCalls && log.isDebugEnabled()) {
log.debug(String.format("RuntimeContext.jump returning address=0x%08X, returnAddress=0x%08X, $sp=0x%08X", address, returnAddress, cpu._sp));
}
}
public static int call(int address) throws Exception {
if (debugCodeBlockCalls && log.isDebugEnabled()) {
log.debug(String.format("RuntimeContext.call address=0x%08X, $ra=0x%08X", address, cpu._ra));
}
int returnValue = jumpCall(address);
return returnValue;
}
public static int executeInterpreter(int address) throws Exception {
if (debugCodeBlockCalls && log.isDebugEnabled()) {
log.debug(String.format("RuntimeContext.executeInterpreter address=0x%08X", address));
}
boolean interpret = true;
cpu.pc = address;
int returnValue = 0;
while (interpret) {
int opcode = cpu.fetchOpcode();
Instruction insn = Decoder.instruction(opcode);
if (Compiler.log.isDebugEnabled()) {
Compiler.log.debug(String.format("Interpreting 0x%X - %s", cpu.pc - 4, insn.disasm(cpu.pc - 4, opcode)));
if (insn.hasFlags(Instruction.FLAG_HAS_DELAY_SLOT)) {
int opcodeDelaySlot = memory.read32(cpu.pc);
Instruction insnDelaySlot = Decoder.instruction(opcodeDelaySlot);
Compiler.log.debug(String.format("Interpreting 0x%X - %s", cpu.pc, insnDelaySlot.disasm(cpu.pc, opcodeDelaySlot)));
}
}
insn.interpret(processor, opcode);
if (insn.hasFlags(Instruction.FLAG_STARTS_NEW_BLOCK)) {
cpu.pc = jumpCall(cpu.pc);
} else if (insn.hasFlags(Instruction.FLAG_ENDS_BLOCK) && !insn.hasFlags(Instruction.FLAG_IS_CONDITIONAL)) {
interpret = false;
returnValue = cpu.pc;
}
}
return returnValue;
}
public static void execute(int opcode) {
Instruction insn = Decoder.instruction(opcode);
execute(insn, opcode);
}
private static String getDebugCodeBlockStart(int address) {
String comment = "";
int syscallAddress = address + 4;
if (Memory.isAddressGood(syscallAddress)) {
int syscallOpcode = memory.read32(syscallAddress);
Instruction syscallInstruction = Decoder.instruction(syscallOpcode);
if (syscallInstruction == Instructions.SYSCALL) {
String syscallDisasm = syscallInstruction.disasm(syscallAddress, syscallOpcode);
comment = syscallDisasm.substring(19);
}
}
String parameters = "";
Integer numberOfParameters = debugCodeBlocks.get(address);
if (numberOfParameters != null) {
StringBuilder s = new StringBuilder();
int maxRegisterParameters = Math.min(numberOfParameters.intValue(), 8);
for (int i = 0; i < maxRegisterParameters; i++) {
int register = Common._a0 + i;
int parameterValue = cpu.getRegister(register);
if (Memory.isAddressGood(parameterValue)) {
s.append(String.format(", %s = 0x%08X", Common.gprNames[register], parameterValue));
} else {
s.append(String.format(", %s = 0x%X", Common.gprNames[register], parameterValue));
}
}
parameters = s.toString();
}
return String.format("Starting CodeBlock 0x%08X%s%s, $ra=0x%08X, $sp=0x%08X", address, comment, parameters, cpu._ra, cpu._sp);
}
public static void debugCodeBlockStart(int address) {
if (!debugCodeBlocks.isEmpty() && debugCodeBlocks.containsKey(address)) {
if (log.isInfoEnabled()) {
log.info(getDebugCodeBlockStart(address));
}
} else if (log.isDebugEnabled()) {
log.debug(getDebugCodeBlockStart(address));
}
}
public static void debugCodeBlockEnd(int address, int returnAddress) {
if (log.isDebugEnabled()) {
log.debug(String.format("Returning from CodeBlock 0x%08X to 0x%08X, $sp=0x%08X, $v0=0x%08X", address, returnAddress, cpu._sp, cpu._v0));
}
}
public static void debugCodeInstruction(int address, int opcode) {
if (log.isTraceEnabled()) {
cpu.pc = address;
Instruction insn = Decoder.instruction(opcode);
char compileFlag = insn.hasFlags(Instruction.FLAG_INTERPRETED) ? 'I' : 'C';
log.trace(String.format("Executing 0x%08X %c - %s", address, compileFlag, insn.disasm(address, opcode)));
}
}
private static boolean initialise() {
if (!compilerEnabled) {
return false;
}
if (enableDaemonThreadSync && runtimeSyncThread == null) {
runtimeSyncThread = new RuntimeSyncThread();
runtimeSyncThread.setName("Sync Daemon");
runtimeSyncThread.setDaemon(true);
runtimeSyncThread.start();
}
updateMemory();
if (State.debugger != null || (memory instanceof DebuggerMemory) || debugMemoryRead || debugMemoryWrite) {
enableDebugger = true;
} else {
enableDebugger = false;
}
Profiler.initialise();
sceDisplayModule = Modules.sceDisplayModule;
fastExecutableLookup = new IExecutable[(MemoryMap.END_USERSPACE - MemoryMap.START_USERSPACE + 1) >> 2];
fastCodeBlockLookup = new CodeBlockList[(MemoryMap.END_USERSPACE - MemoryMap.START_USERSPACE + 1) >> fastCodeBlockLookupShift];
return true;
}
public static boolean canExecuteCallback(SceKernelThreadInfo callbackThread) {
if (!compilerEnabled) {
return true;
}
// Can the callback be executed in any thread (e.g. is an interrupt)?
if (callbackThread == null) {
return true;
}
if (Modules.ThreadManForUserModule.isIdleThread(callbackThread)) {
return true;
}
Thread currentThread = Thread.currentThread();
if (currentThread instanceof RuntimeThread) {
RuntimeThread currentRuntimeThread = (RuntimeThread) currentThread;
if (callbackThread == currentRuntimeThread.getThreadInfo()) {
return true;
}
}
return false;
}
private static void checkPendingCallbacks() {
if (Modules.ThreadManForUserModule.checkPendingActions()) {
// if some action has been executed, the current thread might be changed. Resync.
if (log.isDebugEnabled()) {
log.debug(String.format("A pending action has been executed for the thread"));
}
wantSync = true;
}
Modules.ThreadManForUserModule.checkPendingCallbacks();
}
public static void executeCallback() {
int pc = cpu.pc;
if (log.isDebugEnabled()) {
log.debug(String.format("Start of Callback 0x%08X", pc));
}
// Switch to the real active thread, even if it is an idle thread
switchRealThread(Modules.ThreadManForUserModule.getCurrentThread());
boolean callbackExited = executeFunction(pc);
if (log.isDebugEnabled()) {
log.debug(String.format("End of Callback 0x%08X", pc));
}
if (cpu.pc == ThreadManForUser.CALLBACK_EXIT_HANDLER_ADDRESS || callbackExited) {
Modules.ThreadManForUserModule.hleKernelExitCallback(Emulator.getProcessor());
// Re-sync the runtime, the current thread might have been rescheduled
wantSync = true;
}
update();
}
private static void updateStaticVariables() {
emulator = Emulator.getInstance();
processor = Emulator.getProcessor();
cpu = processor.cpu;
if (cpu != null) {
fpr = processor.cpu.fpr;
vprFloat = processor.cpu.vprFloat;
vprInt = processor.cpu.vprInt;
}
}
public static void updateMemory() {
memory = Emulator.getMemory();
if (memory instanceof FastMemory) {
memoryInt = ((FastMemory) memory).getAll();
} else {
memoryInt = null;
}
}
public static void update() {
if (!compilerEnabled) {
return;
}
updateStaticVariables();
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
if (IntrManager.getInstance().canExecuteInterruptNow()) {
SceKernelThreadInfo newThread = threadMan.getCurrentThread();
if (newThread != null && newThread != currentThread) {
switchThread(newThread);
}
}
}
private static void switchRealThread(SceKernelThreadInfo threadInfo) {
RuntimeThread thread = threads.get(threadInfo);
if (thread == null) {
thread = new RuntimeThread(threadInfo);
threads.put(threadInfo, thread);
thread.start();
}
currentThread = threadInfo;
currentRuntimeThread = thread;
isIdle = false;
}
private static void switchThread(SceKernelThreadInfo threadInfo) {
if (log.isDebugEnabled()) {
String name;
if (threadInfo == null) {
name = "Idle";
} else {
name = threadInfo.name;
}
if (currentThread == null) {
log.debug("Switching to Thread " + name);
} else {
log.debug("Switching from Thread " + currentThread.name + " to " + name);
}
}
if (threadInfo == null || Modules.ThreadManForUserModule.isIdleThread(threadInfo)) {
isIdle = true;
currentThread = null;
currentRuntimeThread = null;
} else if (toBeStoppedThreads.containsKey(threadInfo)) {
// This thread must stop immediately
isIdle = true;
currentThread = null;
currentRuntimeThread = null;
} else {
switchRealThread(threadInfo);
}
}
private static void syncIdle() throws StopThreadException {
if (isIdle) {
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
Scheduler scheduler = Emulator.getScheduler();
log.debug("Starting Idle State...");
idleDuration.start();
while (isIdle) {
checkStoppedThread();
{
// Do not take the duration of sceDisplay into idleDuration
idleDuration.end();
syncEmulator(true);
idleDuration.start();
}
syncPause();
checkPendingCallbacks();
scheduler.step();
if (threadMan.isIdleThread(threadMan.getCurrentThread())) {
threadMan.checkCallbacks();
threadMan.hleRescheduleCurrentThread();
}
if (isIdle) {
// While being idle, try to reduce the load on the host CPU
// by sleeping as much as possible.
// We can now sleep until the next scheduler action need to be executed.
//
// If the scheduler is receiving from another thread, a new action
// to be executed earlier, the wait state of this thread
// will be interrupted (see onNextScheduleModified()).
// This is for example the case when a GE list is ending (FINISH/SIGNAL + END)
// and a GE callback has to be executed immediately.
long delay = scheduler.getNextActionDelay(idleSleepMicros);
if (delay > 0) {
int intDelay;
if (delay >= idleSleepMicros) {
intDelay = idleSleepMicros;
} else {
intDelay = (int) delay;
}
try {
// Wait for intDelay milliseconds.
// The wait state will be terminated whenever the scheduler
// is receiving a new scheduler action (see onNextScheduleModified()).
synchronized (idleSyncObject) {
idleSyncObject.wait(intDelay / 1000, intDelay % 1000);
}
} catch (InterruptedException e) {
// Ignore exception
}
}
}
}
idleDuration.end();
log.debug("Ending Idle State");
}
}
private static void syncThreadImmediately() throws StopThreadException {
Thread currentThread = Thread.currentThread();
if (currentRuntimeThread != null &&
currentThread != currentRuntimeThread && !alreadySwitchedStoppedThreads.contains(currentThread)) {
currentRuntimeThread.continueRuntimeExecution();
if (currentThread instanceof RuntimeThread) {
RuntimeThread runtimeThread = (RuntimeThread) currentThread;
if (!alreadyStoppedThreads.containsValue(runtimeThread)) {
log.debug("Waiting to be scheduled...");
runtimeThread.suspendRuntimeExecution();
log.debug("Scheduled, restarting...");
checkStoppedThread();
updateStaticVariables();
} else {
alreadySwitchedStoppedThreads.add(currentThread);
}
}
}
checkPendingCallbacks();
}
private static void syncThread() throws StopThreadException {
syncIdle();
if (toBeDeletedThreads.containsValue(getRuntimeThread())) {
return;
}
Thread currentThread = Thread.currentThread();
if (log.isDebugEnabled()) {
log.debug("syncThread currentThread=" + currentThread.getName() + ", currentRuntimeThread=" + currentRuntimeThread.getName());
}
syncThreadImmediately();
}
private static RuntimeThread getRuntimeThread() {
Thread currentThread = Thread.currentThread();
if (currentThread instanceof RuntimeThread) {
return (RuntimeThread) currentThread;
}
return null;
}
private static boolean isStoppedThread() {
if (toBeStoppedThreads.isEmpty()) {
return false;
}
RuntimeThread runtimeThread = getRuntimeThread();
if (runtimeThread != null && toBeStoppedThreads.containsValue(runtimeThread)) {
if (!alreadyStoppedThreads.containsValue(runtimeThread)) {
return true;
}
}
return false;
}
private static void checkStoppedThread() throws StopThreadException {
if (isStoppedThread()) {
throw new StopThreadException("Stopping Thread " + Thread.currentThread().getName());
}
}
private static void syncPause() throws StopThreadException {
if (Emulator.pause) {
Emulator.getClock().pause();
try {
synchronized(emulator) {
while (Emulator.pause) {
checkStoppedThread();
emulator.wait();
}
}
} catch (InterruptedException e){
// Ignore Exception
} finally {
Emulator.getClock().resume();
}
}
}
public static void syncDebugger(int pc) throws StopThreadException {
processor.cpu.pc = pc;
if (State.debugger != null) {
syncDebugger();
syncPause();
} else if (Emulator.pause) {
syncPause();
}
}
private static void syncDebugger() {
if (State.debugger != null) {
State.debugger.step();
}
}
private static void syncEmulator(boolean immediately) {
if (log.isDebugEnabled()) {
log.debug("syncEmulator immediately=" + immediately);
}
Modules.sceGe_userModule.step();
Modules.sceDisplayModule.step(immediately);
}
private static void syncFast() {
// Always sync the display to trigger the GE list processing
Modules.sceDisplayModule.step();
}
public static void sync() throws StopThreadException {
do {
wantSync = false;
if (!IntrManager.getInstance().canExecuteInterruptNow()) {
syncFast();
} else {
syncPause();
Emulator.getScheduler().step();
if (Interrupts.isInterruptsEnabled()) {
Modules.ThreadManForUserModule.hleRescheduleCurrentThread();
}
syncThread();
syncEmulator(false);
syncDebugger();
syncPause();
checkStoppedThread();
}
// Check if a new sync request has been received in the meantime
} while (wantSync);
}
public static void preSyscall() throws StopThreadException {
if (IntrManager.getInstance().canExecuteInterruptNow()) {
syscallRuntimeThread = getRuntimeThread();
if (syscallRuntimeThread != null) {
syscallRuntimeThread.setInSyscall(true);
}
checkStoppedThread();
syncPause();
}
}
public static void postSyscall() throws StopThreadException {
if (!IntrManager.getInstance().canExecuteInterruptNow()) {
postSyscallFast();
} else {
checkStoppedThread();
sync();
if (syscallRuntimeThread != null) {
syscallRuntimeThread.setInSyscall(false);
}
}
}
public static void postSyscallFast() {
syncFast();
}
public static void syscallFast(int code) throws Exception {
// Fast syscall: no context switching
SyscallHandler.syscall(code);
postSyscallFast();
}
public static void syscall(int code) throws Exception {
preSyscall();
SyscallHandler.syscall(code);
postSyscall();
}
private static void execWithReturnAddress(IExecutable executable, int returnAddress) throws Exception {
while (true) {
try {
int address = executable.exec();
if (address != returnAddress) {
jump(address, returnAddress);
}
break;
} catch (StackPopException e) {
log.info("Stack exceeded maximum size, shrinking to top level");
executable = getExecutable(e.getRa());
if (executable == null) {
throw e;
}
}
}
}
public static boolean executeFunction(int address) {
IExecutable executable = getExecutable(address);
int newPc = 0;
int returnAddress = cpu._ra;
boolean exception = false;
try {
execWithReturnAddress(executable, returnAddress);
newPc = returnAddress;
} catch (StopThreadException e) {
// Ignore exception
} catch (Exception e) {
log.error("Catched Throwable in executeCallback:", e);
exception = true;
}
cpu.pc = newPc;
cpu.npc = newPc; // npc is used when context switching
return exception;
}
public static void runThread(RuntimeThread thread) {
thread.setInSyscall(true);
if (isStoppedThread()) {
// This thread has already been stopped before it is really starting...
return;
}
thread.suspendRuntimeExecution();
if (isStoppedThread()) {
// This thread has already been stopped before it is really starting...
return;
}
ThreadManForUser threadMan = Modules.ThreadManForUserModule;
IExecutable executable = getExecutable(processor.cpu.pc);
thread.setInSyscall(false);
try {
updateStaticVariables();
// Execute any thread event handler for THREAD_EVENT_START
// in the thread context, before starting the thread execution.
threadMan.checkPendingCallbacks();
execWithReturnAddress(executable, ThreadManForUser.THREAD_EXIT_HANDLER_ADDRESS);
// NOTE: When a thread exits by itself (without calling sceKernelExitThread),
// it's exitStatus becomes it's return value.
threadMan.hleKernelExitThread(processor.cpu._v0);
} catch (StopThreadException e) {
// Ignore Exception
} catch (Throwable e) {
// Do not spam exceptions when exiting...
if (!Modules.ThreadManForUserModule.exitCalled) {
// Log error in log file and command box
log.error("Catched Throwable in RuntimeThread:", e);
e.printStackTrace();
}
}
SceKernelThreadInfo threadInfo = thread.getThreadInfo();
alreadyStoppedThreads.put(threadInfo, thread);
if (log.isDebugEnabled()) {
log.debug("End of Thread " + threadInfo.name + " - stopped");
}
// Tell stopAllThreads that this thread is stopped.
thread.setInSyscall(true);
threads.remove(threadInfo);
toBeStoppedThreads.remove(threadInfo);
toBeDeletedThreads.remove(threadInfo);
if (!reset) {
// Switch to the currently active thread
try {
if (log.isDebugEnabled()) {
log.debug("End of Thread " + threadInfo.name + " - sync");
}
// Be careful to not execute Interrupts or Callbacks by this thread,
// as it is already stopped and the next active thread
// will be resumed immediately.
syncIdle();
syncThreadImmediately();
} catch (StopThreadException e) {
}
}
alreadyStoppedThreads.remove(threadInfo);
alreadySwitchedStoppedThreads.remove(thread);
if (log.isDebugEnabled()) {
log.debug("End of Thread " + thread.getName());
}
synchronized (waitForEnd) {
waitForEnd.notify();
}
}
private static void computeCodeBlocksRange() {
codeBlocksLowestAddress = Integer.MAX_VALUE;
codeBlocksHighestAddress = Integer.MIN_VALUE;
for (CodeBlock codeBlock : codeBlocks.values()) {
if (!codeBlock.isInternal()) {
codeBlocksLowestAddress = Math.min(codeBlocksLowestAddress, codeBlock.getLowestAddress());
codeBlocksHighestAddress = Math.max(codeBlocksHighestAddress, codeBlock.getHighestAddress());
}
}
}
public static void addCodeBlock(int address, CodeBlock codeBlock) {
CodeBlock previousCodeBlock = codeBlocks.put(address, codeBlock);
if (!codeBlock.isInternal()) {
if (previousCodeBlock != null) {
// One code block has been deleted, recompute the whole code blocks range
computeCodeBlocksRange();
int fastExecutableLoopukIndex = (address - MemoryMap.START_USERSPACE) >> 2;
if (fastExecutableLoopukIndex >= 0 && fastExecutableLoopukIndex < fastExecutableLookup.length) {
fastExecutableLookup[fastExecutableLoopukIndex] = null;
}
} else {
// One new code block has been added, update the code blocks range
codeBlocksLowestAddress = Math.min(codeBlocksLowestAddress, codeBlock.getLowestAddress());
codeBlocksHighestAddress = Math.max(codeBlocksHighestAddress, codeBlock.getHighestAddress());
}
int startIndex = (codeBlock.getLowestAddress() - MemoryMap.START_USERSPACE) >> fastCodeBlockLookupShift;
int endIndex = (codeBlock.getHighestAddress() - MemoryMap.START_USERSPACE) >> fastCodeBlockLookupShift;
for (int i = startIndex; i <= endIndex; i++) {
if (i >= 0 && i < fastCodeBlockLookup.length) {
CodeBlockList codeBlockList = fastCodeBlockLookup[i];
if (codeBlockList != null) {
if (previousCodeBlock != null) {
codeBlockList.remove(previousCodeBlock);
}
int addr = (i << fastCodeBlockLookupShift) + MemoryMap.START_USERSPACE;
int size = 1 << fastCodeBlockLookupShift;
if (codeBlock.isOverlappingWithAddressRange(addr, size)) {
codeBlockList.add(codeBlock);
}
}
}
}
}
}
public static CodeBlock getCodeBlock(int address) {
return codeBlocks.get(address);
}
public static boolean hasCodeBlock(int address) {
return codeBlocks.containsKey(address);
}
public static Map<Integer, CodeBlock> getCodeBlocks() {
return codeBlocks;
}
public static IExecutable getExecutable(int address) {
// Check if we have already the executable in the fastExecutableLookup array
int fastExecutableLoopukIndex = (address - MemoryMap.START_USERSPACE) >> 2;
IExecutable executable;
if (fastExecutableLoopukIndex >= 0 && fastExecutableLoopukIndex < fastExecutableLookup.length) {
executable = fastExecutableLookup[fastExecutableLoopukIndex];
} else {
executable = null;
}
if (executable == null) {
CodeBlock codeBlock = getCodeBlock(address);
if (codeBlock == null) {
executable = Compiler.getInstance().compile(address);
} else {
executable = codeBlock.getExecutable();
}
// Store the executable in the fastExecutableLookup array
if (fastExecutableLoopukIndex >= 0 && fastExecutableLoopukIndex < fastExecutableLookup.length) {
fastExecutableLookup[fastExecutableLoopukIndex] = executable;
}
}
return executable;
}
public static void start() {
Settings.getInstance().registerSettingsListener("RuntimeContext", "emu.compiler", new CompilerEnabledSettingsListerner());
}
public static void run() {
if (Modules.ThreadManForUserModule.exitCalled) {
return;
}
if (!initialise()) {
compilerEnabled = false;
return;
}
log.info("Using Compiler");
while (!toBeStoppedThreads.isEmpty()) {
wakeupToBeStoppedThreads();
sleep(idleSleepMicros);
}
reset = false;
if (currentRuntimeThread == null) {
try {
syncIdle();
} catch (StopThreadException e) {
// Thread is stopped, return immediately
return;
}
if (currentRuntimeThread == null) {
log.error("RuntimeContext.run: nothing to run!");
Emulator.PauseEmuWithStatus(Emulator.EMU_STATUS_UNKNOWN);
return;
}
}
update();
if (processor.cpu.pc == 0) {
Emulator.PauseEmuWithStatus(Emulator.EMU_STATUS_UNKNOWN);
return;
}
currentRuntimeThread.continueRuntimeExecution();
while (!threads.isEmpty() && !reset) {
synchronized(waitForEnd) {
try {
waitForEnd.wait();
} catch (InterruptedException e) {
}
}
}
log.debug("End of run");
}
private static List<RuntimeThread> wakeupToBeStoppedThreads() {
List<RuntimeThread> threadList = new LinkedList<RuntimeThread>();
synchronized (toBeStoppedThreads) {
for (Entry<SceKernelThreadInfo, RuntimeThread> entry : toBeStoppedThreads.entrySet()) {
threadList.add(entry.getValue());
}
}
// Trigger the threads to start execution again.
// Loop on a local list to avoid concurrent modification on toBeStoppedThreads.
for (RuntimeThread runtimeThread : threadList) {
Thread.State threadState = runtimeThread.getState();
log.debug("Thread " + runtimeThread.getName() + ", State=" + threadState);
if (threadState == Thread.State.TERMINATED) {
toBeStoppedThreads.remove(runtimeThread.getThreadInfo());
} else if (threadState == Thread.State.WAITING) {
runtimeThread.continueRuntimeExecution();
}
}
synchronized (Emulator.getInstance()) {
Emulator.getInstance().notifyAll();
}
return threadList;
}
public static void onThreadDeleted(SceKernelThreadInfo thread) {
RuntimeThread runtimeThread = threads.get(thread);
if (runtimeThread != null) {
if (log.isDebugEnabled()) {
log.debug("Deleting Thread " + thread.toString());
}
toBeStoppedThreads.put(thread, runtimeThread);
if (runtimeThread.isInSyscall() && Thread.currentThread() != runtimeThread) {
toBeDeletedThreads.put(thread, runtimeThread);
log.debug("Continue Thread " + runtimeThread.getName());
runtimeThread.continueRuntimeExecution();
}
}
}
public static void onThreadExit(SceKernelThreadInfo thread) {
RuntimeThread runtimeThread = threads.get(thread);
if (runtimeThread != null) {
if (log.isDebugEnabled()) {
log.debug("Exiting Thread " + thread.toString());
}
toBeStoppedThreads.put(thread, runtimeThread);
threads.remove(thread);
}
}
public static void onThreadStart(SceKernelThreadInfo thread) {
// The thread is starting, if a stop was still pending, cancel the stop.
toBeStoppedThreads.remove(thread);
toBeDeletedThreads.remove(thread);
}
private static void stopAllThreads() {
synchronized (threads) {
toBeStoppedThreads.putAll(threads);
threads.clear();
}
List<RuntimeThread> threadList = wakeupToBeStoppedThreads();
// Wait for all threads to enter a syscall.
// When a syscall is entered, the thread will exit
// automatically by calling checkStoppedThread()
boolean waitForThreads = true;
while (waitForThreads) {
waitForThreads = false;
for (RuntimeThread runtimeThread : threadList) {
if (!runtimeThread.isInSyscall()) {
waitForThreads = true;
break;
}
}
if (waitForThreads) {
sleep(idleSleepMicros);
}
}
}
public static void exit() {
if (compilerEnabled) {
log.debug("RuntimeContext.exit");
stopAllThreads();
if (DurationStatistics.collectStatistics) {
log.info(idleDuration);
}
if (enableInstructionTypeCounting) {
long totalCount = 0;
for (Instruction insn : instructionTypeCounts.keySet()) {
int count = instructionTypeCounts.get(insn);
totalCount += count;
}
while (!instructionTypeCounts.isEmpty()) {
Instruction highestCountInsn = null;
int highestCount = -1;
for (Instruction insn : instructionTypeCounts.keySet()) {
int count = instructionTypeCounts.get(insn);
if (count > highestCount) {
highestCount = count;
highestCountInsn = insn;
}
}
instructionTypeCounts.remove(highestCountInsn);
log.info(String.format(" %10s %s %d (%2.2f%%)", highestCountInsn.name(), (highestCountInsn.hasFlags(Instruction.FLAG_INTERPRETED) ? "I" : "C"), highestCount, highestCount * 100.0 / totalCount));
}
}
}
}
public static void reset() {
if (compilerEnabled) {
log.debug("RuntimeContext.reset");
Compiler.getInstance().reset();
codeBlocks.clear();
if (fastExecutableLookup != null) {
Arrays.fill(fastExecutableLookup, null);
}
if (fastCodeBlockLookup != null) {
Arrays.fill(fastCodeBlockLookup, null);
}
currentThread = null;
currentRuntimeThread = null;
reset = true;
stopAllThreads();
synchronized (waitForEnd) {
waitForEnd.notify();
}
}
}
public static void invalidateAll() {
if (compilerEnabled) {
if (invalidateAllCodeBlocks) {
// Simple method: invalidate all the code blocks,
// independently if their were modified or not.
log.debug("RuntimeContext.invalidateAll simple");
codeBlocks.clear();
Arrays.fill(fastExecutableLookup, null);
Arrays.fill(fastCodeBlockLookup, null);
Compiler.getInstance().invalidateAll();
} else {
// Advanced method: check all the code blocks for a modification
// of their opcodes and invalidate only those code blocks that
// have been modified.
log.debug("RuntimeContext.invalidateAll advanced");
Compiler compiler = Compiler.getInstance();
for (CodeBlock codeBlock : codeBlocks.values()) {
if (log.isDebugEnabled()) {
log.debug(String.format("invalidateAll %s: opcodes changed %b", codeBlock, codeBlock.areOpcodesChanged()));
}
if (codeBlock.areOpcodesChanged()) {
compiler.invalidateCodeBlock(codeBlock);
}
}
}
}
}
private static void invalidateRangeFullCheck(int addr, int size) {
Compiler compiler = Compiler.getInstance();
for (CodeBlock codeBlock : codeBlocks.values()) {
if (size == 0x4000 && codeBlock.getHighestAddress() >= addr) {
// Some applications do not clear more than 16KB as this is the size of the complete Instruction Cache.
// Be conservative in this case and check any code block above the given address.
compiler.checkCodeBlockValidity(codeBlock);
} else if (codeBlock.isOverlappingWithAddressRange(addr, size)) {
compiler.checkCodeBlockValidity(codeBlock);
}
}
}
private static CodeBlockList fillFastCodeBlockList(int index) {
int startAddr = (index << fastCodeBlockLookupShift) + MemoryMap.START_USERSPACE;
int size = 1 << fastCodeBlockLookupShift;
if (log.isDebugEnabled()) {
log.debug(String.format("Creating new fastCodeBlockList for 0x%08X", startAddr));
}
CodeBlockList codeBlockList = new CodeBlockList();
for (CodeBlock codeBlock : codeBlocks.values()) {
if (codeBlock.isOverlappingWithAddressRange(startAddr, size)) {
codeBlockList.add(codeBlock);
}
}
fastCodeBlockLookup[index] = codeBlockList;
return codeBlockList;
}
public static void invalidateRange(int addr, int size) {
if (compilerEnabled) {
addr &= Memory.addressMask;
if (log.isDebugEnabled()) {
log.debug(String.format("RuntimeContext.invalidateRange(addr=0x%08X, size=%d)", addr, size));
}
// Fast check: if the address range is outside the largest code blocks range,
// there is noting to do.
if (addr + size < codeBlocksLowestAddress || addr > codeBlocksHighestAddress) {
return;
}
// Check if the code blocks located in the given range have to be invalidated
if (size == fastCodeBlockSize) {
// This is a fast track to avoid checking all the code blocks
int startIndex = (addr - MemoryMap.START_USERSPACE) >> fastCodeBlockLookupShift;
int endIndex = (addr + size - MemoryMap.START_USERSPACE) >> fastCodeBlockLookupShift;
if (startIndex >= 0 && endIndex <= fastCodeBlockLookup.length) {
for (int index = startIndex; index <= endIndex; index++) {
CodeBlockList codeBlockList = fastCodeBlockLookup[index];
if (codeBlockList == null) {
codeBlockList = fillFastCodeBlockList(index);
} else {
if (log.isDebugEnabled()) {
log.debug(String.format("Reusing fastCodeBlockList for 0x%08X (size=%d)", addr, codeBlockList.size()));
}
}
Compiler compiler = Compiler.getInstance();
for (CodeBlock codeBlock : codeBlockList) {
if (codeBlock.isOverlappingWithAddressRange(addr, size)) {
compiler.checkCodeBlockValidity(codeBlock);
}
}
}
} else {
invalidateRangeFullCheck(addr, size);
}
} else {
invalidateRangeFullCheck(addr, size);
}
}
}
public static void instructionTypeCount(Instruction insn, int opcode) {
int count = 0;
if (instructionTypeCounts.containsKey(insn)) {
count = instructionTypeCounts.get(insn);
}
count++;
instructionTypeCounts.put(insn, count);
}
public static void pauseEmuWithStatus(int status) throws StopThreadException {
Emulator.PauseEmuWithStatus(status);
syncPause();
}
public static void logInfo(String message) {
log.info(message);
}
public static boolean checkMemoryPointer(int address) {
if (!Memory.isAddressGood(address)) {
if (!Memory.isRawAddressGood(Memory.normalizeAddress(address))) {
return false;
}
}
return true;
}
public static String readStringNZ(int address, int maxLength) {
return Utilities.readStringNZ(address, maxLength);
}
public static PspString readPspStringNZ(int address, int maxLength, boolean canBeNull) {
return new PspString(address, maxLength, canBeNull);
}
public static int checkMemoryRead32(int address, int pc) throws StopThreadException {
int rawAddress = address & Memory.addressMask;
if (!Memory.isRawAddressGood(rawAddress)) {
if (memory.read32AllowedInvalidAddress(rawAddress)) {
rawAddress = 0;
} else {
int normalizedAddress = Memory.normalizeAddress(address);
if (Memory.isRawAddressGood(normalizedAddress)) {
rawAddress = normalizedAddress;
} else {
processor.cpu.pc = pc;
memory.invalidMemoryAddress(address, "read32", Emulator.EMU_STATUS_MEM_READ);
syncPause();
rawAddress = 0;
}
}
}
return rawAddress;
}
public static int checkMemoryRead16(int address, int pc) throws StopThreadException {
int rawAddress = address & Memory.addressMask;
if (!Memory.isRawAddressGood(rawAddress)) {
int normalizedAddress = Memory.normalizeAddress(address);
if (Memory.isRawAddressGood(normalizedAddress)) {
rawAddress = normalizedAddress;
} else {
processor.cpu.pc = pc;
memory.invalidMemoryAddress(address, "read16", Emulator.EMU_STATUS_MEM_READ);
syncPause();
rawAddress = 0;
}
}
return rawAddress;
}
public static int checkMemoryRead8(int address, int pc) throws StopThreadException {
int rawAddress = address & Memory.addressMask;
if (!Memory.isRawAddressGood(rawAddress)) {
int normalizedAddress = Memory.normalizeAddress(address);
if (Memory.isRawAddressGood(normalizedAddress)) {
rawAddress = normalizedAddress;
} else {
processor.cpu.pc = pc;
memory.invalidMemoryAddress(address, "read8", Emulator.EMU_STATUS_MEM_READ);
syncPause();
rawAddress = 0;
}
}
return rawAddress;
}
public static int checkMemoryWrite32(int address, int pc) throws StopThreadException {
int rawAddress = address & Memory.addressMask;
if (!Memory.isRawAddressGood(rawAddress)) {
int normalizedAddress = Memory.normalizeAddress(address);
if (Memory.isRawAddressGood(normalizedAddress)) {
rawAddress = normalizedAddress;
} else {
processor.cpu.pc = pc;
memory.invalidMemoryAddress(address, "write32", Emulator.EMU_STATUS_MEM_WRITE);
syncPause();
rawAddress = 0;
}
}
sceDisplayModule.write32(rawAddress);
return rawAddress;
}
public static int checkMemoryWrite16(int address, int pc) throws StopThreadException {
int rawAddress = address & Memory.addressMask;
if (!Memory.isRawAddressGood(rawAddress)) {
int normalizedAddress = Memory.normalizeAddress(address);
if (Memory.isRawAddressGood(normalizedAddress)) {
rawAddress = normalizedAddress;
} else {
processor.cpu.pc = pc;
memory.invalidMemoryAddress(address, "write16", Emulator.EMU_STATUS_MEM_WRITE);
syncPause();
rawAddress = 0;
}
}
sceDisplayModule.write16(rawAddress);
return rawAddress;
}
public static int checkMemoryWrite8(int address, int pc) throws StopThreadException {
int rawAddress = address & Memory.addressMask;
if (!Memory.isRawAddressGood(rawAddress)) {
int normalizedAddress = Memory.normalizeAddress(address);
if (Memory.isRawAddressGood(normalizedAddress)) {
rawAddress = normalizedAddress;
} else {
processor.cpu.pc = pc;
memory.invalidMemoryAddress(address, "write8", Emulator.EMU_STATUS_MEM_WRITE);
syncPause();
rawAddress = 0;
}
}
sceDisplayModule.write8(rawAddress);
return rawAddress;
}
public static void debugMemoryReadWrite(int address, int value, int pc, boolean isRead, int width) {
if (log.isTraceEnabled()) {
StringBuilder message = new StringBuilder();
message.append(String.format("0x%08X - ", pc));
if (isRead) {
message.append(String.format("read%d(0x%08X)=0x", width, address));
if (width == 8) {
message.append(String.format("%02X", memory.read8(address)));
} else if (width == 16) {
message.append(String.format("%04X", memory.read16(address)));
} else if (width == 32) {
message.append(String.format("%08X (%f)", memory.read32(address), Float.intBitsToFloat(memory.read32(address))));
}
} else {
message.append(String.format("write%d(0x%08X, 0x", width, address));
if (width == 8) {
message.append(String.format("%02X", value));
} else if (width == 16) {
message.append(String.format("%04X", value));
} else if (width == 32) {
message.append(String.format("%08X (%f)", value, Float.intBitsToFloat(value)));
}
message.append(")");
}
log.trace(message.toString());
}
}
public static void onNextScheduleModified() {
checkSync(false);
// Notify the thread waiting on the idleSyncObject that
// the scheduler has now received a new schedule.
synchronized (idleSyncObject) {
idleSyncObject.notifyAll();
}
}
private static void checkSync(boolean sleep) {
long delay = Emulator.getScheduler().getNextActionDelay(idleSleepMicros);
if (delay > 0) {
if (sleep) {
int intDelay = (int) delay;
sleep(intDelay / 1000, intDelay % 1000);
}
} else if (wantSync) {
if (sleep) {
sleep(idleSleepMicros);
}
} else {
wantSync = true;
}
}
public static boolean syncDaemonStep() {
checkSync(true);
return enableDaemonThreadSync;
}
public static void exitSyncDaemon() {
runtimeSyncThread = null;
}
public static void setIsHomebrew(boolean isHomebrew) {
RuntimeContext.isHomebrew = isHomebrew;
}
public static boolean isHomebrew() {
return isHomebrew;
}
public static void onCodeModification(int pc, int opcode) {
cpu.pc = pc;
log.error(String.format("Code instruction at 0x%08X has been modified, expected 0x%08X, current 0x%08X", pc, opcode, memory.read32(pc)));
Emulator.PauseEmuWithStatus(Emulator.EMU_STATUS_MEM_WRITE);
}
public static void debugMemory(int address, int length) {
if (memory instanceof DebuggerMemory) {
DebuggerMemory debuggerMemory = (DebuggerMemory) memory;
debuggerMemory.addRangeReadWriteBreakpoint(address, address + length - 1);
}
}
public static void debugCodeBlock(int address, int numberOfArguments) {
if (debugCodeBlockCalls) {
debugCodeBlocks.put(address, numberOfArguments);
}
}
public static void setFirmwareVersion(int firmwareVersion) {
RuntimeContext.firmwareVersion = firmwareVersion;
}
public static boolean hasMemoryInt() {
return memoryInt != null;
}
public static int[] getMemoryInt() {
return memoryInt;
}
}