diff options
Diffstat (limited to 'src/jdk/nashorn/internal/codegen/CompilationPhase.java')
-rw-r--r-- | src/jdk/nashorn/internal/codegen/CompilationPhase.java | 886 |
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); + } } |