aboutsummaryrefslogtreecommitdiff
path: root/src/jdk/nashorn/internal/codegen/CompilationPhase.java
blob: 2a1d266120283c82a04484c40ec6c1a94454ac5c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
/*
 * Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package jdk.nashorn.internal.codegen;

import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BUILTINS_TRANSFORMED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_GENERATED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.BYTECODE_INSTALLED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOCAL_VARIABLE_TYPES_CALCULATED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.OPTIMISTIC_TYPES_ASSIGNED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SCOPE_DEPTHS_COMPUTED;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SYMBOLS_ASSIGNED;
import static jdk.nashorn.internal.runtime.logging.DebugLogger.quote;

import java.io.PrintWriter;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import jdk.nashorn.internal.AssertsEnabled;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
import jdk.nashorn.internal.ir.FunctionNode;
import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
import jdk.nashorn.internal.ir.LexicalContext;
import jdk.nashorn.internal.ir.LiteralNode;
import jdk.nashorn.internal.ir.Node;
import jdk.nashorn.internal.ir.debug.ASTWriter;
import jdk.nashorn.internal.ir.debug.PrintVisitor;
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
import jdk.nashorn.internal.runtime.CodeInstaller;
import jdk.nashorn.internal.runtime.FunctionInitializer;
import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
import jdk.nashorn.internal.runtime.ScriptEnvironment;
import jdk.nashorn.internal.runtime.logging.DebugLogger;

/**
 * A compilation phase is a step in the processes of turning a JavaScript
 * FunctionNode into bytecode. It has an optional return value.
 */
enum CompilationPhase {
    /**
     * Constant folding pass Simple constant folding that will make elementary
     * constructs go away
     */
    CONSTANT_FOLDING_PHASE(
            EnumSet.of(
                INITIALIZED,
                PARSED)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            return transformFunction(fn, new FoldConstants(compiler));
        }

        @Override
        public String toString() {
            return "'Constant Folding'";
        }
    },

    /**
     * Lower (Control flow pass) Finalizes the control flow. Clones blocks for
     * finally constructs and similar things. Establishes termination criteria
     * for nodes Guarantee return instructions to method making sure control
     * flow cannot fall off the end. Replacing high level nodes with lower such
     * as runtime nodes where applicable.
     */
    LOWERING_PHASE(
            EnumSet.of(
                INITIALIZED,
                PARSED,
                CONSTANT_FOLDED)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            return transformFunction(fn, new Lower(compiler));
        }

        @Override
        public String toString() {
            return "'Control Flow Lowering'";
        }
    },

    /**
     * Phase used only when doing optimistic code generation. It assigns all potentially
     * optimistic ops a program point so that an UnwarrantedException knows from where
     * a guess went wrong when creating the continuation to roll back this execution
     */
    TRANSFORM_BUILTINS_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED)) {
        //we only do this if we have a param type map, otherwise this is not a specialized recompile
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            return setStates(transformFunction(fn, new ApplySpecialization(compiler)), BUILTINS_TRANSFORMED);
        }

        @Override
        public String toString() {
            return "'Builtin Replacement'";
        }
    },

    /**
     * Splitter Split the AST into several compile units based on a heuristic size calculation.
     * Split IR can lead to scope information being changed.
     */
    SPLITTING_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            final CompileUnit  outermostCompileUnit = compiler.addCompileUnit(0L);

            FunctionNode newFunctionNode;

            //ensure elementTypes, postsets and presets exist for splitter and arraynodes
            newFunctionNode = transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
                @Override
                public LiteralNode<?> leaveLiteralNode(final LiteralNode<?> literalNode) {
                    return literalNode.initialize(lc);
                }
            });

            newFunctionNode = new Splitter(compiler, newFunctionNode, outermostCompileUnit).split(newFunctionNode, true);
            newFunctionNode = transformFunction(newFunctionNode, new SplitIntoFunctions(compiler));
            assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
            assert newFunctionNode.isStrict() == compiler.isStrict() : "functionNode.isStrict() != compiler.isStrict() for " + quote(newFunctionNode.getName());

            return newFunctionNode;
        }

        @Override
        public String toString() {
            return "'Code Splitting'";
        }
    },

    PROGRAM_POINT_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            return transformFunction(fn, new ProgramPoints());
        }

        @Override
        public String toString() {
            return "'Program Point Calculation'";
        }
    },

    SERIALIZE_SPLIT_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            return transformFunction(fn, new NodeVisitor<LexicalContext>(new LexicalContext()) {
                @Override
                public boolean enterFunctionNode(final FunctionNode functionNode) {
                    if (functionNode.isSplit()) {
                        compiler.serializeAst(functionNode);
                    }
                    return true;
                }
            });
        }

        @Override
        public String toString() {
            return "'Serialize Split Functions'";
        }
    },

    SYMBOL_ASSIGNMENT_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            return transformFunction(fn, new AssignSymbols(compiler));
        }

        @Override
        public String toString() {
            return "'Symbol Assignment'";
        }
    },

    SCOPE_DEPTH_COMPUTATION_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT,
                    SYMBOLS_ASSIGNED)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            return transformFunction(fn, new FindScopeDepths(compiler));
        }

        @Override
        public String toString() {
            return "'Scope Depth Computation'";
        }
    },

    OPTIMISTIC_TYPE_ASSIGNMENT_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT,
                    SYMBOLS_ASSIGNED,
                    SCOPE_DEPTHS_COMPUTED)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            if (compiler.useOptimisticTypes()) {
                return transformFunction(fn, new OptimisticTypesCalculator(compiler));
            }
            return setStates(fn, OPTIMISTIC_TYPES_ASSIGNED);
        }

        @Override
        public String toString() {
            return "'Optimistic Type Assignment'";
        }
    },

    LOCAL_VARIABLE_TYPE_CALCULATION_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT,
                    SYMBOLS_ASSIGNED,
                    SCOPE_DEPTHS_COMPUTED,
                    OPTIMISTIC_TYPES_ASSIGNED)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            final FunctionNode newFunctionNode = transformFunction(fn, new LocalVariableTypesCalculator(compiler));
            final ScriptEnvironment senv = compiler.getScriptEnvironment();
            final PrintWriter       err  = senv.getErr();

            //TODO separate phase for the debug printouts for abstraction and clarity
            if (senv._print_lower_ast || fn.getFlag(FunctionNode.IS_PRINT_LOWER_AST)) {
                err.println("Lower AST for: " + quote(newFunctionNode.getName()));
                err.println(new ASTWriter(newFunctionNode));
            }

            if (senv._print_lower_parse || fn.getFlag(FunctionNode.IS_PRINT_LOWER_PARSE)) {
                err.println("Lower AST for: " + quote(newFunctionNode.getName()));
                err.println(new PrintVisitor(newFunctionNode));
            }

            return newFunctionNode;
        }

        @Override
        public String toString() {
            return "'Local Variable Type Calculation'";
        }
    },


    /**
     * Reuse compile units, if they are already present. We are using the same compiler
     * to recompile stuff
     */
    REUSE_COMPILE_UNITS_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT,
                    SYMBOLS_ASSIGNED,
                    SCOPE_DEPTHS_COMPUTED,
                    OPTIMISTIC_TYPES_ASSIGNED,
                    LOCAL_VARIABLE_TYPES_CALCULATED)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            assert phases.isRestOfCompilation() : "reuse compile units currently only used for Rest-Of methods";

            final Map<CompileUnit, CompileUnit> map = new HashMap<>();
            final Set<CompileUnit> newUnits = CompileUnit.createCompileUnitSet();

            final DebugLogger log = compiler.getLogger();

            log.fine("Clearing bytecode cache");
            compiler.clearBytecode();

            for (final CompileUnit oldUnit : compiler.getCompileUnits()) {
                assert map.get(oldUnit) == null;
                final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
                log.fine("Creating new compile unit ", oldUnit, " => ", newUnit);
                map.put(oldUnit, newUnit);
                assert newUnit != null;
                newUnits.add(newUnit);
            }

            log.fine("Replacing compile units in Compiler...");
            compiler.replaceCompileUnits(newUnits);
            log.fine("Done");

            //replace old compile units in function nodes, if any are assigned,
            //for example by running the splitter on this function node in a previous
            //partial code generation
            final FunctionNode newFunctionNode = transformFunction(fn, new ReplaceCompileUnits() {
                @Override
                CompileUnit getReplacement(CompileUnit original) {
                    return map.get(original);
                }

                @Override
                public Node leaveDefault(final Node node) {
                    return node.ensureUniqueLabels(lc);
                }
            });

            return newFunctionNode;
        }

        @Override
        public String toString() {
            return "'Reuse Compile Units'";
        }
    },

    REINITIALIZE_SERIALIZED(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT)) {
        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            final Set<CompileUnit> unitSet = CompileUnit.createCompileUnitSet();
            final Map<CompileUnit, CompileUnit> unitMap = new HashMap<>();

            // Ensure that the FunctionNode's compile unit is the first in the list of new units. Install phase
            // will use that as the root class.
            createCompileUnit(fn.getCompileUnit(), unitSet, unitMap, compiler, phases);

            final FunctionNode newFn = transformFunction(fn, new ReplaceCompileUnits() {
                @Override
                CompileUnit getReplacement(final CompileUnit oldUnit) {
                    final CompileUnit existing = unitMap.get(oldUnit);
                    if (existing != null) {
                        return existing;
                    }
                    return createCompileUnit(oldUnit, unitSet, unitMap, compiler, phases);
                }

                @Override
                public Node leaveFunctionNode(final FunctionNode fn2) {
                    return super.leaveFunctionNode(
                            // restore flags for deserialized nested function nodes
                            compiler.getScriptFunctionData(fn2.getId()).restoreFlags(lc, fn2));
                };
            });
            compiler.replaceCompileUnits(unitSet);
            return newFn;
        }

        private CompileUnit createCompileUnit(final CompileUnit oldUnit, final Set<CompileUnit> unitSet,
                final Map<CompileUnit, CompileUnit> unitMap, final Compiler compiler, final CompilationPhases phases) {
            final CompileUnit newUnit = createNewCompileUnit(compiler, phases);
            unitMap.put(oldUnit, newUnit);
            unitSet.add(newUnit);
            return newUnit;
        }

        @Override
        public String toString() {
            return "'Deserialize'";
        }
    },

    /**
     * Bytecode generation:
     *
     * Generate the byte code class(es) resulting from the compiled FunctionNode
     */
    BYTECODE_GENERATION_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT,
                    SYMBOLS_ASSIGNED,
                    SCOPE_DEPTHS_COMPUTED,
                    OPTIMISTIC_TYPES_ASSIGNED,
                    LOCAL_VARIABLE_TYPES_CALCULATED)) {

        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            final ScriptEnvironment senv = compiler.getScriptEnvironment();

            FunctionNode newFunctionNode = fn;

            //root class is special, as it is bootstrapped from createProgramFunction, thus it's skipped
            //in CodeGeneration - the rest can be used as a working "is compile unit used" metric
            fn.getCompileUnit().setUsed();

            compiler.getLogger().fine("Starting bytecode generation for ", quote(fn.getName()), " - restOf=", phases.isRestOfCompilation());

            final CodeGenerator codegen = new CodeGenerator(compiler, phases.isRestOfCompilation() ? compiler.getContinuationEntryPoints() : null);

            try {
                // Explicitly set BYTECODE_GENERATED here; it can not be set in case of skipping codegen for :program
                // in the lazy + optimistic world. See CodeGenerator.skipFunction().
                newFunctionNode = transformFunction(newFunctionNode, codegen).setState(null, BYTECODE_GENERATED);
                codegen.generateScopeCalls();
            } catch (final VerifyError e) {
                if (senv._verify_code || senv._print_code) {
                    senv.getErr().println(e.getClass().getSimpleName() + ": "  + e.getMessage());
                    if (senv._dump_on_error) {
                        e.printStackTrace(senv.getErr());
                    }
                } else {
                    throw e;
                }
            } catch (final Throwable e) {
                // Provide source file and line number being compiled when the assertion occurred
                throw new AssertionError("Failed generating bytecode for " + fn.getSourceName() + ":" + codegen.getLastLineNumber(), e);
            }

            for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
                final ClassEmitter classEmitter = compileUnit.getClassEmitter();
                classEmitter.end();

                if (!compileUnit.isUsed()) {
                    compiler.getLogger().fine("Skipping unused compile unit ", compileUnit);
                    continue;
                }

                final byte[] bytecode = classEmitter.toByteArray();
                assert bytecode != null;

                final String className = compileUnit.getUnitClassName();
                compiler.addClass(className, bytecode); //classes are only added to the bytecode map if compile unit is used

                CompileUnit.increaseEmitCount();

                // should we verify the generated code?
                if (senv._verify_code) {
                    compiler.getCodeInstaller().verify(bytecode);
                }

                DumpBytecode.dumpBytecode(senv, compiler.getLogger(), bytecode, className);
            }

            return newFunctionNode;
        }

        @Override
        public String toString() {
            return "'Bytecode Generation'";
        }
    },

     INSTALL_PHASE(
            EnumSet.of(
                    INITIALIZED,
                    PARSED,
                    CONSTANT_FOLDED,
                    LOWERED,
                    BUILTINS_TRANSFORMED,
                    SPLIT,
                    SYMBOLS_ASSIGNED,
                    SCOPE_DEPTHS_COMPUTED,
                    OPTIMISTIC_TYPES_ASSIGNED,
                    LOCAL_VARIABLE_TYPES_CALCULATED,
                    BYTECODE_GENERATED)) {

        @Override
        FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode fn) {
            final DebugLogger log = compiler.getLogger();

            final Map<String, Class<?>> installedClasses = new LinkedHashMap<>();

            boolean first = true;
            Class<?> rootClass = null;
            long length = 0L;

            final CodeInstaller<ScriptEnvironment> codeInstaller = compiler.getCodeInstaller();
            final Map<String, byte[]>              bytecode      = compiler.getBytecode();

            for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
                final String className = entry.getKey();
                //assert !first || className.equals(compiler.getFirstCompileUnit().getUnitClassName()) : "first=" + first + " className=" + className + " != " + compiler.getFirstCompileUnit().getUnitClassName();
                final byte[] code = entry.getValue();
                length += code.length;

                final Class<?> clazz = codeInstaller.install(className, code);
                if (first) {
                    rootClass = clazz;
                    first = false;
                }
                installedClasses.put(className, clazz);
            }

            if (rootClass == null) {
                throw new CompilationException("Internal compiler error: root class not found!");
            }

            final Object[] constants = compiler.getConstantData().toArray();
            codeInstaller.initialize(installedClasses.values(), compiler.getSource(), constants);

            // initialize transient fields on recompilable script function data
            for (final Object constant: constants) {
                if (constant instanceof RecompilableScriptFunctionData) {
                    ((RecompilableScriptFunctionData)constant).initTransients(compiler.getSource(), codeInstaller);
                }
            }

            // initialize function in the compile units
            for (final CompileUnit unit : compiler.getCompileUnits()) {
                if (!unit.isUsed()) {
                    continue;
                }
                unit.setCode(installedClasses.get(unit.getUnitClassName()));
            }

            if (!compiler.isOnDemandCompilation()) {
                // Initialize functions
                final Map<Integer, FunctionInitializer> initializers = compiler.getFunctionInitializers();
                if (initializers != null) {
                    for (final Entry<Integer, FunctionInitializer> entry : initializers.entrySet()) {
                        final FunctionInitializer initializer = entry.getValue();
                        initializer.setCode(installedClasses.get(initializer.getClassName()));
                        compiler.getScriptFunctionData(entry.getKey()).initializeCode(initializer);
                    }
                }
            }

            if (log.isEnabled()) {
                final StringBuilder sb = new StringBuilder();

                sb.append("Installed class '").
                    append(rootClass.getSimpleName()).
                    append('\'').
                    append(" [").
                    append(rootClass.getName()).
                    append(", size=").
                    append(length).
                    append(" bytes, ").
                    append(compiler.getCompileUnits().size()).
                    append(" compile unit(s)]");

                log.fine(sb.toString());
            }

            return setStates(fn.setRootClass(null, rootClass), BYTECODE_INSTALLED);
        }

        @Override
        public String toString() {
            return "'Class Installation'";
        }

     };

    /** pre conditions required for function node to which this transform is to be applied */
    private final EnumSet<CompilationState> pre;

    /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
    private long startTime;

    /** start time of transform - used for timing, see {@link jdk.nashorn.internal.runtime.Timing} */
    private long endTime;

    /** boolean that is true upon transform completion */
    private boolean isFinished;

    private CompilationPhase(final EnumSet<CompilationState> pre) {
        this.pre = pre;
    }

    private static FunctionNode setStates(final FunctionNode functionNode, final CompilationState state) {
        if (!AssertsEnabled.assertsEnabled()) {
            return functionNode;
        }
        return transformFunction(functionNode, new NodeVisitor<LexicalContext>(new LexicalContext()) {
            @Override
            public Node leaveFunctionNode(final FunctionNode fn) {
                return fn.setState(lc, state);
           }
        });
    }

    /**
     * Start a compilation phase
     * @param compiler the compiler to use
     * @param functionNode function to compile
     * @return function node
     */
    protected FunctionNode begin(final Compiler compiler, final FunctionNode functionNode) {
        compiler.getLogger().indent();

        assert pre != null;

        if (!functionNode.hasState(pre)) {
            final StringBuilder sb = new StringBuilder("Compilation phase ");
            sb.append(this).
                append(" is not applicable to ").
                append(quote(functionNode.getName())).
                append("\n\tFunctionNode state = ").
                append(functionNode.getState()).
                append("\n\tRequired state     = ").
                append(this.pre);

            throw new CompilationException(sb.toString());
         }

         startTime = System.nanoTime();

         return functionNode;
     }

    /**
     * End a compilation phase
     * @param compiler the compiler
     * @param functionNode function node to compile
     * @return function node
     */
    protected FunctionNode end(final Compiler compiler, final FunctionNode functionNode) {
        compiler.getLogger().unindent();
        endTime = System.nanoTime();
        compiler.getScriptEnvironment()._timing.accumulateTime(toString(), endTime - startTime);

        isFinished = true;
        return functionNode;
    }

    boolean isFinished() {
        return isFinished;
    }

    long getStartTime() {
        return startTime;
    }

    long getEndTime() {
        return endTime;
    }

    abstract FunctionNode transform(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException;

    /**
     * Apply a transform to a function node, returning the transfored function node. If the transform is not
     * applicable, an exception is thrown. Every transform requires the function to have a certain number of
     * states to operate. It can have more states set, but not fewer. The state list, i.e. the constructor
     * arguments to any of the CompilationPhase enum entries, is a set of REQUIRED states.
     *
     * @param compiler     compiler
     * @param phases       current complete pipeline of which this phase is one
     * @param functionNode function node to transform
     *
     * @return transformed function node
     *
     * @throws CompilationException if function node lacks the state required to run the transform on it
     */
    final FunctionNode apply(final Compiler compiler, final CompilationPhases phases, final FunctionNode functionNode) throws CompilationException {
        assert phases.contains(this);

        return end(compiler, transform(compiler, phases, begin(compiler, functionNode)));
    }

    private static FunctionNode transformFunction(final FunctionNode fn, final NodeVisitor<?> visitor) {
        return (FunctionNode) fn.accept(visitor);
    }

    private static CompileUnit createNewCompileUnit(final Compiler compiler, final CompilationPhases phases) {
        final StringBuilder sb = new StringBuilder(compiler.nextCompileUnitName());
        if (phases.isRestOfCompilation()) {
            sb.append("$restOf");
        }
        //it's ok to not copy the initCount, methodCount and clinitCount here, as codegen is what
        //fills those out anyway. Thus no need for a copy constructor
        return compiler.createCompileUnit(sb.toString(), 0);
    }
}