src/jdk/nashorn/internal/codegen/CompilationPhase.java

Fri, 05 Jul 2013 15:10:47 +0200

author
attila
date
Fri, 05 Jul 2013 15:10:47 +0200
changeset 416
ce9cbe70f915
parent 290
6fc7b51e83d6
child 430
2c007a8bb0e7
permissions
-rw-r--r--

8019819: scope symbol didn't get a slot in certain cases
Reviewed-by: hannesw, jlaskey, lagergren, sundar

     1 package jdk.nashorn.internal.codegen;
     3 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.ATTR;
     4 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
     5 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.FINALIZED;
     6 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
     7 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
     8 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
     9 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
    11 import java.io.File;
    12 import java.io.FileOutputStream;
    13 import java.io.IOException;
    14 import java.util.ArrayDeque;
    15 import java.util.ArrayList;
    16 import java.util.Deque;
    17 import java.util.EnumSet;
    18 import java.util.HashSet;
    19 import java.util.List;
    20 import java.util.Set;
    21 import jdk.nashorn.internal.codegen.types.Range;
    22 import jdk.nashorn.internal.codegen.types.Type;
    23 import jdk.nashorn.internal.ir.CallNode;
    24 import jdk.nashorn.internal.ir.FunctionNode;
    25 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
    26 import jdk.nashorn.internal.ir.LexicalContext;
    27 import jdk.nashorn.internal.ir.Node;
    28 import jdk.nashorn.internal.ir.ReturnNode;
    29 import jdk.nashorn.internal.ir.Symbol;
    30 import jdk.nashorn.internal.ir.TemporarySymbols;
    31 import jdk.nashorn.internal.ir.debug.ASTWriter;
    32 import jdk.nashorn.internal.ir.debug.PrintVisitor;
    33 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
    34 import jdk.nashorn.internal.runtime.ECMAErrors;
    35 import jdk.nashorn.internal.runtime.ScriptEnvironment;
    36 import jdk.nashorn.internal.runtime.Timing;
    38 /**
    39  * A compilation phase is a step in the processes of turning a JavaScript
    40  * FunctionNode into bytecode. It has an optional return value.
    41  */
    42 enum CompilationPhase {
    44     /*
    45      * Lazy initialization - tag all function nodes not the script as lazy as
    46      * default policy. The will get trampolines and only be generated when
    47      * called
    48      */
    49     LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
    50         @Override
    51         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
    53             /*
    54              * For lazy compilation, we might be given a node previously marked
    55              * as lazy to compile as the outermost function node in the
    56              * compiler. Unmark it so it can be compiled and not cause
    57              * recursion. Make sure the return type is unknown so it can be
    58              * correctly deduced. Return types are always Objects in Lazy nodes
    59              * as we haven't got a change to generate code for them and decude
    60              * its parameter specialization
    61              *
    62              * TODO: in the future specializations from a callsite will be
    63              * passed here so we can generate a better non-lazy version of a
    64              * function from a trampoline
    65              */
    67             final FunctionNode outermostFunctionNode = fn;
    69             final Set<FunctionNode> neverLazy = new HashSet<>();
    70             final Set<FunctionNode> lazy      = new HashSet<>();
    72             FunctionNode newFunctionNode = outermostFunctionNode;
    74             newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
    75                 // self references are done with invokestatic and thus cannot
    76                 // have trampolines - never lazy
    77                 @Override
    78                 public boolean enterCallNode(final CallNode node) {
    79                     final Node callee = node.getFunction();
    80                     if (callee instanceof FunctionNode) {
    81                         neverLazy.add(((FunctionNode)callee));
    82                         return false;
    83                     }
    84                     return true;
    85                 }
    87                 //any function that isn't the outermost one must be marked as lazy
    88                 @Override
    89                 public boolean enterFunctionNode(final FunctionNode node) {
    90                     assert compiler.isLazy();
    91                     lazy.add(node);
    92                     return true;
    93                 }
    94             });
    96             //at least one method is non lazy - the outermost one
    97             neverLazy.add(newFunctionNode);
    99             for (final FunctionNode node : neverLazy) {
   100                 Compiler.LOG.fine(
   101                         "Marking ",
   102                         node.getName(),
   103                         " as non lazy, as it's a self reference");
   104                 lazy.remove(node);
   105             }
   107             newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   108                 @Override
   109                 public Node leaveFunctionNode(final FunctionNode functionNode) {
   110                     if (lazy.contains(functionNode)) {
   111                         Compiler.LOG.fine(
   112                                 "Marking ",
   113                                 functionNode.getName(),
   114                                 " as lazy");
   115                         final FunctionNode parent = lc.getParentFunction(functionNode);
   116                         assert parent != null;
   117                         lc.setFlag(parent, FunctionNode.HAS_LAZY_CHILDREN);
   118                         lc.setBlockNeedsScope(parent.getBody());
   119                         lc.setFlag(functionNode, FunctionNode.IS_LAZY);
   120                         return functionNode;
   121                     }
   123                     return functionNode.
   124                         clearFlag(lc, FunctionNode.IS_LAZY).
   125                         setReturnType(lc, Type.UNKNOWN);
   126                 }
   127             });
   129             return newFunctionNode;
   130         }
   132         @Override
   133         public String toString() {
   134             return "[Lazy JIT Initialization]";
   135         }
   136     },
   138     /*
   139      * Constant folding pass Simple constant folding that will make elementary
   140      * constructs go away
   141      */
   142     CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
   143         @Override
   144         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
   145             return (FunctionNode)fn.accept(new FoldConstants());
   146         }
   148         @Override
   149         public String toString() {
   150             return "[Constant Folding]";
   151         }
   152     },
   154     /*
   155      * Lower (Control flow pass) Finalizes the control flow. Clones blocks for
   156      * finally constructs and similar things. Establishes termination criteria
   157      * for nodes Guarantee return instructions to method making sure control
   158      * flow cannot fall off the end. Replacing high level nodes with lower such
   159      * as runtime nodes where applicable.
   160      */
   161     LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) {
   162         @Override
   163         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
   164             return (FunctionNode)fn.accept(new Lower());
   165         }
   167         @Override
   168         public String toString() {
   169             return "[Control Flow Lowering]";
   170         }
   171     },
   173     /*
   174      * Attribution Assign symbols and types to all nodes.
   175      */
   176     ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) {
   177         @Override
   178         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
   179             final TemporarySymbols ts = compiler.getTemporarySymbols();
   180             final FunctionNode newFunctionNode = (FunctionNode)enterAttr(fn, ts).accept(new Attr(ts));
   181             if (compiler.getEnv()._print_mem_usage) {
   182                 Compiler.LOG.info("Attr temporary symbol count: " + ts.getTotalSymbolCount());
   183             }
   184             return newFunctionNode;
   185         }
   187         /**
   188          * Pessimistically set all lazy functions' return types to Object
   189          * and the function symbols to object
   190          * @param functionNode node where to start iterating
   191          */
   192         private FunctionNode enterAttr(final FunctionNode functionNode, final TemporarySymbols ts) {
   193             return (FunctionNode)functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   194                 @Override
   195                 public Node leaveFunctionNode(final FunctionNode node) {
   196                     if (node.isLazy()) {
   197                         FunctionNode newNode = node.setReturnType(lc, Type.OBJECT);
   198                         return ts.ensureSymbol(lc, Type.OBJECT, newNode);
   199                     }
   200                     //node may have a reference here that needs to be nulled if it was referred to by
   201                     //its outer context, if it is lazy and not attributed
   202                     return node.setReturnType(lc, Type.UNKNOWN).setSymbol(lc, null);
   203                 }
   204             });
   205         }
   207         @Override
   208         public String toString() {
   209             return "[Type Attribution]";
   210         }
   211     },
   213     /*
   214      * Range analysis
   215      *    Conservatively prove that certain variables can be narrower than
   216      *    the most generic number type
   217      */
   218     RANGE_ANALYSIS_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
   219         @Override
   220         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
   221             if (!compiler.getEnv()._range_analysis) {
   222                 return fn;
   223             }
   225             FunctionNode newFunctionNode = (FunctionNode)fn.accept(new RangeAnalyzer());
   226             final List<ReturnNode> returns = new ArrayList<>();
   228             newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
   229                 private final Deque<ArrayList<ReturnNode>> returnStack = new ArrayDeque<>();
   231                 @Override
   232                 public boolean enterFunctionNode(final FunctionNode functionNode) {
   233                     returnStack.push(new ArrayList<ReturnNode>());
   234                     return true;
   235                 }
   237                 @Override
   238                 public Node leaveFunctionNode(final FunctionNode functionNode) {
   239                     Type returnType = Type.UNKNOWN;
   240                     for (final ReturnNode ret : returnStack.pop()) {
   241                         if (ret.getExpression() == null) {
   242                             returnType = Type.OBJECT;
   243                             break;
   244                         }
   245                         returnType = Type.widest(returnType, ret.getExpression().getType());
   246                     }
   247                     return functionNode.setReturnType(lc, returnType);
   248                 }
   250                 @Override
   251                 public Node leaveReturnNode(final ReturnNode returnNode) {
   252                     final ReturnNode result = (ReturnNode)leaveDefault(returnNode);
   253                     returns.add(result);
   254                     return result;
   255                 }
   257                 @Override
   258                 public Node leaveDefault(final Node node) {
   259                     final Symbol symbol = node.getSymbol();
   260                     if (symbol != null) {
   261                         final Range range  = symbol.getRange();
   262                         final Type  symbolType = symbol.getSymbolType();
   263                         if (!symbolType.isNumeric()) {
   264                             return node;
   265                         }
   266                         final Type  rangeType  = range.getType();
   267                         if (!Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range
   268                             RangeAnalyzer.LOG.info("[", lc.getCurrentFunction().getName(), "] ", symbol, " can be ", range.getType(), " ", symbol.getRange());
   269                             return node.setSymbol(lc, symbol.setTypeOverrideShared(range.getType(), compiler.getTemporarySymbols()));
   270                         }
   271                     }
   272                     return node;
   273                 }
   274             });
   276             Type returnType = Type.UNKNOWN;
   277             for (final ReturnNode node : returns) {
   278                 if (node.getExpression() != null) {
   279                     returnType = Type.widest(returnType, node.getExpression().getType());
   280                 } else {
   281                     returnType = Type.OBJECT;
   282                     break;
   283                 }
   284             }
   286             return newFunctionNode.setReturnType(null, returnType);
   287         }
   289         @Override
   290         public String toString() {
   291             return "[Range Analysis]";
   292         }
   293     },
   296     /*
   297      * Splitter Split the AST into several compile units based on a size
   298      * heuristic Splitter needs attributed AST for weight calculations (e.g. is
   299      * a + b a ScriptRuntime.ADD with call overhead or a dadd with much less).
   300      * Split IR can lead to scope information being changed.
   301      */
   302     SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
   303         @Override
   304         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
   305             final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName());
   307             final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn);
   309             assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
   311             if (newFunctionNode.isStrict()) {
   312                 assert compiler.getStrictMode();
   313                 compiler.setStrictMode(true);
   314             }
   316             return newFunctionNode;
   317         }
   319         @Override
   320         public String toString() {
   321             return "[Code Splitting]";
   322         }
   323     },
   325     /*
   326      * FinalizeTypes
   327      *
   328      * This pass finalizes the types for nodes. If Attr created wider types than
   329      * known during the first pass, convert nodes are inserted or access nodes
   330      * are specialized where scope accesses.
   331      *
   332      * Runtime nodes may be removed and primitivized or reintroduced depending
   333      * on information that was established in Attr.
   334      *
   335      * Contract: all variables must have slot assignments and scope assignments
   336      * before type finalization.
   337      */
   338     TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT)) {
   339         @Override
   340         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
   341             final ScriptEnvironment env = compiler.getEnv();
   343             final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes(compiler.getTemporarySymbols()));
   345             if (env._print_lower_ast) {
   346                 env.getErr().println(new ASTWriter(newFunctionNode));
   347             }
   349             if (env._print_lower_parse) {
   350                 env.getErr().println(new PrintVisitor(newFunctionNode));
   351             }
   353             return newFunctionNode;
   354         }
   356         @Override
   357         public String toString() {
   358             return "[Type Finalization]";
   359         }
   360     },
   362     /*
   363      * Bytecode generation:
   364      *
   365      * Generate the byte code class(es) resulting from the compiled FunctionNode
   366      */
   367     BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED)) {
   368         @Override
   369         FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
   370             final ScriptEnvironment env = compiler.getEnv();
   371             FunctionNode newFunctionNode = fn;
   373             try {
   374                 final CodeGenerator codegen = new CodeGenerator(compiler);
   375                 newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen);
   376                 codegen.generateScopeCalls();
   377             } catch (final VerifyError e) {
   378                 if (env._verify_code || env._print_code) {
   379                     env.getErr().println(e.getClass().getSimpleName() + ": "  + e.getMessage());
   380                     if (env._dump_on_error) {
   381                         e.printStackTrace(env.getErr());
   382                     }
   383                 } else {
   384                     throw e;
   385                 }
   386             }
   388             for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
   389                 final ClassEmitter classEmitter = compileUnit.getClassEmitter();
   390                 classEmitter.end();
   392                 final byte[] bytecode = classEmitter.toByteArray();
   393                 assert bytecode != null;
   395                 final String className = compileUnit.getUnitClassName();
   397                 compiler.addClass(className, bytecode);
   399                 // should could be printed to stderr for generate class?
   400                 if (env._print_code) {
   401                     final StringBuilder sb = new StringBuilder();
   402                     sb.append("class: " + className).append('\n')
   403                             .append(ClassEmitter.disassemble(bytecode))
   404                             .append("=====");
   405                     env.getErr().println(sb);
   406                 }
   408                 // should we verify the generated code?
   409                 if (env._verify_code) {
   410                     compiler.getCodeInstaller().verify(bytecode);
   411                 }
   413                 // should code be dumped to disk - only valid in compile_only
   414                 // mode?
   415                 if (env._dest_dir != null && env._compile_only) {
   416                     final String fileName = className.replace('.', File.separatorChar) + ".class";
   417                     final int index = fileName.lastIndexOf(File.separatorChar);
   419                     if (index != -1) {
   420                         final File dir = new File(fileName.substring(0, index));
   421                         try {
   422                             if (!dir.exists() && !dir.mkdirs()) {
   423                                 throw new IOException(dir.toString());
   424                             }
   425                             final File file = new File(env._dest_dir, fileName);
   426                             try (final FileOutputStream fos = new FileOutputStream(file)) {
   427                                 fos.write(bytecode);
   428                             }
   429                         } catch (final IOException e) {
   430                             Compiler.LOG.warning("Skipping class dump for ",
   431                                     className,
   432                                     ": ",
   433                                     ECMAErrors.getMessage(
   434                                         "io.error.cant.write",
   435                                         dir.toString()));
   436                         }
   437                     }
   438                 }
   439             }
   441             return newFunctionNode;
   442         }
   444         @Override
   445         public String toString() {
   446             return "[Bytecode Generation]";
   447         }
   448     };
   450     private final EnumSet<CompilationState> pre;
   451     private long startTime;
   452     private long endTime;
   453     private boolean isFinished;
   455     private CompilationPhase(final EnumSet<CompilationState> pre) {
   456         this.pre = pre;
   457     }
   459     boolean isApplicable(final FunctionNode functionNode) {
   460         return functionNode.hasState(pre);
   461     }
   463     protected FunctionNode begin(final FunctionNode functionNode) {
   464         if (pre != null) {
   465             // check that everything in pre is present
   466             for (final CompilationState state : pre) {
   467                 assert functionNode.hasState(state);
   468             }
   469             // check that nothing else is present
   470             for (final CompilationState state : CompilationState.values()) {
   471                 assert !(functionNode.hasState(state) && !pre.contains(state));
   472             }
   473         }
   475         startTime = System.currentTimeMillis();
   476         return functionNode;
   477     }
   479     protected FunctionNode end(final FunctionNode functionNode) {
   480         endTime = System.currentTimeMillis();
   481         Timing.accumulateTime(toString(), endTime - startTime);
   483         isFinished = true;
   484         return functionNode;
   485     }
   487     boolean isFinished() {
   488         return isFinished;
   489     }
   491     long getStartTime() {
   492         return startTime;
   493     }
   495     long getEndTime() {
   496         return endTime;
   497     }
   499     abstract FunctionNode transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException;
   501     final FunctionNode apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException {
   502         if (!isApplicable(functionNode)) {
   503             throw new CompilationException("compile phase not applicable: " + this + " to " + functionNode.getName() + " state=" + functionNode.getState());
   504         }
   505         return end(transform(compiler, begin(functionNode)));
   506     }
   508 }

mercurial