| /* |
| * 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 |
| } |
| } |
| } |
| } |