Codegen.java

/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package org.mozilla.javascript.optimizer;

import static org.mozilla.classfile.ClassFileWriter.ACC_FINAL;
import static org.mozilla.classfile.ClassFileWriter.ACC_PRIVATE;
import static org.mozilla.classfile.ClassFileWriter.ACC_PUBLIC;
import static org.mozilla.classfile.ClassFileWriter.ACC_STATIC;
import static org.mozilla.classfile.ClassFileWriter.ACC_VOLATILE;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.mozilla.classfile.ByteCode;
import org.mozilla.classfile.ClassFileWriter;
import org.mozilla.javascript.CodeGenUtils;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.Evaluator;
import org.mozilla.javascript.Function;
import org.mozilla.javascript.GeneratedClassLoader;
import org.mozilla.javascript.JSDescriptor;
import org.mozilla.javascript.JSFunction;
import org.mozilla.javascript.JSScript;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
import org.mozilla.javascript.ScriptOrFn;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.SecurityController;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.TemplateCharacters;

/**
 * This class generates code for a given IR tree.
 *
 * @author Norris Boyd
 * @author Roger Lawrence
 */
public class Codegen implements Evaluator {
    @Override
    public void captureStackInfo(RhinoException ex) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getSourcePositionFromStack(Context cx, int[] linep) {
        throw new UnsupportedOperationException();
    }

    @Override
    public String getPatchedStack(RhinoException ex, String nativeStackTrace) {
        throw new UnsupportedOperationException();
    }

    @Override
    public List<String> getScriptStack(RhinoException ex) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void setEvalScriptFlag(Script script) {
        throw new UnsupportedOperationException();
    }

    private static class CompilationResult<T extends ScriptOrFn<T>> {
        final JSDescriptor.Builder<T> builder;
        final String className;
        final byte[] bytecode;
        final OptJSCode.BuilderEnv builderEnv;

        CompilationResult(
                JSDescriptor.Builder<T> builder,
                String className,
                byte[] bytecode,
                OptJSCode.BuilderEnv builderEnv) {
            this.builder = builder;
            this.className = className;
            this.bytecode = bytecode;
            this.builderEnv = builderEnv;
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public Object compile(
            CompilerEnvirons compilerEnv,
            ScriptNode tree,
            String rawSource,
            boolean returnFunction) {
        int serial;
        synchronized (globalLock) {
            serial = ++globalSerialClassCounter;
        }

        String baseName = "c";
        if (tree.getSourceName().length() > 0) {
            baseName = tree.getSourceName().replaceAll("\\W", "_");
            if (!Character.isJavaIdentifierStart(baseName.charAt(0))) {
                baseName = "_" + baseName;
            }
        }

        String mainClassName = "org.mozilla.javascript.gen." + baseName + "_" + serial;

        JSDescriptor.Builder<?> builder = new JSDescriptor.Builder();
        OptJSCode.BuilderEnv builderEnv = new OptJSCode.BuilderEnv(mainClassName);
        byte[] mainClassBytes =
                compileToClassFile(
                        compilerEnv,
                        builder,
                        builderEnv,
                        mainClassName,
                        tree,
                        rawSource,
                        returnFunction);

        return new CompilationResult(builder, mainClassName, mainClassBytes, builderEnv);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Script createScriptObject(Object bytecode, Object staticSecurityDomain) {
        JSDescriptor<JSScript> desc =
                defineClass((CompilationResult<JSScript>) bytecode, staticSecurityDomain);
        return JSFunction.createScript(desc, null, staticSecurityDomain);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Function createFunctionObject(
            Context cx, Scriptable scope, Object bytecode, Object staticSecurityDomain) {
        JSDescriptor<JSFunction> desc =
                defineClass((CompilationResult<JSFunction>) bytecode, staticSecurityDomain);
        return JSFunction.createFunction(cx, scope, desc, null, staticSecurityDomain);
    }

    private <T extends ScriptOrFn<T>> JSDescriptor<T> defineClass(
            CompilationResult<T> compiled, Object staticSecurityDomain) {
        // The generated classes in this case refer only to Rhino classes
        // which must be accessible through this class loader
        ClassLoader rhinoLoader = getClass().getClassLoader();
        GeneratedClassLoader loader;
        loader = SecurityController.createLoader(rhinoLoader, staticSecurityDomain);
        Exception e;
        try {
            Class<?> cl = loader.defineClass(compiled.className, compiled.bytecode);
            loader.linkClass(cl);
            compiled.builderEnv.compiledClass = cl;
            var descs = new ArrayList<JSDescriptor<?>>();
            JSDescriptor<T> desc = compiled.builder.build(d -> descs.add(d));
            cl.getField(DESCRIPTORS_FIELD_NAME).set(null, descs.toArray(new JSDescriptor[0]));
            if (compiled.builderEnv.hasRegExpLiterals) {
                cl.getMethod(REGEXP_INIT_METHOD_NAME, Context.class)
                        .invoke(null, Context.getCurrentContext());
            }
            if (compiled.builderEnv.hasTemplateLiterals) {
                cl.getMethod(TEMPLATE_LITERAL_INIT_METHOD_NAME).invoke(null);
            }
            return desc;
        } catch (InvocationTargetException x) {
            var cause = x.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            e = x;
        } catch (SecurityException
                | IllegalArgumentException
                | IllegalAccessException
                | NoSuchFieldException
                | NoSuchMethodException x) {
            e = x;
        }
        throw new RuntimeException(e);
    }

    public byte[] compileToClassFile(
            CompilerEnvirons compilerEnv,
            JSDescriptor.Builder<?> builder,
            OptJSCode.BuilderEnv builderEnv,
            String mainClassName,
            ScriptNode scriptOrFn,
            String rawSource,
            boolean returnFunction) {
        this.compilerEnv = compilerEnv;

        transform(scriptOrFn);

        if (Token.printTrees) {
            System.out.println(scriptOrFn.toStringTree(scriptOrFn));
        }

        if (returnFunction) {
            CodeGenUtils.fillInForTopLevelFunction(
                    builder, scriptOrFn.getFunctionNode(0), rawSource, compilerEnv);
            scriptOrFn = scriptOrFn.getFunctionNode(0);
        } else {
            CodeGenUtils.fillInForScript(builder, scriptOrFn, rawSource, compilerEnv);
        }

        this.mainClassName = mainClassName;
        this.mainClassSignature = ClassFileWriter.classNameToSignature(mainClassName);

        initScriptNodesData(scriptOrFn, builder, builderEnv);

        return generateCode(rawSource);
    }

    private void transform(ScriptNode tree) {
        initOptFunctions_r(tree);

        if (compilerEnv.isInterpretedMode()) {
            // Kit.codeBug("Codegen must not run in interpreted Mode");
            throw new Error();
        }
        Map<String, OptFunctionNode> possibleDirectCalls = null;
        /*
         * Collect all of the contained functions into a hashtable
         * so that the call optimizer can access the class name & parameter
         * count for any call it encounters
         */
        if (tree.getType() == Token.SCRIPT) {
            int functionCount = tree.getFunctionCount();
            for (int i = 0; i != functionCount; ++i) {
                OptFunctionNode ofn = OptFunctionNode.get(tree, i);
                if (ofn.fnode.getFunctionType() == FunctionNode.FUNCTION_STATEMENT) {
                    String name = ofn.fnode.getName();
                    if (name.length() != 0) {
                        if (possibleDirectCalls == null) {
                            possibleDirectCalls = new HashMap<>();
                        }
                        possibleDirectCalls.put(name, ofn);
                    }
                }
            }
        }

        if (possibleDirectCalls != null) {
            directCallTargets = new ArrayList<>();
        }

        OptTransformer ot = new OptTransformer(possibleDirectCalls, directCallTargets);
        ot.transform(tree, compilerEnv);

        new Optimizer().optimize(tree);
    }

    private static void initOptFunctions_r(ScriptNode scriptOrFn) {
        for (int i = 0, N = scriptOrFn.getFunctionCount(); i != N; ++i) {
            FunctionNode fn = scriptOrFn.getFunctionNode(i);
            new OptFunctionNode(fn);
            initOptFunctions_r(fn);
        }
    }

    private <U extends ScriptOrFn<U>> void initScriptNodesData(
            ScriptNode scriptOrFn,
            JSDescriptor.Builder<U> builder,
            OptJSCode.BuilderEnv builderEnv) {
        ArrayList<ScriptNode> x = new ArrayList<>();
        ArrayList<JSDescriptor.Builder<?>> b = new ArrayList<>();
        collectScriptNodes_r(scriptOrFn, builder, builderEnv, x, b);

        int count = x.size();
        scriptOrFnNodes = new ScriptNode[count];
        builders = new JSDescriptor.Builder[count];
        scriptOrFnNodes = x.toArray(scriptOrFnNodes);
        builders = b.toArray(builders);

        scriptOrFnIndexes = new HashMap<>();
        for (int i = 0; i != count; ++i) {
            scriptOrFnIndexes.put(scriptOrFnNodes[i], i);
        }
    }

    private <U extends ScriptOrFn<U>> void collectScriptNodes_r(
            ScriptNode n,
            JSDescriptor.Builder<U> builder,
            OptJSCode.BuilderEnv builderEnv,
            List<ScriptNode> x,
            List<JSDescriptor.Builder<?>> b) {

        @SuppressWarnings("unchecked")
        OptJSCode.Builder<U> code =
                (OptJSCode.Builder<U>)
                        ((n instanceof FunctionNode)
                                ? new OptJSFunctionCode.Builder(builderEnv)
                                : new OptJSScriptCode.Builder(builderEnv));
        code.index = x.size();
        code.methodName = getBodyMethodName(n, x.size());
        code.methodType = getNonDirectBodyMethodSIgnature(n);
        if (isGenerator(n)) {
            code.resumeName = code.methodName + "_gen";
            code.resumeType = GENERATOR_METHOD_SIGNATURE;
        }
        builder.setCode(code);
        builderEnv.hasRegExpLiterals |= (n.getRegexpCount() > 0);
        builderEnv.hasTemplateLiterals |= (n.getTemplateLiteralCount() > 0);
        CodeGenUtils.setConstructor(builder, n);

        x.add(n);
        b.add(builder);
        int nestedCount = n.getFunctionCount();
        for (int i = 0; i != nestedCount; ++i) {
            var f = n.getFunctionNode(i);
            var fb = builder.createChildBuilder();
            CodeGenUtils.fillInForNestedFunction(fb, builder, f);
            collectScriptNodes_r(f, fb, builderEnv, x, b);
        }
    }

    static byte[] generateOptJSCode(
            String mainClass,
            String methodName,
            String methodType,
            String resumeName,
            String resumeType,
            boolean isFunction,
            int index) {
        String sourceFile = "";
        ClassFileWriter cfw =
                new ClassFileWriter(
                        mainClass + "ojsc" + Integer.toString(index),
                        isFunction
                                ? "org.mozilla.javascript.optimizer.OptJSFunctionCode"
                                : "org.mozilla.javascript.optimizer.OptJSScriptCode",
                        sourceFile);
        generateOptJSCodeCtor(cfw, isFunction);
        generateOptJSCodeExecute(cfw, mainClass, methodName, methodType);
        generateOptJSCodeResume(cfw, mainClass, resumeName, GENERATOR_METHOD_SIGNATURE);
        return cfw.toByteArray();
    }

    private static void generateOptJSCodeCtor(ClassFileWriter cfw, boolean isFunction) {
        cfw.startMethod("<init>", "()V", ACC_PUBLIC);
        cfw.addALoad(0);
        cfw.addInvoke(
                ByteCode.INVOKESPECIAL,
                isFunction
                        ? "org.mozilla.javascript.optimizer.OptJSFunctionCode"
                        : "org.mozilla.javascript.optimizer.OptJSScriptCode",
                "<init>",
                "()V");
        cfw.add(ByteCode.RETURN);
        cfw.stopMethod(1);
    }

    private static void generateOptJSCodeExecute(
            ClassFileWriter cfw, String mainClass, String methodName, String methodType) {
        cfw.startMethod("execute", methodType, (short) (ACC_PUBLIC | ACC_FINAL));
        cfw.addALoad(1);
        cfw.addALoad(2);
        cfw.addALoad(3);
        cfw.addALoad(4);
        cfw.addALoad(5);
        cfw.addALoad(6);
        cfw.addInvoke(ByteCode.INVOKESTATIC, mainClass, methodName, methodType);
        cfw.add(ByteCode.ARETURN);
        cfw.stopMethod((short) 7);
        // 5: this, cx, js function, new.target, scope, js this, args[]
    }

    private static void generateOptJSCodeResume(
            ClassFileWriter cfw, String mainClass, String methodName, String methodType) {
        cfw.startMethod("resume", methodType, (short) (ACC_PUBLIC | ACC_FINAL));
        if (methodName == null) {
            cfw.add(ByteCode.ACONST_NULL);
        } else {
            cfw.addALoad(1);
            cfw.addALoad(2);
            cfw.addALoad(3);
            cfw.addALoad(4);
            cfw.addILoad(5);
            cfw.addALoad(6);
            cfw.addInvoke(ByteCode.INVOKESTATIC, mainClass, methodName, methodType);
        }
        cfw.add(ByteCode.ARETURN);
        cfw.stopMethod((short) 7);
        // 5: this, cx, js function, new.target, scope, js this, args[]
    }

    private byte[] generateCode(String rawSource) {
        boolean hasScript = (scriptOrFnNodes[0].getType() == Token.SCRIPT);
        boolean hasFunctions = (scriptOrFnNodes.length > 1 || !hasScript);
        boolean isStrictMode = scriptOrFnNodes[0].isInStrictMode();

        String sourceFile = scriptOrFnNodes[0].getSourceName();
        ClassFileWriter cfw = new ClassFileWriter(mainClassName, SUPER_CLASS_NAME, sourceFile);
        cfw.addField(ID_FIELD_NAME, "I", ACC_PRIVATE);
        cfw.addField(
                DESCRIPTORS_FIELD_NAME,
                "[Lorg/mozilla/javascript/JSDescriptor;",
                (short) (ACC_PUBLIC | ACC_STATIC));

        generateLookupAccessor(cfw);

        int count = scriptOrFnNodes.length;
        for (int i = 0; i != count; ++i) {
            ScriptNode n = scriptOrFnNodes[i];

            BodyCodegen bodygen = new BodyCodegen();
            bodygen.cfw = cfw;
            bodygen.codegen = this;
            bodygen.compilerEnv = compilerEnv;
            bodygen.scriptOrFn = n;
            bodygen.scriptOrFnIndex = i;
            if (n instanceof FunctionNode) {
                bodygen.scriptOrFnType = "Lorg/mozilla/javascript/JSFunction;";
                bodygen.scriptOrFnClass = "org.mozilla.javascript.JSFunction";
            } else {
                bodygen.scriptOrFnType = "Lorg/mozilla/javascript/JSScript;";
                bodygen.scriptOrFnClass = "org.mozilla.javascript.JSScript";
            }

            bodygen.generateBodyCode();

            if (n.getType() == Token.FUNCTION) {
                OptFunctionNode ofn = OptFunctionNode.get(n);
                if (ofn.isTargetOfDirectCall()) {
                    emitDirectConstructor(cfw, ofn);
                    int pcount = ofn.fnode.getParamCount();
                    if (pcount != 0) {
                        emitNonDirectCall(cfw, ofn);
                    }
                }
            }
        }

        emitRegExpInit(cfw);
        emitTemplateLiteralInit(cfw);
        emitConstantDudeInitializers(cfw);

        return cfw.toByteArray();
    }

    private void emitNonDirectCall(ClassFileWriter cfw, OptFunctionNode ofn) {
        // We'll make a method with the same name as the body method but with a non direct
        // signature.

        cfw.startMethod(
                getBodyMethodName(ofn.fnode),
                getNonDirectBodyMethodSIgnature(ofn.fnode),
                (short) (ACC_STATIC | ACC_PUBLIC));

        cfw.addALoad(0);
        cfw.addALoad(1);
        cfw.addALoad(2);
        cfw.addALoad(3);
        cfw.addALoad(4);
        cfw.addALoad(5);
        int pcount = ofn.fnode.getParamCount();
        if (pcount != 0) {
            // loop invariant:
            // stack top == arguments array from addALoad4()
            for (int p = 0; p != pcount; ++p) {
                cfw.add(ByteCode.ARRAYLENGTH);
                cfw.addPush(p);
                int undefArg = cfw.acquireLabel();
                int beyond = cfw.acquireLabel();
                cfw.add(ByteCode.IF_ICMPLE, undefArg);
                // get array[p]
                cfw.addALoad(5);
                cfw.addPush(p);
                cfw.add(ByteCode.AALOAD);
                cfw.add(ByteCode.GOTO, beyond);
                cfw.markLabel(undefArg);
                pushUndefined(cfw);
                cfw.markLabel(beyond);
                // Only one push
                cfw.adjustStackTop(-1);
                cfw.addPush(0.0);
                // restore invariant
                cfw.addALoad(5);
            }
        }

        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                mainClassName,
                getBodyMethodName(ofn.fnode),
                getBodyMethodSignature(ofn.fnode));
        cfw.add(ByteCode.ARETURN);

        cfw.stopMethod(6);
        // 5: function, cx, scope, js this, args[]
    }

    private void emitDirectConstructor(ClassFileWriter cfw, OptFunctionNode ofn) {
        /*
            we generate ..
                Scriptable directConstruct(<directCallArgs>) {
                    Scriptable newInstance = createObject(cx, scope);
                    Object val = <body-name>(cx, scope, newInstance, <directCallArgs>);
                    if (val instanceof Scriptable) {
                        return (Scriptable) val;
                    }
                    return newInstance;
                }
        */
        cfw.startMethod(
                getDirectCtorName(ofn.fnode),
                getBodyMethodSignature(ofn.fnode),
                (short) (ACC_STATIC | ACC_PUBLIC));

        int argCount = ofn.fnode.getParamCount();
        int firstLocal = (5 + argCount * 3) + 1;

        cfw.addALoad(1); // this
        cfw.add(ByteCode.CHECKCAST, Codegen.JSFUNCTION_CLASS_NAME);
        cfw.addALoad(0); // cx
        cfw.addALoad(3); // scope
        cfw.addInvoke(
                ByteCode.INVOKEVIRTUAL,
                "org/mozilla/javascript/BaseFunction",
                "createObject",
                "(Lorg/mozilla/javascript/Context;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + ")Lorg/mozilla/javascript/Scriptable;");
        cfw.addAStore(firstLocal);

        cfw.addALoad(0);
        cfw.addALoad(1);
        cfw.add(ByteCode.ACONST_NULL);
        cfw.addALoad(2);
        cfw.addALoad(firstLocal);
        for (int i = 0; i < argCount; i++) {
            cfw.addALoad(5 + (i * 3));
            cfw.addDLoad(6 + (i * 3));
        }
        cfw.addALoad(5 + argCount * 3);
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                mainClassName,
                getBodyMethodName(ofn.fnode),
                getBodyMethodSignature(ofn.fnode));
        int exitLabel = cfw.acquireLabel();
        cfw.add(ByteCode.DUP); // make a copy of direct call result
        cfw.add(ByteCode.INSTANCEOF, "org/mozilla/javascript/Scriptable");
        cfw.add(ByteCode.IFEQ, exitLabel);
        // cast direct call result
        cfw.add(ByteCode.CHECKCAST, "org/mozilla/javascript/Scriptable");
        cfw.add(ByteCode.ARETURN);
        cfw.markLabel(exitLabel);

        cfw.addALoad(firstLocal);
        cfw.add(ByteCode.ARETURN);

        cfw.stopMethod((short) (firstLocal + 1));
    }

    static boolean isGenerator(ScriptNode node) {
        return (node.getType() == Token.FUNCTION) && ((FunctionNode) node).isGenerator();
    }

    private void generateLookupAccessor(ClassFileWriter cfw) {
        cfw.startMethod(
                "getLookup",
                "()Ljava/lang/invoke/MethodHandles$Lookup;",
                (short) (ACC_STATIC | ACC_PUBLIC));
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "java.lang.invoke.MethodHandles",
                "lookup",
                "()Ljava/lang/invoke/MethodHandles$Lookup;");
        cfw.add(ByteCode.ARETURN);
        cfw.stopMethod(0);
    }

    private void emitRegExpInit(ClassFileWriter cfw) {
        // precompile all regexp literals

        int totalRegCount = 0;
        for (int i = 0; i != scriptOrFnNodes.length; ++i) {
            totalRegCount += scriptOrFnNodes[i].getRegexpCount();
        }
        if (totalRegCount == 0) {
            return;
        }

        cfw.startMethod(
                REGEXP_INIT_METHOD_NAME,
                REGEXP_INIT_METHOD_SIGNATURE,
                (short) (ACC_STATIC | ACC_PUBLIC));
        cfw.addField("_reInitDone", "Z", (short) (ACC_STATIC | ACC_PRIVATE | ACC_VOLATILE));
        cfw.add(ByteCode.GETSTATIC, mainClassName, "_reInitDone", "Z");
        int doInit = cfw.acquireLabel();
        cfw.add(ByteCode.IFEQ, doInit);
        cfw.add(ByteCode.RETURN);
        cfw.markLabel(doInit);

        // get regexp proxy and store it in local slot 1
        cfw.addALoad(0); // context
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "org/mozilla/javascript/ScriptRuntime",
                "checkRegExpProxy",
                "(Lorg/mozilla/javascript/Context;" + ")Lorg/mozilla/javascript/RegExpProxy;");
        cfw.addAStore(1); // proxy

        // We could apply double-checked locking here but concurrency
        // shouldn't be a problem in practice
        for (int i = 0; i != scriptOrFnNodes.length; ++i) {
            ScriptNode n = scriptOrFnNodes[i];
            int regCount = n.getRegexpCount();
            for (int j = 0; j != regCount; ++j) {
                String reFieldName = getCompiledRegexpName(n, j);
                String reFieldType = "Ljava/lang/Object;";
                String reString = n.getRegexpString(j);
                String reFlags = n.getRegexpFlags(j);
                cfw.addField(reFieldName, reFieldType, (short) (ACC_STATIC | ACC_PRIVATE));
                cfw.addALoad(1); // proxy
                cfw.addALoad(0); // context
                cfw.addPush(reString);
                if (reFlags == null) {
                    cfw.add(ByteCode.ACONST_NULL);
                } else {
                    cfw.addPush(reFlags);
                }
                cfw.addInvoke(
                        ByteCode.INVOKEINTERFACE,
                        "org/mozilla/javascript/RegExpProxy",
                        "compileRegExp",
                        "(Lorg/mozilla/javascript/Context;"
                                + "Ljava/lang/String;Ljava/lang/String;"
                                + ")Ljava/lang/Object;");
                cfw.add(ByteCode.PUTSTATIC, mainClassName, reFieldName, reFieldType);
            }
        }

        cfw.addPush(1);
        cfw.add(ByteCode.PUTSTATIC, mainClassName, "_reInitDone", "Z");
        cfw.add(ByteCode.RETURN);
        cfw.stopMethod((short) 2);
    }

    /**
     * Overview:
     *
     * <pre>
     * for each fn in functions(script) do
     *   let field = []
     *   for each templateLiteral in templateLiterals(fn) do
     *     let values = concat([[cooked(s), raw(s)] | s <- strings(templateLiteral)])
     *     field.push(values)
     *   end
     *   class[getTemplateLiteralName(fn)] = field
     * end
     * </pre>
     */
    private void emitTemplateLiteralInit(ClassFileWriter cfw) {
        // emit all template literals

        int totalTemplateLiteralCount = 0;
        for (ScriptNode n : scriptOrFnNodes) {
            totalTemplateLiteralCount += n.getTemplateLiteralCount();
        }

        cfw.startMethod(
                TEMPLATE_LITERAL_INIT_METHOD_NAME,
                TEMPLATE_LITERAL_INIT_METHOD_SIGNATURE,
                (short) (ACC_STATIC | ACC_PUBLIC));
        cfw.addField("_qInitDone", "Z", (short) (ACC_STATIC | ACC_PRIVATE | ACC_VOLATILE));

        cfw.add(ByteCode.GETSTATIC, mainClassName, "_qInitDone", "Z");
        int doInit = cfw.acquireLabel();
        cfw.add(ByteCode.IFEQ, doInit);
        cfw.add(ByteCode.RETURN);
        cfw.markLabel(doInit);

        // We could apply double-checked locking here but concurrency
        // shouldn't be a problem in practice
        for (ScriptNode n : scriptOrFnNodes) {
            int qCount = n.getTemplateLiteralCount();
            if (qCount == 0) continue;
            String qFieldName = getTemplateLiteralName(n);
            String qFieldType = "[Ljava/lang/Object;";
            cfw.addField(qFieldName, qFieldType, (short) (ACC_STATIC | ACC_PRIVATE));
            cfw.addPush(qCount);
            cfw.add(ByteCode.ANEWARRAY, "java/lang/Object");
            for (int j = 0; j < qCount; ++j) {
                List<TemplateCharacters> strings = n.getTemplateLiteralStrings(j);
                cfw.add(ByteCode.DUP);
                cfw.addPush(j);
                cfw.addPush(strings.size() * 2);
                cfw.add(ByteCode.ANEWARRAY, "java/lang/String");
                int k = 0;
                for (TemplateCharacters s : strings) {
                    // cooked value
                    cfw.add(ByteCode.DUP);
                    cfw.addPush(k++);
                    if (s.getValue() != null) {
                        cfw.addPush(s.getValue());
                    } else {
                        cfw.add(ByteCode.ACONST_NULL);
                    }
                    cfw.add(ByteCode.AASTORE);
                    // raw value
                    cfw.add(ByteCode.DUP);
                    cfw.addPush(k++);
                    cfw.addPush(s.getRawValue());
                    cfw.add(ByteCode.AASTORE);
                }
                cfw.add(ByteCode.AASTORE);
            }
            cfw.add(ByteCode.PUTSTATIC, mainClassName, qFieldName, qFieldType);
        }

        cfw.addPush(true);
        cfw.add(ByteCode.PUTSTATIC, mainClassName, "_qInitDone", "Z");
        cfw.add(ByteCode.RETURN);
        cfw.stopMethod((short) 0);
    }

    private void emitConstantDudeInitializers(ClassFileWriter cfw) {
        int N = itsConstantListSize;
        if (N == 0) return;

        cfw.startMethod("<clinit>", "()V", (short) (ACC_STATIC | ACC_FINAL));

        double[] array = itsConstantList;
        for (int i = 0; i != N; ++i) {
            double num = array[i];
            String constantName = "_k" + i;
            String constantType = getStaticConstantWrapperType(num);
            cfw.addField(constantName, constantType, (short) (ACC_STATIC | ACC_PRIVATE));
            int inum = (int) num;
            if (inum == num) {
                cfw.addPush(inum);
                cfw.addInvoke(
                        ByteCode.INVOKESTATIC,
                        "java/lang/Integer",
                        "valueOf",
                        "(I)Ljava/lang/Integer;");
            } else {
                cfw.addPush(num);
                addDoubleWrap(cfw);
            }
            cfw.add(ByteCode.PUTSTATIC, mainClassName, constantName, constantType);
        }

        cfw.add(ByteCode.RETURN);
        cfw.stopMethod((short) 0);
    }

    void pushNumberAsObject(ClassFileWriter cfw, double num) {
        if (num == 0.0) {
            if (1 / num > 0) {
                // +0.0
                cfw.add(
                        ByteCode.GETSTATIC,
                        "org/mozilla/javascript/ScriptRuntime",
                        "zeroObj",
                        "Ljava/lang/Integer;");
            } else {
                cfw.addPush(num);
                addDoubleWrap(cfw);
            }

        } else if (num == 1.0) {
            cfw.add(
                    ByteCode.GETSTATIC,
                    "org/mozilla/javascript/optimizer/OptRuntime",
                    "oneObj",
                    "Ljava/lang/Integer;");
            return;

        } else if (num == -1.0) {
            cfw.add(
                    ByteCode.GETSTATIC,
                    "org/mozilla/javascript/optimizer/OptRuntime",
                    "minusOneObj",
                    "Ljava/lang/Integer;");

        } else if (Double.isNaN(num)) {
            cfw.add(
                    ByteCode.GETSTATIC,
                    "org/mozilla/javascript/ScriptRuntime",
                    "NaNobj",
                    "Ljava/lang/Double;");

        } else if (itsConstantListSize >= 2000) {
            // There appears to be a limit in the JVM on either the number
            // of static fields in a class or the size of the class
            // initializer. Either way, we can't have any more than 2000
            // statically init'd constants.
            cfw.addPush(num);
            addDoubleWrap(cfw);

        } else {
            int N = itsConstantListSize;
            int index = 0;
            if (N == 0) {
                itsConstantList = new double[64];
            } else {
                double[] array = itsConstantList;
                while (index != N && array[index] != num) {
                    ++index;
                }
                if (N == array.length) {
                    array = new double[N * 2];
                    System.arraycopy(itsConstantList, 0, array, 0, N);
                    itsConstantList = array;
                }
            }
            if (index == N) {
                itsConstantList[N] = num;
                itsConstantListSize = N + 1;
            }
            String constantName = "_k" + index;
            String constantType = getStaticConstantWrapperType(num);
            cfw.add(ByteCode.GETSTATIC, mainClassName, constantName, constantType);
        }
    }

    private static void addDoubleWrap(ClassFileWriter cfw) {
        cfw.addInvoke(
                ByteCode.INVOKESTATIC,
                "org/mozilla/javascript/optimizer/OptRuntime",
                "wrapDouble",
                "(D)Ljava/lang/Double;");
    }

    private static String getStaticConstantWrapperType(double num) {
        int inum = (int) num;
        if (inum == num) {
            return "Ljava/lang/Integer;";
        }
        return "Ljava/lang/Double;";
    }

    static void pushUndefined(ClassFileWriter cfw) {
        cfw.add(
                ByteCode.GETSTATIC,
                "org/mozilla/javascript/Undefined",
                "instance",
                "Ljava/lang/Object;");
    }

    int getIndex(ScriptNode n) {
        return scriptOrFnIndexes.get(n);
    }

    String getDirectCtorName(ScriptNode n) {
        return "_n" + getIndex(n);
    }

    String getBodyMethodName(ScriptNode n) {
        return getBodyMethodName(n, getIndex(n));
    }

    String getBodyMethodName(ScriptNode n, int index) {
        return "_c_" + cleanName(n) + "_" + index;
    }

    /** Gets a Java-compatible "informative" name for the the ScriptOrFnNode */
    String cleanName(final ScriptNode n) {
        String result = "";
        if (n instanceof FunctionNode) {
            Name name = ((FunctionNode) n).getFunctionName();
            if (name == null) {
                result = "anonymous";
            } else {
                result = name.getIdentifier();
            }
        } else {
            result = "script";
        }
        return result;
    }

    String getNonDirectBodyMethodSIgnature(ScriptNode n) {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        sb.append("Lorg/mozilla/javascript/Context;");
        if (n instanceof FunctionNode) {
            sb.append("Lorg/mozilla/javascript/JSFunction;");
        } else {
            sb.append("Lorg/mozilla/javascript/JSScript;");
        }
        sb.append(
                "Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Ljava/lang/Object;");
        sb.append("[Ljava/lang/Object;)Ljava/lang/Object;");
        return sb.toString();
    }

    String getBodyMethodSignature(ScriptNode n) {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        sb.append("Lorg/mozilla/javascript/Context;");
        if (n instanceof FunctionNode) {
            sb.append("Lorg/mozilla/javascript/JSFunction;");
        } else {
            sb.append("Lorg/mozilla/javascript/JSScript;");
        }
        sb.append(
                "Ljava/lang/Object;"
                        + "Lorg/mozilla/javascript/Scriptable;"
                        + "Ljava/lang/Object;");
        if (n.getType() == Token.FUNCTION) {
            OptFunctionNode ofn = OptFunctionNode.get(n);
            if (ofn.isTargetOfDirectCall()) {
                int pCount = ofn.fnode.getParamCount();
                for (int i = 0; i != pCount; i++) {
                    sb.append("Ljava/lang/Object;D");
                }
            }
        }
        sb.append("[Ljava/lang/Object;)Ljava/lang/Object;");
        return sb.toString();
    }

    String getCodeInitMethodName(OptFunctionNode ofn) {
        return "_i" + getIndex(ofn.fnode);
    }

    String getCompiledRegexpName(ScriptNode n, int regexpIndex) {
        return "_re" + getIndex(n) + "_" + regexpIndex;
    }

    String getTemplateLiteralName(ScriptNode n) {
        return "_q" + getIndex(n);
    }

    static RuntimeException badTree() {
        throw new RuntimeException("Bad tree in codegen");
    }

    public void setMainMethodClass(String className) {
        mainMethodClass = className;
    }

    static final String DEFAULT_MAIN_METHOD_CLASS = "org.mozilla.javascript.optimizer.OptRuntime";

    private static final String SUPER_CLASS_NAME = "java.lang.Object";

    static final String ID_FIELD_NAME = "_id";

    static final String DESCRIPTOR_CLASS_SIGNATURE = "Lorg/mozilla/javascript/JSDescriptor;";
    static final String DESCRIPTORS_FIELD_NAME = "_descriptors";
    static final String DESCRIPTORS_FIELD_SIGNATURE = "[" + DESCRIPTOR_CLASS_SIGNATURE;

    static final String REGEXP_INIT_METHOD_NAME = "_reInit";
    static final String REGEXP_INIT_METHOD_SIGNATURE = "(Lorg/mozilla/javascript/Context;)V";

    static final String TEMPLATE_LITERAL_INIT_METHOD_NAME = "_qInit";
    static final String TEMPLATE_LITERAL_INIT_METHOD_SIGNATURE = "()V";

    static final String CODE_INIT_SIGNATURE = "(Lorg/mozilla/javascript/Context;" + ")V";

    static final String CODE_CONSTRUCTOR_SIGNATURE = "(Lorg/mozilla/javascript/Context;I)V";

    static final String JSFUNCTION_CLASS_NAME = "org.mozilla.javascript.JSFunction";
    static final String JSFUNCTION_CLASS_SIGNATURE = "org/mozilla/javascript/JSFunction";
    static final String JSFUNCTION_CONSTRUCTOR_SIGNATURE =
            "("
                    + "Lorg/mozilla/javascript/Context;"
                    + "Lorg/mozilla/javascript/Scriptable;"
                    + "Lorg/mozilla/javascript/JSDescriptor;"
                    + "Lorg/mozilla/javascript/Scriptable;"
                    + "Lorg/mozilla/javascript/Scriptable;"
                    + ")V";

    static final String GENERATOR_METHOD_SIGNATURE =
            "("
                    + "Lorg/mozilla/javascript/Context;"
                    + "Lorg/mozilla/javascript/JSFunction;"
                    + "Ljava/lang/Object;"
                    + "Lorg/mozilla/javascript/Scriptable;"
                    + "I"
                    + "Ljava/lang/Object;)Ljava/lang/Object;";

    private static final Object globalLock = new Object();
    private static int globalSerialClassCounter;

    private CompilerEnvirons compilerEnv;

    private List<OptFunctionNode> directCallTargets;
    ScriptNode[] scriptOrFnNodes;
    JSDescriptor.Builder[] builders;
    private HashMap<ScriptNode, Integer> scriptOrFnIndexes;

    private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS;

    String mainClassName;
    String mainClassSignature;

    private double[] itsConstantList;
    private int itsConstantListSize;
}