blob: 5dea042bed715264943bf385e08d3be0bd741459 [file] [log] [blame]
/*
* Copyright (c) 2010, 2014, 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.ir.FunctionNode.CompilationState.BUILTINS_TRANSFORMED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_GENERATED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_INSTALLED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOCAL_VARIABLE_TYPES_CALCULATED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.OPTIMISTIC_TYPES_ASSIGNED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SCOPE_DEPTHS_COMPUTED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SYMBOLS_ASSIGNED;
import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.SplitNode;
import jdk.nashorn.internal.ir.debug.ASTWriter;
import jdk.nashorn.internal.ir.debug.PrintVisitor;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.CodeInstaller;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.logging.DebugLogger;
/**
* A compilation phase is a step in the processes of turning a JavaScript
* FunctionNode into bytecode. It has an optional return value.
*/
enum CompilationPhase {
/**
* Constant folding pass Simple constant folding that will make elementary
* constructs go away
*/
CONSTANT_FOLDING_PHASE(
EnumSet.of(
INITIALIZED,
PARSED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new FoldConstants(compiler));
}
@Override
public String toString() {
return "'Constant Folding'";
}
},
/**
* Lower (Control flow pass) Finalizes the control flow. Clones blocks for
* finally constructs and similar things. Establishes termination criteria
* for nodes Guarantee return instructions to method making sure control
* flow cannot fall off the end. Replacing high level nodes with lower such
* as runtime nodes where applicable.
*/
LOWERING_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new Lower(compiler));
}
@Override
public String toString() {
return "'Control Flow Lowering'";
}
},
/**
* Phase used only when doing optimistic code generation. It assigns all potentially
* optimistic ops a program point so that an UnwarrantedException knows from where
* a guess went wrong when creating the continuation to roll back this execution
*/
PROGRAM_POINT_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new ProgramPoints());
}
@Override
public String toString() {
return "'Program Point Calculation'";
}
},
TRANSFORM_BUILTINS_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED)) {
//we only do this if we have a param type map, otherwise this is not a specialized recompile
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new ApplySpecialization(compiler));
return (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public Node leaveFunctionNode(final FunctionNode node) {
return node.setState(lc, BUILTINS_TRANSFORMED);
}
});
}
@Override
public String toString() {
return "'Builtin Replacement'";
}
},
/**
* Splitter Split the AST into several compile units based on a heuristic size calculation.
* Split IR can lead to scope information being changed.
*/
SPLITTING_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final CompileUnit outermostCompileUnit = compiler.addCompileUnit(0L);
FunctionNode newFunctionNode;
//ensure elementTypes, postsets and presets exist for splitter and arraynodes
newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public LiteralNode<?> leaveLiteralNode(final LiteralNode<?> literalNode) {
return literalNode.initialize(lc);
}
});
newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true);
assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName());
return newFunctionNode;
}
@Override
public String toString() {
return "'Code Splitting'";
}
},
SYMBOL_ASSIGNMENT_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new AssignSymbols(compiler));
}
@Override
public String toString() {
return "'Symbol Assignment'";
}
},
SCOPE_DEPTH_COMPUTATION_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT,
SYMBOLS_ASSIGNED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
return (FunctionNode)fn.accept(new FindScopeDepths(compiler));
}
@Override
public String toString() {
return "'Scope Depth Computation'";
}
},
OPTIMISTIC_TYPE_ASSIGNMENT_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT,
SYMBOLS_ASSIGNED,
SCOPE_DEPTHS_COMPUTED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
if (compiler.useOptimisticTypes()) {
return (FunctionNode)fn.accept(new OptimisticTypesCalculator(compiler));
}
return setStates(fn, OPTIMISTIC_TYPES_ASSIGNED);
}
@Override
public String toString() {
return "'Optimistic Type Assignment'";
}
},
LOCAL_VARIABLE_TYPE_CALCULATION_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT,
SYMBOLS_ASSIGNED,
SCOPE_DEPTHS_COMPUTED,
OPTIMISTIC_TYPES_ASSIGNED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new LocalVariableTypesCalculator(compiler));
final ScriptEnvironment senv = compiler.getScriptEnvironment();
final PrintWriter err = senv.getErr();
//TODO separate phase for the debug printouts for abstraction and clarity
if (senv._print_lower_ast) {
err.println("Lower AST for: " + quote(newFunctionNode.getName()));
err.println(new ASTWriter(newFunctionNode));
}
if (senv._print_lower_parse) {
err.println("Lower AST for: " + quote(newFunctionNode.getName()));
err.println(new PrintVisitor(newFunctionNode));
}
return newFunctionNode;
}
@Override
public String toString() {
return "'Local Variable Type Calculation'";
}
},
/**
* Reuse compile units, if they are already present. We are using the same compiler
* to recompile stuff
*/
REUSE_COMPILE_UNITS_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT,
SYMBOLS_ASSIGNED,
SCOPE_DEPTHS_COMPUTED,
OPTIMISTIC_TYPES_ASSIGNED,
LOCAL_VARIABLE_TYPES_CALCULATED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
assert phases.isRestOfCompilation() : "reuse compile units currently only used for Rest-Of methods";
final Map<CompileUnit, CompileUnit> map = new HashMap<>();
final Set<CompileUnit> newUnits = CompileUnit.createCompileUnitSet();
final DebugLogger log = compiler.getLogger();
log.fine("Clearing bytecode cache");
for (final CompileUnit oldUnit : compiler.getCompileUnits()) {
CompileUnit newUnit = map.get(oldUnit);
assert map.get(oldUnit) == null;
final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
if (phases.isRestOfCompilation()) {
sb.append("$restOf");
}
newUnit = compiler.createCompileUnit(sb.toString(), oldUnit.getWeight());
log.fine("Creating new compile unit ", oldUnit, " => ", newUnit);
map.put(oldUnit, newUnit);
assert newUnit != null;
newUnits.add(newUnit);
}
log.fine("Replacing compile units in Compiler...");
compiler.replaceCompileUnits(newUnits);
log.fine("Done");
//replace old compile units in function nodes, if any are assigned,
//for example by running the splitter on this function node in a previous
//partial code generation
final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public Node leaveFunctionNode(final FunctionNode node) {
final CompileUnit oldUnit = node.getCompileUnit();
assert oldUnit != null : "no compile unit in function node";
final CompileUnit newUnit = map.get(oldUnit);
assert newUnit != null : "old unit has no mapping to new unit " + oldUnit;
log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName()));
return node.setCompileUnit(lc, newUnit).setState(lc, CompilationState.COMPILE_UNITS_REUSED);
}
@Override
public Node leaveSplitNode(final SplitNode node) {
final CompileUnit oldUnit = node.getCompileUnit();
assert oldUnit != null : "no compile unit in function node";
final CompileUnit newUnit = map.get(oldUnit);
assert newUnit != null : "old unit has no mapping to new unit " + oldUnit;
log.fine("Replacing compile unit: ", oldUnit, " => ", newUnit, " in ", quote(node.getName()));
return node.setCompileUnit(lc, newUnit);
}
@Override
public Node leaveLiteralNode(final LiteralNode<?> node) {
if (node instanceof ArrayLiteralNode) {
final ArrayLiteralNode aln = (ArrayLiteralNode)node;
if (aln.getUnits() == null) {
return node;
}
final List<ArrayUnit> newArrayUnits = new ArrayList<>();
for (final ArrayUnit au : aln.getUnits()) {
final CompileUnit newUnit = map.get(au.getCompileUnit());
assert newUnit != null;
newArrayUnits.add(new ArrayUnit(newUnit, au.getLo(), au.getHi()));
}
return aln.setUnits(lc, newArrayUnits);
}
return node;
}
@Override
public Node leaveDefault(final Node node) {
return node.ensureUniqueLabels(lc);
}
});
return newFunctionNode;
}
@Override
public String toString() {
return "'Reuse Compile Units'";
}
},
/**
* Bytecode generation:
*
* Generate the byte code class(es) resulting from the compiled FunctionNode
*/
BYTECODE_GENERATION_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT,
SYMBOLS_ASSIGNED,
SCOPE_DEPTHS_COMPUTED,
OPTIMISTIC_TYPES_ASSIGNED,
LOCAL_VARIABLE_TYPES_CALCULATED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final ScriptEnvironment senv = compiler.getScriptEnvironment();
FunctionNode newFunctionNode = fn;
compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation());
final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null);
try {
// Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program
// in the lazy + optimistic world. See CodeGenerator.skipFunction().
newFunctionNode = ((FunctionNode)newFunctionNode.accept(codegen)).setState(null, BYTECODE_GENERATED);
codegen.generateScopeCalls();
} catch (final VerifyError e) {
if (senv._verify_code || senv._print_code) {
senv.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage());
if (senv._dump_on_error) {
e.printStackTrace(senv.getErr());
}
} else {
throw e;
}
} catch (final Throwable e) {
// Provide source file and line number being compiled when the assertion occurred
throw new AssertionError("Failed generating bytecode for " + fn.getSourceName() + ":" + codegen.getLastLineNumber(), e);
}
for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
final ClassEmitter classEmitter = compileUnit.getClassEmitter();
classEmitter.end();
final byte[] bytecode = classEmitter.toByteArray();
assert bytecode != null;
final String className = compileUnit.getUnitClassName();
compiler.addClass(className, bytecode);
// should we verify the generated code?
if (senv._verify_code) {
compiler.getCodeInstaller().verify(bytecode);
}
DumpBytecode.dumpBytecode(senv, compiler.getLogger(), bytecode, className);
}
return newFunctionNode;
}
@Override
public String toString() {
return "'Bytecode Generation'";
}
},
INSTALL_PHASE(
EnumSet.of(
INITIALIZED,
PARSED,
CONSTANT_FOLDED,
LOWERED,
BUILTINS_TRANSFORMED,
SPLIT,
SYMBOLS_ASSIGNED,
SCOPE_DEPTHS_COMPUTED,
OPTIMISTIC_TYPES_ASSIGNED,
LOCAL_VARIABLE_TYPES_CALCULATED,
BYTECODE_GENERATED)) {
@Override
FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
final DebugLogger log = compiler.getLogger();
final Map<String, Class<?>> installedClasses = new LinkedHashMap<>();
boolean first = true;
Class<?> rootClass = null;
long length = 0L;
final CodeInstaller<ScriptEnvironment> codeInstaller = compiler.getCodeInstaller();
final Map<String, byte[]> bytecode = compiler.getBytecode();
for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
final String className = entry.getKey();
//assert !first || className.equals(compiler.getFirstCompileUnit().getUnitClassName()) : "first=" + first + " className=" + className + " != " + compiler.getFirstCompileUnit().getUnitClassName();
final byte[] code = entry.getValue();
length += code.length;
final Class<?> clazz = codeInstaller.install(className, code);
if (first) {
rootClass = clazz;
first = false;
}
installedClasses.put(className, clazz);
}
if (rootClass == null) {
throw new CompilationException("Internal compiler error: root class not found!");
}
final Object[] constants = compiler.getConstantData().toArray();
codeInstaller.initialize(installedClasses.values(), compiler.getSource(), constants);
// initialize transient fields on recompilable script function data
for (final Object constant: constants) {
if (constant instanceof RecompilableScriptFunctionData) {
((RecompilableScriptFunctionData)constant).initTransients(compiler.getSource(), codeInstaller);
}
}
// initialize function in the compile units
for (final CompileUnit unit : compiler.getCompileUnits()) {
unit.setCode(installedClasses.get(unit.getUnitClassName()));
unit.initializeFunctionsCode();
}
if (!compiler.isOnDemandCompilation()) {
codeInstaller.storeCompiledScript(compiler.getSource(), compiler.getFirstCompileUnit().getUnitClassName(), bytecode, constants);
}
// remove installed bytecode from table in case compiler is reused
for (final String className : installedClasses.keySet()) {
log.fine("Removing installed class ", quote(className), " from bytecode table...");
compiler.removeClass(className);
}
if (log.isEnabled()) {
final StringBuilder sb = new StringBuilder();
sb.append("Installed class '").
append(rootClass.getSimpleName()).
append('\'').
append(" [").
append(rootClass.getName()).
append(", size=").
append(length).
append(" bytes, ").
append(compiler.getCompileUnits().size()).
append(" compile unit(s)]");
log.fine(sb.toString());
}
return setStates(fn.setRootClass(null, rootClass), BYTECODE_INSTALLED);
}
@Override
public String toString() {
return "'Class Installation'";
}
};
/** pre conditions required for function node to which this transform is to be applied */
private final EnumSet<CompilationState> pre;
/** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
private long startTime;
/** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
private long endTime;
/** boolean that is true upon transform completion */
private boolean isFinished;
private CompilationPhase(final EnumSet<CompilationState> pre) {
this.pre = pre;
}
boolean isApplicable(final FunctionNode functionNode) {
//this means that all in pre are present in state. state can be larger
return functionNode.hasState(pre);
}
private static FunctionNode setStates(final FunctionNode functionNode, final CompilationState state) {
return (FunctionNode)functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public Node leaveFunctionNode(final FunctionNode fn) {
return fn.setState(lc, state);
}
});
}
/**
* Start a compilation phase
* @param compiler
* @param functionNode function to compile
* @return function node
*/
protected FunctionNode begin(final Compiler compiler, final FunctionNode functionNode) {
compiler.getLogger().indent();
assert pre != null;
if (!isApplicable(functionNode)) {
final StringBuilder sb = new StringBuilder("Compilation phase ");
sb.append(this).
append(" is not applicable to ").
append(quote(functionNode.getName())).
append("\n\tFunctionNode state = ").
append(functionNode.getState()).
append("\n\tRequired state = ").
append(this.pre);
throw new CompilationException(sb.toString());
}
startTime = System.currentTimeMillis();
return functionNode;
}
/**
* End a compilation phase
* @param compiler the compiler
* @param functionNode function node to compile
* @return function node
*/
protected FunctionNode end(final Compiler compiler, final FunctionNode functionNode) {
compiler.getLogger().unindent();
endTime = System.currentTimeMillis();
compiler.getScriptEnvironment()._timing.accumulateTime(toString(), endTime - startTime);
isFinished = true;
return functionNode;
}
boolean isFinished() {
return isFinished;
}
long getStartTime() {
return startTime;
}
long getEndTime() {
return endTime;
}
abstract FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException;
/**
* Apply a transform to a function node, returning the transfored function node. If the transform is not
* applicable, an exception is thrown. Every transform requires the function to have a certain number of
* states to operate. It can have more states set, but not fewer. The state list, i.e. the constructor
* arguments to any of the CompilationPhase enum entries, is a set of REQUIRED states.
*
* @param compiler compiler
* @param phases current complete pipeline of which this phase is one
* @param functionNode function node to transform
*
* @return transformed function node
*
* @throws CompilationException if function node lacks the state required to run the transform on it
*/
final FunctionNode apply(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException {
assert phases.contains(this);
return end(compiler, transform(compiler, phases, begin(compiler, functionNode)));
}
}