diff options
Diffstat (limited to 'src/jdk/nashorn/internal/codegen/Compiler.java')
-rw-r--r-- | src/jdk/nashorn/internal/codegen/Compiler.java | 990 |
1 files changed, 624 insertions, 366 deletions
diff --git a/src/jdk/nashorn/internal/codegen/Compiler.java b/src/jdk/nashorn/internal/codegen/Compiler.java index e4af587b..740022b2 100644 --- a/src/jdk/nashorn/internal/codegen/Compiler.java +++ b/src/jdk/nashorn/internal/codegen/Compiler.java @@ -27,47 +27,48 @@ 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.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.RETURN; 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 static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS; +import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote; import java.io.File; -import java.lang.reflect.Field; -import java.security.AccessController; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; +import java.lang.invoke.MethodType; +import java.util.ArrayList; 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.Map.Entry; import java.util.Set; -import java.util.TreeSet; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; 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.Expression; import jdk.nashorn.internal.ir.FunctionNode; -import jdk.nashorn.internal.ir.FunctionNode.CompilationState; -import jdk.nashorn.internal.ir.TemporarySymbols; +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.DebugLogger; +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.Timing; -import jdk.nashorn.internal.runtime.options.Options; +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 @@ -75,7 +76,8 @@ import jdk.nashorn.internal.runtime.options.Options; * predefined Code installation policy, given to it at construction time. * @see CodeInstaller */ -public final class Compiler { +@Logger(name="compiler") +public final class Compiler implements Loggable { /** Name of the scripts package */ public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts"; @@ -83,9 +85,15 @@ public final class Compiler { /** Name of the objects package */ public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects"; - private Source source; + private final ScriptEnvironment env; + + private final Source source; + + private final String sourceName; - private String sourceName; + private final ErrorManager errors; + + private final boolean optimistic; private final Map<String, byte[]> bytecode; @@ -93,403 +101,584 @@ public final class Compiler { private final ConstantData constantData; - private final CompilationSequence sequence; + private final CodeInstaller<ScriptEnvironment> installer; - private final ScriptEnvironment env; + /** logger for compiler, trampolines, splits and related code generation events + * that affect classes */ + private final DebugLogger log; - private String scriptName; + private final Context context; - private boolean strict; + private final TypeMap types; - private final CodeInstaller<ScriptEnvironment> installer; + // 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 TemporarySymbols temporarySymbols = new TemporarySymbols(); + private final boolean strict; - /** logger for compiler, trampolines, splits and related code generation events - * that affect classes */ - public static final DebugLogger LOG = new DebugLogger("compiler"); + private final boolean onDemand; /** - * 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. + * 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 static String[] RESERVED_NAMES = { - SCOPE.symbolName(), - THIS.symbolName(), - RETURN.symbolName(), - CALLEE.symbolName(), - VARARGS.symbolName(), - ARGUMENTS.symbolName() - }; + 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; /** - * This class makes it possible to do your own compilation sequence - * from the code generation package. There are predefined compilation - * sequences already + * Most compile unit names are longer than the default StringBuilder buffer, + * worth startup performance when massive class generation is going on to increase + * this */ - @SuppressWarnings("serial") - static class CompilationSequence extends LinkedList<CompilationPhase> { + private static final int COMPILE_UNIT_NAME_BUFFER_SIZE = 32; - CompilationSequence(final CompilationPhase... phases) { - super(Arrays.asList(phases)); + private final Map<Integer, byte[]> serializedAsts = new HashMap<>(); + + /** + * 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 serialized. + */ + private final static CompilationPhases COMPILE_UPTO_SERIALIZABLE = new CompilationPhases( + "Common initial phases", + CompilationPhase.CONSTANT_FOLDING_PHASE, + CompilationPhase.LOWERING_PHASE, + CompilationPhase.TRANSFORM_BUILTINS_PHASE, + CompilationPhase.SPLITTING_PHASE, + CompilationPhase.PROGRAM_POINT_PHASE, + CompilationPhase.SERIALIZE_SPLIT_PHASE + ); + + private final static CompilationPhases COMPILE_SERIALIZABLE_UPTO_BYTECODE = new CompilationPhases( + "After common phases, before bytecode generator", + CompilationPhase.SYMBOL_ASSIGNMENT_PHASE, + CompilationPhase.SCOPE_DEPTH_COMPUTATION_PHASE, + CompilationPhase.OPTIMISTIC_TYPE_ASSIGNMENT_PHASE, + CompilationPhase.LOCAL_VARIABLE_TYPE_CALCULATION_PHASE + ); + + /** + * Singleton that describes additional steps to be taken after deserializing, all the way up to (but not + * including) generating and installing code. + */ + public final static CompilationPhases RECOMPILE_SERIALIZED_UPTO_BYTECODE = new CompilationPhases( + "Recompile serialized function up to bytecode", + CompilationPhase.REINITIALIZE_SERIALIZED, + COMPILE_SERIALIZABLE_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_SERIALIZABLE, + COMPILE_SERIALIZABLE_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_SERIALIZED = new CompilationPhases( + "Eager compilation from serializaed state", + RECOMPILE_SERIALIZED_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_SERIALIZED_RESTOF = new CompilationPhases( + "Compile serialized, rest of", + RECOMPILE_SERIALIZED_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)); } - CompilationSequence(final CompilationSequence sequence) { - this(sequence.toArray(new CompilationPhase[sequence.size()])); + private CompilationPhases(final String desc, final CompilationPhases base, final CompilationPhase... phases) { + this(desc, concat(base.phases, Arrays.asList(phases))); } - 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; + private CompilationPhases(final String desc, final CompilationPhase first, final CompilationPhases rest) { + this(desc, concat(Collections.singletonList(first), rest.phases)); } - 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); + 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); } - assert newSeq.contains(newPhase); - return newSeq; + l.trimToSize(); + return l; } - CompilationSequence insertFirst(final CompilationPhase phase) { - final CompilationSequence newSeq = new CompilationSequence(this); - newSeq.addFirst(phase); - return newSeq; + 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; } - CompilationSequence insertLast(final CompilationPhase phase) { - final CompilationSequence newSeq = new CompilationSequence(this); - newSeq.addLast(phase); - return newSeq; + @Override + public String toString() { + return "'" + desc + "' " + phases.toString(); } - } - /** - * Environment information known to the compile, e.g. params - */ - public static class Hints { - private final Type[] paramTypes; + boolean contains(final CompilationPhase phase) { + return phases.contains(phase); + } - /** singleton empty hints */ - public static final Hints EMPTY = new Hints(); + @Override + public Iterator<CompilationPhase> iterator() { + return phases.iterator(); + } - private Hints() { - this.paramTypes = null; + boolean isRestOfCompilation() { + return this == COMPILE_ALL_RESTOF || this == GENERATE_BYTECODE_AND_INSTALL_RESTOF || this == COMPILE_SERIALIZED_RESTOF; } - /** - * Constructor - * @param paramTypes known parameter types for this callsite - */ - public Hints(final Type[] paramTypes) { - this.paramTypes = paramTypes; + String getDesc() { + return desc; } - /** - * Get the parameter type for this parameter position, or - * null if now known - * @param pos position - * @return parameter type for this callsite if known - */ - public Type getParameterType(final int pos) { - if (paramTypes != null && pos < paramTypes.length) { - return paramTypes[pos]; + String toString(final String prefix) { + final StringBuilder sb = new StringBuilder(); + for (final CompilationPhase phase : phases) { + sb.append(prefix).append(phase).append('\n'); } - return null; + return sb.toString(); } } /** - * 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 + * 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. */ - final static CompilationSequence SEQUENCE_EAGER = new CompilationSequence( - CompilationPhase.CONSTANT_FOLDING_PHASE, - CompilationPhase.LOWERING_PHASE, - CompilationPhase.ATTRIBUTION_PHASE, - CompilationPhase.RANGE_ANALYSIS_PHASE, - CompilationPhase.SPLITTING_PHASE, - CompilationPhase.TYPE_FINALIZATION_PHASE, - CompilationPhase.BYTECODE_GENERATION_PHASE); - - final static CompilationSequence SEQUENCE_LAZY = - SEQUENCE_EAGER.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE); + private static String[] RESERVED_NAMES = { + SCOPE.symbolName(), + THIS.symbolName(), + RETURN.symbolName(), + CALLEE.symbolName(), + VARARGS.symbolName(), + ARGUMENTS.symbolName() + }; - private static CompilationSequence sequence(final boolean lazy) { - return lazy ? SEQUENCE_LAZY : SEQUENCE_EAGER; - } + // per instance + private final int compilationId = COMPILATION_ID.getAndIncrement(); - boolean isLazy() { - return sequence == SEQUENCE_LAZY; - } + // per instance + private final AtomicInteger nextCompileUnitId = new AtomicInteger(0); - private static String lazyTag(final FunctionNode functionNode) { - if (functionNode.isLazy()) { - return '$' + LAZY.symbolName() + '$' + functionNode.getName(); - } - return ""; - } + private static final AtomicInteger COMPILATION_ID = new AtomicInteger(0); /** * Constructor * - * @param env script environment - * @param installer code installer - * @param sequence {@link Compiler.CompilationSequence} of {@link CompilationPhase}s to apply as this compilation - * @param strict should this compilation use strict mode semantics + * @param context context + * @param env script environment + * @param installer code installer + * @param source source to compile + * @param errors error manager + * @param isStrict is this a strict compilation */ - //TODO support an array of FunctionNodes for batch lazy compilation - Compiler(final ScriptEnvironment env, final CodeInstaller<ScriptEnvironment> installer, final CompilationSequence sequence, final boolean strict) { - this.env = env; - this.sequence = sequence; - this.installer = installer; - this.constantData = new ConstantData(); - this.compileUnits = new TreeSet<>(); - this.bytecode = new LinkedHashMap<>(); - } - - private void initCompiler(final FunctionNode functionNode) { - this.strict = strict || functionNode.isStrict(); - final StringBuilder sb = new StringBuilder(); - sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.symbolName() + lazyTag(functionNode))). - append('$'). - append(safeSourceName(functionNode.getSource())); - this.source = functionNode.getSource(); - this.sourceName = functionNode.getSourceName(); - this.scriptName = sb.toString(); + public Compiler( + final Context context, + final ScriptEnvironment env, + final CodeInstaller<ScriptEnvironment> installer, + final Source source, + final ErrorManager errors, + final boolean isStrict) { + this(context, env, installer, source, errors, isStrict, false, null, null, null, null, null, null); } /** * Constructor * - * @param installer code installer - * @param strict should this compilation use strict mode semantics + * @param context context + * @param env script environment + * @param installer code installer + * @param source source to compile + * @param errors error manager + * @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 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} */ - public Compiler(final CodeInstaller<ScriptEnvironment> installer, final boolean strict) { - this(installer.getOwner(), installer, sequence(installer.getOwner()._lazy_compilation), strict); + @SuppressWarnings("unused") + public Compiler( + final Context context, + final ScriptEnvironment env, + final CodeInstaller<ScriptEnvironment> 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 = env; + 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<Integer, Type>() : 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 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(); + } + + // 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; } - /** - * Constructor - compilation will use the same strict semantics as in script environment - * - * @param installer code installer - */ - public Compiler(final CodeInstaller<ScriptEnvironment> installer) { - this(installer.getOwner(), installer, sequence(installer.getOwner()._lazy_compilation), installer.getOwner()._strict); + 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(); } - /** - * Constructor - compilation needs no installer, but uses a script environment - * Used in "compile only" scenarios - * @param env a script environment - */ - public Compiler(final ScriptEnvironment env) { - this(env, null, sequence(env._lazy_compilation), env._strict); + 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(Compiler.safeSourceName(env, installer, source)); + + return sb.toString(); } - private static void printMemoryUsage(final String phaseName, final FunctionNode functionNode) { - LOG.info(phaseName + " finished. Doing IR size calculation..."); + void declareLocalSymbol(final String symbolName) { + typeEvaluator.declareLocalSymbol(symbolName); + } - final ObjectSizeCalculator osc = new ObjectSizeCalculator(ObjectSizeCalculator.getEffectiveMemoryLayoutSpecification()); - osc.calculateObjectSize(functionNode); + void setData(final RecompilableScriptFunctionData data) { + assert this.compiledFunction == null : data; + this.compiledFunction = data; + } - final List<ClassHistogramElement> list = osc.getClassHistogram(); + @Override + public DebugLogger getLogger() { + return log; + } - 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); + @Override + public DebugLogger initLogger(final Context ctxt) { + final boolean optimisticTypes = env._optimistic_types; + final boolean lazyCompilation = env._lazy_compilation; - Collections.sort(list, new Comparator<ClassHistogramElement>() { + return ctxt.getLogger(this.getClass(), new Consumer<DebugLogger>() { @Override - public int compare(ClassHistogramElement o1, ClassHistogramElement o2) { - final long diff = o1.getBytes() - o2.getBytes(); - if (diff < 0) { - return 1; - } else if (diff > 0) { - return -1; - } else { - return 0; + 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."); } }); - 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 - } - } } - /** - * Execute the compilation this Compiler was created with - * @param functionNode function node to compile from its current state - * @throws CompilationException if something goes wrong - * @return function node that results from code transforms - */ - public FunctionNode compile(final FunctionNode functionNode) throws CompilationException { - FunctionNode newFunctionNode = functionNode; - - initCompiler(newFunctionNode); //TODO move this state into functionnode? + ScriptEnvironment getScriptEnvironment() { + return env; + } - for (final String reservedName : RESERVED_NAMES) { - newFunctionNode.uniqueName(reservedName); - } + boolean isOnDemandCompilation() { + return onDemand; + } - final boolean fine = !LOG.levelAbove(Level.FINE); - final boolean info = !LOG.levelAbove(Level.INFO); + boolean useOptimisticTypes() { + return optimistic; + } - long time = 0L; + Context getContext() { + return context; + } - for (final CompilationPhase phase : sequence) { - newFunctionNode = phase.apply(this, newFunctionNode); + Type getOptimisticType(final Optimistic node) { + return typeEvaluator.getOptimisticType(node); + } - if (env._print_mem_usage) { - printMemoryUsage(phase.toString(), newFunctionNode); - } + /** + * 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); + } - final long duration = Timing.isEnabled() ? (phase.getEndTime() - phase.getStartTime()) : 0L; - time += duration; + void addInvalidatedProgramPoint(final int programPoint, final Type type) { + invalidatedProgramPoints.put(programPoint, type); + } - if (fine) { - final StringBuilder sb = new StringBuilder(); - sb.append(phase.toString()). - append(" done for function '"). - append(newFunctionNode.getName()). - append('\''); + /** + * 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 == null ? null : new TreeMap<>(invalidatedProgramPoints); + } - if (duration > 0L) { - sb.append(" in "). - append(duration). - append(" ms "); - } + TypeMap getTypeMap() { + return types; + } - LOG.fine(sb); - } + MethodType getCallSiteType(final FunctionNode fn) { + if (types == null || !isOnDemandCompilation()) { + return null; } + return types.getCallSiteType(fn); + } - if (info) { - final StringBuilder sb = new StringBuilder(); - sb.append("Compile job for '"). - append(newFunctionNode.getSource()). - append(':'). - append(newFunctionNode.getName()). - append("' finished"); + Type getParamType(final FunctionNode fn, final int pos) { + return types == null ? null : types.get(fn, pos); + } - if (time > 0L) { - sb.append(" in "). - append(time). - append(" ms"); - } + /** + * Do a compilation job + * + * @param functionNode function node to compile + * @param phases phases of compilation transforms to apply to function - LOG.info(sb); + * @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(); } - return newFunctionNode; - } + final String name = DebugLogger.quote(functionNode.getName()); - private Class<?> install(final String className, final byte[] code, final Object[] constants) { - return installer.install(className, code, source, constants); - } + FunctionNode newFunctionNode = functionNode; - /** - * Install compiled classes into a given loader - * @param functionNode function node to install - must be in {@link CompilationState#EMITTED} state - * @return root script class - if there are several compile units they will also be installed - */ - public Class<?> install(final FunctionNode functionNode) { - final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L; + for (final String reservedName : RESERVED_NAMES) { + newFunctionNode.uniqueName(reservedName); + } - assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " has no bytecode and cannot be installed"; + final boolean info = log.levelFinerThanOrEqual(Level.INFO); - final Map<String, Class<?>> installedClasses = new HashMap<>(); - final Object[] constants = getConstantData().toArray(); + final DebugLogger timeLogger = env.isTimingEnabled() ? env._timing.getLogger() : null; - final String rootClassName = firstCompileUnitName(); - final byte[] rootByteCode = bytecode.get(rootClassName); - final Class<?> rootClass = install(rootClassName, rootByteCode, constants); + long time = 0L; - if (!isLazy()) { - installer.storeCompiledScript(source, rootClassName, bytecode, constants); - } + for (final CompilationPhase phase : phases) { + log.fine(phase, " starting for ", name); - int length = rootByteCode.length; + 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; + } - installedClasses.put(rootClassName, rootClass); + log.fine(phase, " done for function ", quote(name)); - for (final Entry<String, byte[]> entry : bytecode.entrySet()) { - final String className = entry.getKey(); - if (className.equals(rootClassName)) { - continue; + if (env._print_mem_usage) { + printMemoryUsage(functionNode, phase.toString()); } - final byte[] code = entry.getValue(); - length += code.length; - installedClasses.put(className, install(className, code, constants)); + time += (env.isTimingEnabled() ? phase.getEndTime() - phase.getStartTime() : 0L); } - for (final CompileUnit unit : compileUnits) { - unit.setCode(installedClasses.get(unit.getUnitClassName())); - } - - final StringBuilder sb; - if (LOG.isEnabled()) { - sb = new StringBuilder(); - sb.append("Installed class '"). - append(rootClass.getSimpleName()). - append('\''). - append(" bytes="). - append(length). - append('.'); - if (bytecode.size() > 1) { - sb.append(' ').append(bytecode.size()).append(" compile units."); - } - } else { - sb = null; + if (typeInformationFile != null && !phases.isRestOfCompilation()) { + OptimisticTypesPersistence.store(typeInformationFile, invalidatedProgramPoints); } - if (Timing.isEnabled()) { - final long duration = System.currentTimeMillis() - t0; - Timing.accumulateTime("[Code Installation]", duration); - if (sb != null) { - sb.append(" Install time: ").append(duration).append(" ms"); + 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(time).append(" ms"); } + log.info(sb); } - if (sb != null) { - LOG.fine(sb); - } + return newFunctionNode; + } - return rootClass; + Source getSource() { + return source; } - Set<CompileUnit> getCompileUnits() { - return compileUnits; + Map<String, byte[]> getBytecode() { + return Collections.unmodifiableMap(bytecode); } - boolean getStrictMode() { - return strict; + /** + * Reset bytecode cache for compiler reuse. + */ + void clearBytecode() { + bytecode.clear(); } - void setStrictMode(final boolean strict) { - this.strict = strict; + CompileUnit getFirstCompileUnit() { + assert !compileUnits.isEmpty(); + return compileUnits.iterator().next(); + } + + Set<CompileUnit> getCompileUnits() { + return compileUnits; } ConstantData getConstantData() { @@ -500,76 +689,102 @@ public final class Compiler { return installer; } - TemporarySymbols getTemporarySymbols() { - return temporarySymbols; - } - void addClass(final String name, final byte[] code) { bytecode.put(name, code); } - ScriptEnvironment getEnv() { - return this.env; + 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(); } - private String safeSourceName(final Source src) { - String baseName = new File(src.getName()).getName(); + Map<Integer, FunctionInitializer> functionInitializers; - final int index = baseName.lastIndexOf(".js"); - if (index != -1) { - baseName = baseName.substring(0, index); + void addFunctionInitializer(final RecompilableScriptFunctionData functionData, final FunctionNode functionNode) { + if (functionInitializers == null) { + functionInitializers = new HashMap<>(); } - - baseName = baseName.replace('.', '_').replace('-', '_'); - if (! env._loader_per_compile) { - baseName = baseName + installer.getUniqueScriptId(); + if (!functionInitializers.containsKey(functionData)) { + functionInitializers.put(functionData.getFunctionNodeId(), new FunctionInitializer(functionNode)); } - final String mangled = NameCodec.encode(baseName); - - return mangled != null ? mangled : baseName; } - private int nextCompileUnitIndex() { - return compileUnits.size() + 1; + Map<Integer, FunctionInitializer> getFunctionInitializers() { + return functionInitializers; } - String firstCompileUnitName() { - return SCRIPTS_PACKAGE + '/' + scriptName; + /** + * 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) { + Map<Integer, FunctionInitializer> initializers; + // If this is an on-demand compilation create a function initializer for the function being compiled. + // Otherwise use function initializer map generated by codegen. + if (functionInitializers == null) { + initializers = new HashMap<>(); + final FunctionInitializer initializer = new FunctionInitializer(functionNode, getInvalidatedProgramPoints()); + initializers.put(functionNode.getId(), initializer); + } else { + initializers = functionInitializers; + } + final String mainClassName = getFirstCompileUnit().getUnitClassName(); + installer.storeScript(cacheKey, source, mainClassName, bytecode, initializers, constantData.toArray(), compilationId); + } } - private String nextCompileUnitName() { - return firstCompileUnitName() + '$' + nextCompileUnitIndex(); + /** + * 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) { - return addCompileUnit(nextCompileUnitName(), initialWeight); + final CompileUnit compileUnit = createCompileUnit(initialWeight); + compileUnits.add(compileUnit); + log.fine("Added compile unit ", compileUnit); + return compileUnit; } - CompileUnit addCompileUnit(final String unitClassName) { - return addCompileUnit(unitClassName, 0L); - } + 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(); - private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) { - final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight); - compileUnits.add(compileUnit); - LOG.fine("Added compile unit ", compileUnit); return compileUnit; } - private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) { - final ClassEmitter classEmitter = new ClassEmitter(env, sourceName, unitClassName, strict); - final CompileUnit compileUnit = new CompileUnit(unitClassName, classEmitter, initialWeight); + private CompileUnit createCompileUnit(final long initialWeight) { + return createCompileUnit(nextCompileUnitName(), initialWeight); + } - classEmitter.begin(); + boolean isStrict() { + return strict; + } - 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(); + void replaceCompileUnits(final Set<CompileUnit> newUnits) { + compileUnits.clear(); + compileUnits.addAll(newUnits); + } - return compileUnit; + void serializeAst(final FunctionNode fn) { + serializedAsts.put(fn.getId(), AstSerializer.serialize(fn)); + } + + byte[] removeSerializedAst(final int fnId) { + return serializedAsts.remove(fnId); } CompileUnit findUnit(final long weight) { @@ -593,22 +808,65 @@ public final class Compiler { return name.replace('/', '.'); } - /** - * Should we use integers for arithmetic operations as well? - * TODO: We currently generate no overflow checks so this is - * disabled - * - * @return true if arithmetic operations should not widen integer - * operands by default. - */ - static boolean shouldUseIntegerArithmetic() { - return USE_INT_ARITH; + RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { + assert compiledFunction != null; + final RecompilableScriptFunctionData fn = compiledFunction.getScriptFunctionData(functionId); + assert fn != null : functionId; + return fn; } - private static final boolean USE_INT_ARITH; + boolean isGlobalSymbol(final FunctionNode fn, final String name) { + return getScriptFunctionData(fn.getId()).isGlobalSymbol(fn, name); + } - static { - USE_INT_ARITH = Options.getBooleanProperty("nashorn.compiler.intarithmetic"); - assert !USE_INT_ARITH : "Integer arithmetic is not enabled"; + 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 + } + } } } |