emulators/springcoat-vm/src/main/java/cc/squirreljme/vm/springcoat/MLEThread.java
// -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
// ---------------------------------------------------------------------------
// SquirrelJME
// Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
// ---------------------------------------------------------------------------
// SquirrelJME is under the Mozilla Public License Version 2.0.
// See license.mkd for licensing and copyright information.
// ---------------------------------------------------------------------------
package cc.squirreljme.vm.springcoat;
import cc.squirreljme.emulator.profiler.ProfiledFrame;
import cc.squirreljme.jdwp.host.JDWPHostController;
import cc.squirreljme.jdwp.host.trips.JDWPGlobalTrip;
import cc.squirreljme.jdwp.host.trips.JDWPTripThread;
import cc.squirreljme.jdwp.host.trips.JDWPTripVmState;
import cc.squirreljme.jvm.mle.ThreadShelf;
import cc.squirreljme.jvm.mle.brackets.TracePointBracket;
import cc.squirreljme.jvm.mle.brackets.VMThreadBracket;
import cc.squirreljme.jvm.mle.constants.ThreadModelType;
import cc.squirreljme.jvm.mle.constants.ThreadStatusType;
import cc.squirreljme.jvm.mle.constants.VerboseDebugFlag;
import cc.squirreljme.runtime.cldc.debug.CallTraceElement;
import cc.squirreljme.vm.springcoat.brackets.VMThreadObject;
import cc.squirreljme.vm.springcoat.exceptions.SpringMLECallError;
import net.multiphasicapps.classfile.ClassName;
import net.multiphasicapps.classfile.MethodNameAndType;
/**
* Functions for {@link ThreadShelf}.
*
* @since 2020/06/18
*/
public enum MLEThread
implements MLEFunction
{
/** {@link ThreadShelf#aliveThreadCount(boolean, boolean)}. */
ALIVE_THREAD_COUNT("aliveThreadCount:(ZZ)I")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
boolean includeMain = ((int)__args[0] != 0);
boolean includeDaemon = ((int)__args[1] != 0);
// Count every thread
int count = 0;
SpringMachine machine = __thread.machine;
synchronized (machine)
{
for (SpringThread thread : machine.getThreads())
{
// Ignore any threads that are marked terminated or has not
// been started as it has no frames
if (thread.isTerminated() || thread.numFrames() == 0)
continue;
boolean isMain = thread.isMain();
boolean isDaemon = thread.isDaemon();
if ((includeMain && isMain) ||
(includeDaemon && isDaemon) ||
(!isMain && !isDaemon))
count++;
}
}
return count;
}
},
/** {@link ThreadShelf#createVMThread(Thread, String)}. */
CREATE_VM_THREAD( "createVMThread:(Ljava/lang/Thread;" +
"Ljava/lang/String;)Lcc/" +
"squirreljme/jvm/mle/brackets/VMThreadBracket;")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
SpringSimpleObject javaThread = MLEThread.__javaThread(__thread,
__args[0]);
String name = (__args[1] == null ||
__args[1] == SpringNullObject.NULL ? null :
__thread.<String>asNativeObject(String.class, __args[1]));
// Find the thread which the given passed object is bound to, this
// is the target thread
SpringThread target = null;
SpringMachine machine = __thread.machine;
synchronized (machine)
{
// Search through every thread
SpringThread[] threads = machine.getThreads();
for (SpringThread thread : threads)
{
SpringObject instance;
try
{
instance = thread.threadInstance();
}
catch (IllegalStateException ignored)
{
continue;
}
// If this is the thread for this, then use that!
if (javaThread == instance)
{
target = thread;
break;
}
}
// If there is exactly one thread, we can rather get into a bit
// of a loop where our main thread is created outside of normal
// means by the VM and not by any other thread.. but only if
// this initial thread has no actual instance
if (threads.length == 1 && !threads[0].hasThreadInstance())
target = threads[0];
// No actual thread exists that the object is bound to, so
// oops! We need to actually create one here and bind it
// accordingly!
if (target == null)
target = machine.createThread(name,
false, false);
}
// New thread?
if (__thread.verboseCheck(VerboseDebugFlag.THREAD_NEW))
__thread.verboseEmit("New Thread: %s", target);
// Inherit verbose flags for this new thread?
if (__thread.verboseCheck(VerboseDebugFlag.INHERIT_VERBOSE_FLAGS))
target._initVerboseFlags = __thread.verbose().activeFlags();
// Create object with this attached thread
VMThreadObject vmThread = new VMThreadObject(machine, target);
// The thread gets these as well
target.setThreadInstance(javaThread);
target.setVMThread(vmThread);
// If we are debugging, we are going to need to tell the debugger
// some important details
JDWPHostController jdwp = target.machine()
.taskManager().jdwpController;
if (jdwp != null)
{
// If we are debugging, we need to tell the debugger that the
// virtual machine actually started
if (target.machine().rootVm && target.isMain())
jdwp.<JDWPTripVmState>trip(JDWPTripVmState.class,
JDWPGlobalTrip.VM_STATE).alive(target, true);
// If we are debugging, signal that this thread is in the start
// state. We need the instance to have been set for this to
// even properly work!
jdwp.<JDWPTripThread>trip(JDWPTripThread.class,
JDWPGlobalTrip.THREAD).alive(target, true);
}
return vmThread;
}
},
/** {@link ThreadShelf#currentExitCode()}. */
CURRENT_EXIT_CODE("currentExitCode:()I")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return __thread.machine.getExitCode();
}
},
/** {@link ThreadShelf#currentJavaThread()}. */
CURRENT_JAVA_THREAD("currentJavaThread:()Ljava/lang/Thread;")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return __thread.thread.threadInstance();
}
},
/** {@link ThreadShelf#currentVMThread()}. */
CURRENT_VM_THREAD("currentVMThread:" +
"()Lcc/squirreljme/jvm/mle/brackets/VMThreadBracket;")
{
/**
* {@inheritDoc}
* @since 2021/05/08
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return new VMThreadObject(__thread.machine, __thread.thread);
}
},
/** {@link ThreadShelf#equals(VMThreadBracket, VMThreadBracket)}. */
EQUALS("equals:(Lcc/squirreljme/jvm/mle/brackets/VMThreadBracket;" +
"Lcc/squirreljme/jvm/mle/brackets/VMThreadBracket;)Z")
{
/**
* {@inheritDoc}
* @since 2021/05/08
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return MLEThread.__vmThread(__args[0]).getThread() ==
MLEThread.__vmThread(__args[1]).getThread();
}
},
/** {@link ThreadShelf#javaThreadClearInterrupt(Thread)}. */
JAVA_THREAD_CLEAR_INTERRUPT("javaThreadClearInterrupt:" +
"(Ljava/lang/Thread;)Z")
{
/**
* {@inheritDoc}
* @since 2020/06/28
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
SpringFieldStorage field = MLEThread.__javaThread(__thread,
__args[0]).fieldByNameAndType(false,
"_interrupted", "Z");
// Get and clear the field value
Object old = field.get();
field.set(false);
return old;
}
},
/** {@link ThreadShelf#javaThreadFlagStarted(Thread)}. */
JAVA_THREAD_FLAG_STARTED("javaThreadFlagStarted:(Ljava/lang/" +
"Thread;)V")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
// Just set the started field to true
MLEThread.__javaThread(__thread, __args[0]).fieldByNameAndType(
false, "_started", "Z").set(true);
return null;
}
},
/** {@link ThreadShelf#javaThreadIsStarted(Thread)}. */
JAVA_THREAD_IS_STARTED("javaThreadIsStarted:(Ljava/lang/Thread;)Z")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
// Just get the state of the given field
return MLEThread.__javaThread(__thread, __args[0])
.fieldByNameAndType(false,
"_started", "Z").get();
}
},
/** {@link ThreadShelf#javaThreadRunnable(Thread)}. */
JAVA_THREAD_RUNNABLE("javaThreadRunnable:(Ljava/lang/Thread;)" +
"Ljava/lang/Runnable;")
{
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
// Just get the state of the given field
return MLEThread.__javaThread(__thread, __args[0])
.fieldByNameAndType(
false, "_runnable",
"Ljava/lang/Runnable;").get();
}
},
/** {@link ThreadShelf#javaThreadSetAlive(Thread, boolean)}. */
JAVA_THREAD_SET_ALIVE("javaThreadSetAlive:(Ljava/lang/Thread;Z)V")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
// Just set the started field to true
MLEThread.__javaThread(__thread, __args[0]).fieldByNameAndType(
false, "_isAlive", "Z")
.set((int)__args[1] != 0);
return null;
}
},
/** {@link ThreadShelf#javaThreadSetDaemon(Thread)}. */
JAVA_THREAD_SET_DAEMON("javaThreadSetDaemon:(Ljava/lang/Thread;)V")
{
/**
* {@inheritDoc}
* @since 2020/09/12
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
SpringThread vmThread = MLEThread.__vmThread(
MLEThread.TO_VM_THREAD.handle(__thread, __args[0]))
.getThread();
synchronized (vmThread)
{
// Cannot be changed once started
if (vmThread.isTerminated() || vmThread.numFrames() > 0)
throw new SpringMLECallError("Thread is started.");
// Set as a daemon thread
vmThread.setDaemon();
}
// No value is returned
return null;
}
},
/** {@link ThreadShelf#model()}. */
MODEL("model:()I")
{
/**
* {@inheritDoc}
* @since 2021/05/07
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
// SpringCoat is always multi-threaded
return ThreadModelType.SIMULTANEOUS_MULTI_THREAD;
}
},
/** {@link ThreadShelf#runProcessMain()}. */
RUN_PROCESS_MAIN("runProcessMain:()V")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
__thread.runProcessMain();
return null;
}
},
/** {@link ThreadShelf#setCurrentExitCode(int)}. */
SET_CURRENT_EXIT_CODE("setCurrentExitCode:(I)V")
{
/**
* {@inheritDoc}
* @since 2020/06/27
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
int exitCode = (int)__args[0];
__thread.machine.setExitCode(exitCode);
return null;
}
},
/** {@link ThreadShelf#setTrace(String, TracePointBracket[])}. */
SET_TRACE("setTrace:(Ljava/lang/String;[Lcc/squirreljme/" +
"jvm/mle/brackets/TracePointBracket;)V")
{
/**
* {@inheritDoc}
* @since 2020/07/06
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
if (!(__args[1] instanceof SpringArrayObjectGeneric))
throw new SpringMLECallError("Wrong trace array type.");
SpringObject[] gen = ((SpringArrayObjectGeneric)__args[1]).array();
// Get the message used
String message = __thread.<String>asNativeObject(String.class,
__args[0]);
if (message == null)
throw new SpringMLECallError("No message set.");
// Map trace points to the call trace for future get
int n = gen.length;
CallTraceElement[] trace = new CallTraceElement[n];
for (int i = 0; i < n; i++)
trace[i] = MLEDebug.__trace(gen[i]).getTrace();
// Store the call trace for other tasks to get
__thread.machine.storeTrace(message, trace);
return null;
}
},
/** {@link ThreadShelf#sleep(int, int)}. */
SLEEP("sleep:(II)Z")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
int ms = (int)__args[0];
int ns = (int)__args[1];
if (ms < 0 || ns < 0 || ns > 1000000000)
throw new SpringMLECallError("Out of range time.");
// Get the profiler information
SpringThreadFrame currentFrame = __thread.thread.currentFrame();
ProfiledFrame profiler = (currentFrame == null ? null :
currentFrame._profiler);
// We need to restore profiler states
boolean interrupted = false;
try
{
// Indicate that we are in sleep mode
__thread.thread.setStatus(ThreadStatusType.SLEEPING);
// Stop counting CPU time for this
if (profiler != null)
profiler.sleep(true, System.nanoTime());
// Just giving up CPU time?
if (ms == 0 && ns == 0)
Thread.yield();
// Normal sleep
else
try
{
Thread.sleep(ms, ns);
}
catch (InterruptedException ignored)
{
interrupted = true;
}
}
finally
{
// We have left sleep mode
__thread.thread.setStatus(ThreadStatusType.RUNNING);
// Continue counting CPU time
if (profiler != null)
profiler.sleep(false, System.nanoTime());
}
return interrupted;
}
},
/** {@link ThreadShelf#toJavaThread(VMThreadBracket)}. */
TO_JAVA_THREAD("toJavaThread:(Lcc/squirreljme/jvm/mle/" +
"brackets/VMThreadBracket;)Ljava/lang/Thread;")
{
/**
* {@inheritDoc}
* @since 2020/06/29
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
VMThreadObject vmThread = MLEThread.__vmThread(__args[0]);
return vmThread.getThread().threadInstance();
}
},
/** {@link ThreadShelf#toVMThread(Thread)}. */
TO_VM_THREAD("toVMThread:(Ljava/lang/Thread;)Lcc/squirreljme/" +
"jvm/mle/brackets/VMThreadBracket;")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return MLEThread.__javaThread(__thread, __args[0]).fieldByField(
__thread.resolveClass("java/lang/Thread")
.lookupField(false, "_vmThread",
"Lcc/squirreljme/jvm/mle/brackets/VMThreadBracket;"))
.get();
}
},
/** {@link ThreadShelf#vmThreadEnd(VMThreadBracket)}. */
VM_THREAD_END("vmThreadEnd:(Lcc/squirreljme/jvm/mle/brackets/" +
"VMThreadBracket;)V")
{
/**
* {@inheritDoc}
* @since 2021/03/14
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
SpringThread thread = MLEThread.__vmThread(__args[0]).getThread();
// If debugging, signal that the thread has ended
JDWPHostController jdwp = thread.machine()
.taskManager().jdwpController;
if (jdwp != null)
jdwp.<JDWPTripThread>trip(JDWPTripThread.class,
JDWPGlobalTrip.THREAD).alive(thread, true);
return null;
}
},
/** {@link ThreadShelf#vmThreadId(VMThreadBracket)}. */
VM_THREAD_ID("vmThreadId:(Lcc/squirreljme/jvm/mle/brackets/" +
"VMThreadBracket;)I")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return MLEThread.__vmThread(__args[0]).getThread().id;
}
},
/** {@link ThreadShelf#vmThreadInterrupt(VMThreadBracket)}. */
VM_THREAD_INTERRUPT("vmThreadInterrupt:(Lcc/squirreljme/jvm/mle/" +
"brackets/VMThreadBracket;)V")
{
/**
* {@inheritDoc}
* @since 2020/06/22
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
VMThreadObject vmThread = MLEThread.__vmThread(__args[0]);
// Send an interrupt to the thread
vmThread.getThread().hardInterrupt();
return null;
}
},
/** {@link ThreadShelf#vmThreadIsMain(VMThreadBracket)}. */
VM_THREAD_IS_MAIN("vmThreadIsMain:(Lcc/squirreljme/jvm/mle/" +
"brackets/VMThreadBracket;)Z")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return MLEThread.__vmThread(__args[0]).getThread().isMain();
}
},
/** {@link ThreadShelf#vmThreadSetPriority(VMThreadBracket, int)}. */
VM_THREAD_SET_PRIORITY("vmThreadSetPriority:(Lcc/squirreljme/" +
"jvm/mle/brackets/VMThreadBracket;I)V")
{
/**
* {@inheritDoc}
* @since 2020/06/29
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
SpringThread thread = MLEThread.__vmThread(__args[0]).getThread();
int priority = (int)__args[1];
if (priority < Thread.MIN_PRIORITY ||
priority > Thread.MAX_PRIORITY)
throw new SpringMLECallError(
"Thread priority out of range.");
// Try to set the priority
try
{
if (thread._worker == null)
thread._initPriority = priority;
else
thread._worker.setPriority(priority);
}
catch (IllegalArgumentException|SecurityException e)
{
throw new SpringMLECallError(
"Could not set priority.", e);
}
return null;
}
},
/** {@link ThreadShelf#vmThreadStart(VMThreadBracket)}. */
VM_THREAD_START("vmThreadStart:(Lcc/squirreljme/jvm/mle/brackets/" +
"VMThreadBracket;)Z")
{
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
SpringThread target = MLEThread.__vmThread(__args[0]).getThread();
// Create worker for thread and start it
SpringThreadWorker worker = new SpringThreadWorker(
__thread.machine, target, false);
// Inherited verbose flags for this new thread?
if (target._initVerboseFlags != 0)
worker.verbose().add(0, target._initVerboseFlags);
// Enter the base setup frame
target.enterFrame(worker.loadClass(MLEThread._START_CLASS)
.lookupMethod(true, MLEThread._BASE_THREAD_METHOD));
// Try to start it
try
{
worker.start();
return true;
}
catch (IllegalThreadStateException ignored)
{
return false;
}
}
},
/** {@link ThreadShelf#vmThreadTask(VMThreadBracket)}. */
VM_THREAD_TASK("vmThreadTask:(Lcc/squirreljme/jvm/mle/brackets/" +
"VMThreadBracket;)Lcc/squirreljme/jvm/mle/brackets/TaskBracket;")
{
/**
* {@inheritDoc}
* @since 2021/05/08
*/
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
return MLEThread.__vmThread(__args[0]).getThread()
.machine().taskObject(__thread.machine);
}
},
/** {@link ThreadShelf#waitForUpdate(int)}. */
WAIT_FOR_UPDATE("waitForUpdate:(I)Z")
{
/**
* {@inheritDoc}
* @since 2020/06/29
*/
@SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter")
@Override
public Object handle(SpringThreadWorker __thread, Object... __args)
{
int ms = (int)__args[0];
if (ms < 0)
throw new SpringMLECallError("Negative milliseconds");
// Waiting for nothing? just give up our slice
if (ms == 0)
{
Thread.yield();
return false;
}
// Wait until the monitor is hit
SpringMachine machine = __thread.machine;
synchronized (machine)
{
try
{
machine.wait(ms);
}
catch (InterruptedException e)
{
return true;
}
}
// Assume not interrupted
return false;
}
},
/* End. */
;
/** The class which contains the thread starting point. */
static final ClassName _START_CLASS =
new ClassName("java/lang/__Start__");
/** The method to enter for main threads. */
static final MethodNameAndType _BASE_THREAD_METHOD =
new MethodNameAndType("__base", "()V");
/** The dispatch key. */
protected final String key;
/**
* Initializes the dispatcher info.
*
* @param __key The key.
* @throws NullPointerException On null arguments.
* @since 2020/06/18
*/
MLEThread(String __key)
throws NullPointerException
{
if (__key == null)
throw new NullPointerException("NARG");
this.key = __key;
}
/**
* {@inheritDoc}
* @since 2020/06/18
*/
@Override
public String key()
{
return this.key;
}
/**
* Checks if this is a Java thread.
*
* @param __thread The context thread.
* @param __object The object to check.
* @return The verified object.
* @throws SpringMLECallError If {@code __object} is {@code null} or is
* not an instance of {@link Throwable}.
* @since 2020/06/28
*/
static SpringSimpleObject __javaThread(SpringThreadWorker __thread,
Object __object)
throws SpringMLECallError
{
if (__thread == null)
throw new NullPointerException("NARG");
if (!(__object instanceof SpringSimpleObject))
throw new SpringMLECallError("Not a Java Thread");
SpringSimpleObject rv = (SpringSimpleObject)__object;
if (!__thread.resolveClass("java/lang/Thread")
.isAssignableFrom(rv.type()))
throw new SpringMLECallError("Not instance of Thread.");
return rv;
}
/**
* Ensures that this is a {@link VMThreadObject}.
*
* @param __object The object to check.
* @return As a {@link VMThreadObject}.
* @throws SpringMLECallError If this is not one.
* @since 2020/06/27
*/
static VMThreadObject __vmThread(Object __object)
throws SpringMLECallError
{
if (!(__object instanceof VMThreadObject))
throw new SpringMLECallError("Not a VMThreadObject.");
return (VMThreadObject)__object;
}
}