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

org.mozilla.javascript.optimizer.Codegen Maven / Gradle / Ivy

Go to download

Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically embedded into Java applications to provide scripting to end users.

There is a newer version: 1.7.15
Show newest version
/* -*- 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_PROTECTED;
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.Constructor;
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.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.Kit;
import org.mozilla.javascript.NativeFunction;
import org.mozilla.javascript.ObjArray;
import org.mozilla.javascript.ObjToIntMap;
import org.mozilla.javascript.RhinoException;
import org.mozilla.javascript.Script;
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;

/**
 * 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 getScriptStack(RhinoException ex) {
        throw new UnsupportedOperationException();
    }

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

    @Override
    public Object compile(CompilerEnvirons compilerEnv,
                          ScriptNode tree,
                          String encodedSource,
                          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;

        byte[] mainClassBytes = compileToClassFile(compilerEnv, mainClassName,
                                                   tree, encodedSource,
                                                   returnFunction);

        return new Object[] { mainClassName, mainClassBytes };
    }

    @Override
    public Script createScriptObject(Object bytecode,
                                     Object staticSecurityDomain)
    {
        Class cl = defineClass(bytecode, staticSecurityDomain);

        Script script;
        try {
            script = (Script)cl.newInstance();
        } catch (Exception ex) {
            throw new RuntimeException
                ("Unable to instantiate compiled class:" + ex.toString());
        }
        return script;
    }

    @Override
    public Function createFunctionObject(Context cx, Scriptable scope,
                                         Object bytecode,
                                         Object staticSecurityDomain)
    {
        Class cl = defineClass(bytecode, staticSecurityDomain);

        NativeFunction f;
        try {
            Constructorctor = cl.getConstructors()[0];
            Object[] initArgs = { scope, cx, Integer.valueOf(0) };
            f = (NativeFunction)ctor.newInstance(initArgs);
        } catch (Exception ex) {
            throw new RuntimeException
                ("Unable to instantiate compiled class:"+ex.toString());
        }
        return f;
    }

    private Class defineClass(Object bytecode,
                                 Object staticSecurityDomain)
    {
        Object[] nameBytesPair = (Object[])bytecode;
        String className = (String)nameBytesPair[0];
        byte[] classBytes = (byte[])nameBytesPair[1];

        // 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(className, classBytes);
            loader.linkClass(cl);
            return cl;
        } catch (SecurityException x) {
            e = x;
        } catch (IllegalArgumentException x) {
            e = x;
        }
        throw new RuntimeException("Malformed optimizer package " + e);
    }

    public byte[] compileToClassFile(CompilerEnvirons compilerEnv,
                                     String mainClassName,
                                     ScriptNode scriptOrFn,
                                     String encodedSource,
                                     boolean returnFunction)
    {
        this.compilerEnv = compilerEnv;

        transform(scriptOrFn);

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

        if (returnFunction) {
            scriptOrFn = scriptOrFn.getFunctionNode(0);
        }

        initScriptNodesData(scriptOrFn);

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

        return generateCode(encodedSource);
    }

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

        int optLevel = compilerEnv.getOptimizationLevel();

        Map possibleDirectCalls = null;
        if (optLevel > 0) {
           /*
            * 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 ObjArray();
        }

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

        if (optLevel > 0) {
            (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 void initScriptNodesData(ScriptNode scriptOrFn)
    {
        ObjArray x = new ObjArray();
        collectScriptNodes_r(scriptOrFn, x);

        int count = x.size();
        scriptOrFnNodes = new ScriptNode[count];
        x.toArray(scriptOrFnNodes);

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

    private static void collectScriptNodes_r(ScriptNode n,
                                                 ObjArray x)
    {
        x.add(n);
        int nestedCount = n.getFunctionCount();
        for (int i = 0; i != nestedCount; ++i) {
            collectScriptNodes_r(n.getFunctionNode(i), x);
        }
    }

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

        String sourceFile = null;
        if (compilerEnv.isGenerateDebugInfo()) {
            sourceFile = scriptOrFnNodes[0].getSourceName();
        }

        ClassFileWriter cfw = new ClassFileWriter(mainClassName,
                                                  SUPER_CLASS_NAME,
                                                  sourceFile);
        cfw.addField(ID_FIELD_NAME, "I", ACC_PRIVATE);

        if (hasFunctions) {
            generateFunctionConstructor(cfw);
        }

        if (hasScript) {
            cfw.addInterface("org/mozilla/javascript/Script");
            generateScriptCtor(cfw);
            generateMain(cfw);
            generateExecute(cfw);
        }

        generateCallMethod(cfw, isStrictMode);
        generateResumeGenerator(cfw);

        generateNativeFunctionOverrides(cfw, encodedSource);

        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;

            bodygen.generateBodyCode();

            if (n.getType() == Token.FUNCTION) {
                OptFunctionNode ofn = OptFunctionNode.get(n);
                generateFunctionInit(cfw, ofn);
                if (ofn.isTargetOfDirectCall()) {
                    emitDirectConstructor(cfw, ofn);
                }
            }
        }

        emitRegExpInit(cfw);
        emitConstantDudeInitializers(cfw);

        return cfw.toByteArray();
    }

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

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

        cfw.addALoad(0); // this
        cfw.addALoad(1); // cx
        cfw.addALoad(2); // 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.addALoad(2);
        cfw.addALoad(firstLocal);
        for (int i = 0; i < argCount; i++) {
            cfw.addALoad(4 + (i * 3));
            cfw.addDLoad(5 + (i * 3));
        }
        cfw.addALoad(4 + 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();
    }

    // How dispatch to generators works:
    // Two methods are generated corresponding to a user-written generator.
    // One of these creates a generator object (NativeGenerator), which is
    // returned to the user. The other method contains all of the body code
    // of the generator.
    // When a user calls a generator, the call() method dispatches control to
    // to the method that creates the NativeGenerator object. Subsequently when
    // the user invokes .next(), .send() or any such method on the generator
    // object, the resumeGenerator() below dispatches the call to the
    // method corresponding to the generator body. As a matter of convention
    // the generator body is given the name of the generator activation function
    // appended by "_gen".
    private void generateResumeGenerator(ClassFileWriter cfw)
    {
        boolean hasGenerators = false;
        for (int i=0; i < scriptOrFnNodes.length; i++) {
            if (isGenerator(scriptOrFnNodes[i]))
                hasGenerators = true;
        }

        // if there are no generators defined, we don't implement a
        // resumeGenerator(). The base class provides a default implementation.
        if (!hasGenerators)
            return;

        cfw.startMethod("resumeGenerator",
                        "(Lorg/mozilla/javascript/Context;" +
                        "Lorg/mozilla/javascript/Scriptable;" +
                        "ILjava/lang/Object;" +
                        "Ljava/lang/Object;)Ljava/lang/Object;",
                        (short)(ACC_PUBLIC | ACC_FINAL));

        // load arguments for dispatch to the corresponding *_gen method
        cfw.addALoad(0);
        cfw.addALoad(1);
        cfw.addALoad(2);
        cfw.addALoad(4);
        cfw.addALoad(5);
        cfw.addILoad(3);

        cfw.addLoadThis();
        cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");

        int startSwitch = cfw.addTableSwitch(0, scriptOrFnNodes.length - 1);
        cfw.markTableSwitchDefault(startSwitch);
        int endlabel = cfw.acquireLabel();

        for (int i = 0; i < scriptOrFnNodes.length; i++) {
            ScriptNode n = scriptOrFnNodes[i];
            cfw.markTableSwitchCase(startSwitch, i, (short)6);
            if (isGenerator(n)) {
                String type = "(" +
                              mainClassSignature +
                              "Lorg/mozilla/javascript/Context;" +
                              "Lorg/mozilla/javascript/Scriptable;" +
                              "Ljava/lang/Object;" +
                              "Ljava/lang/Object;I)Ljava/lang/Object;";
                cfw.addInvoke(ByteCode.INVOKESTATIC,
                              mainClassName,
                              getBodyMethodName(n) + "_gen",
                              type);
                cfw.add(ByteCode.ARETURN);
            } else {
                cfw.add(ByteCode.GOTO, endlabel);
            }
        }

        cfw.markLabel(endlabel);
        pushUndefined(cfw);
        cfw.add(ByteCode.ARETURN);


        // this method uses as many locals as there are arguments (hence 6)
        cfw.stopMethod((short)6);
    }

    private void generateCallMethod(ClassFileWriter cfw, boolean isStrictMode)
    {
        cfw.startMethod("call",
                        "(Lorg/mozilla/javascript/Context;" +
                        "Lorg/mozilla/javascript/Scriptable;" +
                        "Lorg/mozilla/javascript/Scriptable;" +
                        "[Ljava/lang/Object;)Ljava/lang/Object;",
                        (short)(ACC_PUBLIC | ACC_FINAL));

        // Generate code for:
        // if (!ScriptRuntime.hasTopCall(cx)) {
        //     return ScriptRuntime.doTopCall(this, cx, scope, thisObj, args);
        // }

        int nonTopCallLabel = cfw.acquireLabel();
        cfw.addALoad(1); //cx
        cfw.addInvoke(ByteCode.INVOKESTATIC,
                      "org/mozilla/javascript/ScriptRuntime",
                      "hasTopCall",
                      "(Lorg/mozilla/javascript/Context;"
                      +")Z");
        cfw.add(ByteCode.IFNE, nonTopCallLabel);
        cfw.addALoad(0);
        cfw.addALoad(1);
        cfw.addALoad(2);
        cfw.addALoad(3);
        cfw.addALoad(4);
        cfw.addPush(isStrictMode);
        cfw.addInvoke(ByteCode.INVOKESTATIC,
                      "org/mozilla/javascript/ScriptRuntime",
                      "doTopCall",
                      "(Lorg/mozilla/javascript/Callable;"
                      +"Lorg/mozilla/javascript/Context;"
                      +"Lorg/mozilla/javascript/Scriptable;"
                      +"Lorg/mozilla/javascript/Scriptable;"
                      +"[Ljava/lang/Object;"
                      +"Z"
                      +")Ljava/lang/Object;");
        cfw.add(ByteCode.ARETURN);
        cfw.markLabel(nonTopCallLabel);

        // Now generate switch to call the real methods
        cfw.addALoad(0);
        cfw.addALoad(1);
        cfw.addALoad(2);
        cfw.addALoad(3);
        cfw.addALoad(4);

        int end = scriptOrFnNodes.length;
        boolean generateSwitch = (2 <= end);

        int switchStart = 0;
        int switchStackTop = 0;
        if (generateSwitch) {
            cfw.addLoadThis();
            cfw.add(ByteCode.GETFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");
            // do switch from (1,  end - 1) mapping 0 to
            // the default case
            switchStart = cfw.addTableSwitch(1, end - 1);
        }

        for (int i = 0; i != end; ++i) {
            ScriptNode n = scriptOrFnNodes[i];
            if (generateSwitch) {
                if (i == 0) {
                    cfw.markTableSwitchDefault(switchStart);
                    switchStackTop = cfw.getStackTop();
                } else {
                    cfw.markTableSwitchCase(switchStart, i - 1,
                                            switchStackTop);
                }
            }
            if (n.getType() == Token.FUNCTION) {
                OptFunctionNode ofn = OptFunctionNode.get(n);
                if (ofn.isTargetOfDirectCall()) {
                    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(4);
                            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(4);
                        }
                    }
                }
            }
            cfw.addInvoke(ByteCode.INVOKESTATIC,
                          mainClassName,
                          getBodyMethodName(n),
                          getBodyMethodSignature(n));
            cfw.add(ByteCode.ARETURN);
        }
        cfw.stopMethod((short)5);
        // 5: this, cx, scope, js this, args[]
    }

    private void generateMain(ClassFileWriter cfw)
    {
        cfw.startMethod("main", "([Ljava/lang/String;)V",
                        (short)(ACC_PUBLIC | ACC_STATIC));

        // load new ScriptImpl()
        cfw.add(ByteCode.NEW, cfw.getClassName());
        cfw.add(ByteCode.DUP);
        cfw.addInvoke(ByteCode.INVOKESPECIAL, cfw.getClassName(),
                      "", "()V");
         // load 'args'
        cfw.add(ByteCode.ALOAD_0);
        // Call mainMethodClass.main(Script script, String[] args)
        cfw.addInvoke(ByteCode.INVOKESTATIC,
                      mainMethodClass,
                      "main",
                      "(Lorg/mozilla/javascript/Script;[Ljava/lang/String;)V");
        cfw.add(ByteCode.RETURN);
        // 1 = String[] args
        cfw.stopMethod((short)1);
    }

    private static void generateExecute(ClassFileWriter cfw)
    {
        cfw.startMethod("exec",
                        "(Lorg/mozilla/javascript/Context;"
                        +"Lorg/mozilla/javascript/Scriptable;"
                        +")Ljava/lang/Object;",
                        (short)(ACC_PUBLIC | ACC_FINAL));

        final int CONTEXT_ARG = 1;
        final int SCOPE_ARG = 2;

        cfw.addLoadThis();
        cfw.addALoad(CONTEXT_ARG);
        cfw.addALoad(SCOPE_ARG);
        cfw.add(ByteCode.DUP);
        cfw.add(ByteCode.ACONST_NULL);
        cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
                      cfw.getClassName(),
                      "call",
                      "(Lorg/mozilla/javascript/Context;"
                      +"Lorg/mozilla/javascript/Scriptable;"
                      +"Lorg/mozilla/javascript/Scriptable;"
                      +"[Ljava/lang/Object;"
                      +")Ljava/lang/Object;");

        cfw.add(ByteCode.ARETURN);
        // 3 = this + context + scope
        cfw.stopMethod((short)3);
    }

    private static void generateScriptCtor(ClassFileWriter cfw)
    {
        cfw.startMethod("", "()V", ACC_PUBLIC);

        cfw.addLoadThis();
        cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME,
                      "", "()V");
        // set id to 0
        cfw.addLoadThis();
        cfw.addPush(0);
        cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");

        cfw.add(ByteCode.RETURN);
        // 1 parameter = this
        cfw.stopMethod((short)1);
    }

    private void generateFunctionConstructor(ClassFileWriter cfw)
    {
        final int SCOPE_ARG = 1;
        final int CONTEXT_ARG = 2;
        final int ID_ARG = 3;

        cfw.startMethod("", FUNCTION_CONSTRUCTOR_SIGNATURE, ACC_PUBLIC);
        cfw.addALoad(0);
        cfw.addInvoke(ByteCode.INVOKESPECIAL, SUPER_CLASS_NAME,
                      "", "()V");

        cfw.addLoadThis();
        cfw.addILoad(ID_ARG);
        cfw.add(ByteCode.PUTFIELD, cfw.getClassName(), ID_FIELD_NAME, "I");

        cfw.addLoadThis();
        cfw.addALoad(CONTEXT_ARG);
        cfw.addALoad(SCOPE_ARG);

        int start = (scriptOrFnNodes[0].getType() == Token.SCRIPT) ? 1 : 0;
        int end = scriptOrFnNodes.length;
        if (start == end) throw badTree();
        boolean generateSwitch = (2 <= end - start);

        int switchStart = 0;
        int switchStackTop = 0;
        if (generateSwitch) {
            cfw.addILoad(ID_ARG);
            // do switch from (start + 1,  end - 1) mapping start to
            // the default case
            switchStart = cfw.addTableSwitch(start + 1, end - 1);
        }

        for (int i = start; i != end; ++i) {
            if (generateSwitch) {
                if (i == start) {
                    cfw.markTableSwitchDefault(switchStart);
                    switchStackTop = cfw.getStackTop();
                } else {
                    cfw.markTableSwitchCase(switchStart, i - 1 - start,
                                            switchStackTop);
                }
            }
            OptFunctionNode ofn = OptFunctionNode.get(scriptOrFnNodes[i]);
            cfw.addInvoke(ByteCode.INVOKESPECIAL,
                          mainClassName,
                          getFunctionInitMethodName(ofn),
                          FUNCTION_INIT_SIGNATURE);
            cfw.add(ByteCode.RETURN);
        }

        // 4 = this + scope + context + id
        cfw.stopMethod((short)4);
    }

    private void generateFunctionInit(ClassFileWriter cfw,
                                      OptFunctionNode ofn)
    {
        final int CONTEXT_ARG = 1;
        final int SCOPE_ARG = 2;
        cfw.startMethod(getFunctionInitMethodName(ofn),
                        FUNCTION_INIT_SIGNATURE,
                        (short)(ACC_PRIVATE | ACC_FINAL));

        // Call NativeFunction.initScriptFunction
        cfw.addLoadThis();
        cfw.addALoad(CONTEXT_ARG);
        cfw.addALoad(SCOPE_ARG);
        cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
                      "org/mozilla/javascript/NativeFunction",
                      "initScriptFunction",
                      "(Lorg/mozilla/javascript/Context;"
                      +"Lorg/mozilla/javascript/Scriptable;"
                      +")V");

        // precompile all regexp literals
        if (ofn.fnode.getRegexpCount() != 0) {
            cfw.addALoad(CONTEXT_ARG);
            cfw.addInvoke(ByteCode.INVOKESTATIC, mainClassName,
                          REGEXP_INIT_METHOD_NAME, REGEXP_INIT_METHOD_SIGNATURE);
        }

        cfw.add(ByteCode.RETURN);
        // 3 = (scriptThis/functionRef) + scope + context
        cfw.stopMethod((short)3);
    }

    private void generateNativeFunctionOverrides(ClassFileWriter cfw,
                                                 String encodedSource)
    {
        // Override NativeFunction.getLanguageVersion() with
        // public int getLanguageVersion() { return ; }

        cfw.startMethod("getLanguageVersion", "()I", ACC_PUBLIC);

        cfw.addPush(compilerEnv.getLanguageVersion());
        cfw.add(ByteCode.IRETURN);

        // 1: this and no argument or locals
        cfw.stopMethod((short)1);

        // The rest of NativeFunction overrides require specific code for each
        // script/function id

        final int Do_getFunctionName      = 0;
        final int Do_getParamCount        = 1;
        final int Do_getParamAndVarCount  = 2;
        final int Do_getParamOrVarName    = 3;
        final int Do_getEncodedSource     = 4;
        final int Do_getParamOrVarConst   = 5;
        final int Do_isGeneratorFunction  = 6;
        final int SWITCH_COUNT            = 7;

        for (int methodIndex = 0; methodIndex != SWITCH_COUNT; ++methodIndex) {
            if (methodIndex == Do_getEncodedSource && encodedSource == null) {
                continue;
            }

            // Generate:
            //   prologue;
            //   switch over function id to implement function-specific action
            //   epilogue

            short methodLocals;
            switch (methodIndex) {
              case Do_getFunctionName:
                methodLocals = 1; // Only this
                cfw.startMethod("getFunctionName", "()Ljava/lang/String;",
                                ACC_PUBLIC);
                break;
              case Do_getParamCount:
                methodLocals = 1; // Only this
                cfw.startMethod("getParamCount", "()I",
                                ACC_PUBLIC);
                break;
              case Do_getParamAndVarCount:
                methodLocals = 1; // Only this
                cfw.startMethod("getParamAndVarCount", "()I",
                                ACC_PUBLIC);
                break;
              case Do_getParamOrVarName:
                methodLocals = 1 + 1; // this + paramOrVarIndex
                cfw.startMethod("getParamOrVarName", "(I)Ljava/lang/String;",
                                ACC_PUBLIC);
                break;
              case Do_getParamOrVarConst:
                methodLocals = 1 + 1 + 1; // this + paramOrVarName
                cfw.startMethod("getParamOrVarConst", "(I)Z",
                                ACC_PUBLIC);
                break;
              case Do_getEncodedSource:
                methodLocals = 1; // Only this
                cfw.startMethod("getEncodedSource", "()Ljava/lang/String;",
                                ACC_PUBLIC);
                cfw.addPush(encodedSource);
                break;
              case Do_isGeneratorFunction:
                methodLocals = 1; // Only this
                cfw.startMethod("isGeneratorFunction", "()Z",
                                ACC_PROTECTED);
                break;
              default:
                throw Kit.codeBug();
            }

            int count = scriptOrFnNodes.length;

            int switchStart = 0;
            int switchStackTop = 0;
            if (count > 1) {
                // Generate switch but only if there is more then one
                // script/function
                cfw.addLoadThis();
                cfw.add(ByteCode.GETFIELD, cfw.getClassName(),
                        ID_FIELD_NAME, "I");

                // do switch from 1 .. count - 1 mapping 0 to the default case
                switchStart = cfw.addTableSwitch(1, count - 1);
            }

            for (int i = 0; i != count; ++i) {
                ScriptNode n = scriptOrFnNodes[i];
                if (i == 0) {
                    if (count > 1) {
                        cfw.markTableSwitchDefault(switchStart);
                        switchStackTop = cfw.getStackTop();
                    }
                } else {
                    cfw.markTableSwitchCase(switchStart, i - 1,
                                            switchStackTop);
                }

                // Impelemnet method-specific switch code
                switch (methodIndex) {
                  case Do_getFunctionName:
                    // Push function name
                    if (n.getType() == Token.SCRIPT) {
                        cfw.addPush("");
                    } else {
                        String name = ((FunctionNode)n).getName();
                        cfw.addPush(name);
                    }
                    cfw.add(ByteCode.ARETURN);
                    break;

                  case Do_getParamCount:
                    // Push number of defined parameters
                    cfw.addPush(n.getParamCount());
                    cfw.add(ByteCode.IRETURN);
                    break;

                  case Do_getParamAndVarCount:
                    // Push number of defined parameters and declared variables
                    cfw.addPush(n.getParamAndVarCount());
                    cfw.add(ByteCode.IRETURN);
                    break;

                  case Do_getParamOrVarName:
                    // Push name of parameter using another switch
                    // over paramAndVarCount
                    int paramAndVarCount = n.getParamAndVarCount();
                    if (paramAndVarCount == 0) {
                        // The runtime should never call the method in this
                        // case but to make bytecode verifier happy return null
                        // as throwing execption takes more code
                        cfw.add(ByteCode.ACONST_NULL);
                        cfw.add(ByteCode.ARETURN);
                    } else if (paramAndVarCount == 1) {
                        // As above do not check for valid index but always
                        // return the name of the first param
                        cfw.addPush(n.getParamOrVarName(0));
                        cfw.add(ByteCode.ARETURN);
                    } else {
                        // Do switch over getParamOrVarName
                        cfw.addILoad(1); // param or var index
                        // do switch from 1 .. paramAndVarCount - 1 mapping 0
                        // to the default case
                        int paramSwitchStart = cfw.addTableSwitch(
                                                   1, paramAndVarCount - 1);
                        for (int j = 0; j != paramAndVarCount; ++j) {
                            if (cfw.getStackTop() != 0) Kit.codeBug();
                            String s = n.getParamOrVarName(j);
                            if (j == 0) {
                                cfw.markTableSwitchDefault(paramSwitchStart);
                            } else {
                                cfw.markTableSwitchCase(paramSwitchStart, j - 1,
                                                        0);
                            }
                            cfw.addPush(s);
                            cfw.add(ByteCode.ARETURN);
                        }
                    }
                    break;

                    case Do_getParamOrVarConst:
                        // Push name of parameter using another switch
                        // over paramAndVarCount
                        paramAndVarCount = n.getParamAndVarCount();
                        boolean [] constness = n.getParamAndVarConst();
                        if (paramAndVarCount == 0) {
                            // The runtime should never call the method in this
                            // case but to make bytecode verifier happy return null
                            // as throwing execption takes more code
                            cfw.add(ByteCode.ICONST_0);
                            cfw.add(ByteCode.IRETURN);
                        } else if (paramAndVarCount == 1) {
                            // As above do not check for valid index but always
                            // return the name of the first param
                            cfw.addPush(constness[0]);
                            cfw.add(ByteCode.IRETURN);
                        } else {
                            // Do switch over getParamOrVarName
                            cfw.addILoad(1); // param or var index
                            // do switch from 1 .. paramAndVarCount - 1 mapping 0
                            // to the default case
                            int paramSwitchStart = cfw.addTableSwitch(
                                                       1, paramAndVarCount - 1);
                            for (int j = 0; j != paramAndVarCount; ++j) {
                                if (cfw.getStackTop() != 0) Kit.codeBug();
                                if (j == 0) {
                                    cfw.markTableSwitchDefault(paramSwitchStart);
                                } else {
                                    cfw.markTableSwitchCase(paramSwitchStart, j - 1,
                                                            0);
                                }
                                cfw.addPush(constness[j]);
                                cfw.add(ByteCode.IRETURN);
                            }
                        }
                      break;

                    case Do_isGeneratorFunction:
                        // Push a boolean if it's a generator
                        if (n instanceof FunctionNode) {
                            cfw.addPush(((FunctionNode) n).isES6Generator());
                        } else {
                            cfw.addPush(false);
                        }
                        cfw.add(ByteCode.IRETURN);
                        break;

                  case Do_getEncodedSource:
                    // Push number encoded source start and end
                    // to prepare for encodedSource.substring(start, end)
                    cfw.addPush(n.getEncodedSourceStart());
                    cfw.addPush(n.getEncodedSourceEnd());
                    cfw.addInvoke(ByteCode.INVOKEVIRTUAL,
                                  "java/lang/String",
                                  "substring",
                                  "(II)Ljava/lang/String;");
                    cfw.add(ByteCode.ARETURN);
                    break;

                  default:
                    throw Kit.codeBug();
                }
            }

            cfw.stopMethod(methodLocals);
        }
    }

    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_PRIVATE));
        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);
    }

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

        cfw.startMethod("", "()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/Double;");
            } else {
                cfw.addPush(num);
                addDoubleWrap(cfw);
            }

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

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

        } 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.getExisting(n);
    }

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

    String getBodyMethodName(ScriptNode n)
    {
        return "_c_" + cleanName(n) + "_" + getIndex(n);
    }

    /**
     * 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 getBodyMethodSignature(ScriptNode n)
    {
        StringBuilder sb = new StringBuilder();
        sb.append('(');
        sb.append(mainClassSignature);
        sb.append("Lorg/mozilla/javascript/Context;"
                  +"Lorg/mozilla/javascript/Scriptable;"
                  +"Lorg/mozilla/javascript/Scriptable;");
        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 getFunctionInitMethodName(OptFunctionNode ofn)
    {
        return "_i"+getIndex(ofn.fnode);
    }

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

    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
        = "org.mozilla.javascript.NativeFunction";

    static final String ID_FIELD_NAME = "_id";

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

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

   static final String FUNCTION_CONSTRUCTOR_SIGNATURE
        = "(Lorg/mozilla/javascript/Scriptable;"
          +"Lorg/mozilla/javascript/Context;I)V";

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

    private CompilerEnvirons compilerEnv;

    private ObjArray directCallTargets;
    ScriptNode[] scriptOrFnNodes;
    private ObjToIntMap scriptOrFnIndexes;

    private String mainMethodClass = DEFAULT_MAIN_METHOD_CLASS;

    String mainClassName;
    String mainClassSignature;

    private double[] itsConstantList;
    private int itsConstantListSize;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy