CompilationPhase.java revision 1256:b275aac76cdd
1/*
2 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.codegen;
27
28import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BUILTINS_TRANSFORMED;
29import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_GENERATED;
30import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_INSTALLED;
31import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
32import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
33import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOCAL_VARIABLE_TYPES_CALCULATED;
34import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
35import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.OPTIMISTIC_TYPES_ASSIGNED;
36import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
37import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SCOPE_DEPTHS_COMPUTED;
38import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
39import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SYMBOLS_ASSIGNED;
40import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;
41
42import java.io.PrintWriter;
43import java.util.EnumSet;
44import java.util.HashMap;
45import java.util.LinkedHashMap;
46import java.util.Map;
47import java.util.Map.Entry;
48import java.util.Set;
49import jdk.nashorn.internal.AssertsEnabled;
50import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
51import jdk.nashorn.internal.ir.FunctionNode;
52import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
53import jdk.nashorn.internal.ir.LexicalContext;
54import jdk.nashorn.internal.ir.LiteralNode;
55import jdk.nashorn.internal.ir.Node;
56import jdk.nashorn.internal.ir.debug.ASTWriter;
57import jdk.nashorn.internal.ir.debug.PrintVisitor;
58import jdk.nashorn.internal.ir.visitor.NodeVisitor;
59import jdk.nashorn.internal.runtime.CodeInstaller;
60import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
61import jdk.nashorn.internal.runtime.ScriptEnvironment;
62import jdk.nashorn.internal.runtime.logging.DebugLogger;
63
64/**
65 * A compilation phase is a step in the processes of turning a JavaScript
66 * FunctionNode into bytecode. It has an optional return value.
67 */
68enum CompilationPhase {
69    /**
70     * Constant folding pass Simple constant folding that will make elementary
71     * constructs go away
72     */
73    CONSTANT_FOLDING_PHASE(
74            EnumSet.of(
75                INITIALIZED,
76                PARSED)) {
77        @Override
78        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
79            return transformFunction(fn, new FoldConstants(compiler));
80        }
81
82        @Override
83        public String toString() {
84            return "'Constant Folding'";
85        }
86    },
87
88    /**
89     * Lower (Control flow pass) Finalizes the control flow. Clones blocks for
90     * finally constructs and similar things. Establishes termination criteria
91     * for nodes Guarantee return instructions to method making sure control
92     * flow cannot fall off the end. Replacing high level nodes with lower such
93     * as runtime nodes where applicable.
94     */
95    LOWERING_PHASE(
96            EnumSet.of(
97                INITIALIZED,
98                PARSED,
99                CONSTANT_FOLDED)) {
100        @Override
101        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
102            return transformFunction(fn, new Lower(compiler));
103        }
104
105        @Override
106        public String toString() {
107            return "'Control Flow Lowering'";
108        }
109    },
110
111    /**
112     * Phase used only when doing optimistic code generation. It assigns all potentially
113     * optimistic ops a program point so that an UnwarrantedException knows from where
114     * a guess went wrong when creating the continuation to roll back this execution
115     */
116    TRANSFORM_BUILTINS_PHASE(
117            EnumSet.of(
118                    INITIALIZED,
119                    PARSED,
120                    CONSTANT_FOLDED,
121                    LOWERED)) {
122        //we only do this if we have a param type map, otherwise this is not a specialized recompile
123        @Override
124        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
125            return setStates(transformFunction(fn, new ApplySpecialization(compiler)), BUILTINS_TRANSFORMED);
126        }
127
128        @Override
129        public String toString() {
130            return "'Builtin Replacement'";
131        }
132    },
133
134    /**
135     * Splitter Split the AST into several compile units based on a heuristic size calculation.
136     * Split IR can lead to scope information being changed.
137     */
138    SPLITTING_PHASE(
139            EnumSet.of(
140                    INITIALIZED,
141                    PARSED,
142                    CONSTANT_FOLDED,
143                    LOWERED,
144                    BUILTINS_TRANSFORMED)) {
145        @Override
146        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
147            final CompileUnit  outermostCompileUnit = compiler.addCompileUnit(0L);
148
149            FunctionNode newFunctionNode;
150
151            //ensure elementTypes, postsets and presets exist for splitter and arraynodes
152            newFunctionNode = transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
153                @Override
154                public LiteralNode<?> leaveLiteralNode(final LiteralNode<?> literalNode) {
155                    return literalNode.initialize(lc);
156                }
157            });
158
159            newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true);
160            newFunctionNode = transformFunction(newFunctionNode, new SplitIntoFunctions(compiler));
161            assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
162            assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName());
163
164            return newFunctionNode;
165        }
166
167        @Override
168        public String toString() {
169            return "'Code Splitting'";
170        }
171    },
172
173    PROGRAM_POINT_PHASE(
174            EnumSet.of(
175                    INITIALIZED,
176                    PARSED,
177                    CONSTANT_FOLDED,
178                    LOWERED,
179                    BUILTINS_TRANSFORMED,
180                    SPLIT)) {
181        @Override
182        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
183            return transformFunction(fn, new ProgramPoints());
184        }
185
186        @Override
187        public String toString() {
188            return "'Program Point Calculation'";
189        }
190    },
191
192    SERIALIZE_SPLIT_PHASE(
193            EnumSet.of(
194                    INITIALIZED,
195                    PARSED,
196                    CONSTANT_FOLDED,
197                    LOWERED,
198                    BUILTINS_TRANSFORMED,
199                    SPLIT)) {
200        @Override
201        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
202            return transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
203                @Override
204                public boolean enterFunctionNode(final FunctionNode functionNode) {
205                    if (functionNode.isSplit()) {
206                        compiler.serializeAst(functionNode);
207                    }
208                    return true;
209                }
210            });
211        }
212
213        @Override
214        public String toString() {
215            return "'Serialize Split Functions'";
216        }
217    },
218
219    SYMBOL_ASSIGNMENT_PHASE(
220            EnumSet.of(
221                    INITIALIZED,
222                    PARSED,
223                    CONSTANT_FOLDED,
224                    LOWERED,
225                    BUILTINS_TRANSFORMED,
226                    SPLIT)) {
227        @Override
228        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
229            return transformFunction(fn, new AssignSymbols(compiler));
230        }
231
232        @Override
233        public String toString() {
234            return "'Symbol Assignment'";
235        }
236    },
237
238    SCOPE_DEPTH_COMPUTATION_PHASE(
239            EnumSet.of(
240                    INITIALIZED,
241                    PARSED,
242                    CONSTANT_FOLDED,
243                    LOWERED,
244                    BUILTINS_TRANSFORMED,
245                    SPLIT,
246                    SYMBOLS_ASSIGNED)) {
247        @Override
248        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
249            return transformFunction(fn, new FindScopeDepths(compiler));
250        }
251
252        @Override
253        public String toString() {
254            return "'Scope Depth Computation'";
255        }
256    },
257
258    OPTIMISTIC_TYPE_ASSIGNMENT_PHASE(
259            EnumSet.of(
260                    INITIALIZED,
261                    PARSED,
262                    CONSTANT_FOLDED,
263                    LOWERED,
264                    BUILTINS_TRANSFORMED,
265                    SPLIT,
266                    SYMBOLS_ASSIGNED,
267                    SCOPE_DEPTHS_COMPUTED)) {
268        @Override
269        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
270            if (compiler.useOptimisticTypes()) {
271                return transformFunction(fn, new OptimisticTypesCalculator(compiler));
272            }
273            return setStates(fn, OPTIMISTIC_TYPES_ASSIGNED);
274        }
275
276        @Override
277        public String toString() {
278            return "'Optimistic Type Assignment'";
279        }
280    },
281
282    LOCAL_VARIABLE_TYPE_CALCULATION_PHASE(
283            EnumSet.of(
284                    INITIALIZED,
285                    PARSED,
286                    CONSTANT_FOLDED,
287                    LOWERED,
288                    BUILTINS_TRANSFORMED,
289                    SPLIT,
290                    SYMBOLS_ASSIGNED,
291                    SCOPE_DEPTHS_COMPUTED,
292                    OPTIMISTIC_TYPES_ASSIGNED)) {
293        @Override
294        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
295            final FunctionNode newFunctionNode = transformFunction(fn, new LocalVariableTypesCalculator(compiler));
296            final ScriptEnvironment senv = compiler.getScriptEnvironment();
297            final PrintWriter       err  = senv.getErr();
298
299            //TODO separate phase for the debug printouts for abstraction and clarity
300            if (senv._print_lower_ast || fn.getFlag(FunctionNode.IS_PRINT_LOWER_AST)) {
301                err.println("Lower AST for: " + quote(newFunctionNode.getName()));
302                err.println(new ASTWriter(newFunctionNode));
303            }
304
305            if (senv._print_lower_parse || fn.getFlag(FunctionNode.IS_PRINT_LOWER_PARSE)) {
306                err.println("Lower AST for: " + quote(newFunctionNode.getName()));
307                err.println(new PrintVisitor(newFunctionNode));
308            }
309
310            return newFunctionNode;
311        }
312
313        @Override
314        public String toString() {
315            return "'Local Variable Type Calculation'";
316        }
317    },
318
319
320    /**
321     * Reuse compile units, if they are already present. We are using the same compiler
322     * to recompile stuff
323     */
324    REUSE_COMPILE_UNITS_PHASE(
325            EnumSet.of(
326                    INITIALIZED,
327                    PARSED,
328                    CONSTANT_FOLDED,
329                    LOWERED,
330                    BUILTINS_TRANSFORMED,
331                    SPLIT,
332                    SYMBOLS_ASSIGNED,
333                    SCOPE_DEPTHS_COMPUTED,
334                    OPTIMISTIC_TYPES_ASSIGNED,
335                    LOCAL_VARIABLE_TYPES_CALCULATED)) {
336        @Override
337        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
338            assert phases.isRestOfCompilation() : "reuse compile units currently only used for Rest-Of methods";
339
340            final Map<CompileUnit, CompileUnit> map = new HashMap<>();
341            final Set<CompileUnit> newUnits = CompileUnit.createCompileUnitSet();
342
343            final DebugLogger log = compiler.getLogger();
344
345            log.fine("Clearing bytecode cache");
346            compiler.clearBytecode();
347
348            for (final CompileUnit oldUnit : compiler.getCompileUnits()) {
349                assert map.get(oldUnit) == null;
350                final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
351                log.fine("Creating new compile unit ", oldUnit, " => ", newUnit);
352                map.put(oldUnit, newUnit);
353                assert newUnit != null;
354                newUnits.add(newUnit);
355            }
356
357            log.fine("Replacing compile units in Compiler...");
358            compiler.replaceCompileUnits(newUnits);
359            log.fine("Done");
360
361            //replace old compile units in function nodes, if any are assigned,
362            //for example by running the splitter on this function node in a previous
363            //partial code generation
364            final FunctionNode newFunctionNode = transformFunction(fn, new ReplaceCompileUnits() {
365                @Override
366                CompileUnit getReplacement(CompileUnit original) {
367                    return map.get(original);
368                }
369
370                @Override
371                public Node leaveDefault(final Node node) {
372                    return node.ensureUniqueLabels(lc);
373                }
374            });
375
376            return newFunctionNode;
377        }
378
379        @Override
380        public String toString() {
381            return "'Reuse Compile Units'";
382        }
383    },
384
385    REINITIALIZE_SERIALIZED(
386            EnumSet.of(
387                    INITIALIZED,
388                    PARSED,
389                    CONSTANT_FOLDED,
390                    LOWERED,
391                    BUILTINS_TRANSFORMED,
392                    SPLIT)) {
393        @Override
394        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
395            final Set<CompileUnit> unitSet = CompileUnit.createCompileUnitSet();
396            final Map<CompileUnit, CompileUnit> unitMap = new HashMap<>();
397
398            // Ensure that the FunctionNode's compile unit is the first in the list of new units. Install phase
399            // will use that as the root class.
400            createCompileUnit(fn.getCompileUnit(), unitSet, unitMap, compiler, phases);
401
402            final FunctionNode newFn = transformFunction(fn, new ReplaceCompileUnits() {
403                @Override
404                CompileUnit getReplacement(final CompileUnit oldUnit) {
405                    final CompileUnit existing = unitMap.get(oldUnit);
406                    if (existing != null) {
407                        return existing;
408                    }
409                    return createCompileUnit(oldUnit, unitSet, unitMap, compiler, phases);
410                }
411
412                @Override
413                public Node leaveFunctionNode(final FunctionNode fn2) {
414                    return super.leaveFunctionNode(
415                            // restore flags for deserialized nested function nodes
416                            compiler.getScriptFunctionData(fn2.getId()).restoreFlags(lc, fn2));
417                };
418            });
419            compiler.replaceCompileUnits(unitSet);
420            return newFn;
421        }
422
423        private CompileUnit createCompileUnit(final CompileUnit oldUnit, final Set<CompileUnit> unitSet,
424                final Map<CompileUnit, CompileUnit> unitMap, final Compiler compiler, final CompilationPhases phases) {
425            final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
426            unitMap.put(oldUnit, newUnit);
427            unitSet.add(newUnit);
428            return newUnit;
429        }
430
431        @Override
432        public String toString() {
433            return "'Deserialize'";
434        }
435    },
436
437    /**
438     * Bytecode generation:
439     *
440     * Generate the byte code class(es) resulting from the compiled FunctionNode
441     */
442    BYTECODE_GENERATION_PHASE(
443            EnumSet.of(
444                    INITIALIZED,
445                    PARSED,
446                    CONSTANT_FOLDED,
447                    LOWERED,
448                    BUILTINS_TRANSFORMED,
449                    SPLIT,
450                    SYMBOLS_ASSIGNED,
451                    SCOPE_DEPTHS_COMPUTED,
452                    OPTIMISTIC_TYPES_ASSIGNED,
453                    LOCAL_VARIABLE_TYPES_CALCULATED)) {
454
455        @Override
456        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
457            final ScriptEnvironment senv = compiler.getScriptEnvironment();
458
459            FunctionNode newFunctionNode = fn;
460
461            //root class is special, as it is bootstrapped from createProgramFunction, thus it's skipped
462            //in CodeGeneration - the rest can be used as a working "is compile unit used" metric
463            fn.getCompileUnit().setUsed();
464
465            compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation());
466
467            final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null);
468
469            try {
470                // Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program
471                // in the lazy + optimistic world. See CodeGenerator.skipFunction().
472                newFunctionNode = transformFunction(newFunctionNode, codegen).setState(null, BYTECODE_GENERATED);
473                codegen.generateScopeCalls();
474            } catch (final VerifyError e) {
475                if (senv._verify_code || senv._print_code) {
476                    senv.getErr().println(e.getClass().getSimpleName() + ": "  + e.getMessage());
477                    if (senv._dump_on_error) {
478                        e.printStackTrace(senv.getErr());
479                    }
480                } else {
481                    throw e;
482                }
483            } catch (final Throwable e) {
484                // Provide source file and line number being compiled when the assertion occurred
485                throw new AssertionError("Failed generating bytecode for " + fn.getSourceName() + ":" + codegen.getLastLineNumber(), e);
486            }
487
488            for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
489                final ClassEmitter classEmitter = compileUnit.getClassEmitter();
490                classEmitter.end();
491
492                if (!compileUnit.isUsed()) {
493                    compiler.getLogger().fine("Skipping unused compile unit ", compileUnit);
494                    continue;
495                }
496
497                final byte[] bytecode = classEmitter.toByteArray();
498                assert bytecode != null;
499
500                final String className = compileUnit.getUnitClassName();
501                compiler.addClass(className, bytecode); //classes are only added to the bytecode map if compile unit is used
502
503                CompileUnit.increaseEmitCount();
504
505                // should we verify the generated code?
506                if (senv._verify_code) {
507                    compiler.getCodeInstaller().verify(bytecode);
508                }
509
510                DumpBytecode.dumpBytecode(senv, compiler.getLogger(), bytecode, className);
511            }
512
513            return newFunctionNode;
514        }
515
516        @Override
517        public String toString() {
518            return "'Bytecode Generation'";
519        }
520    },
521
522     INSTALL_PHASE(
523            EnumSet.of(
524                    INITIALIZED,
525                    PARSED,
526                    CONSTANT_FOLDED,
527                    LOWERED,
528                    BUILTINS_TRANSFORMED,
529                    SPLIT,
530                    SYMBOLS_ASSIGNED,
531                    SCOPE_DEPTHS_COMPUTED,
532                    OPTIMISTIC_TYPES_ASSIGNED,
533                    LOCAL_VARIABLE_TYPES_CALCULATED,
534                    BYTECODE_GENERATED)) {
535
536        @Override
537        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
538            final DebugLogger log = compiler.getLogger();
539
540            final Map<String, Class<?>> installedClasses = new LinkedHashMap<>();
541
542            boolean first = true;
543            Class<?> rootClass = null;
544            long length = 0L;
545
546            final CodeInstaller<ScriptEnvironment> codeInstaller = compiler.getCodeInstaller();
547            final Map<String, byte[]>              bytecode      = compiler.getBytecode();
548
549            for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
550                final String className = entry.getKey();
551                //assert !first || className.equals(compiler.getFirstCompileUnit().getUnitClassName()) : "first=" + first + " className=" + className + " != " + compiler.getFirstCompileUnit().getUnitClassName();
552                final byte[] code = entry.getValue();
553                length += code.length;
554
555                final Class<?> clazz = codeInstaller.install(className, code);
556                if (first) {
557                    rootClass = clazz;
558                    first = false;
559                }
560                installedClasses.put(className, clazz);
561            }
562
563            if (rootClass == null) {
564                throw new CompilationException("Internal compiler error: root class not found!");
565            }
566
567            final Object[] constants = compiler.getConstantData().toArray();
568            codeInstaller.initialize(installedClasses.values(), compiler.getSource(), constants);
569
570            // initialize transient fields on recompilable script function data
571            for (final Object constant: constants) {
572                if (constant instanceof RecompilableScriptFunctionData) {
573                    ((RecompilableScriptFunctionData)constant).initTransients(compiler.getSource(), codeInstaller);
574                }
575            }
576
577            // initialize function in the compile units
578            for (final CompileUnit unit : compiler.getCompileUnits()) {
579                if (!unit.isUsed()) {
580                    continue;
581                }
582                unit.setCode(installedClasses.get(unit.getUnitClassName()));
583                unit.initializeFunctionsCode();
584            }
585
586            if (log.isEnabled()) {
587                final StringBuilder sb = new StringBuilder();
588
589                sb.append("Installed class '").
590                    append(rootClass.getSimpleName()).
591                    append('\'').
592                    append(" [").
593                    append(rootClass.getName()).
594                    append(", size=").
595                    append(length).
596                    append(" bytes, ").
597                    append(compiler.getCompileUnits().size()).
598                    append(" compile unit(s)]");
599
600                log.fine(sb.toString());
601            }
602
603            return setStates(fn.setRootClass(null, rootClass), BYTECODE_INSTALLED);
604        }
605
606        @Override
607        public String toString() {
608            return "'Class Installation'";
609        }
610
611     };
612
613    /** pre conditions required for function node to which this transform is to be applied */
614    private final EnumSet<CompilationState> pre;
615
616    /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
617    private long startTime;
618
619    /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
620    private long endTime;
621
622    /** boolean that is true upon transform completion */
623    private boolean isFinished;
624
625    private CompilationPhase(final EnumSet<CompilationState> pre) {
626        this.pre = pre;
627    }
628
629    private static FunctionNode setStates(final FunctionNode functionNode, final CompilationState state) {
630        if (!AssertsEnabled.assertsEnabled()) {
631            return functionNode;
632        }
633        return transformFunction(functionNode, new NodeVisitor<LexicalContext>(new LexicalContext()) {
634            @Override
635            public Node leaveFunctionNode(final FunctionNode fn) {
636                return fn.setState(lc, state);
637           }
638        });
639    }
640
641    /**
642     * Start a compilation phase
643     * @param compiler the compiler to use
644     * @param functionNode function to compile
645     * @return function node
646     */
647    protected FunctionNode begin(final Compiler compiler, final FunctionNode functionNode) {
648        compiler.getLogger().indent();
649
650        assert pre != null;
651
652        if (!functionNode.hasState(pre)) {
653            final StringBuilder sb = new StringBuilder("Compilation phase ");
654            sb.append(this).
655                append(" is not applicable to ").
656                append(quote(functionNode.getName())).
657                append("\n\tFunctionNode state = ").
658                append(functionNode.getState()).
659                append("\n\tRequired state     = ").
660                append(this.pre);
661
662            throw new CompilationException(sb.toString());
663         }
664
665         startTime = System.nanoTime();
666
667         return functionNode;
668     }
669
670    /**
671     * End a compilation phase
672     * @param compiler the compiler
673     * @param functionNode function node to compile
674     * @return function node
675     */
676    protected FunctionNode end(final Compiler compiler, final FunctionNode functionNode) {
677        compiler.getLogger().unindent();
678        endTime = System.nanoTime();
679        compiler.getScriptEnvironment()._timing.accumulateTime(toString(), endTime - startTime);
680
681        isFinished = true;
682        return functionNode;
683    }
684
685    boolean isFinished() {
686        return isFinished;
687    }
688
689    long getStartTime() {
690        return startTime;
691    }
692
693    long getEndTime() {
694        return endTime;
695    }
696
697    abstract FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException;
698
699    /**
700     * Apply a transform to a function node, returning the transfored function node. If the transform is not
701     * applicable, an exception is thrown. Every transform requires the function to have a certain number of
702     * states to operate. It can have more states set, but not fewer. The state list, i.e. the constructor
703     * arguments to any of the CompilationPhase enum entries, is a set of REQUIRED states.
704     *
705     * @param compiler     compiler
706     * @param phases       current complete pipeline of which this phase is one
707     * @param functionNode function node to transform
708     *
709     * @return transformed function node
710     *
711     * @throws CompilationException if function node lacks the state required to run the transform on it
712     */
713    final FunctionNode apply(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException {
714        assert phases.contains(this);
715
716        return end(compiler, transform(compiler, phases, begin(compiler, functionNode)));
717    }
718
719    private static FunctionNode transformFunction(final FunctionNode fn, final NodeVisitor<?> visitor) {
720        return (FunctionNode) fn.accept(visitor);
721    }
722
723    private static CompileUnit createNewCompileUnit(final Compiler compiler, final CompilationPhases phases) {
724        final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
725        if (phases.isRestOfCompilation()) {
726            sb.append("$restOf");
727        }
728        //it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what
729        //fills those out anyway. Thus no need for a copy constructor
730        return compiler.createCompileUnit(sb.toString(), 0);
731    }
732}
733