aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/codegen/CompilationPhase.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/jdk/nashorn/internal/codegen/CompilationPhase.java')
-rw-r--r--src/jdk/nashorn/internal/codegen/CompilationPhase.java886
1 files changed, 557 insertions, 329 deletions
diff --git a/src/jdk/nashorn/internal/codegen/CompilationPhase.java b/src/jdk/nashorn/internal/codegen/CompilationPhase.java
index 6c0a673d..2a1d2661 100644
--- a/src/jdk/nashorn/internal/codegen/CompilationPhase.java
+++ b/src/jdk/nashorn/internal/codegen/CompilationPhase.java
@@ -1,492 +1,694 @@
+/*
+ * 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.ATTR;
+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.FINALIZED;
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.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Deque;
+import java.io.PrintWriter;
import java.util.EnumSet;
-import java.util.HashSet;
-import java.util.List;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
-import jdk.nashorn.internal.codegen.types.Range;
-import jdk.nashorn.internal.codegen.types.Type;
-import jdk.nashorn.internal.ir.CallNode;
-import jdk.nashorn.internal.ir.Expression;
+import jdk.nashorn.internal.AssertsEnabled;
+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.Node;
-import jdk.nashorn.internal.ir.ReturnNode;
-import jdk.nashorn.internal.ir.Symbol;
-import jdk.nashorn.internal.ir.TemporarySymbols;
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.ECMAErrors;
+import jdk.nashorn.internal.runtime.CodeInstaller;
+import jdk.nashorn.internal.runtime.FunctionInitializer;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
-import jdk.nashorn.internal.runtime.Timing;
+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 {
-
- /*
- * Lazy initialization - tag all function nodes not the script as lazy as
- * default policy. The will get trampolines and only be generated when
- * called
+ /**
+ * Constant folding pass Simple constant folding that will make elementary
+ * constructs go away
*/
- LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
+ CONSTANT_FOLDING_PHASE(
+ EnumSet.of(
+ INITIALIZED,
+ PARSED)) {
@Override
- FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
-
- /*
- * For lazy compilation, we might be given a node previously marked
- * as lazy to compile as the outermost function node in the
- * compiler. Unmark it so it can be compiled and not cause
- * recursion. Make sure the return type is unknown so it can be
- * correctly deduced. Return types are always Objects in Lazy nodes
- * as we haven't got a change to generate code for them and decude
- * its parameter specialization
- *
- * TODO: in the future specializations from a callsite will be
- * passed here so we can generate a better non-lazy version of a
- * function from a trampoline
- */
-
- final FunctionNode outermostFunctionNode = fn;
-
- final Set<FunctionNode> neverLazy = new HashSet<>();
- final Set<FunctionNode> lazy = new HashSet<>();
-
- FunctionNode newFunctionNode = outermostFunctionNode;
-
- newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
- // self references are done with invokestatic and thus cannot
- // have trampolines - never lazy
- @Override
- public boolean enterCallNode(final CallNode node) {
- final Node callee = node.getFunction();
- if (callee instanceof FunctionNode) {
- neverLazy.add(((FunctionNode)callee));
- return false;
- }
- return true;
- }
-
- //any function that isn't the outermost one must be marked as lazy
- @Override
- public boolean enterFunctionNode(final FunctionNode node) {
- assert compiler.isLazy();
- lazy.add(node);
- return true;
- }
- });
-
- //at least one method is non lazy - the outermost one
- neverLazy.add(newFunctionNode);
-
- for (final FunctionNode node : neverLazy) {
- Compiler.LOG.fine(
- "Marking ",
- node.getName(),
- " as non lazy, as it's a self reference");
- lazy.remove(node);
- }
-
- newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
- @Override
- public Node leaveFunctionNode(final FunctionNode functionNode) {
- if (lazy.contains(functionNode)) {
- Compiler.LOG.fine(
- "Marking ",
- functionNode.getName(),
- " as lazy");
- final FunctionNode parent = lc.getParentFunction(functionNode);
- assert parent != null;
- lc.setFlag(parent, FunctionNode.HAS_LAZY_CHILDREN);
- lc.setBlockNeedsScope(parent.getBody());
- lc.setFlag(functionNode, FunctionNode.IS_LAZY);
- return functionNode;
- }
-
- return functionNode.
- clearFlag(lc, FunctionNode.IS_LAZY).
- setReturnType(lc, Type.UNKNOWN);
- }
- });
-
- return newFunctionNode;
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ return transformFunction(fn, new FoldConstants(compiler));
}
@Override
public String toString() {
- return "[Lazy JIT Initialization]";
+ return "'Constant Folding'";
}
},
- /*
- * Constant folding pass Simple constant folding that will make elementary
- * constructs go away
+ /**
+ * 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.
*/
- CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
+ LOWERING_PHASE(
+ EnumSet.of(
+ INITIALIZED,
+ PARSED,
+ CONSTANT_FOLDED)) {
@Override
- FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
- return (FunctionNode)fn.accept(new FoldConstants());
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ return transformFunction(fn, new Lower(compiler));
}
@Override
public String toString() {
- return "[Constant Folding]";
+ return "'Control Flow Lowering'";
}
},
- /*
- * 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.
+ /**
+ * 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
*/
- LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) {
+ 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 FunctionNode fn) {
- return (FunctionNode)fn.accept(new Lower(compiler.getCodeInstaller()));
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ return setStates(transformFunction(fn, new ApplySpecialization(compiler)), BUILTINS_TRANSFORMED);
}
@Override
public String toString() {
- return "[Control Flow Lowering]";
+ return "'Builtin Replacement'";
}
},
- /*
- * Attribution Assign symbols and types to all nodes.
+ /**
+ * Splitter Split the AST into several compile units based on a heuristic size calculation.
+ * Split IR can lead to scope information being changed.
*/
- ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) {
+ SPLITTING_PHASE(
+ EnumSet.of(
+ INITIALIZED,
+ PARSED,
+ CONSTANT_FOLDED,
+ LOWERED,
+ BUILTINS_TRANSFORMED)) {
@Override
- FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
- final TemporarySymbols ts = compiler.getTemporarySymbols();
- final FunctionNode newFunctionNode = (FunctionNode)enterAttr(fn, ts).accept(new Attr(ts));
- if (compiler.getEnv()._print_mem_usage) {
- Compiler.LOG.info("Attr temporary symbol count: " + ts.getTotalSymbolCount());
- }
- return newFunctionNode;
- }
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ final CompileUnit outermostCompileUnit = compiler.addCompileUnit(0L);
+
+ FunctionNode newFunctionNode;
- /**
- * Pessimistically set all lazy functions' return types to Object
- * and the function symbols to object
- * @param functionNode node where to start iterating
- */
- private FunctionNode enterAttr(final FunctionNode functionNode, final TemporarySymbols ts) {
- return (FunctionNode)functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ //ensure elementTypes, postsets and presets exist for splitter and arraynodes
+ newFunctionNode = transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
- public Node leaveFunctionNode(final FunctionNode node) {
- if (node.isLazy()) {
- FunctionNode newNode = node.setReturnType(lc, Type.OBJECT);
- return ts.ensureSymbol(lc, Type.OBJECT, newNode);
- }
- //node may have a reference here that needs to be nulled if it was referred to by
- //its outer context, if it is lazy and not attributed
- return node.setReturnType(lc, Type.UNKNOWN).setSymbol(lc, null);
+ public LiteralNode<?> leaveLiteralNode(final LiteralNode<?> literalNode) {
+ return literalNode.initialize(lc);
}
});
+
+ newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true);
+ newFunctionNode = transformFunction(newFunctionNode, new SplitIntoFunctions(compiler));
+ 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 "[Type Attribution]";
+ return "'Code Splitting'";
}
},
- /*
- * Range analysis
- * Conservatively prove that certain variables can be narrower than
- * the most generic number type
- */
- RANGE_ANALYSIS_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
+ PROGRAM_POINT_PHASE(
+ EnumSet.of(
+ INITIALIZED,
+ PARSED,
+ CONSTANT_FOLDED,
+ LOWERED,
+ BUILTINS_TRANSFORMED,
+ SPLIT)) {
@Override
- FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
- if (!compiler.getEnv()._range_analysis) {
- return fn;
- }
-
- FunctionNode newFunctionNode = (FunctionNode)fn.accept(new RangeAnalyzer());
- final List<ReturnNode> returns = new ArrayList<>();
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ return transformFunction(fn, new ProgramPoints());
+ }
- newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
- private final Deque<ArrayList<ReturnNode>> returnStack = new ArrayDeque<>();
+ @Override
+ public String toString() {
+ return "'Program Point Calculation'";
+ }
+ },
+ SERIALIZE_SPLIT_PHASE(
+ EnumSet.of(
+ INITIALIZED,
+ PARSED,
+ CONSTANT_FOLDED,
+ LOWERED,
+ BUILTINS_TRANSFORMED,
+ SPLIT)) {
+ @Override
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ return transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
@Override
public boolean enterFunctionNode(final FunctionNode functionNode) {
- returnStack.push(new ArrayList<ReturnNode>());
+ if (functionNode.isSplit()) {
+ compiler.serializeAst(functionNode);
+ }
return true;
}
+ });
+ }
- @Override
- public Node leaveFunctionNode(final FunctionNode functionNode) {
- Type returnType = Type.UNKNOWN;
- for (final ReturnNode ret : returnStack.pop()) {
- if (ret.getExpression() == null) {
- returnType = Type.OBJECT;
- break;
- }
- returnType = Type.widest(returnType, ret.getExpression().getType());
- }
- return functionNode.setReturnType(lc, returnType);
- }
+ @Override
+ public String toString() {
+ return "'Serialize Split Functions'";
+ }
+ },
- @Override
- public Node leaveReturnNode(final ReturnNode returnNode) {
- final ReturnNode result = (ReturnNode)leaveDefault(returnNode);
- returns.add(result);
- return result;
- }
+ 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 transformFunction(fn, new AssignSymbols(compiler));
+ }
- @Override
- public Node leaveDefault(final Node node) {
- if(node instanceof Expression) {
- final Expression expr = (Expression)node;
- final Symbol symbol = expr.getSymbol();
- if (symbol != null) {
- final Range range = symbol.getRange();
- final Type symbolType = symbol.getSymbolType();
- if (!symbolType.isNumeric()) {
- return expr;
- }
- final Type rangeType = range.getType();
- if (!Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range
- RangeAnalyzer.LOG.info("[", lc.getCurrentFunction().getName(), "] ", symbol, " can be ", range.getType(), " ", symbol.getRange());
- return expr.setSymbol(lc, symbol.setTypeOverrideShared(range.getType(), compiler.getTemporarySymbols()));
- }
- }
- }
- return node;
- }
- });
+ @Override
+ public String toString() {
+ return "'Symbol Assignment'";
+ }
+ },
- Type returnType = Type.UNKNOWN;
- for (final ReturnNode node : returns) {
- if (node.getExpression() != null) {
- returnType = Type.widest(returnType, node.getExpression().getType());
- } else {
- returnType = Type.OBJECT;
- break;
- }
+ 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 transformFunction(fn, 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 transformFunction(fn, new OptimisticTypesCalculator(compiler));
}
+ return setStates(fn, OPTIMISTIC_TYPES_ASSIGNED);
+ }
- return newFunctionNode.setReturnType(null, returnType);
+ @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 = transformFunction(fn, 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 || fn.getFlag(FunctionNode.IS_PRINT_LOWER_AST)) {
+ err.println("Lower AST for: " + quote(newFunctionNode.getName()));
+ err.println(new ASTWriter(newFunctionNode));
+ }
+
+ if (senv._print_lower_parse || fn.getFlag(FunctionNode.IS_PRINT_LOWER_PARSE)) {
+ err.println("Lower AST for: " + quote(newFunctionNode.getName()));
+ err.println(new PrintVisitor(newFunctionNode));
+ }
+
+ return newFunctionNode;
}
@Override
public String toString() {
- return "[Range Analysis]";
+ return "'Local Variable Type Calculation'";
}
},
- /*
- * Splitter Split the AST into several compile units based on a size
- * heuristic Splitter needs attributed AST for weight calculations (e.g. is
- * a + b a ScriptRuntime.ADD with call overhead or a dadd with much less).
- * Split IR can lead to scope information being changed.
+ /**
+ * Reuse compile units, if they are already present. We are using the same compiler
+ * to recompile stuff
*/
- SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
+ 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 FunctionNode fn) {
- final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName());
+ 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 FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn);
+ final DebugLogger log = compiler.getLogger();
- assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
+ log.fine("Clearing bytecode cache");
+ compiler.clearBytecode();
- if (newFunctionNode.isStrict()) {
- assert compiler.getStrictMode();
- compiler.setStrictMode(true);
+ for (final CompileUnit oldUnit : compiler.getCompileUnits()) {
+ assert map.get(oldUnit) == null;
+ final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
+ 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 = transformFunction(fn, new ReplaceCompileUnits() {
+ @Override
+ CompileUnit getReplacement(CompileUnit original) {
+ return map.get(original);
+ }
+
+ @Override
+ public Node leaveDefault(final Node node) {
+ return node.ensureUniqueLabels(lc);
+ }
+ });
+
return newFunctionNode;
}
@Override
public String toString() {
- return "[Code Splitting]";
+ return "'Reuse Compile Units'";
}
},
- /*
- * FinalizeTypes
- *
- * This pass finalizes the types for nodes. If Attr created wider types than
- * known during the first pass, convert nodes are inserted or access nodes
- * are specialized where scope accesses.
- *
- * Runtime nodes may be removed and primitivized or reintroduced depending
- * on information that was established in Attr.
- *
- * Contract: all variables must have slot assignments and scope assignments
- * before type finalization.
- */
- TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT)) {
+ REINITIALIZE_SERIALIZED(
+ EnumSet.of(
+ INITIALIZED,
+ PARSED,
+ CONSTANT_FOLDED,
+ LOWERED,
+ BUILTINS_TRANSFORMED,
+ SPLIT)) {
@Override
- FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
- final ScriptEnvironment env = compiler.getEnv();
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ final Set<CompileUnit> unitSet = CompileUnit.createCompileUnitSet();
+ final Map<CompileUnit, CompileUnit> unitMap = new HashMap<>();
- final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes(compiler.getTemporarySymbols()));
+ // Ensure that the FunctionNode's compile unit is the first in the list of new units. Install phase
+ // will use that as the root class.
+ createCompileUnit(fn.getCompileUnit(), unitSet, unitMap, compiler, phases);
- if (env._print_lower_ast) {
- env.getErr().println(new ASTWriter(newFunctionNode));
- }
+ final FunctionNode newFn = transformFunction(fn, new ReplaceCompileUnits() {
+ @Override
+ CompileUnit getReplacement(final CompileUnit oldUnit) {
+ final CompileUnit existing = unitMap.get(oldUnit);
+ if (existing != null) {
+ return existing;
+ }
+ return createCompileUnit(oldUnit, unitSet, unitMap, compiler, phases);
+ }
- if (env._print_lower_parse) {
- env.getErr().println(new PrintVisitor(newFunctionNode));
- }
+ @Override
+ public Node leaveFunctionNode(final FunctionNode fn2) {
+ return super.leaveFunctionNode(
+ // restore flags for deserialized nested function nodes
+ compiler.getScriptFunctionData(fn2.getId()).restoreFlags(lc, fn2));
+ };
+ });
+ compiler.replaceCompileUnits(unitSet);
+ return newFn;
+ }
- return newFunctionNode;
+ private CompileUnit createCompileUnit(final CompileUnit oldUnit, final Set<CompileUnit> unitSet,
+ final Map<CompileUnit, CompileUnit> unitMap, final Compiler compiler, final CompilationPhases phases) {
+ final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
+ unitMap.put(oldUnit, newUnit);
+ unitSet.add(newUnit);
+ return newUnit;
}
@Override
public String toString() {
- return "[Type Finalization]";
+ return "'Deserialize'";
}
},
- /*
+ /**
* Bytecode generation:
*
* Generate the byte code class(es) resulting from the compiled FunctionNode
*/
- BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED)) {
+ 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 FunctionNode fn) {
- final ScriptEnvironment env = compiler.getEnv();
+ FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
+ final ScriptEnvironment senv = compiler.getScriptEnvironment();
+
FunctionNode newFunctionNode = fn;
+ //root class is special, as it is bootstrapped from createProgramFunction, thus it's skipped
+ //in CodeGeneration - the rest can be used as a working "is compile unit used" metric
+ fn.getCompileUnit().setUsed();
+
+ 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 {
- final CodeGenerator codegen = new CodeGenerator(compiler);
- newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen);
+ // 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 = transformFunction(newFunctionNode, codegen).setState(null, BYTECODE_GENERATED);
codegen.generateScopeCalls();
} catch (final VerifyError e) {
- if (env._verify_code || env._print_code) {
- env.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage());
- if (env._dump_on_error) {
- e.printStackTrace(env.getErr());
+ 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();
+ if (!compileUnit.isUsed()) {
+ compiler.getLogger().fine("Skipping unused compile unit ", compileUnit);
+ continue;
+ }
+
final byte[] bytecode = classEmitter.toByteArray();
assert bytecode != null;
final String className = compileUnit.getUnitClassName();
+ compiler.addClass(className, bytecode); //classes are only added to the bytecode map if compile unit is used
- compiler.addClass(className, bytecode);
-
- // should could be printed to stderr for generate class?
- if (env._print_code) {
- final StringBuilder sb = new StringBuilder();
- sb.append("class: " + className).append('\n')
- .append(ClassEmitter.disassemble(bytecode))
- .append("=====");
- env.getErr().println(sb);
- }
+ CompileUnit.increaseEmitCount();
// should we verify the generated code?
- if (env._verify_code) {
+ if (senv._verify_code) {
compiler.getCodeInstaller().verify(bytecode);
}
- // should code be dumped to disk - only valid in compile_only mode?
- if (env._dest_dir != null && env._compile_only) {
- final String fileName = className.replace('.', File.separatorChar) + ".class";
- final int index = fileName.lastIndexOf(File.separatorChar);
+ DumpBytecode.dumpBytecode(senv, compiler.getLogger(), bytecode, className);
+ }
- final File dir;
- if (index != -1) {
- dir = new File(env._dest_dir, fileName.substring(0, index));
- } else {
- dir = new File(env._dest_dir);
- }
+ return newFunctionNode;
+ }
- try {
- if (!dir.exists() && !dir.mkdirs()) {
- throw new IOException(dir.toString());
- }
- final File file = new File(env._dest_dir, fileName);
- try (final FileOutputStream fos = new FileOutputStream(file)) {
- fos.write(bytecode);
- }
- Compiler.LOG.info("Wrote class to '" + file.getAbsolutePath() + '\'');
- } catch (final IOException e) {
- Compiler.LOG.warning("Skipping class dump for ",
- className,
- ": ",
- ECMAErrors.getMessage(
- "io.error.cant.write",
- dir.toString()));
+ @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()) {
+ if (!unit.isUsed()) {
+ continue;
+ }
+ unit.setCode(installedClasses.get(unit.getUnitClassName()));
+ }
+
+ if (!compiler.isOnDemandCompilation()) {
+ // Initialize functions
+ final Map<Integer, FunctionInitializer> initializers = compiler.getFunctionInitializers();
+ if (initializers != null) {
+ for (final Entry<Integer, FunctionInitializer> entry : initializers.entrySet()) {
+ final FunctionInitializer initializer = entry.getValue();
+ initializer.setCode(installedClasses.get(initializer.getClassName()));
+ compiler.getScriptFunctionData(entry.getKey()).initializeCode(initializer);
}
}
}
- return newFunctionNode;
+ 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 "[Bytecode Generation]";
+ 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) {
- return functionNode.hasState(pre);
- }
-
- protected FunctionNode begin(final FunctionNode functionNode) {
- if (pre != null) {
- // check that everything in pre is present
- for (final CompilationState state : pre) {
- assert functionNode.hasState(state);
- }
- // check that nothing else is present
- for (final CompilationState state : CompilationState.values()) {
- assert !(functionNode.hasState(state) && !pre.contains(state));
- }
+ private static FunctionNode setStates(final FunctionNode functionNode, final CompilationState state) {
+ if (!AssertsEnabled.assertsEnabled()) {
+ return functionNode;
}
-
- startTime = System.currentTimeMillis();
- return functionNode;
+ return transformFunction(functionNode, new NodeVisitor<LexicalContext>(new LexicalContext()) {
+ @Override
+ public Node leaveFunctionNode(final FunctionNode fn) {
+ return fn.setState(lc, state);
+ }
+ });
}
- protected FunctionNode end(final FunctionNode functionNode) {
- endTime = System.currentTimeMillis();
- Timing.accumulateTime(toString(), endTime - startTime);
+ /**
+ * Start a compilation phase
+ * @param compiler the compiler to use
+ * @param functionNode function to compile
+ * @return function node
+ */
+ protected FunctionNode begin(final Compiler compiler, final FunctionNode functionNode) {
+ compiler.getLogger().indent();
+
+ assert pre != null;
+
+ if (!functionNode.hasState(pre)) {
+ 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.nanoTime();
+
+ 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.nanoTime();
+ compiler.getScriptEnvironment()._timing.accumulateTime(toString(), endTime - startTime);
isFinished = true;
return functionNode;
@@ -504,13 +706,39 @@ enum CompilationPhase {
return endTime;
}
- abstract FunctionNode transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException;
+ abstract FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException;
- final FunctionNode apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException {
- if (!isApplicable(functionNode)) {
- throw new CompilationException("compile phase not applicable: " + this + " to " + functionNode.getName() + " state=" + functionNode.getState());
- }
- return end(transform(compiler, begin(functionNode)));
+ /**
+ * 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)));
+ }
+
+ private static FunctionNode transformFunction(final FunctionNode fn, final NodeVisitor<?> visitor) {
+ return (FunctionNode) fn.accept(visitor);
}
+ private static CompileUnit createNewCompileUnit(final Compiler compiler, final CompilationPhases phases) {
+ final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
+ if (phases.isRestOfCompilation()) {
+ sb.append("$restOf");
+ }
+ //it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what
+ //fills those out anyway. Thus no need for a copy constructor
+ return compiler.createCompileUnit(sb.toString(), 0);
+ }
}