SquirrelJME/SquirrelJME

View on GitHub
modules/aot/src/main/java/cc/squirreljme/jvm/aot/Main.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.jvm.aot;

import cc.squirreljme.jvm.manifest.JavaManifest;
import cc.squirreljme.runtime.cldc.util.StreamUtils;
import cc.squirreljme.vm.JarClassLibrary;
import cc.squirreljme.vm.SummerCoatJarLibrary;
import cc.squirreljme.vm.VMClassLibrary;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collection;
import java.util.Deque;
import java.util.LinkedList;
import java.util.ServiceLoader;
import net.multiphasicapps.zip.streamreader.ZipStreamEntry;
import net.multiphasicapps.zip.streamreader.ZipStreamReader;

/**
 * Main entry point for the AOT compilation backend.
 *
 * @since 2020/11/21
 */
public class Main
{
    /**
     * Finds the given backend.
     * 
     * @param __compiler The compiler to select.
     * @return The backend.
     * @throws IllegalArgumentException If the backend is not valid.
     * @throws NullPointerException On null arguments.
     * @since 2020/11/22
     */
    public static Backend findBackend(String __compiler)
        throws IllegalArgumentException, NullPointerException
    {
        if (__compiler == null)
            throw new NullPointerException("NARG");
        
        // Find matching backend with the given name
        for (Backend backend : ServiceLoader.load(Backend.class))
            if (__compiler.equals(backend.name()))
                return backend;
        
        /* {@squirreljme.error AE04 The given compiler does not exist.
        (The compiler)} */
        throw new IllegalArgumentException("AE04 " + __compiler);
    }
    
    /**
     * Main entry point for the compiler interface.
     * 
     * @param __args Arguments to the main class.
     * @throws Throwable On any exception.
     * @since 2023/10/14
     */
    public static void main(String... __args)
        throws Throwable
    {
        try
        {
            Main.mainWrapped(__args);
        }
        catch (Throwable t)
        {
            t.printStackTrace(System.err);
            
            if (t instanceof Error)
                throw (Error)t;
            
            throw t;
        }
    }
    
    /**
     * Main entry point for the compiler interface.
     * 
     * @param __args Arguments to the main class.
     * @throws IOException On read errors.
     * @since 2020/11/21
     */
    public static void mainWrapped(String... __args)
        throws IOException
    {
        // Push all arguments to the queue
        Deque<String> args = new LinkedList<>(Arrays.asList(__args));
        
        // Selection commands
        String compiler = null;
        String name = "undefined";
        String mode = null;
        String clutterLevel = null;
        String sourceSet = null;
        String originalLibHash = null;
        
        // Parse input arguments
        while (!args.isEmpty())
        {
            String arg = args.removeFirst();
            
            // Compiler to select
            if (arg.startsWith("-Xcompiler:"))
                compiler = arg.substring("-Xcompiler:".length());
            
            // The name of the JAR being compiled
            else if (arg.startsWith("-Xname:"))
                name = arg.substring("-Xname:".length());
            
            // The current clutter level
            else if (arg.startsWith("-XclutterLevel"))
                clutterLevel = arg.substring("-XclutterLevel:".length());
            
            // The source set being compiled
            else if (arg.startsWith("-XsourceSet"))
                sourceSet = arg.substring("-XsourceSet:".length());
            
            // Original library hash code
            else if (arg.startsWith("-XoriginalLibHash:"))
                originalLibHash = arg.substring("-XoriginalLibHash:".length());
            
            // End of switches
            else if (!arg.startsWith("-"))
            {
                mode = arg;
                break;
            }
            
            /* {@squirreljme.error AE01 Unknown argument. (The argument)} */
            else
                throw new IllegalArgumentException("AE01 " + arg);
        }
        
        /* {@squirreljme.error AE03 Mode was not specified.} */
        if (mode == null)
            throw new IllegalArgumentException("AE03");
        
        // Find the backend to use
        Backend backend = Main.findBackend(compiler);
        
        // Store into settings
        AOTSettings aotSettings = new AOTSettings(compiler,
            name, mode, sourceSet, clutterLevel, originalLibHash);
        
        // Use explicit input/output
        try (InputStream in = new StandardInputStream();
            OutputStream out = System.out)
        {
            // Which mode should occur?
            switch (mode)
            {
                    // Compile code
                case "compile":
                    Main.mainCompile(aotSettings, backend, in, out, args);
                    break;
                    
                    // Dump the result of "compile"
                case "dumpCompile":
                    Main.dumpCompile(aotSettings, backend, in, out);
                    break;
                    
                    // Link multiple libraries into one
                case "rom":
                    Main.mainRom(aotSettings, backend, out, args);
                    break;
                
                /* {@squirreljme.error AE02 Unknown mode. (The mode)} */
                default:
                    throw new IllegalArgumentException("AE02 " + mode);
            }
        }
    }
    
    /**
     * Dumps the result of the compilation to a readable text format used
     * for debugging.
     *
     * @param __aotSettings AOT settings.
     * @param __backend The backend to use.
     * @param __inGlob The input glob.
     * @param __out Where to write the output.
     * @throws IOException On read/write errors.
     * @throws NullPointerException On null arguments.
     * @since 2021/05/16
     */
    public static void dumpCompile(AOTSettings __aotSettings,
        Backend __backend, InputStream __inGlob,
        OutputStream __out)
        throws IOException, NullPointerException
    {
        if (__backend == null || __inGlob == null || __out == null ||
            __aotSettings == null)
            throw new NullPointerException("NARG");
        
        // Read in the entire contents of the data
        byte[] dump;
        try (InputStream in = __inGlob)
        {
            dump = StreamUtils.readAll(in);
        }
        
        // Dump the output
        try (PrintStream out = new PrintStream(__out, true))
        {
            __backend.dumpGlob(__aotSettings, dump, out);
        }
    }
    
    /**
     * Handles the main compilation stage.
     *
     * @param __aotSettings AOT settings.
     * @param __backend The backend to use.
     * @param __inZip The input stream of the input ZIP.
     * @param __outGlob The output stream of the Glob.
     * @param __args The arguments to use.
     * @throws IOException On read errors.
     * @throws NullPointerException On null arguments.
     * @since 2020/11/22
     */
    public static void mainCompile(AOTSettings __aotSettings,
        Backend __backend, InputStream __inZip,
        OutputStream __outGlob, Deque<String> __args)
        throws IOException, NullPointerException
    {
        if (__backend == null || __aotSettings == null || __args == null ||
            __inZip == null || __outGlob == null)
            throw new NullPointerException("NARG");
        
        // Parse compilation arguments
        CompileSettings settings = CompileSettings.parse(__args);
        
        // Setup glob for final linking
        try (LinkGlob glob = __backend.linkGlob(__aotSettings, settings,
            __outGlob))
        {
            // Starting linking
            glob.initialize();
            
            // Read input JAR and perform inline compilation
            try (InputStream in = __inZip;
                 ZipStreamReader zip = new ZipStreamReader(in))
            {
                // Process JAR entries and compile them into individual class
                // fragments
                for (;;)
                    try (ZipStreamEntry entry = zip.nextEntry())
                    {
                        // No more entries to process
                        if (entry == null)
                            break;
                        
                        // Ignore directories
                        String name = entry.name();
                        if (name.endsWith("/"))
                            continue;
                        
                        // Compile resource file?
                        if (!Main.__isValidClass(name))
                        {
                            // If not the manifest, do not keep a copy of it
                            InputStream actual;
                            boolean isManifest =
                                name.equals("META-INF/MANIFEST.MF");
                            boolean isTestList =
                                name.equals("META-INF/services/net." +
                                    "multiphasicapps.tac.TestInterface");
                            if (!isManifest && !isTestList)
                                actual = entry;
                            
                            // Otherwise do make a copy of it
                            else
                            {
                                // Load this into the glob, if it cares
                                byte[] data = StreamUtils.readAll(entry);
                                try (InputStream rawIn =
                                     new ByteArrayInputStream(data))
                                {
                                    if (isManifest)
                                        glob.rememberManifest(
                                            new JavaManifest(rawIn));
                                    else
                                        glob.rememberTests(
                                            StreamUtils.readAllLines(rawIn,
                                                "utf-8"));
                                }
                                
                                // Use this for the resource read
                                actual = new ByteArrayInputStream(data);
                            }
                            
                            // Build the resource
                            __backend.compileResource(settings, glob, name,
                                actual);
                        }
                        
                        // Compile class file?
                        else
                            __backend.compileClass(settings, glob,
                                name.substring(0,
                                    name.length() - ".class".length()),
                                entry);
                    }
            }
            
            // Linking stage is finished
            glob.finish();
        }
    }
    
    /**
     * Links the ROM together as one.
     *
     * @param __aotSettings AOT settings.
     * @param __backend The backend to use.
     * @param __out Where the resultant ROM is to be written.
     * @param __args The arguments to the ROM linking.
     * @throws IOException On read/write errors.
     * @throws NullPointerException On null arguments.
     * @since 2020/11/27
     */
    public static void mainRom(AOTSettings __aotSettings, Backend __backend,
        OutputStream __out, Deque<String> __args)
        throws IOException, NullPointerException
    {
        if (__backend == null || __args == null || __out == null)
            throw new NullPointerException("NARG");
        
        // Parse rom arguments
        RomSettings settings = RomSettings.parse(__args);
        
        // Load all libraries
        Collection<VMClassLibrary> libs = new LinkedList<>();
        while (!__args.isEmpty())
        {
            String arg = __args.removeFirst();
            
            // Determine the correct kind of library to load
            VMClassLibrary lib;
            if (SummerCoatJarLibrary.isSqc(arg))
                lib = new SummerCoatJarLibrary(Paths.get(arg));
            else
                lib = JarClassLibrary.of(Paths.get(arg));
            
            libs.add(lib);
        }
        
        /* {@squirreljme.error AE08 No libraries specified to link together.} */
        if (libs.isEmpty())
            throw new IllegalArgumentException("AE08");
        
        // Extra arrays accordingly
        VMClassLibrary[] vmLibs = libs.toArray(
            new VMClassLibrary[libs.size()]);
        
        // Perform combined linking
        __backend.rom(__aotSettings, settings, __out, vmLibs);
    }
    
    /**
     * Is this a valid class?
     * 
     * @param __name The file name.
     * @return If this is a valid class.
     * @throws NullPointerException On null arguments.
     * @since 2023/05/28
     */
    private static boolean __isValidClass(String __name)
        throws NullPointerException
    {
        if (__name == null)
            throw new NullPointerException("NARG");
        
        // If not a class file, ignore
        if (!__name.endsWith(".class"))
            return false;
        
        // Check for invalid characters
        for (int i = 0, n = __name.length() - 6; i < n; i++)
        {
            char c = __name.charAt(i);
            
            // Only these are invalid
            if (c == '-' || c == '.' || c == ';' || c == '[')
                return false;
        }
        
        // No failure, so is a valid class
        return true;
    }
}