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