aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/codegen/Compiler.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/codegen/Compiler.java')
-rw-r--r--src/jdk/nashorn/internal/codegen/Compiler.java990
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
+ }
+ }
}
}