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

org.teavm.backend.wasm.WasmTarget Maven / Gradle / Ivy

There is a newer version: 0.2.8
Show newest version
/*
 *  Copyright 2016 Alexey Andreev.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.teavm.backend.wasm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import org.teavm.ast.InvocationExpr;
import org.teavm.ast.decompilation.Decompiler;
import org.teavm.backend.lowlevel.analyze.LowLevelInliningFilterFactory;
import org.teavm.backend.lowlevel.dependency.StringsDependencyListener;
import org.teavm.backend.lowlevel.generate.NameProvider;
import org.teavm.backend.lowlevel.transform.CoroutineTransformation;
import org.teavm.backend.wasm.binary.BinaryWriter;
import org.teavm.backend.wasm.generate.WasmClassGenerator;
import org.teavm.backend.wasm.generate.WasmDependencyListener;
import org.teavm.backend.wasm.generate.WasmGenerationContext;
import org.teavm.backend.wasm.generate.WasmGenerator;
import org.teavm.backend.wasm.generate.WasmInteropFunctionGenerator;
import org.teavm.backend.wasm.generate.WasmNameProvider;
import org.teavm.backend.wasm.generate.WasmSpecialFunctionGenerator;
import org.teavm.backend.wasm.generate.WasmStringPool;
import org.teavm.backend.wasm.generators.ArrayGenerator;
import org.teavm.backend.wasm.generators.WasmMethodGenerator;
import org.teavm.backend.wasm.generators.WasmMethodGeneratorContext;
import org.teavm.backend.wasm.intrinsics.AddressIntrinsic;
import org.teavm.backend.wasm.intrinsics.AllocatorIntrinsic;
import org.teavm.backend.wasm.intrinsics.ClassIntrinsic;
import org.teavm.backend.wasm.intrinsics.ConsoleIntrinsic;
import org.teavm.backend.wasm.intrinsics.DoubleIntrinsic;
import org.teavm.backend.wasm.intrinsics.ExceptionHandlingIntrinsic;
import org.teavm.backend.wasm.intrinsics.FloatIntrinsic;
import org.teavm.backend.wasm.intrinsics.FunctionIntrinsic;
import org.teavm.backend.wasm.intrinsics.GCIntrinsic;
import org.teavm.backend.wasm.intrinsics.IntegerIntrinsic;
import org.teavm.backend.wasm.intrinsics.LongIntrinsic;
import org.teavm.backend.wasm.intrinsics.MemoryTraceIntrinsic;
import org.teavm.backend.wasm.intrinsics.MutatorIntrinsic;
import org.teavm.backend.wasm.intrinsics.ObjectIntrinsic;
import org.teavm.backend.wasm.intrinsics.PlatformClassIntrinsic;
import org.teavm.backend.wasm.intrinsics.PlatformClassMetadataIntrinsic;
import org.teavm.backend.wasm.intrinsics.PlatformIntrinsic;
import org.teavm.backend.wasm.intrinsics.PlatformObjectIntrinsic;
import org.teavm.backend.wasm.intrinsics.RuntimeClassIntrinsic;
import org.teavm.backend.wasm.intrinsics.ShadowStackIntrinsic;
import org.teavm.backend.wasm.intrinsics.StructureIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmHeapIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsic;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactory;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicFactoryContext;
import org.teavm.backend.wasm.intrinsics.WasmIntrinsicManager;
import org.teavm.backend.wasm.intrinsics.WasmRuntimeIntrinsic;
import org.teavm.backend.wasm.model.WasmFunction;
import org.teavm.backend.wasm.model.WasmLocal;
import org.teavm.backend.wasm.model.WasmMemorySegment;
import org.teavm.backend.wasm.model.WasmModule;
import org.teavm.backend.wasm.model.WasmType;
import org.teavm.backend.wasm.model.expression.WasmBlock;
import org.teavm.backend.wasm.model.expression.WasmBranch;
import org.teavm.backend.wasm.model.expression.WasmCall;
import org.teavm.backend.wasm.model.expression.WasmConditional;
import org.teavm.backend.wasm.model.expression.WasmExpression;
import org.teavm.backend.wasm.model.expression.WasmGetLocal;
import org.teavm.backend.wasm.model.expression.WasmInt32Constant;
import org.teavm.backend.wasm.model.expression.WasmInt32Subtype;
import org.teavm.backend.wasm.model.expression.WasmIntBinary;
import org.teavm.backend.wasm.model.expression.WasmIntBinaryOperation;
import org.teavm.backend.wasm.model.expression.WasmIntType;
import org.teavm.backend.wasm.model.expression.WasmLoadInt32;
import org.teavm.backend.wasm.model.expression.WasmReturn;
import org.teavm.backend.wasm.model.expression.WasmSetLocal;
import org.teavm.backend.wasm.model.expression.WasmStoreInt32;
import org.teavm.backend.wasm.model.expression.WasmUnreachable;
import org.teavm.backend.wasm.optimization.UnusedFunctionElimination;
import org.teavm.backend.wasm.render.WasmBinaryRenderer;
import org.teavm.backend.wasm.render.WasmBinaryVersion;
import org.teavm.backend.wasm.render.WasmBinaryWriter;
import org.teavm.backend.wasm.render.WasmCRenderer;
import org.teavm.backend.wasm.render.WasmRenderer;
import org.teavm.backend.wasm.transformation.IndirectCallTraceTransformation;
import org.teavm.backend.wasm.transformation.MemoryAccessTraceTransformation;
import org.teavm.common.ServiceRepository;
import org.teavm.dependency.ClassDependency;
import org.teavm.dependency.DependencyAnalyzer;
import org.teavm.dependency.DependencyListener;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.Address;
import org.teavm.interop.DelegateTo;
import org.teavm.interop.Import;
import org.teavm.interop.Memory;
import org.teavm.interop.Platforms;
import org.teavm.interop.StaticInit;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHierarchy;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassHolderTransformer;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.FieldReader;
import org.teavm.model.FieldReference;
import org.teavm.model.Instruction;
import org.teavm.model.ListableClassHolderSource;
import org.teavm.model.ListableClassReaderSource;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.ValueType;
import org.teavm.model.analysis.ClassMetadataRequirements;
import org.teavm.model.classes.TagRegistry;
import org.teavm.model.classes.VirtualTableBuilder;
import org.teavm.model.classes.VirtualTableProvider;
import org.teavm.model.instructions.CloneArrayInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.lowlevel.CallSiteDescriptor;
import org.teavm.model.lowlevel.Characteristics;
import org.teavm.model.lowlevel.CheckInstructionTransformation;
import org.teavm.model.lowlevel.ClassInitializerEliminator;
import org.teavm.model.lowlevel.ClassInitializerTransformer;
import org.teavm.model.lowlevel.LowLevelNullCheckFilter;
import org.teavm.model.lowlevel.ShadowStackTransformer;
import org.teavm.model.lowlevel.WriteBarrierInsertion;
import org.teavm.model.optimization.InliningFilterFactory;
import org.teavm.model.transformation.BoundCheckInsertion;
import org.teavm.model.transformation.ClassPatch;
import org.teavm.model.transformation.NullCheckInsertion;
import org.teavm.model.util.AsyncMethodFinder;
import org.teavm.runtime.Allocator;
import org.teavm.runtime.EventQueue;
import org.teavm.runtime.ExceptionHandling;
import org.teavm.runtime.Fiber;
import org.teavm.runtime.GC;
import org.teavm.runtime.RuntimeArray;
import org.teavm.runtime.RuntimeClass;
import org.teavm.runtime.RuntimeObject;
import org.teavm.runtime.ShadowStack;
import org.teavm.runtime.WasiMain;
import org.teavm.vm.BuildTarget;
import org.teavm.vm.TeaVMEntryPoint;
import org.teavm.vm.TeaVMTarget;
import org.teavm.vm.TeaVMTargetController;
import org.teavm.vm.spi.TeaVMHostExtension;

public class WasmTarget implements TeaVMTarget, TeaVMWasmHost {
    private static final MethodReference INIT_HEAP_REF = new MethodReference(WasmHeap.class, "initHeap",
            Address.class, int.class, int.class, int.class, void.class);
    private static final MethodReference RESIZE_HEAP_REF = new MethodReference(WasmHeap.class, "resizeHeap",
            int.class, void.class);
    private static final Set VIRTUAL_METHODS = new HashSet<>(Arrays.asList(
            new MethodReference(Object.class, "clone", Object.class)));
    private static final MethodReference WASI_START_MAIN = new MethodReference(WasiMain.class, "startMain",
            void.class);

    private TeaVMTargetController controller;
    private Characteristics characteristics;
    private boolean debugging;
    private boolean wastEmitted;
    private boolean cEmitted;
    private boolean cLineNumbersEmitted = Boolean.parseBoolean(System.getProperty("wasm.c.lineNumbers", "false"));
    private ClassInitializerEliminator classInitializerEliminator;
    private ClassInitializerTransformer classInitializerTransformer;
    private ShadowStackTransformer shadowStackTransformer;
    private WriteBarrierInsertion writeBarrierInsertion;
    private WasmBinaryVersion version = WasmBinaryVersion.V_0x1;
    private List additionalIntrinsics = new ArrayList<>();
    private NullCheckInsertion nullCheckInsertion;
    private BoundCheckInsertion boundCheckInsertion = new BoundCheckInsertion();
    private CheckInstructionTransformation checkTransformation = new CheckInstructionTransformation();
    private int minHeapSize = 2 * 1024 * 1024;
    private int maxHeapSize = 128 * 1024 * 1024;
    private boolean obfuscated;
    private Set asyncMethods;
    private boolean hasThreads;
    private final WasmDependencyListener wasmDependencyListener = new WasmDependencyListener();

    @Override
    public void setController(TeaVMTargetController controller) {
        this.controller = controller;
        characteristics = new Characteristics(controller.getUnprocessedClassSource());
        classInitializerEliminator = new ClassInitializerEliminator(controller.getUnprocessedClassSource());
        classInitializerTransformer = new ClassInitializerTransformer();
        shadowStackTransformer = new ShadowStackTransformer(characteristics, true);
        nullCheckInsertion = new NullCheckInsertion(new LowLevelNullCheckFilter(characteristics));
        writeBarrierInsertion = new WriteBarrierInsertion(characteristics);

        controller.addVirtualMethods(VIRTUAL_METHODS::contains);
    }

    @Override
    public void add(WasmIntrinsicFactory intrinsic) {
        additionalIntrinsics.add(intrinsic);
    }

    @Override
    public List getHostExtensions() {
        return Collections.singletonList(this);
    }

    @Override
    public boolean requiresRegisterAllocation() {
        return true;
    }

    @Override
    public List getTransformers() {
        List transformers = new ArrayList<>();
        transformers.add(new ClassPatch());
        transformers.add(wasmDependencyListener);
        return transformers;
    }

    @Override
    public List getDependencyListeners() {
        List listeners = new ArrayList<>();
        listeners.add(wasmDependencyListener);
        return listeners;
    }

    public boolean isDebugging() {
        return debugging;
    }

    public void setDebugging(boolean debugging) {
        this.debugging = debugging;
    }

    public boolean isWastEmitted() {
        return wastEmitted;
    }

    public void setWastEmitted(boolean wastEmitted) {
        this.wastEmitted = wastEmitted;
    }

    public boolean isCEmitted() {
        return cEmitted;
    }

    public void setCEmitted(boolean cEmitted) {
        this.cEmitted = cEmitted;
    }

    public void setCLineNumbersEmitted(boolean cLineNumbersEmitted) {
        this.cLineNumbersEmitted = cLineNumbersEmitted;
    }

    public WasmBinaryVersion getVersion() {
        return version;
    }

    public void setVersion(WasmBinaryVersion version) {
        this.version = version;
    }

    public void setMinHeapSize(int minHeapSize) {
        this.minHeapSize = minHeapSize;
    }

    public void setMaxHeapSize(int maxHeapSize) {
        this.maxHeapSize = maxHeapSize;
    }

    public void setObfuscated(boolean obfuscated) {
        this.obfuscated = obfuscated;
    }

    @Override
    public void contributeDependencies(DependencyAnalyzer dependencyAnalyzer) {
        for (Class type : Arrays.asList(int.class, long.class, float.class, double.class)) {
            MethodReference method = new MethodReference(WasmRuntime.class, "compare", type, type, int.class);
            dependencyAnalyzer.linkMethod(method).use();
        }
        for (Class type : Arrays.asList(float.class, double.class)) {
            MethodReference method = new MethodReference(WasmRuntime.class, "remainder", type, type, type);
            dependencyAnalyzer.linkMethod(method).use();
        }

        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "align", Address.class, int.class,
                Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "fill", Address.class, int.class,
                int.class, void.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "fillZero", Address.class, int.class,
                void.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "moveMemoryBlock", Address.class,
                Address.class, int.class, void.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "allocStack",
                int.class, Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getStackTop", Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getNextStackFrame", Address.class,
                Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getStackRootCount", Address.class,
                int.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getStackRootPointer", Address.class,
                Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "setExceptionHandlerId", Address.class,
                int.class, void.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "getCallSiteId", Address.class,
                int.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "resourceMapKeys", Address.class,
                String[].class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "lookupResource", Address.class,
                String.class, Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "wasiPrintString",
                String.class, void.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "wasiPrintInt",
                int.class, void.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(WasmRuntime.class, "wasiPrintOutOfMemory",
                void.class)).use();

        dependencyAnalyzer.linkMethod(INIT_HEAP_REF).use();
        dependencyAnalyzer.linkMethod(RESIZE_HEAP_REF).use();

        dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocate",
                RuntimeClass.class, Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocateArray",
                RuntimeClass.class, int.class, Address.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "allocateMultiArray",
                RuntimeClass.class, Address.class, int.class, RuntimeArray.class)).use();

        dependencyAnalyzer.linkMethod(new MethodReference(Allocator.class, "", void.class)).use();

        dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "throwException",
                Throwable.class, void.class)).use();

        dependencyAnalyzer.linkMethod(new MethodReference(ExceptionHandling.class, "catchException",
                Throwable.class)).use();

        dependencyAnalyzer.linkField(new FieldReference("java.lang.Object", "monitor"));

        dependencyAnalyzer.linkMethod(new MethodReference(String.class, "allocate", int.class, String.class))
                .use();

        ClassDependency runtimeClassDep = dependencyAnalyzer.linkClass(RuntimeClass.class.getName());
        ClassDependency runtimeObjectDep = dependencyAnalyzer.linkClass(RuntimeObject.class.getName());
        ClassDependency runtimeArrayDep = dependencyAnalyzer.linkClass(RuntimeArray.class.getName());
        for (ClassDependency classDep : Arrays.asList(runtimeClassDep, runtimeObjectDep, runtimeArrayDep)) {
            for (FieldReader field : classDep.getClassReader().getFields()) {
                dependencyAnalyzer.linkField(field.getReference());
            }
        }

        dependencyAnalyzer.linkMethod(new MethodReference(Fiber.class, "isResuming", boolean.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(Fiber.class, "isSuspending", boolean.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(Fiber.class, "current", Fiber.class)).use();
        dependencyAnalyzer.linkMethod(WASI_START_MAIN).use();
        dependencyAnalyzer.linkMethod(new MethodReference(EventQueue.class, "processSingle", void.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(EventQueue.class, "isStopped", boolean.class)).use();
        dependencyAnalyzer.linkMethod(new MethodReference(Thread.class, "setCurrentThread", Thread.class,
                void.class)).use();

        ClassReader fiberClass = dependencyAnalyzer.getClassSource().get(Fiber.class.getName());
        for (MethodReader method : fiberClass.getMethods()) {
            if (method.getName().startsWith("pop") || method.getName().equals("push")) {
                dependencyAnalyzer.linkMethod(method.getReference()).use();
            }
        }

        dependencyAnalyzer.addDependencyListener(new StringsDependencyListener());
    }

    @Override
    public void analyzeBeforeOptimizations(ListableClassReaderSource classSource) {
        AsyncMethodFinder asyncFinder = new AsyncMethodFinder(controller.getDependencyInfo().getCallGraph(),
                controller.getDependencyInfo());
        asyncFinder.find(classSource);
        asyncMethods = new HashSet<>(asyncFinder.getAsyncMethods());
        asyncMethods.addAll(asyncFinder.getAsyncFamilyMethods());
        hasThreads = asyncFinder.hasAsyncMethods();
    }

    @Override
    public void beforeOptimizations(Program program, MethodReader method) {
        nullCheckInsertion.transformProgram(program, method.getReference());
        boundCheckInsertion.transformProgram(program, method.getReference());
    }

    @Override
    public void afterOptimizations(Program program, MethodReader method) {
        classInitializerEliminator.apply(program);
        classInitializerTransformer.transform(program);
        new CoroutineTransformation(controller.getUnprocessedClassSource(), asyncMethods, hasThreads)
                .apply(program, method.getReference());
        checkTransformation.apply(program, method.getResultType());
        shadowStackTransformer.apply(program, method);
        writeBarrierInsertion.apply(program);
    }

    @Override
    public void emit(ListableClassHolderSource classes, BuildTarget buildTarget, String outputName)
            throws IOException {
        WasmModule module = new WasmModule();
        WasmFunction initFunction = new WasmFunction("__start__");

        VirtualTableProvider vtableProvider = createVirtualTableProvider(classes);
        ClassHierarchy hierarchy = new ClassHierarchy(classes);
        TagRegistry tagRegistry = new TagRegistry(classes, hierarchy);
        BinaryWriter binaryWriter = new BinaryWriter(Memory.HEAP_OFFSET);
        NameProvider names = new WasmNameProvider();
        ClassMetadataRequirements metadataRequirements = new ClassMetadataRequirements(controller.getDependencyInfo());
        WasmClassGenerator classGenerator = new WasmClassGenerator(classes, controller.getUnprocessedClassSource(),
                vtableProvider, tagRegistry, binaryWriter, names, metadataRequirements,
                controller.getClassInitializerInfo());

        Decompiler decompiler = new Decompiler(classes, new HashSet<>(), false);
        WasmStringPool stringPool = classGenerator.getStringPool();
        WasmGenerationContext context = new WasmGenerationContext(classes, module, controller.getDiagnostics(),
                vtableProvider, tagRegistry, stringPool, names);

        context.addIntrinsic(new AddressIntrinsic(classGenerator));
        context.addIntrinsic(new StructureIntrinsic(classes, classGenerator));
        context.addIntrinsic(new FunctionIntrinsic(classGenerator));
        WasmRuntimeIntrinsic wasmRuntimeIntrinsic = new WasmRuntimeIntrinsic();
        context.addIntrinsic(wasmRuntimeIntrinsic);
        context.addIntrinsic(new AllocatorIntrinsic(classGenerator));
        context.addIntrinsic(new PlatformIntrinsic());
        context.addIntrinsic(new PlatformClassIntrinsic());
        context.addIntrinsic(new PlatformObjectIntrinsic(classGenerator));
        context.addIntrinsic(new PlatformClassMetadataIntrinsic());
        context.addIntrinsic(new ClassIntrinsic());
        context.addIntrinsic(new RuntimeClassIntrinsic());
        context.addIntrinsic(new FloatIntrinsic());
        context.addIntrinsic(new DoubleIntrinsic());
        context.addIntrinsic(new LongIntrinsic());
        context.addIntrinsic(new IntegerIntrinsic());
        context.addIntrinsic(new ObjectIntrinsic());
        context.addIntrinsic(new ConsoleIntrinsic());
        context.addGenerator(new ArrayGenerator());
        if (!Boolean.parseBoolean(System.getProperty("teavm.wasm.vmAssertions", "false"))) {
            context.addIntrinsic(new MemoryTraceIntrinsic());
        }
        context.addIntrinsic(new WasmHeapIntrinsic());
        context.addIntrinsic(new FiberIntrinsic());

        IntrinsicFactoryContext intrinsicFactoryContext = new IntrinsicFactoryContext();
        for (WasmIntrinsicFactory additionalIntrinsicFactory : additionalIntrinsics) {
            context.addIntrinsic(additionalIntrinsicFactory.create(intrinsicFactoryContext));
        }

        GCIntrinsic gcIntrinsic = new GCIntrinsic();
        context.addIntrinsic(gcIntrinsic);
        MutatorIntrinsic mutatorIntrinsic = new MutatorIntrinsic();
        context.addIntrinsic(mutatorIntrinsic);
        context.addIntrinsic(new ShadowStackIntrinsic());
        ExceptionHandlingIntrinsic exceptionHandlingIntrinsic = new ExceptionHandlingIntrinsic(binaryWriter,
                classGenerator, stringPool, obfuscated);
        context.addIntrinsic(exceptionHandlingIntrinsic);

        WasmGenerator generator = new WasmGenerator(decompiler, classes, context, classGenerator, binaryWriter,
                asyncMethods::contains);

        generateMethods(classes, context, generator, classGenerator, binaryWriter, module);
        new WasmInteropFunctionGenerator(classGenerator).generateFunctions(module);
        exceptionHandlingIntrinsic.postProcess(CallSiteDescriptor.extract(classes, classes.getClassNames()));
        generateIsSupertypeFunctions(tagRegistry, module, classGenerator);
        classGenerator.postProcess();
        new WasmSpecialFunctionGenerator(classGenerator, gcIntrinsic.regionSizeExpressions)
                .generateSpecialFunctions(module);
        mutatorIntrinsic.setStaticGcRootsAddress(classGenerator.getStaticGcRootsAddress());
        mutatorIntrinsic.setClassesAddress(classGenerator.getClassesAddress());
        mutatorIntrinsic.setClassCount(classGenerator.getClassCount());

        WasmMemorySegment dataSegment = new WasmMemorySegment();
        dataSegment.setData(binaryWriter.getData());
        dataSegment.setOffset(Memory.HEAP_OFFSET);
        module.getSegments().add(dataSegment);

        renderMemoryLayout(module, binaryWriter.getAddress(), gcIntrinsic);
        renderClinit(classes, classGenerator, module);
        if (controller.wasCancelled()) {
            return;
        }

        generateInitFunction(classes, initFunction, names, binaryWriter.getAddress());
        module.add(initFunction);
        module.setStartFunction(initFunction);
        module.add(createStartFunction(names));
        module.setCustomSections(wasmDependencyListener.getCustomSections());

        for (String functionName : classGenerator.getFunctionTable()) {
            WasmFunction function = module.getFunctions().get(functionName);
            assert function != null : "Function referenced from function table not found: " + functionName;
            module.getFunctionTable().add(function);
        }

        new UnusedFunctionElimination(module).apply();

        if (Boolean.parseBoolean(System.getProperty("wasm.memoryTrace", "false"))) {
            new MemoryAccessTraceTransformation(module).apply();
        }
        if (Boolean.parseBoolean(System.getProperty("wasm.indirectCallTrace", "false"))) {
            new IndirectCallTraceTransformation(module).apply();
        }

        WasmBinaryWriter writer = new WasmBinaryWriter();
        WasmBinaryRenderer renderer = new WasmBinaryRenderer(writer, version, obfuscated);
        renderer.render(module);

        try (OutputStream output = buildTarget.createResource(outputName)) {
            output.write(writer.getData());
            output.flush();
        }

        if (wastEmitted) {
            emitWast(module, buildTarget, getBaseName(outputName) + ".wast");
        }
        if (cEmitted) {
            emitC(module, buildTarget, getBaseName(outputName) + ".wasm.c");
        }

        emitRuntime(buildTarget, getBaseName(outputName) + ".wasm-runtime.js");
    }

    private WasmFunction createStartFunction(NameProvider names) {
        WasmFunction function = new WasmFunction("teavm_start");
        function.setExportName("_start");
        function.getBody().add(new WasmCall(names.forMethod(WASI_START_MAIN)));
        return function;
    }

    private class IntrinsicFactoryContext implements WasmIntrinsicFactoryContext {
        @Override
        public ClassReaderSource getClassSource() {
            return controller.getUnprocessedClassSource();
        }

        @Override
        public ClassLoader getClassLoader() {
            return controller.getClassLoader();
        }

        @Override
        public ServiceRepository getServices() {
            return controller.getServices();
        }

        @Override
        public Properties getProperties() {
            return controller.getProperties();
        }
    }

    private void generateInitFunction(ListableClassReaderSource classes, WasmFunction initFunction,
            NameProvider names, int heapAddress) {

        for (Class javaCls : new Class[] { WasmRuntime.class, WasmHeap.class }) {
            ClassReader cls = classes.get(javaCls.getName());
            MethodReader clinit = cls.getMethod(new MethodDescriptor("", void.class));
            if (clinit == null) {
                continue;
            }
            initFunction.getBody().add(new WasmCall(names.forClassInitializer(cls.getName())));
        }

        initFunction.getBody().add(new WasmCall(names.forMethod(INIT_HEAP_REF),
                new WasmInt32Constant(heapAddress), new WasmInt32Constant(minHeapSize),
                new WasmInt32Constant(maxHeapSize), new WasmInt32Constant(WasmHeap.DEFAULT_STACK_SIZE)));

        for (Class javaCls : new Class[] { GC.class }) {
            ClassReader cls = classes.get(javaCls.getName());
            MethodReader clinit = cls.getMethod(new MethodDescriptor("", void.class));
            if (clinit == null) {
                continue;
            }
            initFunction.getBody().add(new WasmCall(names.forClassInitializer(cls.getName())));
        }

        for (String className : classes.getClassNames()) {
            if (className.equals(WasmRuntime.class.getName()) || className.equals(WasmHeap.class.getName())
                    || className.equals(GC.class.getName())) {
                continue;
            }
            ClassReader cls = classes.get(className);
            if (cls.getAnnotations().get(StaticInit.class.getName()) == null) {
                continue;
            }
            MethodReader clinit = cls.getMethod(new MethodDescriptor("", void.class));
            if (clinit == null) {
                continue;
            }
            initFunction.getBody().add(new WasmCall(names.forClassInitializer(className)));
        }
    }

    private String getBaseName(String name) {
        int index = name.lastIndexOf('.');
        return index < 0 ? name : name.substring(0, index);
    }

    private void emitWast(WasmModule module, BuildTarget buildTarget, String outputName) throws IOException {
        WasmRenderer renderer = new WasmRenderer();
        renderer.setLineNumbersEmitted(debugging);
        renderer.render(module);
        try (OutputStream output = buildTarget.createResource(outputName);
                Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
            writer.write(renderer.toString());
        }
    }

    private void emitC(WasmModule module, BuildTarget buildTarget, String outputName) throws IOException {
        WasmCRenderer renderer = new WasmCRenderer();
        renderer.setLineNumbersEmitted(cLineNumbersEmitted);
        renderer.setMemoryAccessChecked(Boolean.parseBoolean(System.getProperty("wasm.c.assertMemory", "false")));
        renderer.render(module);
        try (OutputStream output = buildTarget.createResource(outputName);
                Writer writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
            writer.write(renderer.toString());
        }
    }

    private void emitRuntime(BuildTarget buildTarget, String outputName) throws IOException {
        ClassLoader loader = controller.getClassLoader();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(
                loader.getResourceAsStream("org/teavm/backend/wasm/wasm-runtime.js"), StandardCharsets.UTF_8));
                Writer writer = new OutputStreamWriter(buildTarget.createResource(outputName),
                        StandardCharsets.UTF_8)) {
            while (true) {
                String line = reader.readLine();
                if (line == null) {
                    break;
                }
                writer.append(line).append('\n');
            }
        }

        String[] paths = new String[] {
            "node_modules/@bjorn3/browser_wasi_shim/src/fd.js",
            "node_modules/@bjorn3/browser_wasi_shim/src/fs_fd.js",
            "node_modules/@bjorn3/browser_wasi_shim/src/fs_core.js",
            "node_modules/@bjorn3/browser_wasi_shim/src/wasi.js",
            "node_modules/@bjorn3/browser_wasi_shim/src/wasi_defs.js",
        };

        for (String path : paths) {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(
                    loader.getResourceAsStream("org/teavm/backend/wasm/" + path), StandardCharsets.UTF_8));
                 Writer writer = new OutputStreamWriter(buildTarget.createResource(path),
                         StandardCharsets.UTF_8)) {
                while (true) {
                    String line = reader.readLine();
                    if (line == null) {
                        break;
                    }
                    writer.append(line).append('\n');
                }
            }
        }
    }

    private void generateMethods(ListableClassHolderSource classes, WasmGenerationContext context,
            WasmGenerator generator, WasmClassGenerator classGenerator, BinaryWriter binaryWriter, WasmModule module) {
        List methods = new ArrayList<>();
        for (String className : classes.getClassNames()) {
            ClassHolder cls = classes.get(className);
            for (MethodHolder method : cls.getMethods()) {
                if (method.hasModifier(ElementModifier.ABSTRACT)
                        || context.getIntrinsic(method.getReference()) != null) {
                    continue;
                }
                module.add(generator.generateDefinition(method.getReference()));
                methods.add(method);
            }
        }

        MethodGeneratorContextImpl methodGeneratorContext = new MethodGeneratorContextImpl(binaryWriter,
                context.getStringPool(), context.getDiagnostics(), context.names, classGenerator, classes);

        for (MethodHolder method : methods) {
            ClassHolder cls = classes.get(method.getOwnerName());

            MethodHolder implementor = method;
            AnnotationHolder delegateAnnot = method.getAnnotations().get(DelegateTo.class.getName());
            if (delegateAnnot != null) {
                String methodName = delegateAnnot.getValue("value").getString();
                boolean found = false;
                for (MethodHolder candidate : cls.getMethods()) {
                    if (candidate.getName().equals(methodName)) {
                        if (found) {
                            controller.getDiagnostics().error(new CallLocation(method.getReference()),
                                    "Method is delegated to " + methodName + " but several implementations "
                                            + "found");
                            break;
                        }
                        implementor = candidate;
                        found = true;
                    }
                }
            }

            if (implementor.hasModifier(ElementModifier.NATIVE)) {
                WasmMethodGenerator methodGenerator = context.getGenerator(method.getReference());
                if (methodGenerator != null) {
                    WasmFunction function = context.getFunction(context.names.forMethod(method.getReference()));
                    methodGenerator.apply(method.getReference(), function, methodGeneratorContext);
                } else if (!isShadowStackMethod(method.getReference())) {
                    if (context.getImportedMethod(method.getReference()) == null) {
                        CallLocation location = new CallLocation(method.getReference());
                        controller.getDiagnostics().error(location, "Method {{m0}} is native but "
                                + "has no {{c1}} annotation on it", method.getReference(), Import.class.getName());
                    }
                    generator.generateNative(method.getReference());
                }
                continue;
            }
            if (implementor.getProgram() == null || implementor.getProgram().basicBlockCount() == 0) {
                continue;
            }
            if (method == implementor) {
                generator.generate(method.getReference(), implementor);
            } else {
                generateStub(context.names, module, method, implementor);
            }
            if (controller.wasCancelled()) {
                return;
            }
        }
    }

    private boolean isShadowStackMethod(MethodReference method) {
        if (!method.getClassName().equals(ShadowStack.class.getName())) {
            return false;
        }
        switch (method.getName()) {
            case "allocStack":
            case "registerGCRoot":
            case "removeGCRoot":
            case "releaseStack":
                return true;
            default:
                return false;
        }
    }

    private void generateIsSupertypeFunctions(TagRegistry tagRegistry, WasmModule module,
            WasmClassGenerator classGenerator) {
        for (ValueType type : classGenerator.getRegisteredClasses()) {
            WasmFunction function = new WasmFunction(classGenerator.names.forSupertypeFunction(type));
            function.getParameters().add(WasmType.INT32);
            function.setResult(WasmType.INT32);
            module.add(function);

            WasmLocal subtypeVar = new WasmLocal(WasmType.INT32, "subtype");
            function.add(subtypeVar);

            if (type instanceof ValueType.Object) {
                String className = ((ValueType.Object) type).getClassName();
                generateIsClass(subtypeVar, classGenerator, tagRegistry, className, function.getBody());
            } else if (type instanceof ValueType.Array) {
                ValueType itemType = ((ValueType.Array) type).getItemType();
                generateIsArray(subtypeVar, classGenerator, itemType, function.getBody());
            } else {
                int expected = classGenerator.getClassPointer(type);
                WasmExpression condition = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.EQ,
                        new WasmGetLocal(subtypeVar), new WasmInt32Constant(expected));
                function.getBody().add(new WasmReturn(condition));
            }
        }
    }

    private void generateIsClass(WasmLocal subtypeVar, WasmClassGenerator classGenerator, TagRegistry tagRegistry,
            String className, List body) {
        List ranges = tagRegistry.getRanges(className);
        if (ranges.isEmpty()) {
            body.add(new WasmReturn(new WasmInt32Constant(0)));
            return;
        }

        int tagOffset = classGenerator.getFieldOffset(new FieldReference(RuntimeClass.class.getName(), "tag"));

        WasmExpression tagExpression = new WasmGetLocal(subtypeVar);
        tagExpression = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, tagExpression,
                new WasmInt32Constant(tagOffset));
        tagExpression = new WasmLoadInt32(4, tagExpression, WasmInt32Subtype.INT32);
        body.add(new WasmSetLocal(subtypeVar, tagExpression));

        ranges.sort(Comparator.comparingInt(range -> range.lower));

        int lower = ranges.get(0).lower;
        int upper = ranges.get(ranges.size() - 1).upper;

        WasmExpression lowerCondition = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.LT_SIGNED,
                new WasmGetLocal(subtypeVar), new WasmInt32Constant(lower));
        WasmConditional testLower = new WasmConditional(lowerCondition);
        testLower.getThenBlock().getBody().add(new WasmReturn(new WasmInt32Constant(0)));
        body.add(testLower);

        WasmExpression upperCondition = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.GE_SIGNED,
                new WasmGetLocal(subtypeVar), new WasmInt32Constant(upper));
        WasmConditional testUpper = new WasmConditional(upperCondition);
        testUpper.getThenBlock().getBody().add(new WasmReturn(new WasmInt32Constant(0)));
        body.add(testUpper);

        for (int i = 1; i < ranges.size(); ++i) {
            int lowerHole = ranges.get(i - 1).upper;
            int upperHole = ranges.get(i).lower;

            lowerCondition = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.GT_SIGNED,
                    new WasmGetLocal(subtypeVar), new WasmInt32Constant(lowerHole));
            testLower = new WasmConditional(lowerCondition);
            body.add(testLower);

            upperCondition = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.LT_SIGNED,
                    new WasmGetLocal(subtypeVar), new WasmInt32Constant(upperHole));
            testUpper = new WasmConditional(upperCondition);
            testUpper.getThenBlock().getBody().add(new WasmReturn(new WasmInt32Constant(0)));

            testLower.getThenBlock().getBody().add(testUpper);
        }

        body.add(new WasmReturn(new WasmInt32Constant(1)));
    }

    private void generateIsArray(WasmLocal subtypeVar, WasmClassGenerator classGenerator, ValueType itemType,
            List body) {
        int itemOffset = classGenerator.getFieldOffset(new FieldReference(RuntimeClass.class.getName(), "itemType"));

        WasmExpression itemExpression = new WasmGetLocal(subtypeVar);
        itemExpression = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.ADD, itemExpression,
                new WasmInt32Constant(itemOffset));
        itemExpression = new WasmLoadInt32(4, itemExpression, WasmInt32Subtype.INT32);
        body.add(new WasmSetLocal(subtypeVar, itemExpression));

        WasmExpression itemCondition = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.EQ,
                new WasmGetLocal(subtypeVar), new WasmInt32Constant(0));
        WasmConditional itemTest = new WasmConditional(itemCondition);
        itemTest.setType(WasmType.INT32);
        itemTest.getThenBlock().getBody().add(new WasmInt32Constant(0));

        WasmCall delegateToItem = new WasmCall(classGenerator.names.forSupertypeFunction(itemType));
        delegateToItem.getArguments().add(new WasmGetLocal(subtypeVar));
        itemTest.getElseBlock().getBody().add(delegateToItem);

        body.add(new WasmReturn(itemTest));
    }

    private void generateStub(NameProvider names, WasmModule module, MethodHolder method, MethodHolder implementor) {
        WasmFunction function = module.getFunctions().get(names.forMethod(method.getReference()));

        WasmCall call = new WasmCall(names.forMethod(implementor.getReference()));
        for (WasmType param : function.getParameters()) {
            WasmLocal local = new WasmLocal(param);
            function.add(local);
            call.getArguments().add(new WasmGetLocal(local));
        }

        if (method.getResultType() == ValueType.VOID) {
            function.getBody().add(call);
        } else {
            function.getBody().add(new WasmReturn(call));
        }
    }

    private void renderClinit(ListableClassReaderSource classes, WasmClassGenerator classGenerator,
            WasmModule module) {
        for (ValueType type : classGenerator.getRegisteredClasses()) {
            if (!(type instanceof ValueType.Object)) {
                continue;
            }
            String className = ((ValueType.Object) type).getClassName();
            if (classGenerator.isStructure(className)) {
                continue;
            }

            ClassReader cls = classes.get(className);
            if (cls == null) {
                continue;
            }
            MethodReader method = cls.getMethod(new MethodDescriptor("", void.class));
            if (method == null) {
                continue;
            }

            WasmFunction initFunction = new WasmFunction(classGenerator.names.forClassInitializer(className));
            module.add(initFunction);

            WasmBlock block = new WasmBlock(false);

            int index = classGenerator.getClassPointer(ValueType.object(className))
                    + classGenerator.getFieldOffset(new FieldReference(RuntimeClass.class.getName(), "flags"));
            WasmExpression initFlag = new WasmLoadInt32(4, new WasmInt32Constant(index), WasmInt32Subtype.INT32);
            initFlag = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.AND, initFlag,
                    new WasmInt32Constant(RuntimeClass.INITIALIZED));
            block.getBody().add(new WasmBranch(initFlag, block));
            initFunction.getBody().add(block);

            initFlag = new WasmLoadInt32(4, new WasmInt32Constant(index), WasmInt32Subtype.INT32);
            initFlag = new WasmIntBinary(WasmIntType.INT32, WasmIntBinaryOperation.OR, initFlag,
                    new WasmInt32Constant(RuntimeClass.INITIALIZED));
            block.getBody().add(new WasmStoreInt32(4, new WasmInt32Constant(index), initFlag,
                    WasmInt32Subtype.INT32));

            block.getBody().add(new WasmCall(classGenerator.names.forMethod(method.getReference())));

            if (controller.wasCancelled()) {
                break;
            }
        }
    }

    private void renderMemoryLayout(WasmModule module, int address, GCIntrinsic gcIntrinsic) {
        module.setMinMemorySize(WasmRuntime.align(address, WasmHeap.PAGE_SIZE) / WasmHeap.PAGE_SIZE);

        int newStorageSize = WasmHeap.calculateStorageSize(maxHeapSize);
        int newRegionsCount = WasmHeap.calculateRegionsCount(maxHeapSize, WasmHeap.DEFAULT_REGION_SIZE);
        int newRegionsSize = WasmHeap.calculateRegionsSize(newRegionsCount);

        address = WasmRuntime.align(address + WasmHeap.DEFAULT_STACK_SIZE, 16);
        address = WasmRuntime.align(address + maxHeapSize, 16);
        address = WasmRuntime.align(address + newRegionsSize, 16);
        address = WasmRuntime.align(address + newRegionsCount, 16);
        address = WasmRuntime.align(address + newStorageSize, 16);
        gcIntrinsic.setRegionSize(WasmHeap.DEFAULT_REGION_SIZE);

        module.setMaxMemorySize(WasmRuntime.align(address, WasmHeap.PAGE_SIZE) / WasmHeap.PAGE_SIZE);
    }

    private VirtualTableProvider createVirtualTableProvider(ListableClassHolderSource classes) {
        VirtualTableBuilder builder = new VirtualTableBuilder(classes);
        builder.setMethodsUsedAtCallSites(getMethodsUsedOnCallSites(classes));
        builder.setMethodCalledVirtually(controller::isVirtual);
        return builder.build();
    }

    private Set getMethodsUsedOnCallSites(ListableClassHolderSource classes) {
        Set virtualMethods = new HashSet<>();

        for (String className : classes.getClassNames()) {
            ClassHolder cls = classes.get(className);
            for (MethodHolder method : cls.getMethods()) {
                Program program = method.getProgram();
                if (program == null) {
                    continue;
                }
                for (int i = 0; i < program.basicBlockCount(); ++i) {
                    BasicBlock block = program.basicBlockAt(i);
                    for (Instruction insn : block) {
                        if (insn instanceof InvokeInstruction) {
                            InvokeInstruction invoke = (InvokeInstruction) insn;
                            if (invoke.getType() == InvocationType.VIRTUAL) {
                                virtualMethods.add(invoke.getMethod());
                            }
                        } else if (insn instanceof CloneArrayInstruction) {
                            virtualMethods.add(new MethodReference(Object.class, "clone", Object.class));
                        }
                    }
                }
            }
        }

        return virtualMethods;
    }

    @Override
    public String[] getPlatformTags() {
        return new String[] { Platforms.WEBASSEMBLY, Platforms.LOW_LEVEL };
    }

    @Override
    public boolean isAsyncSupported() {
        return false;
    }

    @Override
    public InliningFilterFactory getInliningFilter() {
        return new LowLevelInliningFilterFactory(characteristics);
    }

    static class MethodGeneratorContextImpl implements WasmMethodGeneratorContext {
        private BinaryWriter binaryWriter;
        private WasmStringPool stringPool;
        private Diagnostics diagnostics;
        private NameProvider names;
        private WasmClassGenerator classGenerator;
        private ClassReaderSource classSource;

        MethodGeneratorContextImpl(BinaryWriter binaryWriter, WasmStringPool stringPool,
                Diagnostics diagnostics, NameProvider names, WasmClassGenerator classGenerator,
                ClassReaderSource classSource) {
            this.binaryWriter = binaryWriter;
            this.stringPool = stringPool;
            this.diagnostics = diagnostics;
            this.names = names;
            this.classGenerator = classGenerator;
            this.classSource = classSource;
        }

        @Override
        public BinaryWriter getBinaryWriter() {
            return binaryWriter;
        }

        @Override
        public WasmStringPool getStringPool() {
            return stringPool;
        }

        @Override
        public Diagnostics getDiagnostics() {
            return diagnostics;
        }

        @Override
        public NameProvider getNames() {
            return names;
        }

        @Override
        public WasmClassGenerator getClassGenerator() {
            return classGenerator;
        }

        @Override
        public ClassReaderSource getClassSource() {
            return classSource;
        }
    }


    class FiberIntrinsic implements WasmIntrinsic {
        @Override
        public boolean isApplicable(MethodReference methodReference) {
            if (!methodReference.getClassName().equals(Fiber.class.getName())) {
                return false;
            }
            switch (methodReference.getName()) {
                case "runMain":
                case "setCurrentThread":
                    return true;
                default:
                    return false;
            }
        }

        @Override
        public WasmExpression apply(InvocationExpr invocation, WasmIntrinsicManager manager) {
            switch (invocation.getMethod().getName()) {
                case "runMain": {
                    Iterator entryPointIter = controller.getEntryPoints().values()
                            .iterator();
                    if (entryPointIter.hasNext()) {
                        TeaVMEntryPoint entryPoint = entryPointIter.next();
                        String name = manager.getNames().forMethod(entryPoint.getMethod());
                        WasmCall call = new WasmCall(name);
                        call.getArguments().add(manager.generate(invocation.getArguments().get(0)));
                        call.setLocation(invocation.getLocation());
                        return call;
                    } else {
                        WasmUnreachable unreachable = new WasmUnreachable();
                        unreachable.setLocation(invocation.getLocation());
                        return unreachable;
                    }
                }
                case "setCurrentThread": {
                    String name = manager.getNames().forMethod(new MethodReference(Thread.class,
                            "setCurrentThread", Thread.class, void.class));
                    WasmCall call = new WasmCall(name);
                    call.getArguments().add(manager.generate(invocation.getArguments().get(0)));
                    call.setLocation(invocation.getLocation());
                    return call;
                }
                default:
                    throw new IllegalArgumentException();
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy