/*
* 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 Also: - CodeInstaller
/**
* 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 /** Name of the scripts package */
public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts";
Name of the objects package /** 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 /** 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.
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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
/**
* 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
/**
* 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
/**
* 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.
/**
* 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.
/**
* 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 COMPILE_UPTO_BYTECODE
/**
* Singleton that describes back end of method generation, given that we have generated the normal
* method up to CodeGenerator as in {@link CompilationPhases#COMPILE_UPTO_BYTECODE}
*/
public final static CompilationPhases 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 /** 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 /** 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 /** 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/** 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 COMPILE_UPTO_BYTECODE
/**
* 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 /** 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 /** 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 CompilerConstants
for special names used for structures during a compile. /**
* 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.
Params: - installer – code installer
- source – source to compile
- errors – error manager
- isStrict – is this a strict compilation
Returns: a new compiler
/**
* 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 --compile-only
command line option. Params: - context – the current context
- source – source to compile
- isStrict – is this a strict compilation
Returns: a new compiler
/**
* 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.
Params: - installer – code installer
- source – source to compile
- isStrict – is this a strict compilation
- compiledFunction – compiled function, if any
- types – parameter and return value type information, if any is known
- invalidatedProgramPoints – invalidated program points for recompilation
- typeInformationFile – descriptor of the location where type information is persisted
- continuationEntryPoints – continuation entry points for restof method
- runtimeScope – runtime scope for recompilation type lookup in
TypeEvaluator
Returns: a new compiler
/**
* 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.
/**
* 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 ScriptRuntime.toPropertyIterator(Object)
. It is used to avoid optimistic assumptions about its property name types. Params: - expr – the expression to test
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 iterators.
/**
* 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.
Returns: a copy of this compiler's current mapping of invalidated optimistic program points to their types.
/**
* 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
Params: - functionNode – function node to compile
- phases – phases of compilation transforms to apply to function
Throws: - CompilationException – if error occurs during compilation
Returns: transformed function
/**
* 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.
/**
* 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 cacheKey
. Params: - cacheKey – cache key
- functionNode – function node
/**
* 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 value
. Params: - value – compilation id value
/**
* 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.
Params: - name – Package/class name.
Returns: Binary name.
/**
* 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
}
}
}
}