--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk.scripting.nashorn/share/classes/jdk/nashorn/internal/codegen/Compiler.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,887 @@
+/*
+ * 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.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+import java.util.logging.Level;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.Expression;
+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.ErrorManager;
+import jdk.nashorn.internal.runtime.FunctionInitializer;
+import jdk.nashorn.internal.runtime.ParserException;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
+import jdk.nashorn.internal.runtime.ScriptEnvironment;
+import jdk.nashorn.internal.runtime.ScriptObject;
+import jdk.nashorn.internal.runtime.ScriptRuntime;
+import jdk.nashorn.internal.runtime.Source;
+import jdk.nashorn.internal.runtime.linker.NameCodec;
+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 ErrorManager errors;
+
+ private final boolean optimistic;
+
+ private final Map<String, byte[]> bytecode;
+
+ private final Set<CompileUnit> compileUnits;
+
+ private final ConstantData constantData;
+
+ private final CodeInstaller installer;
+
+ /** logger for compiler, trampolines 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;
+
+ /**
+ * Descriptor of the location where we write the type information after compilation.
+ */
+ private final Object typeInformationFile;
+
+ /**
+ * 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;
+
+ /**
+ * Most compile unit names are longer than the default StringBuilder buffer,
+ * worth startup performance when massive class generation is going on to increase
+ * this
+ */
+ private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32;
+
+ /**
+ * Compilation phases that a compilation goes through
+ */
+ public static class CompilationPhases implements Iterable<CompilationPhase> {
+
+ /**
+ * Singleton that describes compilation up to the phase where a function can be cached.
+ */
+ private final static CompilationPhases COMPILE_UPTO_CACHED = new CompilationPhases(
+ "Common initial phases",
+ CompilationPhase.CONSTANT_FOLDING_PHASE,
+ CompilationPhase.LOWERING_PHASE,
+ CompilationPhase.APPLY_SPECIALIZATION_PHASE,
+ CompilationPhase.SPLITTING_PHASE,
+ CompilationPhase.PROGRAM_POINT_PHASE,
+ CompilationPhase.SYMBOL_ASSIGNMENT_PHASE,
+ CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE,
+ CompilationPhase.CACHE_AST_PHASE
+ );
+
+ private final static CompilationPhases COMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
+ "After common phases, before bytecode generator",
+ CompilationPhase.DECLARE_LOCAL_SYMBOLS_PHASE,
+ CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE,
+ CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE
+ );
+
+ /**
+ * Singleton that describes additional steps to be taken after retrieving a cached function, all the
+ * way up to (but not including) generating and installing code.
+ */
+ public final static CompilationPhases RECOMPILE_CACHED_UPTO_BYTECODE = new CompilationPhases(
+ "Recompile cached function up to bytecode",
+ CompilationPhase.REINITIALIZE_CACHED,
+ COMPILE_CACHED_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 GENERATE_BYTECODE_AND_INSTALL = new CompilationPhases(
+ "Generate bytecode and install",
+ CompilationPhase.BYTECODE_GENERATION_PHASE,
+ CompilationPhase.INSTALL_PHASE
+ );
+
+ /** Singleton that describes compilation up to the CodeGenerator, but not actually generating code */
+ public final static CompilationPhases COMPILE_UPTO_BYTECODE = new CompilationPhases(
+ "Compile upto bytecode",
+ COMPILE_UPTO_CACHED,
+ COMPILE_CACHED_UPTO_BYTECODE);
+
+ /** Singleton that describes a standard eager compilation, but no installation, for example used by --compile-only */
+ public final static CompilationPhases COMPILE_ALL_NO_INSTALL = new CompilationPhases(
+ "Compile without install",
+ COMPILE_UPTO_BYTECODE,
+ CompilationPhase.BYTECODE_GENERATION_PHASE);
+
+ /** Singleton that describes a standard eager compilation - this includes code installation */
+ public final static CompilationPhases COMPILE_ALL = new CompilationPhases(
+ "Full eager compilation",
+ COMPILE_UPTO_BYTECODE,
+ GENERATE_BYTECODE_AND_INSTALL);
+
+ /** Singleton that describes a full compilation - this includes code installation - from serialized state*/
+ public final static CompilationPhases COMPILE_ALL_CACHED = new CompilationPhases(
+ "Eager compilation from serializaed state",
+ RECOMPILE_CACHED_UPTO_BYTECODE,
+ GENERATE_BYTECODE_AND_INSTALL);
+
+ /**
+ * 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 GENERATE_BYTECODE_AND_INSTALL_RESTOF = new CompilationPhases(
+ "Generate bytecode and install - RestOf method",
+ CompilationPhase.REUSE_COMPILE_UNITS_PHASE,
+ GENERATE_BYTECODE_AND_INSTALL);
+
+ /** Compile all for a rest of method */
+ public final static CompilationPhases COMPILE_ALL_RESTOF = new CompilationPhases(
+ "Compile all, rest of",
+ COMPILE_UPTO_BYTECODE,
+ GENERATE_BYTECODE_AND_INSTALL_RESTOF);
+
+ /** Compile from serialized for a rest of method */
+ public final static CompilationPhases COMPILE_CACHED_RESTOF = new CompilationPhases(
+ "Compile serialized, rest of",
+ RECOMPILE_CACHED_UPTO_BYTECODE,
+ GENERATE_BYTECODE_AND_INSTALL_RESTOF);
+
+ private final List<CompilationPhase> phases;
+
+ private final String desc;
+
+ private CompilationPhases(final String desc, final CompilationPhase... phases) {
+ this(desc, Arrays.asList(phases));
+ }
+
+ private CompilationPhases(final String desc, final CompilationPhases base, final CompilationPhase... phases) {
+ this(desc, concat(base.phases, Arrays.asList(phases)));
+ }
+
+ private CompilationPhases(final String desc, final CompilationPhase first, final CompilationPhases rest) {
+ this(desc, concat(Collections.singletonList(first), rest.phases));
+ }
+
+ private CompilationPhases(final String desc, final CompilationPhases base) {
+ this(desc, base.phases);
+ }
+
+ private CompilationPhases(final String desc, final CompilationPhases... bases) {
+ this(desc, concatPhases(bases));
+ }
+
+ private CompilationPhases(final String desc, final List<CompilationPhase> phases) {
+ this.desc = desc;
+ this.phases = phases;
+ }
+
+ private static List<CompilationPhase> concatPhases(final CompilationPhases[] bases) {
+ final ArrayList<CompilationPhase> l = new ArrayList<>();
+ for(final CompilationPhases base: bases) {
+ l.addAll(base.phases);
+ }
+ l.trimToSize();
+ return l;
+ }
+
+ private static <T> List<T> concat(final List<T> l1, final List<T> l2) {
+ final ArrayList<T> l = new ArrayList<>(l1);
+ l.addAll(l2);
+ l.trimToSize();
+ return l;
+ }
+
+ @Override
+ public String toString() {
+ return "'" + desc + "' " + phases.toString();
+ }
+
+ 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 == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_CACHED_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);
+
+ /**
+ * Creates a new compiler instance for initial compilation of a script.
+ *
+ * @param installer code installer
+ * @param source source to compile
+ * @param errors error manager
+ * @param isStrict is this a strict compilation
+ * @return a new compiler
+ */
+ public static Compiler forInitialCompilation(
+ final CodeInstaller installer,
+ final Source source,
+ final ErrorManager errors,
+ final boolean isStrict) {
+ return new Compiler(installer.getContext(), installer, source, errors, isStrict);
+ }
+
+ /**
+ * Creates a compiler without a code installer. It can only be used to compile code, not install the
+ * generated classes and as such it is useful only for implementation of {@code --compile-only} command
+ * line option.
+ * @param context the current context
+ * @param source source to compile
+ * @param isStrict is this a strict compilation
+ * @return a new compiler
+ */
+ public static Compiler forNoInstallerCompilation(
+ final Context context,
+ final Source source,
+ final boolean isStrict) {
+ return new Compiler(context, null, source, context.getErrorManager(), isStrict);
+ }
+
+ /**
+ * Creates a compiler for an on-demand compilation job.
+ *
+ * @param installer code installer
+ * @param source source to compile
+ * @param isStrict is this a strict 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 typeInformationFile descriptor of the location where type information is persisted
+ * @param continuationEntryPoints continuation entry points for restof method
+ * @param runtimeScope runtime scope for recompilation type lookup in {@code TypeEvaluator}
+ * @return a new compiler
+ */
+ public static Compiler forOnDemandCompilation(
+ final CodeInstaller installer,
+ final Source source,
+ final boolean isStrict,
+ final RecompilableScriptFunctionData compiledFunction,
+ final TypeMap types,
+ final Map<Integer, Type> invalidatedProgramPoints,
+ final Object typeInformationFile,
+ final int[] continuationEntryPoints,
+ final ScriptObject runtimeScope) {
+ final Context context = installer.getContext();
+ return new Compiler(context, installer, source, context.getErrorManager(), isStrict, true,
+ compiledFunction, types, invalidatedProgramPoints, typeInformationFile,
+ continuationEntryPoints, runtimeScope);
+ }
+
+ /**
+ * Convenience constructor for non on-demand compiler instances.
+ */
+ private Compiler(
+ final Context context,
+ final CodeInstaller installer,
+ final Source source,
+ final ErrorManager errors,
+ final boolean isStrict) {
+ this(context, installer, source, errors, isStrict, false, null, null, null, null, null, null);
+ }
+
+ private Compiler(
+ final Context context,
+ final CodeInstaller installer,
+ final Source source,
+ final ErrorManager errors,
+ final boolean isStrict,
+ final boolean isOnDemand,
+ final RecompilableScriptFunctionData compiledFunction,
+ final TypeMap types,
+ final Map<Integer, Type> invalidatedProgramPoints,
+ final Object typeInformationFile,
+ final int[] continuationEntryPoints,
+ final ScriptObject runtimeScope) {
+ this.context = context;
+ this.env = context.getEnv();
+ this.installer = installer;
+ this.constantData = new ConstantData();
+ this.compileUnits = CompileUnit.createCompileUnitSet();
+ this.bytecode = new LinkedHashMap<>();
+ this.log = initLogger(context);
+ this.source = source;
+ this.errors = errors;
+ this.sourceName = FunctionNode.getSourceName(source);
+ this.onDemand = isOnDemand;
+ this.compiledFunction = compiledFunction;
+ this.types = types;
+ this.invalidatedProgramPoints = invalidatedProgramPoints == null ? new HashMap<>() : invalidatedProgramPoints;
+ this.typeInformationFile = typeInformationFile;
+ 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 String safeSourceName() {
+ 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 += installer.getUniqueScriptId();
+ }
+
+ // ASM's bytecode verifier does not allow JVM allowed safe escapes using '\' as escape char.
+ // While ASM accepts such escapes for method names, field names, it enforces Java identifier
+ // for class names. Workaround that ASM bug here by replacing JVM 'dangerous' chars with '_'
+ // rather than safe encoding using '\'.
+ final String mangled = env._verify_code? replaceDangerChars(baseName) : NameCodec.encode(baseName);
+ return mangled != null ? mangled : baseName;
+ }
+
+ private static final String DANGEROUS_CHARS = "\\/.;:$[]<>";
+ private static String replaceDangerChars(final String name) {
+ final int len = name.length();
+ final StringBuilder buf = new StringBuilder();
+ for (int i = 0; i < len; i++) {
+ final char ch = name.charAt(i);
+ if (DANGEROUS_CHARS.indexOf(ch) != -1) {
+ buf.append('_');
+ } else {
+ buf.append(ch);
+ }
+ }
+ return buf.toString();
+ }
+
+ 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('$');
+ }
+
+ if (types != null && compiledFunction.getFunctionNodeId() > 0) {
+ sb.append(compiledFunction.getFunctionNodeId());
+ final Type[] paramTypes = types.getParameterTypes(compiledFunction.getFunctionNodeId());
+ for (final Type t : paramTypes) {
+ sb.append(Type.getShortSignatureDescriptor(t));
+ }
+ sb.append('$');
+ }
+
+ sb.append(safeSourceName());
+
+ 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) {
+ final boolean optimisticTypes = env._optimistic_types;
+ final boolean lazyCompilation = env._lazy_compilation;
+
+ return ctxt.getLogger(this.getClass(), new Consumer<DebugLogger>() {
+ @Override
+ public void accept(final DebugLogger newLogger) {
+ if (!lazyCompilation) {
+ newLogger.warning("WARNING: Running with lazy compilation switched off. This is not a default setting.");
+ }
+ newLogger.warning("Optimistic types are ", optimisticTypes ? "ENABLED." : "DISABLED.");
+ }
+ });
+ }
+
+ 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);
+ }
+
+ /**
+ * Returns true if the expression can be safely evaluated, and its value is an object known to always use
+ * String as the type of its property names retrieved through
+ * {@link ScriptRuntime#toPropertyIterator(Object)}. It is used to avoid optimistic assumptions about its
+ * property name types.
+ * @param expr the expression to test
+ * @return true if the expression can be safely evaluated, and its value is an object known to always use
+ * String as the type of its property iterators.
+ */
+ boolean hasStringPropertyIterator(final Expression expr) {
+ return typeEvaluator.hasStringPropertyIterator(expr);
+ }
+
+ void addInvalidatedProgramPoint(final int programPoint, final Type type) {
+ invalidatedProgramPoints.put(programPoint, type);
+ }
+
+
+ /**
+ * Returns a copy of this compiler's current mapping of invalidated optimistic program points to their types. The
+ * copy is not live with regard to changes in state in this compiler instance, and is mutable.
+ * @return a copy of this compiler's current mapping of invalidated optimistic program points to their types.
+ */
+ public Map<Integer, Type> getInvalidatedProgramPoints() {
+ return invalidatedProgramPoints.isEmpty() ? null : new TreeMap<>(invalidatedProgramPoints);
+ }
+
+ 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 {
+ if (log.isEnabled()) {
+ 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.isLoggable(Level.INFO);
+
+ final DebugLogger timeLogger = env.isTimingEnabled() ? env._timing.getLogger() : null;
+
+ long time = 0L;
+
+ for (final CompilationPhase phase : phases) {
+ log.fine(phase, " starting for ", name);
+
+ try {
+ newFunctionNode = phase.apply(this, phases, newFunctionNode);
+ } catch (final ParserException error) {
+ errors.error(error);
+ if (env._dump_on_error) {
+ error.printStackTrace(env.getErr());
+ }
+ return null;
+ }
+
+ 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);
+ }
+
+ if (typeInformationFile != null && !phases.isRestOfCompilation()) {
+ OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints);
+ }
+
+ log.unindent();
+
+ if (info) {
+ final StringBuilder sb = new StringBuilder("<< Finished compile job for ");
+ sb.append(newFunctionNode.getSource()).
+ append(':').
+ append(quote(newFunctionNode.getName()));
+
+ if (time > 0L && timeLogger != null) {
+ assert env.isTimingEnabled();
+ sb.append(" in ").append(TimeUnit.NANOSECONDS.toMillis(time)).append(" ms");
+ }
+ log.info(sb);
+ }
+
+ return newFunctionNode;
+ }
+
+ Source getSource() {
+ return source;
+ }
+
+ Map<String, byte[]> getBytecode() {
+ return Collections.unmodifiableMap(bytecode);
+ }
+
+ /**
+ * Reset bytecode cache for compiler reuse.
+ */
+ void clearBytecode() {
+ bytecode.clear();
+ }
+
+ CompileUnit getFirstCompileUnit() {
+ assert !compileUnits.isEmpty();
+ return compileUnits.iterator().next();
+ }
+
+ Set<CompileUnit> getCompileUnits() {
+ return compileUnits;
+ }
+
+ ConstantData getConstantData() {
+ return constantData;
+ }
+
+ CodeInstaller getCodeInstaller() {
+ return installer;
+ }
+
+ void addClass(final String name, final byte[] code) {
+ bytecode.put(name, code);
+ }
+
+ String nextCompileUnitName() {
+ final StringBuilder sb = new StringBuilder(COMPILE_UNIT_NAME_BUFFER_SIZE);
+ sb.append(firstCompileUnitName);
+ final int cuid = nextCompileUnitId.getAndIncrement();
+ if (cuid > 0) {
+ sb.append("$cu").append(cuid);
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Persist current compilation with the given {@code cacheKey}.
+ * @param cacheKey cache key
+ * @param functionNode function node
+ */
+ public void persistClassInfo(final String cacheKey, final FunctionNode functionNode) {
+ if (cacheKey != null && env._persistent_cache) {
+ // If this is an on-demand compilation create a function initializer for the function being compiled.
+ // Otherwise use function initializer map generated by codegen.
+ final Map<Integer, FunctionInitializer> initializers = new HashMap<>();
+ if (isOnDemandCompilation()) {
+ initializers.put(functionNode.getId(), new FunctionInitializer(functionNode, getInvalidatedProgramPoints()));
+ } else {
+ for (final CompileUnit compileUnit : getCompileUnits()) {
+ for (final FunctionNode fn : compileUnit.getFunctionNodes()) {
+ initializers.put(fn.getId(), new FunctionInitializer(fn));
+ }
+ }
+ }
+ final String mainClassName = getFirstCompileUnit().getUnitClassName();
+ installer.storeScript(cacheKey, source, mainClassName, bytecode, initializers, constantData.toArray(), compilationId);
+ }
+ }
+
+ /**
+ * Make sure the next compilation id is greater than {@code value}.
+ * @param value compilation id value
+ */
+ public static void updateCompilationId(final int value) {
+ if (value >= COMPILATION_ID.get()) {
+ COMPILATION_ID.set(value + 1);
+ }
+ }
+
+ 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();
+
+ 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 getScriptFunctionData(final int functionId) {
+ assert compiledFunction != null;
+ final RecompilableScriptFunctionData fn = compiledFunction.getScriptFunctionData(functionId);
+ assert fn != null : functionId;
+ return fn;
+ }
+
+ 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
+ }
+ }
+ }
+}