SquirrelJME/SquirrelJME

View on GitHub
emulators/springcoat-vm/src/main/java/cc/squirreljme/vm/springcoat/SpringMachine.java

Summary

Maintainability
A
0 mins
Test Coverage
// -*- 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.ProfilerSnapshot;
import cc.squirreljme.emulator.terminal.TerminalPipeManager;
import cc.squirreljme.emulator.vm.VMResourceAccess;
import cc.squirreljme.emulator.vm.VMSuiteManager;
import cc.squirreljme.emulator.vm.VirtualMachine;
import cc.squirreljme.runtime.cldc.debug.CallTraceElement;
import cc.squirreljme.runtime.cldc.debug.Debugging;
import cc.squirreljme.vm.springcoat.brackets.TaskObject;
import cc.squirreljme.vm.springcoat.exceptions.SpringMachineExitException;
import cc.squirreljme.vm.springcoat.exceptions.SpringVirtualMachineException;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;
import net.multiphasicapps.classfile.ClassName;
import net.multiphasicapps.classfile.ConstantValueString;
import net.multiphasicapps.classfile.MethodDescriptor;
import net.multiphasicapps.classfile.MethodNameAndType;

/**
 * This class contains the instance of the SpringCoat virtual machine and has
 * a classpath along with all the needed storage for variables and such.
 *
 * @since 2018/07/29
 */
@SuppressWarnings("OverlyCoupledClass")
public final class SpringMachine
    implements Runnable, VirtualMachine
{
    /** Exit code indicating bad task things. */
    public static final int EXIT_CODE_FATAL_EXCEPTION =
        123;
    
    /** The class which contains the thread starting point. */
    private static final ClassName _START_CLASS =
        new ClassName("java/lang/__Start__");
    
    /** The method to enter for main threads. */
    private static final MethodNameAndType _MAIN_THREAD_METHOD =
        new MethodNameAndType("__main", "()V");
    
    /** The thread class. */
    private static final ClassName _THREAD_CLASS =
        new ClassName("java/lang/Thread");
    
    /** The new thread instance. */
    private static final MethodDescriptor _THREAD_NEW =
        new MethodDescriptor("(Ljava/lang/String;)V");
    
    /** The next virtual machine ID. */
    private static volatile int _nextVmNumber =
        1; 
    
    /** The class loader. */
    protected final SpringClassLoader classloader;
    
    /** Resources accessor. */
    protected final VMResourceAccess resourceaccessor;
    
    /** The boot class. */
    protected final String bootClass;
    
    /** The manager for suites. */
    protected final VMSuiteManager suites;
    
    /** Task manager. */
    protected final SpringTaskManager tasks;
    
    /** The global VM state. */
    protected final GlobalState globalState;
    
    /** The profiling information. */
    protected final ProfilerSnapshot profiler;
    
    /** The terminal pipe manager. */
    protected final TerminalPipeManager terminalPipes;
    
    /** The virtual machine identifier. */
    protected final String vmId;
    
    /** Is this the root virtual machine? */
    protected final boolean rootVm;
    
    /** State for the callback threader. */
    private final CallbackThreader _cbThreader =
        new CallbackThreader();
    
    /** Threads which are available. */
    private final List<SpringThread> _threads =
        new ArrayList<>();
    
    /** Global strings representing singular constants. */
    private final Map<ConstantValueString, SpringObject> _strings =
        new HashMap<>();
    
    /** Class objects which represent a given class. */
    private final Map<ClassName, SpringObject> _classobjects =
        new HashMap<>();
    
    /** Class names by their objects. */
    private final Map<SpringObject, ClassName> _namesbyclass =
        new HashMap<>();
    
    /** The object that represents a given task, remains for the task. */
    private final Map<SpringMachine, Reference<TaskObject>> _taskObject =
        new WeakHashMap<>();
    
    /** Main entry point arguments. */
    private final String[] _args;
    
    /** System properties. */
    final Map<String, String> _sysproperties;
    
    /** Callback threads that are available for use. */
    private final Collection<CallbackThread> _cbThreads =
        new LinkedList<>();
    
    /** For internal threads, initially set their verbosity. */
    volatile int _verboseInternal;
    
    /** The next thread ID to use. */
    private volatile int _nextthreadid;
    
    /** Is the VM exiting? */
    private volatile boolean _exiting;
    
    /** Exit code of the VM. */
    private int _exitcode;
    
    /** The stored call trace. */
    private CallTraceStorage _storedTrace;
    
    /** Global trace flags. */
    volatile int _globalTrace;
    
    /**
     * Initializes the virtual machine.
     *
     * @param __sm The manager for suites.
     * @param __cl The class loader.
     * @param __tm Task manager.
     * @param __bootcl The boot class.
     * @param __profiler The profiler to use.
     * @param __sprops System properties.
     * @param __gs Global system state.
     * @param __pipes The terminal pipe manager, may be {@code null} in which
     * case it is initialized for the caller.
     * @param __rootVm Is this the root virtual machine?
     * @param __args Main entry point arguments.
     * @throws NullPointerException On null arguments.
     * @since 2018/09/03
     */
    public SpringMachine(VMSuiteManager __sm, SpringClassLoader __cl,
        SpringTaskManager __tm, String __bootcl, ProfilerSnapshot __profiler,
        Map<String, String> __sprops, GlobalState __gs,
        TerminalPipeManager __pipes, boolean __rootVm, String... __args)
        throws NullPointerException
    {
        if (__cl == null || __sm == null || __pipes == null)
            throw new NullPointerException("NARG");
        
        // Bind to this class class loader
        __cl.__bind(this);
        
        this.rootVm = __rootVm;
        this.suites = __sm;
        this.classloader = __cl;
        this.tasks = __tm;
        this.bootClass = __bootcl;
        this.globalState = __gs;
        this.terminalPipes = __pipes;
        this._args = (__args == null ? new String[0] : __args.clone());
        this.profiler = (__profiler != null ? __profiler :
            new ProfilerSnapshot());
        this._sysproperties = (__sprops == null ?
            new HashMap<String, String>() : new HashMap<>(__sprops));
        
        // Setup resource accessor
        this.resourceaccessor = new VMResourceAccess(__sm);
        
        // Determine an ID for the VM, used for profiler information
        synchronized (SpringMachine.class)
        {
            this.vmId = String.format("%s#%d@%08x",
                __cl.bootLibrary().name(),
                SpringMachine._nextVmNumber++,
                System.identityHashCode(this));
        }
    }
    
    /**
     * Returns the class loader.
     *
     * @return The class loader.
     * @since 2018/09/08
     */
    public final SpringClassLoader classLoader()
    {
        return this.classloader;
    }
    
    /**
     * Creates a new thread within the virtual machine.
     *
     * @param __n The name of the thread, may be {@code null} in which case
     * the thread will just get an ID number.
     * @param __main Is this a main thread?
     * @param __noDebugSuspend Do not allow the debugger to suspend this
     * thread.
     * @return The newly created thread.
     * @throws NullPointerException On null arguments.
     * @since 2018/09/01
     */
    public final SpringThread createThread(String __n, boolean __main,
        boolean __noDebugSuspend)
    {
        // Store thread
        List<SpringThread> threads = this._threads;
        synchronized (this)
        {
            // The name of the thread to use
            String usedName = (__n != null ? __n :
                "hwThread-" + this.numThreads());
            
            // Initialize new thread
            int v;
            SpringThread rv = new SpringThread(
                new WeakReference<>(this),
                (v = ++this._nextthreadid),
                this.tasks.nextThreadId(),
                __main,
                usedName,
                this.profiler.measureThread(String.format("VM_%s-%d-%s",
                this.vmId, v, usedName)),
                __noDebugSuspend);
            
            // Signal that a major state has changed
            this.notifyAll();
            
            // Store thread
            threads.add(rv);
            return rv;
        }
    }
    
    /**
     * Exits the virtual machine.
     *
     * @param __code The exit code.
     * @throws SpringMachineExitException To signal virtual machine exit.
     * @since 2018/10/13
     */
    public final void exit(int __code)
        throws SpringMachineExitException
    {
        // Should do the same thing
        this.exitNoException(__code);
        
        // Now signal exit
        throw new SpringMachineExitException(__code);
    }
    
    /**
     * Checks whether the virtual machine is exiting.
     *
     * @throws SpringMachineExitException If the VM is exiting.
     * @since 2018/10/13
     */
    public final void exitCheck()
        throws SpringMachineExitException
    {
        synchronized (this)
        {
            // Only if exiting
            if (this._exiting)
                throw new SpringMachineExitException(this._exitcode);
        }
    }
    
    /**
     * Exits the virtual machine without throwing an exception.
     *
     * @param __code The exit code.
     * @since 2018/10/13
     */
    public final void exitNoException(int __code)
        throws SpringMachineExitException
    {
        synchronized (this)
        {
            // Set as exiting
            this._exitcode = __code;
            this._exiting = true;
            
            // Close all the pipes on exit because otherwise any calling tasks
            // will never be able to finish reading these pipes ever until
            // the process completes
            try
            {
                this.terminalPipes.closeAll();
            }
            catch (IOException e)
            {
                throw new SpringVirtualMachineException(
                    "Could not close pipes.", e);
            }
        }
    }
    
    /**
     * Returns the current exit code.
     * 
     * @return The current exit code.
     * @since 2020/07/08
     */
    public int getExitCode()
    {
        synchronized (this)
        {
            return this._exitcode;
        }
    }
    
    /**
     * Returns the main arguments.
     *
     * @return The main arguments.
     * @since 2020/06/17
     */
    public final String[] getMainArguments()
    {
        return this._args.clone();
    }
    
    /**
     * Gets the thread by the given ID.
     *
     * @param __id The ID of the thread.
     * @return The thread by this ID.
     * @throws NoSuchElementException If there is no thread that exists by
     * that ID.
     * @since 2018/11/21
     */
    public final SpringThread getThread(int __id)
        throws NoSuchElementException
    {
        synchronized (this)
        {
            for (SpringThread t : this.getThreads())
                if (t.id == __id)
                    return t;
        }
        
        throw new NoSuchElementException("No such thread ID: " + __id);
    }
    
    /**
     * Returns all of the process threads.
     *
     * @return All of the current process threads.
     * @since 2020/06/17
     */
    public final SpringThread[] getThreads()
    {
        List<SpringThread> rv = new ArrayList<>();
        
        // Go through threads but also cleanup any that have ended
        List<SpringThread> threads = this._threads;
        synchronized (this)
        {
            for (Iterator<SpringThread> it = threads.iterator(); it.hasNext();)
            {
                SpringThread thread = it.next();
                
                // If the thread is terminating, clean it up
                if (thread.isTerminated())
                {
                    // Remove it
                    it.remove();
                    
                    // Signal that a state of a thread has changed
                    this.notifyAll();
                }
                
                // Otherwise add it
                else
                    rv.add(thread);
            }
            
            // If there are no threads left and we are not yet exiting, exit
            // this task so it finalizes and cleans accordingly
            if (!this.isExiting() && threads.isEmpty())
                this.exitNoException(this.getExitCode());
        }
        
        // Use whatever was found
        return rv.<SpringThread>toArray(new SpringThread[rv.size()]);
    }
    
    /**
     * Returns the stack trace.
     * 
     * @return The trace.
     * @since 2020/07/08
     */
    public final CallTraceStorage getTrace()
    {
        synchronized (this)
        {
            return this._storedTrace;
        }
    }
    
    /**
     * Returns if this task has exited.
     * 
     * @return If this task is exited.
     * @since 2020/07/08
     */
    public final boolean isExiting()
    {
        synchronized (this)
        {
            return this._exiting;
        }
    }
    
    /**
     * Returns the number of threads which are currently alive and active.
     *
     * @return The number of active and alive threads.
     * @since 2018/09/03
     */
    public final int numThreads()
    {
        // Store thread
        List<SpringThread> threads = this._threads;
        synchronized (this)
        {
            return threads.size();
        }
    }
    
    /**
     * Obtains a temporary callback thread.
     *
     * @param __noDebugSuspend Do not allow the debugger to suspend this
     * thread.
     * @return The thread to be used.
     * @throws NullPointerException On null arguments.
     * @since 2020/09/15
     */
    public final CallbackThread obtainCallbackThread(boolean __noDebugSuspend)
        throws NullPointerException
    {
        CallbackThread rv = null;
        
        // This could deadlock on thread initialization, especially if this
        // gets suspended by a debugger!
        Collection<CallbackThread> cbThreads = this._cbThreads;
        synchronized (this)
        {
            // Find the thread that can be opened, assuming it matches the
            // suspension state kind...
            for (CallbackThread thread : cbThreads)
                if (thread.canOpen() &&
                    thread.noDebugSuspend() == __noDebugSuspend)
                {
                    rv = thread;
                    break;
                }
            
            // Use this thread
            if (rv != null)
            {
                rv.open();
                return rv;
            }
        }
            
        // Setup new thread and its worker
        String name = "callback#" + cbThreads.size();
        SpringThread thread = this.createThread(name, false,
            __noDebugSuspend);
        SpringThreadWorker worker = new SpringThreadWorker(this,
            thread, false);
        
        // Initially set internal verbose threads for this callback?
        if (this._verboseInternal != 0)
            worker.verbose().add(0, this._verboseInternal);
        
        // This always is a daemon thread
        thread.setDaemon();
        
        // Enter blank thread so it is always at the ready
        thread.enterBlankFrame();
        
        // Allocate thread object instance, this will get a VM thread
        // created for it in the constructor and otherwise
        SpringObject jvmThread = worker.newInstance(
            SpringMachine._THREAD_CLASS, SpringMachine._THREAD_NEW,
            worker.asVMObject(name));
        thread.setThreadInstance(jvmThread);
        
        // Register this callback thread since it was initialized
        synchronized (this)
        {
            // Register this
            rv = new CallbackThread(thread);
            cbThreads.add(rv);
            
            // Open and use it
            rv.open();
            return rv;
        }
    }
    
    /**
     * Returns the access for resources.
     *
     * @return The resource access.
     * @since 2018/10/07
     */
    public final VMResourceAccess resourceAccess()
    {
        return this.resourceaccessor;
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/09/13
     */
    @Override
    public final void run()
    {
        // Thread that will be used as the main thread of execution, also used
        // to initialize classes when they are requested
        SpringThread mainThread = this.createThread("main", true,
            false);
        
        // We will be using the same logic in the thread worker if we need to
        // initialize any objects or arguments
        SpringThreadWorker worker = new SpringThreadWorker(this,
            mainThread, true);
        mainThread._worker = worker;
        
        // Enter the main entry point which handles the thread logic
        mainThread.enterFrame(worker.loadClass(SpringMachine._START_CLASS)
            .lookupMethod(true, SpringMachine._MAIN_THREAD_METHOD));
        
        // Initialize an instance of Thread for this thread, as this is
        // very important, the call to create VM threads will bind the instance
        // object and the vm thread together.
        worker.newInstance(
            new ClassName("java/lang/Thread"),
            new MethodDescriptor("(Ljava/lang/String;)V"),
            worker.asVMObject("main"));
        
        // The main although it executes in this context will always have the
        // same exact logic as other threads running apart from this main
        // thread, so no code is needed to be duplicated at all.
        try
        {
            worker.run();
        }
        
        // Either failed or threw exit exception
        catch (RuntimeException e)
        {
            throw e;
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2018/11/17
     */
    @Override
    public final int runVm()
    {
        // Run until the VM terminates
        try
        {
            this.run();
            
            // Debug
            Debugging.debugNote("VM exited normally: %d",
                this.getExitCode());
            
            // Success, maybe
            return this.getExitCode();
        }
        
        // Exit VM with given code
        catch (SpringMachineExitException e)
        {
            // Debug
            Debugging.debugNote("VM Exited via exception: %d", e.code());
            
            return e.code();
        }
        
        // Any other exception is fatal and the task must be made to exit
        // with the error code otherwise the VM will stick trying to wait
        // to exit
        catch (RuntimeException|Error e)
        {
            PrintStream err = System.err;
            
            err.println("****************************");
            
            // Print the real stack trace
            err.println("*** EXTERNAL STACK TRACE ***");
            e.printStackTrace(err);
            err.println();
            
            err.println("****************************");
            
            // Errors are rather bad so rethrow them
            if (e instanceof Error)
                throw (Error)e;
            
            return SpringMachine.EXIT_CODE_FATAL_EXCEPTION;
        }
    }
    
    /**
     * Sets the current exit code.
     * 
     * @param __exitCode The exit code to set.
     * @since 2020/06/27
     */
    public final void setExitCode(int __exitCode)
    {
        synchronized (this)
        {
            this._exitcode = __exitCode;
        }
    }
    
    /**
     * {@inheritDoc}
     * @since 2024/01/14
     */
    @Override
    public void setTraceBits(boolean __or, int __bits)
    {
        if (__or)
            this._globalTrace |= __bits;
        else
            this._globalTrace = __bits;
    }
    
    /**
     * Signals that the given thread terminated.
     * 
     * @param __thread The thread that was terminated.
     * @since 2020/06/29
     */
    public void signalThreadTerminate(SpringThread __thread)
    {
        // The act of getting all threads will clear out terminated threads
        this.getThreads();
    }
    
    /**
     * Stores the call trace for this given machine/task.
     * 
     * @param __message The message to use.
     * @param __trace The trace to store.
     * @throws NullPointerException On null arguments.
     * @since 2020/07/06
     */
    public void storeTrace(String __message, CallTraceElement... __trace)
        throws NullPointerException
    {
        if (__message == null || __trace == null)
            throw new NullPointerException("NARG");
        
        synchronized (this)
        {
            this._storedTrace = new CallTraceStorage(__message, __trace); 
        }
    }
    
    /**
     * Returns the suite manager which is available.
     *
     * @return The suite manager that is available.
     * @since 2018/10/26
     */
    public final VMSuiteManager suiteManager()
    {
        return this.suites;
    }
    
    /**
     * Returns the single instance task object that represents this machine
     * for the given machine so that each machine has a semi-singleton
     * reference to a machine.
     * 
     * @param __for The machine to get for.
     * @return The single instance task object that represents this machine for
     * the give machine task.
     * @throws NullPointerException On null arguments.
     * @since 2022/09/24
     */
    public final TaskObject taskObject(SpringMachine __for)
        throws NullPointerException
    {
        Map<SpringMachine, Reference<TaskObject>> taskObjects =
            this._taskObject;
        
        // If the machine we are representing does not have one for this one,
        // yet then we should make one
        Reference<TaskObject> ref = taskObjects.get(__for);
        TaskObject rv = (ref != null ? ref.get() : null);
        if (rv == null)
        {
            // Always refers to _this_ machine!
            rv = new TaskObject(this);
            
            // Store for later use
            taskObjects.put(__for, new WeakReference<>(rv));
        }
        
        return rv;
    }
    
    /**
     * Returns the task manager which is used.
     *
     * @return The task manager.
     * @since 2018/11/04
     */
    public final SpringTaskManager taskManager()
    {
        return this.tasks;
    }
    
    /**
     * {@inheritDoc}
     * @since 2021/03/13
     */
    @Override
    public final String toString()
    {
        return this.vmId;
    }
    
    /**
     * Splits long to integers.
     *
     * @param __dx The index.
     * @param __v The output integers.
     * @param __l The input long.
     * @since 2018/09/29
     */
    public static final void longToInt(int __dx, int[] __v, long __l)
    {
        __v[__dx] = (int)(__l >>> 32);
        __v[__dx + 1] = (int)__l;
    }
    
    /**
     * Returns the mapping of class names to {@link Class} instances.
     *
     * @return The mapping of class names to object instances.
     * @since 2018/09/19
     */
    final Map<ClassName, SpringObject> __classObjectMap()
    {
        return this._classobjects;
    }
    
    /**
     * Returns the mapping for objects to class names.
     *
     * @return The mapping of class objects to names.
     * @since 2018/09/29
     */
    final Map<SpringObject, ClassName> __classObjectToNameMap()
    {
        return this._namesbyclass;
    }
    
    /**
     * Returns the global string map.
     *
     * @return The global string map.
     * @since 2018/09/16
     */
    final Map<ConstantValueString, SpringObject> __stringMap()
    {
        return this._strings;
    }
}