All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.qbicc.main.Main Maven / Gradle / Ivy

There is a newer version: 0.77.0
Show newest version
package org.qbicc.main;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.concurrent.Callable;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
import java.util.jar.JarInputStream;

import io.smallrye.common.constraint.Assert;
import org.apache.maven.settings.Settings;
import org.apache.maven.settings.building.SettingsBuildingException;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.jboss.logmanager.Level;
import org.jboss.logmanager.LogManager;
import org.jboss.logmanager.Logger;
import org.qbicc.context.CompilationContext;
import org.qbicc.context.Diagnostic;
import org.qbicc.context.DiagnosticContext;
import org.qbicc.driver.BaseDiagnosticContext;
import org.qbicc.driver.BuilderStage;
import org.qbicc.driver.ClassPathItem;
import org.qbicc.driver.Driver;
import org.qbicc.driver.ElementBodyCopier;
import org.qbicc.driver.ElementBodyCreator;
import org.qbicc.driver.ElementInitializer;
import org.qbicc.driver.ElementVisitorAdapter;
import org.qbicc.driver.GraphGenConfig;
import org.qbicc.driver.Phase;
import org.qbicc.driver.plugin.DriverPlugin;
import org.qbicc.interpreter.Vm;
import org.qbicc.interpreter.VmThread;
import org.qbicc.interpreter.impl.VmImpl;
import org.qbicc.machine.arch.Cpu;
import org.qbicc.machine.arch.Platform;
import org.qbicc.machine.object.ObjectFileProvider;
import org.qbicc.machine.probe.CProbe;
import org.qbicc.machine.tool.CToolChain;
import org.qbicc.machine.vfs.AbsoluteVirtualPath;
import org.qbicc.machine.vfs.VFSUtils;
import org.qbicc.machine.vfs.VirtualFileSystem;
import org.qbicc.plugin.apploader.InitAppClassLoaderHook;
import org.qbicc.plugin.constants.ConstantBasicBlockBuilder;
import org.qbicc.plugin.conversion.NumericalConversionBasicBlockBuilder;
import org.qbicc.plugin.core.CoreAnnotationTypeBuilder;
import org.qbicc.plugin.coreclasses.ArrayLengthBasicBlockBuilder;
import org.qbicc.plugin.coreclasses.BasicHeaderManualInitializer;
import org.qbicc.plugin.coreclasses.CoreClasses;
import org.qbicc.plugin.correctness.BuildTimeOnlyElementHandler;
import org.qbicc.plugin.correctness.RuntimeChecksBasicBlockBuilder;
import org.qbicc.plugin.correctness.StaticChecksBasicBlockBuilder;
import org.qbicc.plugin.dispatch.DevirtualizingBasicBlockBuilder;
import org.qbicc.plugin.dispatch.DispatchTableBuilder;
import org.qbicc.plugin.dispatch.DispatchTableEmitter;
import org.qbicc.plugin.dot.DotGenerator;
import org.qbicc.plugin.gc.common.GcCommon;
import org.qbicc.plugin.gc.common.MultiNewArrayExpansionBasicBlockBuilder;
import org.qbicc.plugin.gc.nogc.NoGcBasicBlockBuilder;
import org.qbicc.plugin.gc.nogc.NoGcSetupHook;
import org.qbicc.plugin.gc.nogc.NoGcTypeSystemConfigurator;
import org.qbicc.plugin.instanceofcheckcast.InstanceOfCheckCastBasicBlockBuilder;
import org.qbicc.plugin.instanceofcheckcast.SupersDisplayBuilder;
import org.qbicc.plugin.instanceofcheckcast.SupersDisplayEmitter;
import org.qbicc.plugin.intrinsics.IntrinsicBasicBlockBuilder;
import org.qbicc.plugin.intrinsics.core.CoreIntrinsics;
import org.qbicc.plugin.layout.ObjectAccessLoweringBuilder;
import org.qbicc.plugin.linker.EmscriptenLinkStage;
import org.qbicc.plugin.linker.LinkStage;
import org.qbicc.plugin.llvm.LLVMCompatibleBasicBlockBuilder;
import org.qbicc.plugin.llvm.LLVMCompileStage;
import org.qbicc.plugin.llvm.LLVMCompiler;
import org.qbicc.plugin.llvm.LLVMCompilerImpl;
import org.qbicc.plugin.llvm.LLVMDefaultModuleCompileStage;
import org.qbicc.plugin.llvm.LLVMEmscriptenCompiler;
import org.qbicc.plugin.llvm.LLVMGenerator;
import org.qbicc.plugin.llvm.LLVMIntrinsics;
import org.qbicc.plugin.llvm.LLVMReferencePointerFactory;
import org.qbicc.plugin.llvm.LLVMStripStackMapStage;
import org.qbicc.plugin.lowering.AbortingThrowLoweringBasicBlockBuilder;
import org.qbicc.plugin.lowering.BooleanAccessCopier;
import org.qbicc.plugin.lowering.FunctionLoweringElementHandler;
import org.qbicc.plugin.lowering.InitCheckLoweringBasicBlockBuilder;
import org.qbicc.plugin.lowering.InvocationLoweringBasicBlockBuilder;
import org.qbicc.plugin.lowering.LocalVariableFindingBasicBlockBuilder;
import org.qbicc.plugin.lowering.LocalVariableLoweringBasicBlockBuilder;
import org.qbicc.plugin.lowering.MemberPointerCopier;
import org.qbicc.plugin.lowering.StaticFieldLoweringBasicBlockBuilder;
import org.qbicc.plugin.lowering.ThrowExceptionHelper;
import org.qbicc.plugin.lowering.ThrowLoweringBasicBlockBuilder;
import org.qbicc.plugin.lowering.VMHelpersSetupHook;
import org.qbicc.plugin.main_method.AddMainClassHook;
import org.qbicc.plugin.main_method.MainMethod;
import org.qbicc.plugin.methodinfo.MethodDataEmitter;
import org.qbicc.plugin.native_.ConstTypeResolver;
import org.qbicc.plugin.native_.ConstantDefiningBasicBlockBuilder;
import org.qbicc.plugin.native_.ExternExportTypeBuilder;
import org.qbicc.plugin.native_.FunctionTypeResolver;
import org.qbicc.plugin.native_.InternalNativeTypeResolver;
import org.qbicc.plugin.native_.NativeBasicBlockBuilder;
import org.qbicc.plugin.native_.NativeBindingMethodConfigurator;
import org.qbicc.plugin.native_.NativeTypeBuilder;
import org.qbicc.plugin.native_.NativeTypeResolver;
import org.qbicc.plugin.native_.NativeXtorLoweringHook;
import org.qbicc.plugin.native_.PointerBasicBlockBuilder;
import org.qbicc.plugin.native_.PointerTypeResolver;
import org.qbicc.plugin.native_.StructMemberAccessBasicBlockBuilder;
import org.qbicc.plugin.nativeimage.FeatureProcessor;
import org.qbicc.plugin.objectmonitor.ObjectMonitorBasicBlockBuilder;
import org.qbicc.plugin.opt.FinalFieldLoadOptimizer;
import org.qbicc.plugin.opt.GotoRemovingVisitor;
import org.qbicc.plugin.opt.InliningBasicBlockBuilder;
import org.qbicc.plugin.opt.LocalMemoryTrackingBasicBlockBuilder;
import org.qbicc.plugin.opt.PhiOptimizerVisitor;
import org.qbicc.plugin.opt.SimpleOptBasicBlockBuilder;
import org.qbicc.plugin.opt.ea.EscapeAnalysisDotGenerator;
import org.qbicc.plugin.opt.ea.EscapeAnalysisDotVisitor;
import org.qbicc.plugin.opt.ea.EscapeAnalysisInterMethodAnalysis;
import org.qbicc.plugin.opt.ea.EscapeAnalysisIntraMethodAnalysis;
import org.qbicc.plugin.opt.ea.EscapeAnalysisOptimizeVisitor;
import org.qbicc.plugin.patcher.AccessorBasicBlockBuilder;
import org.qbicc.plugin.patcher.AccessorTypeBuilder;
import org.qbicc.plugin.patcher.Patcher;
import org.qbicc.plugin.patcher.PatcherResolverBasicBlockBuilder;
import org.qbicc.plugin.patcher.PatcherTypeResolver;
import org.qbicc.plugin.reachability.ReachabilityAnnotationTypeBuilder;
import org.qbicc.plugin.reachability.ReachabilityBlockBuilder;
import org.qbicc.plugin.reachability.ReachabilityInfo;
import org.qbicc.plugin.reachability.ReachabilityRoots;
import org.qbicc.plugin.reflection.Reflection;
import org.qbicc.plugin.reflection.ReflectionIntrinsics;
import org.qbicc.plugin.reflection.ReflectiveMethodAccessorGenerator;
import org.qbicc.plugin.reflection.VarHandleResolvingBasicBlockBuilder;
import org.qbicc.plugin.serialization.BuildtimeHeap;
import org.qbicc.plugin.serialization.ClassObjectSerializer;
import org.qbicc.plugin.serialization.MethodDataStringsSerializer;
import org.qbicc.plugin.serialization.ObjectLiteralSerializingVisitor;
import org.qbicc.plugin.serialization.StringInternTableEmitter;
import org.qbicc.plugin.threadlocal.ThreadLocalBasicBlockBuilder;
import org.qbicc.plugin.threadlocal.ThreadLocalTypeBuilder;
import org.qbicc.plugin.trycatch.LocalThrowHandlingBasicBlockBuilder;
import org.qbicc.plugin.trycatch.SynchronizedMethodBasicBlockBuilder;
import org.qbicc.plugin.verification.ClassInitializingBasicBlockBuilder;
import org.qbicc.plugin.verification.ClassLoadingBasicBlockBuilder;
import org.qbicc.plugin.verification.LowerVerificationBasicBlockBuilder;
import org.qbicc.plugin.verification.MemberResolvingBasicBlockBuilder;
import org.qbicc.plugin.vfs.VFS;
import org.qbicc.plugin.vio.VIO;
import org.qbicc.tool.llvm.LlvmToolChain;
import org.qbicc.type.TypeSystem;
import picocli.CommandLine;
import picocli.CommandLine.ParameterException;
import picocli.CommandLine.ParseResult;

/**
 * The main entry point, which can be constructed using a builder or directly invoked.
 */
public class Main implements Callable {
    private final List bootPaths;
    private final List appPaths;
    private final ClassLoader hostAppClassLoader;
    private final Path outputPath;
    private final String outputName;
    private final Consumer> diagnosticsHandler;
    private final String mainClass;
    private final String gc;
    private final boolean isPie;
    private final GraphGenConfig graphGenConfig;
    private final boolean compileOutput;
    private final boolean optMemoryTracking;
    private final boolean optPhis;
    private final boolean optGotos;
    private final boolean optInlining;
    private final boolean optEscapeAnalysis;
    private final Platform platform;
    private final boolean isWasm;
    private final boolean smallTypeIds;
    private final List librarySearchPaths;
    private final List buildFeatures;
    private final ClassPathResolver classPathResolver;
    private final Backend backend;
    private final List optOptions;
    private final List llcOptions;

    Main(Builder builder) {
        outputPath = builder.outputPath;
        outputName = builder.outputName;
        diagnosticsHandler = builder.diagnosticsHandler;
        // todo: this becomes optional
        mainClass = Assert.checkNotEmptyParam("builder.mainClass", builder.mainClass);
        gc = builder.gc;
        isPie = builder.isPie;
        graphGenConfig = builder.graphGenConfig;
        compileOutput = builder.compileOutput;
        optMemoryTracking = builder.optMemoryTracking;
        optInlining = builder.optInlining;
        optPhis = builder.optPhis;
        optGotos = builder.optGotos;
        optEscapeAnalysis = builder.optEscapeAnalysis;
        platform = builder.platform;
        isWasm = platform.getCpu() == Cpu.WASM32;
        smallTypeIds = builder.smallTypeIds;
        backend = builder.backend;
        ArrayList bootPaths = new ArrayList<>(builder.bootPathsPrepend.size() + 6 + builder.bootPathsAppend.size());
        bootPaths.addAll(builder.bootPathsPrepend);
        optOptions = builder.optOptions;
        llcOptions = builder.llcOptions;
        // add core things
        bootPaths.add(getCoreComponent("qbicc-runtime-api"));
        bootPaths.add(getCoreComponent("qbicc-runtime-linux"));
        bootPaths.add(getCoreComponent("qbicc-runtime-llvm"));
        bootPaths.add(getCoreComponent("qbicc-runtime-main"));
        bootPaths.add(getCoreComponent("qbicc-runtime-posix"));
        bootPaths.add(getCoreComponent("qbicc-runtime-unwind"));
        bootPaths.add(getCoreComponent("qbicc-runtime-zlib"));
        boolean nogc = gc.equals("none");
        if (nogc) {
            bootPaths.add(getCoreComponent("qbicc-runtime-gc-nogc"));
        }
        bootPaths.add(ClassPathEntry.ofClassLibraries(builder.classLibVersion));
        bootPaths.addAll(builder.bootPathsAppend);
        this.bootPaths = bootPaths;
        appPaths = List.copyOf(builder.appPaths);
        librarySearchPaths = builder.librarySearchPaths;
        buildFeatures = builder.buildFeatures;
        classPathResolver = builder.classPathResolver == null ? this::resolveClassPath : builder.classPathResolver;

        List urls = new ArrayList<>();
        for (ClassPathEntry cpe : appPaths) {
            if (cpe instanceof ClassPathEntry.FilePath fp) {
                try {
                    urls.add(fp.getPath().toUri().toURL());
                } catch (MalformedURLException e) {
                    // Ignore; if it matters will show up CNFE when the ClassLoader is used
                }
            }
        }
        hostAppClassLoader = URLClassLoader.newInstance(urls.toArray(new URL[0]));
    }

    public DiagnosticContext call() {
        BaseDiagnosticContext ctxt = new BaseDiagnosticContext();
        try {
            call0(ctxt);
        } catch (Throwable t) {
            t.printStackTrace(System.err);
            ctxt.error(t, "Compilation failed due to an exception");
        }
        diagnosticsHandler.accept(ctxt.getDiagnostics());
        return ctxt;
    }

    void call0(BaseDiagnosticContext initialContext) {
        final Driver.Builder builder = Driver.builder();
        builder.setInitialContext(initialContext);
        boolean nogc = gc.equals("none");
        boolean llvm = backend.equals(Backend.llvm);
        int errors = initialContext.errors();
        if (errors == 0) {
            builder.setOutputDirectory(outputPath);
            // process the class paths
            try {
                classPathResolver.resolveClassPath(initialContext, builder::addBootClassPathItem, bootPaths);
            } catch (IOException e) {
                // todo: close class path items?
                return;
            }
            try {
                classPathResolver.resolveClassPath(initialContext, builder::addAppClassPathItem, appPaths);
            } catch (IOException e) {
                return;
            }
            // first, probe the target platform
            Platform target = platform;
            builder.setTargetPlatform(target);
            Optional optionalProvider = ObjectFileProvider.findProvider(target.getObjectType(), Main.class.getClassLoader());
            if (optionalProvider.isEmpty()) {
                initialContext.error("No object file provider found for %s", target.getObjectType());
            } else {
                ObjectFileProvider objectFileProvider = optionalProvider.get();
                Iterator toolChains = CToolChain.findAllCToolChains(target, t -> true, Main.class.getClassLoader()).iterator();
                if (! toolChains.hasNext()) {
                    initialContext.error("No working C compiler found");
                } else {
                    CToolChain toolChain = toolChains.next();
                    builder.setToolChain(toolChain);
                    // probe the basic system sizes
                    CProbe.Builder probeBuilder = CProbe.builder();
                    probeBuilder.include("");
                    probeBuilder.include("");
                    // size and signedness of char
                    CProbe.Type char_t = CProbe.Type.builder().setName("char").build();
                    probeBuilder.probeType(char_t);
                    // int sizes
                    CProbe.Type int8_t = CProbe.Type.builder().setName("int8_t").build();
                    probeBuilder.probeType(int8_t);
                    CProbe.Type int16_t = CProbe.Type.builder().setName("int16_t").build();
                    probeBuilder.probeType(int16_t);
                    CProbe.Type int32_t = CProbe.Type.builder().setName("int32_t").build();
                    probeBuilder.probeType(int32_t);
                    CProbe.Type int64_t = CProbe.Type.builder().setName("int64_t").build();
                    probeBuilder.probeType(int64_t);
                    // float sizes
                    CProbe.Type float_t = CProbe.Type.builder().setName("float").build();
                    probeBuilder.probeType(float_t);
                    CProbe.Type double_t = CProbe.Type.builder().setName("double").build();
                    probeBuilder.probeType(double_t);
                    // bool
                    CProbe.Type _Bool = CProbe.Type.builder().setName("_Bool").build();
                    probeBuilder.probeType(_Bool);
                    // pointer
                    CProbe.Type void_p = CProbe.Type.builder().setName("void *").build();
                    probeBuilder.probeType(void_p);
                    // number of bits in char
                    probeBuilder.probeConstant("CHAR_BIT");
                    // max alignment
                    CProbe.Type max_align_t = CProbe.Type.builder().setName("max_align_t").build();
                    probeBuilder.probeType(max_align_t);
                    // execute
                    CProbe probe = probeBuilder.build();
                    try {
                        CProbe.Result probeResult = probe.run(toolChain, objectFileProvider, initialContext);
                        if (probeResult == null) {
                            initialContext.error("Type system probe compiler execution failed");
                        } else {
                            long charSize = probeResult.getTypeInfo(char_t).getSize();
                            if (charSize != 1) {
                                initialContext.error("Unexpected size of `char`: %d", Long.valueOf(charSize));
                            }
                            TypeSystem.Builder tsBuilder = TypeSystem.builder();
                            tsBuilder.setBoolSize((int) probeResult.getTypeInfo(_Bool).getSize());
                            tsBuilder.setBoolAlignment((int) probeResult.getTypeInfo(_Bool).getAlign());
                            tsBuilder.setByteBits(probeResult.getConstantInfo("CHAR_BIT").getValueAsInt());
                            tsBuilder.setSignedChar(probeResult.getTypeInfo(char_t).isSigned());
                            tsBuilder.setInt8Size((int) probeResult.getTypeInfo(int8_t).getSize());
                            tsBuilder.setInt8Alignment((int) probeResult.getTypeInfo(int8_t).getAlign());
                            tsBuilder.setInt16Size((int) probeResult.getTypeInfo(int16_t).getSize());
                            tsBuilder.setInt16Alignment((int) probeResult.getTypeInfo(int16_t).getAlign());
                            tsBuilder.setInt32Size((int) probeResult.getTypeInfo(int32_t).getSize());
                            tsBuilder.setInt32Alignment((int) probeResult.getTypeInfo(int32_t).getAlign());
                            tsBuilder.setInt64Size((int) probeResult.getTypeInfo(int64_t).getSize());
                            tsBuilder.setInt64Alignment((int) probeResult.getTypeInfo(int64_t).getAlign());
                            tsBuilder.setFloat32Size((int) probeResult.getTypeInfo(float_t).getSize());
                            tsBuilder.setFloat32Alignment((int) probeResult.getTypeInfo(float_t).getAlign());
                            tsBuilder.setFloat64Size((int) probeResult.getTypeInfo(double_t).getSize());
                            tsBuilder.setFloat64Alignment((int) probeResult.getTypeInfo(double_t).getAlign());
                            tsBuilder.setPointerSize((int) probeResult.getTypeInfo(void_p).getSize());
                            tsBuilder.setPointerAlignment((int) probeResult.getTypeInfo(void_p).getAlign());
                            tsBuilder.setMaxAlignment((int) probeResult.getTypeInfo(max_align_t).getAlign());
                            // todo: function alignment probe
                            // for now, references == pointers
                            tsBuilder.setReferenceSize((int) probeResult.getTypeInfo(void_p).getSize());
                            tsBuilder.setReferenceAlignment((int) probeResult.getTypeInfo(void_p).getAlign());
                            CProbe.Type type_id_type = smallTypeIds ? int16_t : int32_t;
                            tsBuilder.setTypeIdSize((int) probeResult.getTypeInfo(type_id_type).getSize());
                            tsBuilder.setTypeIdAlignment((int) probeResult.getTypeInfo(type_id_type).getAlign());
                            tsBuilder.setEndianness(probeResult.getByteOrder());
                            if (nogc) {
                                new NoGcTypeSystemConfigurator().accept(tsBuilder);
                            }
                            builder.setTypeSystem(tsBuilder.build());
                            // add additional manual initializers by chaining `.andThen(...)`
                            builder.setVmFactory(cc -> {
                                CoreClasses.init(cc);
                                ThrowExceptionHelper.init(cc);
                                return VmImpl.create(cc,
                                    new BasicHeaderManualInitializer(cc)
                                );
                            });
                            builder.setObjectFileProvider(objectFileProvider);
                            ServiceLoader loader = ServiceLoader.load(DriverPlugin.class);
                            Iterator iterator = loader.iterator();
                            for (;;) try {
                                if (! iterator.hasNext()) {
                                    break;
                                }
                                DriverPlugin plugin = iterator.next();
                                plugin.accept(builder);
                            } catch (ServiceConfigurationError error) {
                                initialContext.error(error, "Failed to load plugin");
                            }
                            errors = initialContext.errors();
                            if (errors == 0 && llvm) {
                                Iterator llvmTools = LlvmToolChain.findAllLlvmToolChains(target, t -> true, Main.class.getClassLoader()).iterator();
                                LlvmToolChain llvmToolChain = null;
                                while (llvmTools.hasNext()) {
                                    llvmToolChain = llvmTools.next();
                                    if (llvmToolChain.compareVersionTo("12") >= 0) {
                                        break;
                                    }
                                    llvmToolChain = null;
                                }
                                if (llvmToolChain == null) {
                                    initialContext.error("No working LLVM toolchain found");
                                    errors = initialContext.errors();
                                } else {
                                    builder.setLlvmToolChain(llvmToolChain);
                                }
                            }
                            if (errors == 0) {
                                assert mainClass != null; // else errors would be != 0
                                // keep it simple to start with
                                builder.setMainClass(mainClass.replace('.', '/'));

                                builder.addTypeBuilderFactory(ExternExportTypeBuilder::new);
                                builder.addTypeBuilderFactory(NativeTypeBuilder::new);
                                builder.addTypeBuilderFactory(ThreadLocalTypeBuilder::new);
                                builder.addTypeBuilderFactory(CoreAnnotationTypeBuilder::new);
                                builder.addTypeBuilderFactory(ReachabilityAnnotationTypeBuilder::new);
                                builder.addTypeBuilderFactory(Patcher::getTypeBuilder);
                                builder.addTypeBuilderFactory(AccessorTypeBuilder::new);

                                builder.setClassContextListener(Patcher::initialize);

                                builder.addNativeMethodConfiguratorFactory(NativeBindingMethodConfigurator::new);

                                builder.addResolverFactory(PatcherTypeResolver::create);
                                builder.addResolverFactory(ConstTypeResolver::new);
                                builder.addResolverFactory(FunctionTypeResolver::new);
                                builder.addResolverFactory(PointerTypeResolver::new);
                                builder.addResolverFactory(InternalNativeTypeResolver::new);
                                builder.addResolverFactory(NativeTypeResolver::new);

                                builder.addTaskWrapperFactory(Phase.ADD, next -> (wrapper, ctxt) -> {
                                    Vm vm = ctxt.getVm();
                                    vm.doAttached(vm.newThread(Thread.currentThread().getName(), vm.getMainThreadGroup(), false, Thread.currentThread().getPriority()), () -> wrapper.accept(ctxt));
                                });
                                if (llvm) {
                                    builder.addPreHook(Phase.ADD, LLVMIntrinsics::register);
                                }
                                builder.addPreHook(Phase.ADD, CoreIntrinsics::register);
                                builder.addPreHook(Phase.ADD, CoreClasses::get);
                                builder.addPreHook(Phase.ADD, ReflectionIntrinsics::register);
                                builder.addPreHook(Phase.ADD, Reflection::get);
                                builder.addPreHook(Phase.ADD, ThrowExceptionHelper::get);
                                builder.addPreHook(Phase.ADD, GcCommon::registerIntrinsics);
                                builder.addPreHook(Phase.ADD, compilationContext -> {
                                    Vm vm = compilationContext.getVm();
                                    VmThread initThread = vm.newThread("initialization", vm.getMainThreadGroup(), false,  Thread.currentThread().getPriority());
                                    vm.doAttached(initThread, vm::initialize);
                                });
                                builder.addPreHook(Phase.ADD, VIO::get);
                                builder.addPreHook(Phase.ADD, VFS::initialize);
                                builder.addPreHook(Phase.ADD, Main::mountInitialFileSystem);
                                builder.addPreHook(Phase.ADD, new VMHelpersSetupHook());
                                builder.addPreHook(Phase.ADD, new InitAppClassLoaderHook());
                                builder.addPreHook(Phase.ADD, compilationContext -> {
                                    Vm vm = compilationContext.getVm();
                                    VmThread initThread = vm.newThread("initialization 2", vm.getMainThreadGroup(), false,  Thread.currentThread().getPriority());
                                    vm.doAttached(initThread, vm::initialize2);
                                });
                                builder.addPreHook(Phase.ADD, new AddMainClassHook());
                                if (nogc) {
                                    builder.addPreHook(Phase.ADD, new NoGcSetupHook());
                                }
                                builder.addPreHook(Phase.ADD, ReachabilityInfo::forceCoreClassesReachable);
                                builder.addPreHook(Phase.ADD, compilationContext -> {
                                    FeatureProcessor.processBuildFeature(compilationContext, buildFeatures, hostAppClassLoader);
                                });
                                builder.addElementHandler(Phase.ADD, new ElementBodyCreator());
                                builder.addElementHandler(Phase.ADD, new BuildTimeOnlyElementHandler());
                                builder.addElementHandler(Phase.ADD, new ElementVisitorAdapter(new DotGenerator(Phase.ADD, graphGenConfig)));
                                builder.addElementHandler(Phase.ADD, new ElementInitializer());
                                builder.addElementHandler(Phase.ADD, elem -> ReachabilityInfo.processReachableElement(elem)); // In the add phase, elements may be enqueued via multiple paths.  This makes sure Reachability Analysis sees them.
                                builder.addElementHandler(Phase.ADD, new ReflectiveMethodAccessorGenerator());
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, IntrinsicBasicBlockBuilder::createForAddPhase);
                                if (nogc) {
                                    builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, MultiNewArrayExpansionBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, PatcherResolverBasicBlockBuilder::createIfNeeded);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, ClassLoadingBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, NativeBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, VarHandleResolvingBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, MemberResolvingBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, AccessorBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, StructMemberAccessBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, PointerBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, ClassInitializingBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, ConstantDefiningBasicBlockBuilder::createIfNeeded);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, ConstantBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, ArrayLengthBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, DevirtualizingBasicBlockBuilder::new);
                                if (optMemoryTracking) {
                                    // TODO: breaks addr_of; should only be done in ANALYZE and then only if addr_of wasn't taken (alias)
                                    // builder.addBuilderFactory(Phase.ADD, BuilderStage.TRANSFORM, LocalMemoryTrackingBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.CORRECT, RuntimeChecksBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.CORRECT, LocalThrowHandlingBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.CORRECT, SynchronizedMethodBasicBlockBuilder::createIfNeeded);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.OPTIMIZE, SimpleOptBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.INTEGRITY, ReachabilityBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ADD, BuilderStage.INTEGRITY, StaticChecksBasicBlockBuilder::new);
                                builder.addPostHook(Phase.ADD, ctxt -> {
                                    Vm vm = ctxt.getVm();
                                    vm.doAttached(vm.newThread("FieldAccessor Generation", vm.getMainThreadGroup(), false, Thread.currentThread().getPriority()), () -> {
                                        Reflection.get(ctxt).generateFieldAccessors();
                                    });
                                });
                                builder.addPostHook(Phase.ADD, ctxt -> Reflection.get(ctxt).transferToReflectionData());
                                builder.addPostHook(Phase.ADD, ReachabilityInfo::reportStats);
                                builder.addPostHook(Phase.ADD, ReachabilityInfo::clear);

                                builder.addPreHook(Phase.ANALYZE, new VMHelpersSetupHook());
                                builder.addPreHook(Phase.ANALYZE, ReachabilityInfo::forceCoreClassesReachable);
                                builder.addPreHook(Phase.ANALYZE, ReachabilityRoots::processReachabilityRoots);
                                builder.addElementHandler(Phase.ANALYZE, new ElementBodyCopier());
                                if (optEscapeAnalysis) {
                                    builder.addElementHandler(Phase.ANALYZE, new ElementVisitorAdapter(new EscapeAnalysisIntraMethodAnalysis()));
                                    builder.addElementHandler(Phase.ANALYZE, new ElementVisitorAdapter(
                                        new DotGenerator(Phase.ANALYZE, "analyze-intra", graphGenConfig).addVisitorFactory(EscapeAnalysisDotVisitor::new))
                                    );
                                } else {
                                    builder.addElementHandler(Phase.ANALYZE, new ElementVisitorAdapter(new DotGenerator(Phase.ANALYZE, graphGenConfig)));
                                }
                                if (optGotos) {
                                    builder.addCopyFactory(Phase.ANALYZE, GotoRemovingVisitor::new);
                                }
                                if (optPhis) {
                                    builder.addCopyFactory(Phase.ANALYZE, PhiOptimizerVisitor::new);
                                }
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.TRANSFORM, IntrinsicBasicBlockBuilder::createForAnalyzePhase);
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.TRANSFORM, FinalFieldLoadOptimizer::new);
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.TRANSFORM, ThreadLocalBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.TRANSFORM, DevirtualizingBasicBlockBuilder::new);
                                if (optMemoryTracking) {
                                    builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.TRANSFORM, LocalMemoryTrackingBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.CORRECT, NumericalConversionBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.OPTIMIZE, SimpleOptBasicBlockBuilder::new);
                                if (optInlining) {
                                    builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.OPTIMIZE, InliningBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.INTEGRITY, ReachabilityBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.INTEGRITY, LocalVariableFindingBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.ANALYZE, BuilderStage.INTEGRITY, StaticChecksBasicBlockBuilder::new);

                                builder.addPostHook(Phase.ANALYZE, ReachabilityInfo::reportStats);
                                if (optEscapeAnalysis) {
                                    builder.addPostHook(Phase.ANALYZE, new EscapeAnalysisInterMethodAnalysis());
                                    builder.addPostHook(Phase.ANALYZE, new EscapeAnalysisDotGenerator(graphGenConfig));
                                }
                                builder.addPostHook(Phase.ANALYZE, new DispatchTableBuilder());
                                builder.addPostHook(Phase.ANALYZE, new SupersDisplayBuilder());

                                builder.addPreHook(Phase.LOWER, ReachabilityRoots::enqueueReachabilityRoots);
                                builder.addPreHook(Phase.LOWER, new ClassObjectSerializer());
                                if (optEscapeAnalysis) {
                                    builder.addCopyFactory(Phase.LOWER, EscapeAnalysisOptimizeVisitor::new);
                                }
                                builder.addElementHandler(Phase.LOWER, new FunctionLoweringElementHandler());
                                builder.addElementHandler(Phase.LOWER, new ElementVisitorAdapter(new DotGenerator(Phase.LOWER, graphGenConfig)));
                                if (optGotos) {
                                    builder.addCopyFactory(Phase.LOWER, GotoRemovingVisitor::new);
                                }
                                if (optPhis) {
                                    builder.addCopyFactory(Phase.LOWER, PhiOptimizerVisitor::new);
                                }
                                builder.addCopyFactory(Phase.LOWER, BooleanAccessCopier::new);
                                builder.addCopyFactory(Phase.LOWER, MemberPointerCopier::new);
                                builder.addCopyFactory(Phase.LOWER, ObjectLiteralSerializingVisitor::new);

                                if (isWasm) {
                                    builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, AbortingThrowLoweringBasicBlockBuilder::new);
                                } else {
                                    builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, ThrowLoweringBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, DevirtualizingBasicBlockBuilder::new);
                                if (nogc) {
                                    builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, NoGcBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, IntrinsicBasicBlockBuilder::createForLowerPhase);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, InvocationLoweringBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, LocalVariableLoweringBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, StaticFieldLoweringBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, InstanceOfCheckCastBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, InitCheckLoweringBasicBlockBuilder::new);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, ObjectAccessLoweringBuilder::new);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, ObjectMonitorBasicBlockBuilder::new);
                                if (llvm) {
                                    builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, LLVMCompatibleBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.OPTIMIZE, SimpleOptBasicBlockBuilder::new);
                                if (optMemoryTracking) {
                                    builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, LocalMemoryTrackingBasicBlockBuilder::new);
                                }
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.INTEGRITY, LowerVerificationBasicBlockBuilder::new);
                                // To avoid serializing Strings we won't need, MethodDataStringsSerializer should be the last "real" BBB
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.TRANSFORM, MethodDataStringsSerializer::new);
                                builder.addBuilderFactory(Phase.LOWER, BuilderStage.INTEGRITY, StaticChecksBasicBlockBuilder::new);
                                builder.addPostHook(Phase.LOWER, NativeXtorLoweringHook::process);
                                builder.addPostHook(Phase.LOWER, BuildtimeHeap::reportStats);

                                LLVMReferencePointerFactory referencePointerFactory =
                                    isWasm ? LLVMReferencePointerFactory.SIMPLE : LLVMReferencePointerFactory.COLLECTED;

                                LLVMCompiler.Factory llvmCompilerFactory =
                                    isWasm? LLVMEmscriptenCompiler::new : LLVMCompilerImpl::new;

                                builder.addPreHook(Phase.GENERATE, new StringInternTableEmitter());
                                builder.addPreHook(Phase.GENERATE, new SupersDisplayEmitter());
                                builder.addPreHook(Phase.GENERATE, new DispatchTableEmitter());

                                if (llvm) {
                                    builder.addPreHook(Phase.GENERATE, new LLVMGenerator(isPie ? 2 : 0, isPie ? 2 : 0, referencePointerFactory));
                                }

                                builder.addPostHook(Phase.GENERATE, new DotGenerator(Phase.GENERATE, graphGenConfig));
                                if (compileOutput) {
                                    if (llvm) {
                                        builder.addPostHook(Phase.GENERATE, new LLVMCompileStage(isPie, llvmCompilerFactory, optOptions, llcOptions));
                                    }
                                }
                                if (llvm) {
                                    builder.addPostHook(Phase.GENERATE, new MethodDataEmitter());
                                    builder.addPostHook(Phase.GENERATE, new LLVMDefaultModuleCompileStage(isPie, compileOutput, referencePointerFactory, llvmCompilerFactory, optOptions, llcOptions));
                                    if (! isWasm) {
                                        builder.addPostHook(Phase.GENERATE, new LLVMStripStackMapStage());
                                    }
                                }
                                if (compileOutput) {
                                    Consumer linkStage =
                                        isWasm ? new EmscriptenLinkStage(outputName, isPie, librarySearchPaths) : new LinkStage(outputName, isPie, librarySearchPaths);
                                    builder.addPostHook(Phase.GENERATE, linkStage);

                                }
                                CompilationContext ctxt;
                                try (Driver driver = builder.build()) {
                                    ctxt = driver.getCompilationContext();
                                    MainMethod.get(ctxt).setMainClass(mainClass);
                                    driver.execute();
                                }
                            }
                        }
                    } catch (IOException e) {
                        initialContext.error(e, "Failed to probe system types from tool chain");
                    }
                }
            }
        }
        return;
    }

    private ClassPathEntry getCoreComponent(final String artifactId) {
        return ClassPathEntry.of(new DefaultArtifact("org.qbicc", artifactId, "jar", MainProperties.QBICC_VERSION));
    }

    private void resolveClassPath(DiagnosticContext ctxt, Consumer classPathItemConsumer, final List paths) throws IOException {
        QbiccMavenResolver resolver = new QbiccMavenResolver(new QbiccServiceLocator());
        File globalSettings = resolver.getGlobalSettings();
        File userSettings = resolver.getUserSettings();
        Settings settings;
        try {
            settings = resolver.createSettings(ctxt, globalSettings, userSettings);
        } catch (SettingsBuildingException e) {
            throw new IOException(e);
        }
        RepositorySystemSession session = resolver.createSession(settings);
        List result = resolver.requestArtifacts(session, settings, paths, ctxt);
        result.forEach(classPathItemConsumer);
    }

    static List splitPathString(String str) {
        if (str == null || str.isEmpty()) {
            return List.of();
        }
        char psc = File.pathSeparatorChar;
        int start = 0;
        int idx = str.indexOf(psc);
        ArrayList list = new ArrayList<>();
        for (;;) {
            String subStr;
            if (idx == -1) {
                subStr = str.substring(start);
            } else {
                subStr = str.substring(start, idx);
            }
            if (! subStr.isEmpty()) {
                list.add(Path.of(subStr));
            }
            if (idx == -1) {
                return list;
            } else {
                start = idx + 1;
            }
            idx = str.indexOf(psc, start);
        }
    }

    private static void mountInitialFileSystem(CompilationContext ctxt) {
        // install all boot classpath items into the VFS
        VFS vfs = VFS.get(ctxt);
        VirtualFileSystem fileSystem = vfs.getFileSystem();
        Driver driver = Driver.get(ctxt);
        AbsoluteVirtualPath javaHome = vfs.getQbiccPath().resolve("java.home");
        try {
            //noinspection OctalInteger
            fileSystem.mkdirs(javaHome, 0755);
        } catch (IOException e) {
            ctxt.error(e, "Failed to create %s", javaHome);
        }
        AbsoluteVirtualPath modulesPath = javaHome.resolve("modules");
        Collection bootModuleNames = driver.getBootModuleNames();
        for (String bootModuleName : bootModuleNames) {
            ClassPathItem bootItem = driver.getBootModuleClassPathItem(bootModuleName);
            try {
                bootItem.mount(fileSystem, modulesPath.resolve(bootModuleName));
            } catch (IOException e) {
                ctxt.error(e, "Failed to mount %s", bootItem);
            }
        }
        // now look for all META-INF/java.home files and link them into the main system
        for (String bootModuleName : bootModuleNames) {
            AbsoluteVirtualPath moduleJavaHome = modulesPath.resolve(bootModuleName).resolve("META-INF").resolve("java.home");
            try {
                int attrs = fileSystem.getBooleanAttributes(moduleJavaHome, false);
                if ((attrs & VFSUtils.BA_EXISTS) != 0) {
                    // do it
                    linkIn(fileSystem, javaHome, moduleJavaHome);
                }
            } catch (IOException e) {
                ctxt.error(e, "Failed to remount %s", moduleJavaHome);
            }
        }
    }

    private static void linkIn(final VirtualFileSystem fileSystem, final AbsoluteVirtualPath toDir, final AbsoluteVirtualPath fromDir) throws IOException {
        Collection names = fileSystem.getDirectoryEntries(fromDir, false);
        for (String name : names) {
            AbsoluteVirtualPath fromPath = fromDir.resolve(name);
            AbsoluteVirtualPath toPath = toDir.resolve(name);
            //noinspection OctalInteger
            fileSystem.mkdirs(toPath.getParent(), 0755);
            if ((fileSystem.getBooleanAttributes(fromPath, false) & VFSUtils.BA_DIRECTORY) != 0) {
                linkIn(fileSystem, toPath, fromPath);
            } else {
                fileSystem.link(toPath, fromPath, false);
            }
        }
    }

    public static void main(String[] args) {
        System.setProperty("java.util.logging.manager", LogManager.class.getName());
        CommandLineProcessor optionsProcessor = new CommandLineProcessor();
        CmdResult result = optionsProcessor.process(args);
        if (result != CmdResult.CMD_RESULT_OK) {
            return;
        }
        if (optionsProcessor.mainClass.isEmpty() && optionsProcessor.inputJar == null) {
            System.err.println("Must either provide a  or use --jar ");
            return;
        }
        Builder mainBuilder = builder();
        mainBuilder.setClassLibVersion(optionsProcessor.rtVersion)
            .appendBootPaths(optionsProcessor.appendedBootPathEntries)
            .prependBootPaths(optionsProcessor.prependedBootPathEntries)
            .addAppPaths(optionsProcessor.appPathEntries)
            .processJarArgument(optionsProcessor.inputJar)
            .addBuildFeatures(optionsProcessor.buildFeatures)
            .setOutputPath(optionsProcessor.outputPath)
            .setOutputName(optionsProcessor.outputName)
            .setMainClass(optionsProcessor.mainClass)
            .setDiagnosticsHandler(diagnostics -> {
                for (Diagnostic diagnostic : diagnostics) {
                    try {
                        diagnostic.appendTo(System.err);
                    } catch (IOException e) {
                        // just give up
                        break;
                    }
                }
            })
            .setGc(optionsProcessor.gc.toString())
            .setIsPie(optionsProcessor.isPie)
            .setCompileOutput(optionsProcessor.compileOutput)
            .setOptMemoryTracking(optionsProcessor.optArgs.optMemoryTracking)
            .setOptInlining(optionsProcessor.optArgs.optInlining)
            .setOptGotos(optionsProcessor.optArgs.optGotos)
            .setOptPhis(optionsProcessor.optArgs.optPhis)
            .setOptEscapeAnalysis(optionsProcessor.optArgs.optEscapeAnalysis)
            .setSmallTypeIds(optionsProcessor.smallTypeIds)
            .setBackend(optionsProcessor.backend)
            .setGraphGenConfig(optionsProcessor.graphGenConfig)
            .setOptOptions(optionsProcessor.optOptions)
            .setLlcOptions(optionsProcessor.llcOptions)
            .addLibrarySearchPaths(splitPathString(System.getenv("LIBRARY_PATH")))
            .addLibrarySearchPaths(optionsProcessor.libSearchPaths);
        Platform platform = optionsProcessor.platform;
        if (platform != null) {
            mainBuilder.setPlatform(platform);
        }

        Main main = mainBuilder.build();
        DiagnosticContext context = main.call();
        int errors = context.errors();
        int warnings = context.warnings();
        if (errors > 0) {
            if (warnings > 0) {
                System.err.printf("Compilation failed with %d error(s) and %d warning(s)%n", Integer.valueOf(errors), Integer.valueOf(warnings));
            } else {
                System.err.printf("Compilation failed with %d error(s)%n", Integer.valueOf(errors));
            }
        } else if (warnings > 0) {
            System.err.printf("Compilation completed with %d warning(s)%n", Integer.valueOf(warnings));
        }
        System.exit(errors == 0 ? 0 : 1);
    }

    private enum CmdResult {
        CMD_RESULT_HELP,
        CMD_RESULT_OK,
        CMD_RESULT_ERROR,
        ;
    }

    @CommandLine.Command(versionProvider = VersionProvider.class, mixinStandardHelpOptions = true)
    private static final class CommandLineProcessor {
        private enum GCType {
            NONE("none"),
            ;
            private final String gcType;

            GCType(String type) {
                this.gcType = type;
            }
            public String toString() {
                return gcType;
            }
        }

        @CommandLine.Option(names = "--boot-path-prepend-artifact", converter = ClassPathEntry.MavenArtifact.Converter.class)
        void prependBootPathArtifact(List artifact) {
            prependedBootPathEntries.addAll(artifact);
        }
        @CommandLine.Option(names = "--boot-path-prepend-file", converter = ClassPathEntry.FilePath.Converter.class)
        void prependBootPathFile(List filePath) {
            prependedBootPathEntries.addAll(filePath);
        }
        private final List prependedBootPathEntries = new ArrayList<>();

        @CommandLine.Option(names = "--boot-path-append-artifact", converter = ClassPathEntry.MavenArtifact.Converter.class)
        void appendBootPathArtifact(List artifact) {
            appendedBootPathEntries.addAll(artifact);
        }
        @CommandLine.Option(names = "--boot-path-append-file", converter = ClassPathEntry.FilePath.Converter.class)
        void appendBootPathFile(List filePath) {
            appendedBootPathEntries.addAll(filePath);
        }
        private final List appendedBootPathEntries = new ArrayList<>();

        @CommandLine.Option(names = "--rt-version")
        private String rtVersion = MainProperties.CLASSLIB_DEFAULT_VERSION;

        @CommandLine.Option(names = "--app-path-artifact", converter = ClassPathEntry.MavenArtifact.Converter.class)
        void addAppPathArtifact(List artifact) {
            appPathEntries.addAll(artifact);
        }
        @CommandLine.Option(names = "--app-path-file", converter = ClassPathEntry.FilePath.Converter.class)
        void addAppPathFile(List filePath) {
            appPathEntries.addAll(filePath);
        }
        private final List appPathEntries = new ArrayList<>();

        @CommandLine.Option(names ="--build-feature", description = "GraalVM native-image Feature")
        void addBuildFeature(List features) { buildFeatures.addAll(features); }
        private final List buildFeatures = new ArrayList<>();

        @CommandLine.Option(names = "--jar", description = "Compile an executable jar")
        private Path inputJar;

        @CommandLine.Option(names = "--output-path", description = "Specify directory where build files are placed")
        private Path outputPath;
        @CommandLine.Option(names = { "--output-name", "-o" }, defaultValue = "a.out", description = "Specify the name of the output executable file or library")
        private String outputName;
        @CommandLine.Option(names = "--no-compile-output", negatable = true, defaultValue = "true", description = "Enable/disable llvm compilation of output files")
        boolean compileOutput;
        @CommandLine.Option(names = "--debug")
        private boolean debug;
        @CommandLine.Option(names = "--debug-vtables")
        private boolean debugVTables;
        @CommandLine.Option(names = "--dispatch-stats")
        private boolean dispatchStats;
        @CommandLine.Option(names = "--debug-reachability")
        private boolean debugReachability;
        @CommandLine.Option(names = "--debug-supers")
        private boolean debugSupers;
        @CommandLine.Option(names = "--debug-devirt")
        private boolean debugDevirt;
        @CommandLine.Option(names = "--debug-interpreter")
        private boolean debugInterpreter;
        @CommandLine.Option(names = "--gc", defaultValue = "none", description = "Type of GC to use. Valid values: ${COMPLETION-CANDIDATES}")
        private GCType gc;
        @CommandLine.Option(names = "--heap-stats")
        private boolean heapStats;
        @CommandLine.Option(names = "--method-data-stats")
        private boolean methodDataStats;
        @CommandLine.Option(names = "--pie", negatable = true, defaultValue = "false", description = "[Disable|Enable] generation of position independent executable")
        private boolean isPie;
        @CommandLine.Option(names = "--platform", converter = PlatformConverter.class)
        private Platform platform;
        @CommandLine.Option(names = "--string-pool-stats")
        private boolean stringPoolStats;

        @CommandLine.Option(names = { "--library-search-path", "-L" }, description = "Additional library search paths")
        private List libSearchPaths;

        @CommandLine.Option(names = "--small-type-ids", negatable = true, defaultValue = "false", description = "Use narrow (16-bit) type ID values if true, wide (32-bit) type ID values if false")
        private boolean smallTypeIds;

        @CommandLine.Option(names = "--backend", defaultValue = "llvm", description = "The backend type to use. Valid values: ${COMPLETION-CANDIDATES}")
        private Backend backend;

        @CommandLine.Parameters(index="0", arity="1", defaultValue = "", description = "Application main class")
        private String mainClass;

        @CommandLine.ArgGroup(exclusive = false, multiplicity = "0..1", heading = "Options for controlling generation of graphs for methods%n")
        private GraphGenArgs graphGenArgs;

        @CommandLine.ArgGroup(exclusive = false, heading = "Options for controlling optimizations%n")
        private OptArgs optArgs = new OptArgs();

        private GraphGenConfig graphGenConfig = new GraphGenConfig();

        private static class GraphGenArgs {
            @CommandLine.Option(names = { "-g", "--gen-graph"}, required = true, description = "Enable generation of graphs")
            boolean genGraph;
            @CommandLine.ArgGroup(exclusive=false, multiplicity = "0..*")
            List methodsAndPhases;
        }

        private static class GraphGenMethodsPhases {
            @CommandLine.Option(names = { "-m", "--methods"}, required = false, split = ",", defaultValue = GraphGenConfig.ALL_METHODS,
                                description = "List of methods separated by comma. Default: ${DEFAULT-VALUE}")
            List methods;
            @CommandLine.Option(names = { "-p", "--phases" }, required = false, split = ",", defaultValue = GraphGenConfig.ALL_PHASES,
                                description = "List of phases separated by comma. Default: ${DEFAULT-VALUE}")
            List phases;
        }

        @CommandLine.Option(names = "--llvm-opt-option", split = ",", description = "Pass options to the LLVM opt command")
        private List optOptions = new ArrayList();

        @CommandLine.Option(names = "--llvm-llc-option", split = ",", description = "Pass options to the LLVM llc command")
        private List llcOptions = new ArrayList();

        static class OptArgs {
            @CommandLine.Option(names = "--opt-memory-tracking", negatable = true, defaultValue = "false", description = "Enable/disable redundant store/load tracking and elimination")
            boolean optMemoryTracking;
            @CommandLine.Option(names = "--opt-inlining", negatable = true, defaultValue = "false", description = "Enable/disable inliner")
            boolean optInlining;
            @CommandLine.Option(names = "--no-opt-phis", negatable = true, defaultValue = "true", description = "Enable/disable `phi` elimination")
            boolean optPhis;
            @CommandLine.Option(names = "--no-opt-gotos", negatable = true, defaultValue = "true", description = "Enable/disable `goto` elimination")
            boolean optGotos;
            @CommandLine.Option(names = "--escape-analysis", negatable = true, defaultValue = "false", description = "Enable/disable escape analysis")
            boolean optEscapeAnalysis;
        }

        public CmdResult process(String[] args) {
            try {
                CommandLine commandLine = new CommandLine(this);
                ParseResult parseResult = commandLine.parseArgs(args);
                if (CommandLine.printHelpIfRequested(parseResult)) {
                    return CmdResult.CMD_RESULT_HELP;
                }
            } catch (ParameterException ex) { // command line arguments could not be parsed
                System.err.println(ex.getMessage());
                ex.getCommandLine().usage(System.err);
                return CmdResult.CMD_RESULT_ERROR;
            }

            if (debug) {
                Logger.getLogger("").setLevel(Level.DEBUG);
            }
            if (debugVTables) {
                Logger.getLogger("org.qbicc.plugin.dispatch.tables").setLevel(Level.DEBUG);
            }
            if (dispatchStats) {
                Logger.getLogger("org.qbicc.plugin.dispatch.stats").setLevel(Level.DEBUG);
            }
            if (debugReachability) {
                Logger.getLogger("org.qbicc.plugin.reachability").setLevel(Level.DEBUG);
            }
            if (debugSupers) {
                Logger.getLogger("org.qbicc.plugin.instanceofcheckcast.supers").setLevel(Level.DEBUG);
            }
            if (debugDevirt) {
                Logger.getLogger("org.qbicc.plugin.dispatch.devirt").setLevel(Level.DEBUG);
            }
            if (debugInterpreter) {
                Logger.getLogger("org.qbicc.interpreter").setLevel(Level.DEBUG);
            }
            if (heapStats) {
                Logger.getLogger("org.qbicc.plugin.serialization.stats").setLevel(Level.DEBUG);
            }
            if (methodDataStats) {
                Logger.getLogger("org.qbicc.plugin.methodinfo.stats").setLevel(Level.DEBUG);
            }
            if (stringPoolStats) {
                Logger.getLogger("org.qbicc.plugin.stringpool.stats").setLevel(Level.DEBUG);
            }
            if (outputPath == null) {
                outputPath = Path.of(System.getProperty("java.io.tmpdir"), "qbicc-output-" + Integer.toHexString(ThreadLocalRandom.current().nextInt()));
            }

            if (graphGenArgs != null && graphGenArgs.genGraph) {
                graphGenConfig.setEnabled(true);
                if (graphGenArgs.methodsAndPhases == null) {
                    graphGenConfig.addMethodAndPhase(GraphGenConfig.ALL_METHODS, GraphGenConfig.ALL_PHASES);
                } else {
                    for (GraphGenMethodsPhases option : graphGenArgs.methodsAndPhases) {
                        for (String method : option.methods) {
                            for (String phase : option.phases) {
                                graphGenConfig.addMethodAndPhase(method, phase);
                            }
                        }
                    }
                }
            }
            return CmdResult.CMD_RESULT_OK;
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    public static final class Builder {
        private final List bootPathsPrepend = new ArrayList<>();
        private final List bootPathsAppend = new ArrayList<>();
        private final List appPaths = new ArrayList<>();
        private String classLibVersion = MainProperties.CLASSLIB_DEFAULT_VERSION;
        private Path outputPath;
        private String outputName = "a.out";
        private Consumer> diagnosticsHandler = diagnostics -> {};
        private Platform platform = Platform.HOST_PLATFORM;
        private String mainClass;
        private String gc = "none";
        // TODO Detect whether the system uses PIEs by default and match that if possible
        private boolean isPie = false;
        private boolean compileOutput = true;
        private boolean optMemoryTracking = false;
        private boolean optInlining = false;
        private boolean optPhis = true;
        private boolean optGotos = true;
        private boolean optEscapeAnalysis = false;
        private GraphGenConfig graphGenConfig;
        private boolean smallTypeIds = false;
        private Backend backend = Backend.llvm;
        private List librarySearchPaths = List.of();
        private List buildFeatures = new ArrayList<>();
        private ClassPathResolver classPathResolver;
        private List optOptions = new ArrayList<>();
        private List llcOptions = new ArrayList<>();

        Builder() {}

        public Builder appendBootPath(ClassPathEntry entry) {
            Assert.checkNotNullParam("entry", entry);
            bootPathsAppend.add(entry);
            return this;
        }

        public Builder appendBootPaths(List entry) {
            Assert.checkNotNullParam("entry", entry);
            bootPathsAppend.addAll(entry);
            return this;
        }

        public Builder prependBootPath(ClassPathEntry entry) {
            Assert.checkNotNullParam("entry", entry);
            bootPathsPrepend.add(entry);
            return this;
        }

        public Builder prependBootPaths(List entry) {
            Assert.checkNotNullParam("entry", entry);
            bootPathsPrepend.addAll(entry);
            return this;
        }

        public Builder setClassLibVersion(String classLibVersion) {
            Assert.checkNotNullParam("classLibVersion", classLibVersion);
            this.classLibVersion = classLibVersion;
            return this;
        }

        public Builder addAppPath(ClassPathEntry entry) {
            Assert.checkNotNullParam("entry", entry);
            appPaths.add(entry);
            return this;
        }

        public Builder addAppPaths(List entry) {
            Assert.checkNotNullParam("entry", entry);
            appPaths.addAll(entry);
            return this;
        }

        public Builder addBuildFeatures(List features) {
            buildFeatures.addAll(features);
            return this;
        }

        public Builder setOutputPath(Path path) {
            Assert.checkNotNullParam("path", path);
            this.outputPath = path;
            return this;
        }

        public Builder setOutputName(String outputName) {
            Assert.checkNotNullParam("outputName", outputName);
            this.outputName = outputName;
            return this;
        }

        public Builder setPlatform(Platform platform) {
            Assert.checkNotNullParam("platform", platform);
            this.platform = platform;
            return this;
        }

        public Builder setDiagnosticsHandler(Consumer> handler) {
            Assert.checkNotNullParam("handler", handler);
            diagnosticsHandler = handler;
            return this;
        }

        public Builder processJarArgument(Path inputJar) {
            if (inputJar == null) {
                return this;
            }

            try {
                JarInputStream jarStream = new JarInputStream(new FileInputStream(inputJar.toAbsolutePath().toString()));
                appPaths.add(ClassPathEntry.of(inputJar));
                mainClass = jarStream.getManifest().getMainAttributes().getValue("Main-Class");
                String classPath = jarStream.getManifest().getMainAttributes().getValue("Class-Path");
                if (classPath != null && !classPath.equals("")) {
                    Path parentDir = inputJar.toAbsolutePath().getParent();
                    for (String e : classPath.split(" ")) {
                        if (!e.isEmpty()) {
                            ClassPathEntry cpe = ClassPathEntry.of(parentDir.resolve(e));
                            appPaths.add(cpe);
                        }
                    }
                }
            } catch (IOException e) {
                System.err.println("Error processing argument \"-jar "+inputJar+"\": "+e.getMessage());
            }
            return this;
        }

        public Builder setMainClass(String mainClass) {
            if (!mainClass.equals("")) {
                this.mainClass = mainClass;
            }
            return this;
        }

        public Builder setGc(String gc) {
            this.gc = Assert.checkNotNullParam("gc", gc);
            return this;
        }

        public Builder setIsPie(boolean isPie) {
            this.isPie = isPie;
            return this;
        }

        public Builder setGraphGenConfig(GraphGenConfig graphGenConfig) {
            Assert.checkNotNullParam("graphGenConfig", graphGenConfig);
            this.graphGenConfig = graphGenConfig;
            return this;
        }

        public Builder setCompileOutput(boolean compileOutput) {
            this.compileOutput = compileOutput;
            return this;
        }

        public Builder setOptMemoryTracking(boolean optMemoryTracking) {
            this.optMemoryTracking = optMemoryTracking;
            return this;
        }

        public Builder setOptInlining(boolean optInlining) {
            this.optInlining = optInlining;
            return this;
        }

        public Builder setOptPhis(boolean optPhis) {
            this.optPhis = optPhis;
            return this;
        }

        public Builder setOptGotos(boolean optGotos) {
            this.optGotos = optGotos;
            return this;
        }

        public Builder setOptEscapeAnalysis(boolean optEscapeAnalysis) {
            this.optEscapeAnalysis = optEscapeAnalysis;
            return this;
        }

        public Builder setBackend(Backend backend) {
            this.backend = Assert.checkNotNullParam("backend", backend);
            return this;
        }

        public Builder setSmallTypeIds(boolean smallTypeIds) {
            this.smallTypeIds = smallTypeIds;
            return this;
        }

        public Builder addLibrarySearchPaths(List librarySearchPaths) {
            if (librarySearchPaths != null && !librarySearchPaths.isEmpty()) {
                if (this.librarySearchPaths.isEmpty()) {
                    this.librarySearchPaths = List.copyOf(librarySearchPaths);
                } else {
                    Path[] p1 = this.librarySearchPaths.toArray(Path[]::new);
                    Path[] p2 = librarySearchPaths.toArray(Path[]::new);
                    Path[] finalPaths = Arrays.copyOf(p1, p1.length + p2.length);
                    System.arraycopy(p2, 0, finalPaths, p1.length, p2.length);
                    this.librarySearchPaths = List.of(finalPaths);
                }
            }
            return this;
        }

        public Builder setClassPathResolver(ClassPathResolver classPathResolver) {
            this.classPathResolver = classPathResolver;
            return this;
        }

        public Builder setOptOptions(List cmd) {
            optOptions.addAll(cmd);
            return this;
        }

        public Builder setLlcOptions(List cmd) {
            llcOptions.addAll(cmd);
            return this;
        }

        public Main build() {
            return new Main(this);
        }
    }

    static class MainProperties {
        static final String CLASSLIB_DEFAULT_VERSION;

        static final String QBICC_VERSION;

        static {
            Properties properties = new Properties();
            InputStream inputStream = MainProperties.class.getClassLoader().getResourceAsStream("main.properties");
            if (inputStream == null) {
                throw new Error("Missing main.properties");
            } else try (inputStream) {
                try (InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8)) {
                    try (BufferedReader br = new BufferedReader(reader)) {
                        properties.load(br);
                    }
                }
            } catch (IOException e) {
                throw new IOError(e);
            }
            CLASSLIB_DEFAULT_VERSION = properties.getProperty("classlib.default-version");

            QBICC_VERSION = properties.getProperty("qbicc.version");
        }
    }

    static class VersionProvider implements CommandLine.IVersionProvider {
        @Override
        public String[] getVersion() {
            return new String[] { "Qbicc version " + MainProperties.QBICC_VERSION };
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy