Mercurial > hg > openjdk > jigsaw > nashorn
view src/jdk/nashorn/internal/codegen/CompilationPhase.java @ 143:4be452026847
8010652: Eliminate non-child references in Block/FunctionNode, and make few node types immutable
Reviewed-by: jlaskey, lagergren
author | attila |
---|---|
date | Sat, 23 Mar 2013 00:58:39 +0100 |
parents | e15806b9d716 |
children |
line wrap: on
line source
package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.ATTR; 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.LOWERED; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; import jdk.nashorn.internal.ir.LexicalContext; 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.NodeOperatorVisitor; import jdk.nashorn.internal.ir.visitor.NodeVisitor; import jdk.nashorn.internal.runtime.ECMAErrors; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.Timing; /** * 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 */ LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) { @Override void 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 */ //compute the signature from the callsite - todo - now just clone object params final FunctionNode outermostFunctionNode = compiler.getFunctionNode(); outermostFunctionNode.setIsLazy(false); outermostFunctionNode.setReturnType(Type.UNKNOWN); final Set<FunctionNode> neverLazy = new HashSet<>(); final Set<FunctionNode> lazy = new HashSet<>(); outermostFunctionNode.accept(new NodeVisitor() { // self references are done with invokestatic and thus cannot have trampolines - never lazy @Override public Node enterCallNode(final CallNode node) { final Node callee = node.getFunction(); if (callee instanceof FunctionNode) { neverLazy.add(((FunctionNode)callee)); return null; } return node; } @Override public Node enterFunctionNode(final FunctionNode node) { if (node == outermostFunctionNode) { return node; } assert compiler.isLazy(); lazy.add(node); //also needs scope, potentially needs arguments etc etc return node; } }); for (final FunctionNode node : neverLazy) { Compiler.LOG.fine("Marking " + node.getName() + " as non lazy, as it's a self reference"); node.setIsLazy(false); lazy.remove(node); } outermostFunctionNode.accept(new NodeOperatorVisitor() { private final LexicalContext lexicalContext = new LexicalContext(); @Override public Node enterFunctionNode(FunctionNode functionNode) { lexicalContext.push(functionNode); if(lazy.contains(functionNode)) { Compiler.LOG.fine("Marking " + functionNode.getName() + " as lazy"); functionNode.setIsLazy(true); lexicalContext.getParentFunction(functionNode).setHasLazyChildren(); } return functionNode; } @Override public Node leaveFunctionNode(FunctionNode functionNode) { lexicalContext.pop(functionNode); return functionNode; } }); } @Override public String toString() { return "[Lazy JIT Initialization]"; } }, /* * Constant folding pass * Simple constant folding that will make elementary constructs go away */ CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) { @Override void transform(final Compiler compiler, final FunctionNode fn) { fn.accept(new FoldConstants()); } @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 void transform(final Compiler compiler, final FunctionNode fn) { fn.accept(new Lower()); } @Override public String toString() { return "[Control Flow Lowering]"; } }, /* * Attribution * Assign symbols and types to all nodes. */ ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) { @Override void transform(final Compiler compiler, final FunctionNode fn) { fn.accept(new Attr()); } @Override public String toString() { return "[Type Attribution]"; } }, /* * 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. */ SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) { @Override void transform(final Compiler compiler, final FunctionNode fn) { final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName()); new Splitter(compiler, fn, outermostCompileUnit).split(); assert fn.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + fn.getCompileUnit() + ") != " + outermostCompileUnit; if (fn.isStrictMode()) { assert compiler.getStrictMode(); compiler.setStrictMode(true); } } @Override public String toString() { return "[Code Splitting]"; } }, /* * 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)) { @Override void transform(final Compiler compiler, final FunctionNode fn) { final ScriptEnvironment env = compiler.getEnv(); fn.accept(new FinalizeTypes()); if (env._print_lower_ast) { env.getErr().println(new ASTWriter(fn)); } if (env._print_lower_parse) { env.getErr().println(new PrintVisitor(fn)); } } @Override public String toString() { return "[Type Finalization]"; } }, /* * 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)) { @Override void transform(final Compiler compiler, final FunctionNode fn) { final ScriptEnvironment env = compiler.getEnv(); try { final CodeGenerator codegen = new CodeGenerator(compiler); fn.accept(codegen); codegen.generateScopeCalls(); fn.accept(new NodeOperatorVisitor() { @Override public Node enterFunctionNode(FunctionNode functionNode) { if(functionNode.isLazy()) { functionNode.resetResolved(); return null; } return fn; } }); } 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()); } } else { throw e; } } for (final CompileUnit compileUnit : compiler.getCompileUnits()) { final ClassEmitter classEmitter = compileUnit.getClassEmitter(); classEmitter.end(); final byte[] bytecode = classEmitter.toByteArray(); assert bytecode != null; final String className = compileUnit.getUnitClassName(); compiler.addClass(className, bytecode); //should 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); } //should we verify the generated code? if (env._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); if (index != -1) { final File dir = new File(fileName.substring(0, index)); 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); } } 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]"; } }; private final EnumSet<CompilationState> pre; private long startTime; private long endTime; private boolean isFinished; private CompilationPhase(final EnumSet<CompilationState> pre) { this.pre = pre; } boolean isApplicable(final FunctionNode functionNode) { return functionNode.hasState(pre); } protected void 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)); } } startTime = System.currentTimeMillis(); } protected void end(final FunctionNode functionNode) { endTime = System.currentTimeMillis(); Timing.accumulateTime(toString(), endTime - startTime); isFinished = true; } boolean isFinished() { return isFinished; } long getStartTime() { return startTime; } long getEndTime() { return endTime; } abstract void transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException; final void apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException { if (!isApplicable(functionNode)) { throw new CompilationException("compile phase not applicable: " + this); } begin(functionNode); transform(compiler, functionNode); end(functionNode); } }