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