nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
author sundar
Wed, 20 Feb 2013 17:08:32 +0530
changeset 16250 96a7a6218ad3
parent 16234 86cb162cec6c
child 16252 3bfe9b68a0fa
permissions -rw-r--r--
8008207: Make constants array and source fields private Reviewed-by: hannesw, lagergren

/*
 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.codegen;

import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME;
import static jdk.nashorn.internal.codegen.CompilerConstants.LAZY;
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;

import java.io.File;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import jdk.internal.dynalink.support.NameCodec;
import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
import jdk.nashorn.internal.codegen.types.Type;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.runtime.CodeInstaller;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.DebugLogger;
import jdk.nashorn.internal.runtime.Source;
import jdk.nashorn.internal.runtime.options.Options;

/**
 * Responsible for converting JavaScripts to java byte code. Main entry
 * point for code generator. The compiler may also install classes given some
 * predefined Code installation policy, given to it at construction time.
 * @see CodeInstaller
 */
public final class Compiler {

    /** Name of the scripts package */
    public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts";

    /** Name of the objects package */
    public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects";

    static final boolean LAZY_JIT = Options.getBooleanProperty("nashorn.compiler.lazy");

    static final boolean TIME_COMPILATION = Options.getBooleanProperty("nashorn.compiler.time");

    private final Map<String, byte[]> bytecode;

    private final Set<CompileUnit> compileUnits;

    private final ConstantData constantData;

    private final FunctionNode functionNode;

    private final CompilationSequence sequence;

    private final Context context;

    private final String scriptName;

    private boolean strict;

    private CodeInstaller<Context> installer;

    static final DebugLogger LOG = new DebugLogger("compiler");

    /**
     * This array contains names that need to be reserved at the start
     * of a compile, to avoid conflict with variable names later introduced.
     * See {@link CompilerConstants} for special names used for structures
     * during a compile.
     */
    private static String[] RESERVED_NAMES = {
        SCOPE.tag(),
        THIS.tag()
    };

    /**
     * This class makes it possible to do your own compilation sequence
     * from the code generation package. There are predefined compilation
     * sequences already
     */
    @SuppressWarnings("serial")
    static class CompilationSequence extends LinkedList<CompilationPhase> {

        CompilationSequence(final CompilationPhase... phases) {
            super(Arrays.asList(phases));
        }

        CompilationSequence(final CompilationSequence sequence) {
            this(sequence.toArray(new CompilationPhase[sequence.size()]));
        }

        CompilationSequence insertAfter(final CompilationPhase phase, final CompilationPhase newPhase) {
            final CompilationSequence newSeq = new CompilationSequence();
            for (final CompilationPhase elem : this) {
                newSeq.add(phase);
                if (elem.equals(phase)) {
                    newSeq.add(newPhase);
                }
            }
            assert newSeq.contains(newPhase);
            return newSeq;
        }

        CompilationSequence insertBefore(final CompilationPhase phase, final CompilationPhase newPhase) {
            final CompilationSequence newSeq = new CompilationSequence();
            for (final CompilationPhase elem : this) {
                if (elem.equals(phase)) {
                    newSeq.add(newPhase);
                }
                newSeq.add(phase);
            }
            assert newSeq.contains(newPhase);
            return newSeq;
        }

        CompilationSequence insertFirst(final CompilationPhase phase) {
            final CompilationSequence newSeq = new CompilationSequence(this);
            newSeq.addFirst(phase);
            return newSeq;
        }

        CompilationSequence insertLast(final CompilationPhase phase) {
            final CompilationSequence newSeq = new CompilationSequence(this);
            newSeq.addLast(phase);
            return newSeq;
        }
    }

    /**
     * Standard (non-lazy) compilation, that basically will take an entire script
     * and JIT it at once. This can lead to long startup time and fewer type
     * specializations
     */
    final static CompilationSequence SEQUENCE_NORMAL = new CompilationSequence(
        CompilationPhase.CONSTANT_FOLDING_PHASE,
        CompilationPhase.LOWERING_PHASE,
        CompilationPhase.ATTRIBUTION_PHASE,
        CompilationPhase.SPLITTING_PHASE,
        CompilationPhase.TYPE_FINALIZATION_PHASE,
        CompilationPhase.BYTECODE_GENERATION_PHASE);

    final static CompilationSequence SEQUENCE_LAZY =
        SEQUENCE_NORMAL.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE);

    final static CompilationSequence SEQUENCE_DEFAULT =
        LAZY_JIT ?
            SEQUENCE_LAZY :
            SEQUENCE_NORMAL;

    /**
     * Constructor
     *
     * @param installer    code installer from
     * @param functionNode function node (in any available {@link CompilationState}) to compile
     * @param sequence     {@link Compiler#CompilationSequence} of {@link CompilationPhase}s to apply as this compilation
     * @param strict       should this compilation use strict mode semantics
     */
    Compiler(final Context context, final CodeInstaller<Context> installer, final FunctionNode functionNode, final CompilationSequence sequence, final boolean strict) {
        this.context       = context;
        this.functionNode  = functionNode;
        this.sequence      = sequence;
        this.installer     = installer;
        this.strict        = strict || functionNode.isStrictMode();
        this.constantData  = new ConstantData();
        this.compileUnits  = new HashSet<>();
        this.bytecode      = new HashMap<>();

        final StringBuilder sb = new StringBuilder();
        sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.tag())).
                append('$').
                append(safeSourceName(functionNode.getSource())).
                append(functionNode.isLazy() ? LAZY.tag() : "");

        this.scriptName = sb.toString();

        LOG.info("Initializing compiler for scriptName = " + scriptName + ", root function: '" + functionNode.getName() + "'");
    }

    /**
     * Constructor
     *
     * @param installer    code installer from context
     * @param functionNode function node (in any available {@link CompilationState}) to compile
     * @param strict       should this compilation use strict mode semantics
     */
    public Compiler(final CodeInstaller<Context> installer, final FunctionNode functionNode, final boolean strict) {
        this(installer.getOwner(), installer, functionNode, SEQUENCE_DEFAULT, strict);
    }

    /**
     * Constructor - compilation will use the same strict semantics as context
     *
     * @param installer    code installer from context
     * @param functionNode function node (in any available {@link CompilationState}) to compile
     */
    public Compiler(final CodeInstaller<Context> installer, final FunctionNode functionNode) {
        this(installer.getOwner(), installer, functionNode, SEQUENCE_DEFAULT, installer.getOwner()._strict);
    }

    /**
     * Constructor - compilation needs no installer, but uses a context
     * Used in "compile only" scenarios
     * @param context a context
     * @param functionNode functionNode to compile
     */
    public Compiler(final Context context, final FunctionNode functionNode) {
        this(context, null, functionNode, SEQUENCE_DEFAULT, context._strict);
    }

    /**
     * Execute the compilation this Compiler was created with
     * @return true if compilation succeeds.
     */
    public boolean compile() {
        for (final String reservedName : RESERVED_NAMES) {
            functionNode.uniqueName(reservedName);
        }

        for (final CompilationPhase phase : sequence) {
            LOG.info("Entering compile phase " + phase + " for function '" + functionNode.getName() + "'");
            if (phase.isApplicable(functionNode)) {
                if (!phase.apply(this, functionNode)) { //TODO exceptions, error logging
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Install compiled classes into a given loader
     * @return root script class - if there are several compile units they will also be installed
     */
    public Class<?> install() {
        Class<?> rootClass = null;

        for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
            final String     className = entry.getKey();
            LOG.info("Installing class " + className);

            final byte[]     code  = entry.getValue();
            final Class<?>   clazz = installer.install(Compiler.binaryName(className), code);

            if (rootClass == null && firstCompileUnitName().equals(className)) {
                rootClass = clazz;
            }

            try {
                final Source source = getSource();
                final Object[] constants = getConstantData().toArray();
                // Need doPrivileged because these fields are private
                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                    @Override
                    public Void run() throws Exception {
                        //use reflection to write source and constants table to installed classes
                        final Field sourceField = clazz.getDeclaredField(SOURCE.tag());
                        final Field constantsField = clazz.getDeclaredField(CONSTANTS.tag());
                        sourceField.setAccessible(true);
                        constantsField.setAccessible(true);
                        sourceField.set(null, source);
                        constantsField.set(null, constants);
                        return null;
                    }
                });
            } catch (final PrivilegedActionException e) {
                throw new RuntimeException(e);
            }
        }

        LOG.info("Root class: " + rootClass);
        return rootClass;
    }

    Set<CompileUnit> getCompileUnits() {
        return compileUnits;
    }

    boolean getStrictMode() {
        return strict;
    }

    void setStrictMode(final boolean strict) {
        this.strict = strict;
    }

    FunctionNode getFunctionNode() {
        return functionNode;
    }

    ConstantData getConstantData() {
        return constantData;
    }

    CodeInstaller<Context> getCodeInstaller() {
        return installer;
    }

    Source getSource() {
        return functionNode.getSource();
    }

    void addClass(final String name, final byte[] code) {
        bytecode.put(name, code);
    }

    Context getContext() {
        return this.context;
    }

    private static String safeSourceName(final Source source) {
        String baseName = new File(source.getName()).getName();

        final int index = baseName.lastIndexOf(".js");
        if (index != -1) {
            baseName = baseName.substring(0, index);
        }

        baseName = baseName.replace('.', '_').replace('-', '_');
        final String mangled = NameCodec.encode(baseName);

        return mangled != null ? mangled : baseName;
    }

    private int nextCompileUnitIndex() {
        return compileUnits.size() + 1;
    }

    String firstCompileUnitName() {
        return SCRIPTS_PACKAGE + '/' + scriptName;
    }

    private String nextCompileUnitName() {
        return firstCompileUnitName() + '$' + nextCompileUnitIndex();
    }

    CompileUnit addCompileUnit(final long initialWeight) {
        return addCompileUnit(nextCompileUnitName(), initialWeight);
    }

    CompileUnit addCompileUnit(final String unitClassName) {
        return addCompileUnit(unitClassName, 0L);
    }

    private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) {
        final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight);
        compileUnits.add(compileUnit);
        LOG.info("Added compile unit " + compileUnit);
        return compileUnit;
    }

    private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) {
        final ClassEmitter classEmitter = new ClassEmitter(context, functionNode.getSource().getName(), unitClassName, strict);
        final CompileUnit  compileUnit  = new CompileUnit(unitClassName, classEmitter, initialWeight);

        classEmitter.begin();

        final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE));
        initMethod.begin();
        initMethod.load(Type.OBJECT, 0);
        initMethod.newInstance(jdk.nashorn.internal.scripts.JS$.class);
        initMethod.returnVoid();
        initMethod.end();

        return compileUnit;
    }

    CompileUnit findUnit(final long weight) {
        for (final CompileUnit unit : compileUnits) {
            if (unit.canHold(weight)) {
                unit.addWeight(weight);
                return unit;
            }
        }

        return addCompileUnit(weight);
    }

    /**
     * Convert a package/class name to a binary name.
     *
     * @param name Package/class name.
     * @return Binary name.
     */
    public static String binaryName(final String name) {
        return name.replace('/', '.');
    }

    /**
     * Should we use integers for arithmetic operations as well?
     * TODO: We currently generate no overflow checks so this is
     * disabled
     *
     * @see #shouldUseIntegers()
     *
     * @return true if arithmetic operations should not widen integer
     *   operands by default.
     */
    static boolean shouldUseIntegerArithmetic() {
        return USE_INT_ARITH;
    }

    private static final boolean USE_INT_ARITH;

    static {
        USE_INT_ARITH  =  Options.getBooleanProperty("nashorn.compiler.intarithmetic");
        assert !USE_INT_ARITH : "Integer arithmetic is not enabled";
    }

    static {
        if (TIME_COMPILATION) {
            Runtime.getRuntime().addShutdownHook(new Thread() {
                @Override
                public void run() {
                    for (final CompilationPhase phase : CompilationPhase.values()) {
                        final StringBuilder sb = new StringBuilder();
                        sb.append(phase);
                        while (sb.length() < 32) {
                            sb.append(' ');
                        }
                        sb.append(CompilationPhase.getAccumulatedTime(phase));
                        sb.append(' ');
                        sb.append(" ms");
                        System.err.println(sb.toString()); //Context err is gone by shutdown TODO
                    }
                }
            });
        }
    }
}