nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
author attila
Wed, 04 Jun 2014 14:32:23 +0200
changeset 24779 3bf490e146c3
parent 24778 2ff5d7041566
child 24993 b707d46bae40
permissions -rw-r--r--
8044502: Get rid of global optimistic flag Reviewed-by: lagergren, sundar

/*
 * 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.ARGUMENTS;
import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;

import java.io.File;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
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.Optimistic;
import jdk.nashorn.internal.ir.debug.ClassHistogramElement;
import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator;
import jdk.nashorn.internal.runtime.CodeInstaller;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.ScriptObject;
import jdk.nashorn.internal.runtime.Source;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
import jdk.nashorn.internal.runtime.logging.Loggable;
import jdk.nashorn.internal.runtime.logging.Logger;

/**
 * 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
 */
@Logger(name="compiler")
public final class Compiler implements Loggable {

    /** 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";

    private final ScriptEnvironment env;

    private final Source source;

    private final String sourceName;

    private final String sourceURL;

    private final boolean optimistic;

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

    private final Set<CompileUnit> compileUnits;

    private final ConstantData constantData;

    private final CodeInstaller<ScriptEnvironment> installer;

    /** logger for compiler, trampolines, splits and related code generation events
     *  that affect classes */
    private final DebugLogger log;

    private final Context context;

    private final TypeMap types;

    // Runtime scope in effect at the time of the compilation. Used to evaluate types of expressions and prevent overly
    // optimistic assumptions (which will lead to unnecessary deoptimizing recompilations).
    private final TypeEvaluator typeEvaluator;

    private final boolean strict;

    private final boolean onDemand;

    /**
     * If this is a recompilation, this is how we pass in the invalidations, e.g. programPoint=17, Type == int means
     * that using whatever was at program point 17 as an int failed.
     */
    private final Map<Integer, Type> invalidatedProgramPoints;

    /**
     * Compile unit name of first compile unit - this prefix will be used for all
     * classes that a compilation generates.
     */
    private final String firstCompileUnitName;

    /**
     * Contains the program point that should be used as the continuation entry point, as well as all previous
     * continuation entry points executed as part of a single logical invocation of the function. In practical terms, if
     * we execute a rest-of method from the program point 17, but then we hit deoptimization again during it at program
     * point 42, and execute a rest-of method from the program point 42, and then we hit deoptimization again at program
     * point 57 and are compiling a rest-of method for it, the values in the array will be [57, 42, 17]. This is only
     * set when compiling a rest-of method. If this method is a rest-of for a non-rest-of method, the array will have
     * one element. If it is a rest-of for a rest-of, the array will have two elements, and so on.
     */
    private final int[] continuationEntryPoints;

    /**
     * ScriptFunction data for what is being compile, where applicable.
     * TODO: make this immutable, propagate it through the CompilationPhases
     */
    private RecompilableScriptFunctionData compiledFunction;

    /**
     * Compilation phases that a compilation goes through
     */
    public static class CompilationPhases implements Iterable<CompilationPhase> {

        /** Singleton that describes a standard eager compilation - this includes code installation */
        public final static CompilationPhases COMPILE_ALL = new CompilationPhases(
                "Compile all",
                new CompilationPhase[] {
                        CompilationPhase.CONSTANT_FOLDING_PHASE,
                        CompilationPhase.LOWERING_PHASE,
                        CompilationPhase.PROGRAM_POINT_PHASE,
                        CompilationPhase.TRANSFORM_BUILTINS_PHASE,
                        CompilationPhase.SPLITTING_PHASE,
                        CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
                        CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
                        CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
                        CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE,
                        CompilationPhase.BYTECODE_GENERATION_PHASE,
                        CompilationPhase.INSTALL_PHASE
                });

        /** Compile all for a rest of method */
        public final static CompilationPhases COMPILE_ALL_RESTOF =
                COMPILE_ALL.setDescription("Compile all, rest of").addAfter(CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE, CompilationPhase.REUSE_COMPILE_UNITS_PHASE);

        /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
        public final static CompilationPhases COMPILE_ALL_NO_INSTALL =
                COMPILE_ALL.
                removeLast().
                setDescription("Compile without install");

        /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
        public final static CompilationPhases COMPILE_UPTO_BYTECODE =
                COMPILE_ALL.
                removeLast().
                removeLast().
                setDescription("Compile upto bytecode");

        /**
         * Singleton that describes back end of method generation, given that we have generated the normal
         * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
         */
        public final static CompilationPhases COMPILE_FROM_BYTECODE = new CompilationPhases(
                "Generate bytecode and install",
                new CompilationPhase[] {
                        CompilationPhase.BYTECODE_GENERATION_PHASE,
                        CompilationPhase.INSTALL_PHASE
                });

        /**
         * Singleton that describes restOf method generation, given that we have generated the normal
         * method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
         */
        public final static CompilationPhases COMPILE_FROM_BYTECODE_RESTOF =
                COMPILE_FROM_BYTECODE.
                addFirst(CompilationPhase.REUSE_COMPILE_UNITS_PHASE).
                setDescription("Generate bytecode and install - RestOf method");

        private final List<CompilationPhase> phases;

        private final String desc;

        private CompilationPhases(final String desc, final CompilationPhase... phases) {
            this.desc = desc;

            final List<CompilationPhase> newPhases = new LinkedList<>();
            newPhases.addAll(Arrays.asList(phases));
            this.phases = Collections.unmodifiableList(newPhases);
        }

        @Override
        public String toString() {
            return "'" + desc + "' " + phases.toString();
        }

        private CompilationPhases setDescription(final String desc) {
            return new CompilationPhases(desc, phases.toArray(new CompilationPhase[phases.size()]));
        }

        private CompilationPhases removeLast() {
            final LinkedList<CompilationPhase> list = new LinkedList<>(phases);
            list.removeLast();
            return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()]));
        }

        private CompilationPhases addFirst(final CompilationPhase phase) {
            if (phases.contains(phase)) {
                return this;
            }
            final LinkedList<CompilationPhase> list = new LinkedList<>(phases);
            list.addFirst(phase);
            return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()]));
        }

        private CompilationPhases addAfter(final CompilationPhase phase, final CompilationPhase newPhase) {
            final LinkedList<CompilationPhase> list = new LinkedList<>();
            for (final CompilationPhase p : phases) {
                list.add(p);
                if (p == phase) {
                    list.add(newPhase);
                }
            }
            return new CompilationPhases(desc, list.toArray(new CompilationPhase[list.size()]));
        }

        boolean contains(final CompilationPhase phase) {
            return phases.contains(phase);
        }

        @Override
        public Iterator<CompilationPhase> iterator() {
            return phases.iterator();
        }

        boolean isRestOfCompilation() {
            return this == COMPILE_ALL_RESTOF || this == COMPILE_FROM_BYTECODE_RESTOF;
        }

        String getDesc() {
            return desc;
        }

        String toString(final String prefix) {
            final StringBuilder sb = new StringBuilder();
            for (final CompilationPhase phase : phases) {
                sb.append(prefix).append(phase).append('\n');
            }
            return sb.toString();
        }
    }

    /**
     * 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.symbolName(),
        THIS.symbolName(),
        RETURN.symbolName(),
        CALLEE.symbolName(),
        VARARGS.symbolName(),
        ARGUMENTS.symbolName()
    };

    // per instance
    private final int compilationId = COMPILATION_ID.getAndIncrement();

    // per instance
    private final AtomicInteger nextCompileUnitId = new AtomicInteger(0);

    private static final AtomicInteger COMPILATION_ID = new AtomicInteger(0);

    /**
     * Constructor
     *
     * @param context                  context
     * @param env                      script environment
     * @param installer                code installer
     * @param source                   source to compile
     * @param sourceURL                source URL, or null if not present
     * @param isStrict                 is this a strict compilation
     */
    public Compiler(
            final Context context,
            final ScriptEnvironment env,
            final CodeInstaller<ScriptEnvironment> installer,
            final Source source,
            final String sourceURL,
            final boolean isStrict) {
        this(context, env, installer, source, sourceURL, isStrict, false, null, null, null, null, null);
    }

    /**
     * Constructor
     *
     * @param context                  context
     * @param env                      script environment
     * @param installer                code installer
     * @param source                   source to compile
     * @param sourceURL                source URL, or null if not present
     * @param isStrict                 is this a strict compilation
     * @param isOnDemand               is this an on demand compilation
     * @param compiledFunction         compiled function, if any
     * @param types                    parameter and return value type information, if any is known
     * @param invalidatedProgramPoints invalidated program points for recompilation
     * @param continuationEntryPoints  continuation entry points for restof method
     * @param runtimeScope             runtime scope for recompilation type lookup in {@code TypeEvaluator}
     */
    public Compiler(
            final Context context,
            final ScriptEnvironment env,
            final CodeInstaller<ScriptEnvironment> installer,
            final Source source,
            final String sourceURL,
            final boolean isStrict,
            final boolean isOnDemand,
            final RecompilableScriptFunctionData compiledFunction,
            final TypeMap types,
            final Map<Integer, Type> invalidatedProgramPoints,
            final int[] continuationEntryPoints,
            final ScriptObject runtimeScope) {
        this.context                  = context;
        this.env                      = env;
        this.installer                = installer;
        this.constantData             = new ConstantData();
        this.compileUnits             = CompileUnit.createCompileUnitSet();
        this.bytecode                 = new LinkedHashMap<>();
        this.log                      = initLogger(context);
        this.source                   = source;
        this.sourceURL                = sourceURL;
        this.sourceName               = FunctionNode.getSourceName(source, sourceURL);
        this.onDemand                 = isOnDemand;
        this.compiledFunction         = compiledFunction;
        this.types                    = types;
        this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap<Integer, Type>() : invalidatedProgramPoints;
        this.continuationEntryPoints  = continuationEntryPoints == null ? null: continuationEntryPoints.clone();
        this.typeEvaluator            = new TypeEvaluator(this, runtimeScope);
        this.firstCompileUnitName     = firstCompileUnitName();
        this.strict                   = isStrict;

        this.optimistic = env._optimistic_types;
    }

    private static String safeSourceName(final ScriptEnvironment env, final CodeInstaller<ScriptEnvironment> installer, 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('-', '_');
        if (!env._loader_per_compile) {
            baseName = baseName + installer.getUniqueScriptId();
        }

        final String mangled = NameCodec.encode(baseName);
        return mangled != null ? mangled : baseName;
    }

    private String firstCompileUnitName() {
        final StringBuilder sb = new StringBuilder(SCRIPTS_PACKAGE).
                append('/').
                append(CompilerConstants.DEFAULT_SCRIPT_NAME.symbolName()).
                append('$');

        if (isOnDemandCompilation()) {
            sb.append(RecompilableScriptFunctionData.RECOMPILATION_PREFIX);
        }

        if (compilationId > 0) {
            sb.append(compilationId).append('$');
        }

        sb.append(Compiler.safeSourceName(env, installer, source));

        return sb.toString();
    }

    void declareLocalSymbol(final String symbolName) {
        typeEvaluator.declareLocalSymbol(symbolName);
    }

    void setData(final RecompilableScriptFunctionData data) {
        assert this.compiledFunction == null : data;
        this.compiledFunction = data;
    }

    @Override
    public DebugLogger getLogger() {
        return log;
    }

    @Override
    public DebugLogger initLogger(final Context ctxt) {
        return ctxt.getLogger(this.getClass());
    }

    ScriptEnvironment getScriptEnvironment() {
        return env;
    }

    boolean isOnDemandCompilation() {
        return onDemand;
    }

    boolean useOptimisticTypes() {
        return optimistic;
    }

    Context getContext() {
        return context;
    }

    Type getOptimisticType(final Optimistic node) {
        return typeEvaluator.getOptimisticType(node);
    }

    void addInvalidatedProgramPoint(final int programPoint, final Type type) {
        invalidatedProgramPoints.put(programPoint, type);
    }

    TypeMap getTypeMap() {
        return types;
    }

    MethodType getCallSiteType(final FunctionNode fn) {
        if (types == null || !isOnDemandCompilation()) {
            return null;
        }
        return types.getCallSiteType(fn);
    }

    Type getParamType(final FunctionNode fn, final int pos) {
        return types == null ? null : types.get(fn, pos);
    }

    /**
     * Do a compilation job
     *
     * @param functionNode function node to compile
     * @param phases phases of compilation transforms to apply to function

     * @return transformed function
     *
     * @throws CompilationException if error occurs during compilation
     */
    public FunctionNode compile(final FunctionNode functionNode, final CompilationPhases phases) throws CompilationException {

        log.info("Starting compile job for ", DebugLogger.quote(functionNode.getName()), " phases=", quote(phases.getDesc()));
        log.indent();

        final String name = DebugLogger.quote(functionNode.getName());

        FunctionNode newFunctionNode = functionNode;

        for (final String reservedName : RESERVED_NAMES) {
            newFunctionNode.uniqueName(reservedName);
        }

        final boolean info = log.levelFinerThanOrEqual(Level.INFO);

        final DebugLogger timeLogger = env.isTimingEnabled() ? env._timing.getLogger() : null;

        long time = 0L;

        for (final CompilationPhase phase : phases) {
            log.fine(phase, " starting for ", quote(name));
            newFunctionNode = phase.apply(this, phases, newFunctionNode);
            log.fine(phase, " done for function ", quote(name));

            if (env._print_mem_usage) {
                printMemoryUsage(functionNode, phase.toString());
            }

            time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L);
        }

        log.unindent();

        if (info) {
            final StringBuilder sb = new StringBuilder();
            sb.append("Compile job for ").append(newFunctionNode.getSource()).append(':').append(quote(newFunctionNode.getName())).append(" finished");
            if (time > 0L && timeLogger != null) {
                assert env.isTimingEnabled();
                sb.append(" in ").append(time).append(" ms");
            }
            log.info(sb);
        }

        return newFunctionNode;
    }

    Source getSource() {
        return source;
    }

    Map<String, byte[]> getBytecode() {
        return Collections.unmodifiableMap(bytecode);
    }

    byte[] getBytecode(final String className) {
        return bytecode.get(className);
    }

    CompileUnit getFirstCompileUnit() {
        assert !compileUnits.isEmpty();
        return compileUnits.iterator().next();
    }

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

    ConstantData getConstantData() {
        return constantData;
    }

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

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

    void removeClass(final String name) {
        assert bytecode.get(name) != null;
        bytecode.remove(name);
    }

    String getSourceURL() {
        return sourceURL;
    }

    String nextCompileUnitName() {
        final StringBuilder sb = new StringBuilder(firstCompileUnitName);
        final int cuid = nextCompileUnitId.getAndIncrement();
        if (cuid > 0) {
            sb.append("$cu").append(cuid);
        }

        return sb.toString();
    }

    void clearCompileUnits() {
        compileUnits.clear();
    }

    CompileUnit addCompileUnit(final long initialWeight) {
        final CompileUnit compileUnit = createCompileUnit(initialWeight);
        compileUnits.add(compileUnit);
        log.fine("Added compile unit ", compileUnit);
        return compileUnit;
    }

    CompileUnit createCompileUnit(final String unitClassName, final long initialWeight) {
        final ClassEmitter classEmitter = new ClassEmitter(context, sourceName, unitClassName, isStrict());
        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;
    }

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

    boolean isStrict() {
        return strict;
    }

    void replaceCompileUnits(final Set<CompileUnit> newUnits) {
        compileUnits.clear();
        compileUnits.addAll(newUnits);
    }

    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('/', '.');
    }

    RecompilableScriptFunctionData getProgram() {
        if (compiledFunction == null) {
            return null;
        }
        return compiledFunction.getProgram();
    }

    RecompilableScriptFunctionData getScriptFunctionData(final int functionId) {
        return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId);
    }

    boolean isGlobalSymbol(final FunctionNode fn, final String name) {
        return getScriptFunctionData(fn.getId()).isGlobalSymbol(fn, name);
    }

    int[] getContinuationEntryPoints() {
        return continuationEntryPoints;
    }

    Type getInvalidatedProgramPointType(final int programPoint) {
        return invalidatedProgramPoints.get(programPoint);
    }

    private void printMemoryUsage(final FunctionNode functionNode, final String phaseName) {
        if (!log.isEnabled()) {
            return;
        }

        log.info(phaseName, "finished. Doing IR size calculation...");

        final ObjectSizeCalculator osc = new ObjectSizeCalculator(ObjectSizeCalculator.getEffectiveMemoryLayoutSpecification());
        osc.calculateObjectSize(functionNode);

        final List<ClassHistogramElement> list      = osc.getClassHistogram();
        final StringBuilder               sb        = new StringBuilder();
        final long                        totalSize = osc.calculateObjectSize(functionNode);

        sb.append(phaseName).
            append(" Total size = ").
            append(totalSize / 1024 / 1024).
            append("MB");
        log.info(sb);

        Collections.sort(list, new Comparator<ClassHistogramElement>() {
            @Override
            public int compare(final ClassHistogramElement o1, final ClassHistogramElement o2) {
                final long diff = o1.getBytes() - o2.getBytes();
                if (diff < 0) {
                    return 1;
                } else if (diff > 0) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        for (final ClassHistogramElement e : list) {
            final String line = String.format("    %-48s %10d bytes (%8d instances)", e.getClazz(), e.getBytes(), e.getInstances());
            log.info(line);
            if (e.getBytes() < totalSize / 200) {
                log.info("    ...");
                break; // never mind, so little memory anyway
            }
        }
    }
}