/* * 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.EnumSet; 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.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.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 transformFunction(fn, 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 transformFunction(fn, 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 */ 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) { return setStates(transformFunction(fn, new ApplySpecialization(compiler)), 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 = transformFunction(fn, new NodeVisitor(new LexicalContext()) { @Override 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 "'Code Splitting'"; } }, PROGRAM_POINT_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 ProgramPoints()); } @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(new LexicalContext()) { @Override public boolean enterFunctionNode(final FunctionNode functionNode) { if (functionNode.isSplit()) { compiler.serializeAst(functionNode); } return true; } }); } @Override public String toString() { return "'Serialize Split Functions'"; } }, 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 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 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); } @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 "'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 map = new HashMap<>(); final Set newUnits = CompileUnit.createCompileUnitSet(); final DebugLogger log = compiler.getLogger(); log.fine("Clearing bytecode cache"); compiler.clearBytecode(); 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(final CompileUnit original) { return map.get(original); } @Override public Node leaveDefault(final Node node) { return node.ensureUniqueLabels(lc); } }); return newFunctionNode; } @Override public String toString() { return "'Reuse Compile Units'"; } }, REINITIALIZE_SERIALIZED( EnumSet.of( INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, BUILTINS_TRANSFORMED, SPLIT)) { @Override FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) { final Set unitSet = CompileUnit.createCompileUnitSet(); final Map unitMap = new HashMap<>(); // 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); 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); } @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; } private CompileUnit createCompileUnit(final CompileUnit oldUnit, final Set unitSet, final Map 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 "'Deserialize'"; } }, /** * 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; //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 { // 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 (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 CompileUnit.increaseEmitCount(); // 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> installedClasses = new LinkedHashMap<>(); boolean first = true; Class rootClass = null; long length = 0L; final CodeInstaller codeInstaller = compiler.getCodeInstaller(); final Map bytecode = compiler.getBytecode(); for (final Entry 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())); unit.initializeFunctionsCode(); } 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 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 pre) { this.pre = pre; } private static FunctionNode setStates(final FunctionNode functionNode, final CompilationState state) { if (!AssertsEnabled.assertsEnabled()) { return functionNode; } return transformFunction(functionNode, new NodeVisitor(new LexicalContext()) { @Override public Node leaveFunctionNode(final FunctionNode fn) { return fn.setState(lc, state); } }); } /** * 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; } 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))); } 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); } }