8008199: Lazy compilation and trampoline implementation

The code pipeline now supports lazy compilation, which can be used to only compile certain FunctionNodes and leave others be, saving startup time. When these uncompiled nodes are hit, a trampoline will force them to be recompiled. This can also be used to specialize compilation fixing parameter types and return types to a callsite specific compilation. This will give performance.

Reviewed-by: attila, sundar
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
index e3bd8ab..1752cfb 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
@@ -84,7 +84,6 @@
 import jdk.nashorn.internal.runtime.PropertyMap;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
-import jdk.nashorn.internal.runtime.Source;
 
 /**
  * This is the attribution pass of the code generator. Attr takes Lowered IR,
@@ -102,11 +101,8 @@
  */
 
 final class Attr extends NodeOperatorVisitor {
-    /** Current compiler. */
-    private final Compiler compiler;
-
-    /** Current source. */
-    private final Source source;
+    /** Context compiler. */
+    private final Context context;
 
     /**
      * Local definitions in current block (to discriminate from function
@@ -122,16 +118,16 @@
      */
     private Set<String> localUses;
 
-    private static final DebugLogger LOG = new DebugLogger("attr");
+    private static final DebugLogger LOG   = new DebugLogger("attr");
+    private static final boolean     DEBUG = LOG.isEnabled();
 
     /**
      * Constructor.
      *
      * @param compiler the compiler
      */
-    Attr(final Compiler compiler) {
-        this.compiler = compiler;
-        this.source   = compiler.getSource();
+    Attr(final Context context) {
+        this.context = context;
     }
 
     @Override
@@ -231,6 +227,11 @@
     @Override
     public Node enter(final FunctionNode functionNode) {
         start(functionNode, false);
+        if (functionNode.isLazy()) {
+            LOG.info("LAZY: " + functionNode.getName());
+            end(functionNode);
+            return null;
+        }
 
         clearLocalDefs();
         clearLocalUses();
@@ -257,7 +258,7 @@
         }
 
         if (functionNode.isScript()) {
-            initFromPropertyMap(compiler.getContext(), functionNode);
+            initFromPropertyMap(context, functionNode);
         }
 
         // Add function name as local symbol
@@ -378,7 +379,7 @@
                 final FunctionNode functionNode = (FunctionNode)symbol.getNode();
                 assert functionNode.getCalleeNode() != null;
 
-                final VarNode var = new VarNode(source, functionNode.getToken(), functionNode.getFinish(), functionNode.getIdent(), functionNode.getCalleeNode());
+                final VarNode var = new VarNode(functionNode.getSource(), functionNode.getToken(), functionNode.getFinish(), functionNode.getIdent(), functionNode.getCalleeNode());
                 //newTemporary(Type.OBJECT, var); //ScriptFunction? TODO
 
                 functionNode.setNeedsSelfSymbol(var);
@@ -489,15 +490,21 @@
         if (functionNode != null) {
             functionNode.addReferencingParentBlock(getCurrentBlock());
         }
-        end(referenceNode);
-
         return referenceNode;
     }
 
     @Override
     public Node leave(final ReferenceNode referenceNode) {
         newTemporary(Type.OBJECT, referenceNode); //reference node type is always an object, i.e. the scriptFunction. the function return type varies though
+
+        final FunctionNode functionNode = referenceNode.getReference();
+        //assert !functionNode.getType().isUnknown() || functionNode.isLazy() : functionNode.getType();
+        if (functionNode.isLazy()) {
+            LOG.info("Lazy function node call reference: " + functionNode.getName() + " => Promoting to OBJECT");
+            functionNode.setReturnType(Type.OBJECT);
+        }
         end(referenceNode);
+
         return referenceNode;
     }
 
@@ -546,7 +553,7 @@
             type = Type.OBJECT;
         }
 
-        switchNode.setTag(newInternal(compiler.uniqueName(SWITCH_TAG_PREFIX.tag()), type));
+        switchNode.setTag(newInternal(getCurrentFunctionNode().uniqueName(SWITCH_TAG_PREFIX.tag()), type));
 
         end(switchNode);
 
@@ -1111,7 +1118,7 @@
     @Override
     public Node leave(final ForNode forNode) {
         if (forNode.isForIn()) {
-            forNode.setIterator(newInternal(getCurrentFunctionNode(), compiler.uniqueName(ITERATOR_PREFIX.tag()), Type.OBJECT)); //NASHORN-73
+            forNode.setIterator(newInternal(getCurrentFunctionNode(), getCurrentFunctionNode().uniqueName(ITERATOR_PREFIX.tag()), Type.OBJECT)); //NASHORN-73
             /*
              * Iterators return objects, so we need to widen the scope of the
              * init variable if it, for example, has been assigned double type
@@ -1321,7 +1328,7 @@
     }
 
     private Symbol exceptionSymbol() {
-        return newInternal(compiler.uniqueName(EXCEPTION_PREFIX.tag()), Type.typeFor(ECMAException.class));
+        return newInternal(getCurrentFunctionNode().uniqueName(EXCEPTION_PREFIX.tag()), Type.typeFor(ECMAException.class));
     }
 
     /**
@@ -1382,6 +1389,11 @@
                     }
                 }
 
+                @Override
+                public Node enter(final FunctionNode node) {
+                    return node.isLazy() ? null : node;
+                }
+
                 /**
                  * Eg.
                  *
@@ -1553,17 +1565,19 @@
     }
 
     private Node start(final Node node, final boolean printNode) {
-        final StringBuilder sb = new StringBuilder();
+        if (DEBUG) {
+            final StringBuilder sb = new StringBuilder();
 
-        sb.append("[ENTER ").
-            append(name(node)).
-            append("] ").
-            append(printNode ? node.toString() : "").
-            append(" in '").
-            append(getCurrentFunctionNode().getName()).
-            append("'");
-        LOG.info(sb.toString());
-        LOG.indent();
+            sb.append("[ENTER ").
+                append(name(node)).
+                append("] ").
+                append(printNode ? node.toString() : "").
+                append(" in '").
+                append(getCurrentFunctionNode().getName()).
+                append("'");
+            LOG.info(sb.toString());
+            LOG.indent();
+        }
 
         return node;
     }
@@ -1573,24 +1587,26 @@
     }
 
     private Node end(final Node node, final boolean printNode) {
-        final StringBuilder sb = new StringBuilder();
+        if (DEBUG) {
+            final StringBuilder sb = new StringBuilder();
 
-        sb.append("[LEAVE ").
-            append(name(node)).
-            append("] ").
-            append(printNode ? node.toString() : "").
-            append(" in '").
-            append(getCurrentFunctionNode().getName());
+            sb.append("[LEAVE ").
+                append(name(node)).
+                append("] ").
+                append(printNode ? node.toString() : "").
+                append(" in '").
+                append(getCurrentFunctionNode().getName());
 
-        if (node.getSymbol() == null) {
-            sb.append(" <NO SYMBOL>");
-        } else {
-            sb.append(" <symbol=").append(node.getSymbol()).append('>');
+            if (node.getSymbol() == null) {
+                sb.append(" <NO SYMBOL>");
+            } else {
+                sb.append(" <symbol=").append(node.getSymbol()).append('>');
+            }
+
+            LOG.unindent();
+            LOG.info(sb.toString());
         }
 
-        LOG.unindent();
-        LOG.info(sb.toString());
-
         return node;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
index 8ad8123..b9f645e 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
@@ -52,6 +52,8 @@
 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.EnumSet;
 import java.util.HashSet;
@@ -166,8 +168,8 @@
      * @param unitClassName Compile unit class name.
      * @param strictMode    Should we generate this method in strict mode
      */
-    ClassEmitter(final Compiler compiler, final String unitClassName, final boolean strictMode) {
-        this(compiler.getContext(),
+    ClassEmitter(final Context context, final String sourceName, final String unitClassName, final boolean strictMode) {
+        this(context,
              new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS) {
                 private static final String OBJECT_CLASS  = "java/lang/Object";
 
@@ -187,13 +189,23 @@
         this.unitClassName        = unitClassName;
         this.constantMethodNeeded = new HashSet<>();
 
-        cw.visit(V1_7, ACC_PUBLIC | ACC_SUPER, unitClassName, null, Compiler.pathName(jdk.nashorn.internal.scripts.JS$.class.getName()), null);
-        cw.visitSource(compiler.getSource().getName(), null);
+        cw.visit(V1_7, ACC_PUBLIC | ACC_SUPER, unitClassName, null, pathName(jdk.nashorn.internal.scripts.JS$.class.getName()), null);
+        cw.visitSource(sourceName, null);
 
         defineCommonStatics(strictMode);
     }
 
     /**
+     * Convert a binary name to a package/class name.
+     *
+     * @param name Binary name.
+     * @return Package/class name.
+     */
+    private static String pathName(final String name) {
+        return name.replace('.', '/');
+    }
+
+    /**
      * Define the static fields common in all scripts.
      * @param strictMode Should we generate this method in strict mode
      */
@@ -295,7 +307,7 @@
      * Ensure a get constant method is issued for the class.
      * @param cls Class of constant.
      */
-    public void needGetConstantMethod(final Class<?> cls) {
+    void needGetConstantMethod(final Class<?> cls) {
         constantMethodNeeded.add(cls);
     }
 
@@ -348,22 +360,15 @@
 
     /**
      * Disassemble an array of byte code.
-     *
-     * @param context   the context
      * @param bytecode  byte array representing bytecode
+     * @return disassembly as human readable string
      */
-    public static void disassemble(final Context context, final byte[] bytecode) {
-        new ClassReader(bytecode).accept(new TraceClassVisitor(context.getErr()), 0);
-    }
-
-    /**
-     * Verify an array of byte code as a valid Java class
-     *
-     * @param context  the context
-     * @param bytecode the bytecode array
-     */
-    public static void verify(final Context context, final byte[] bytecode) {
-        context.verify(bytecode);
+    public static String disassemble(final byte[] bytecode) {
+        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (final PrintWriter pw = new PrintWriter(baos)) {
+            new ClassReader(bytecode).accept(new TraceClassVisitor(pw), 0);
+        }
+        return new String(baos.toByteArray());
     }
 
     /**
@@ -459,7 +464,7 @@
         final MethodVisitor mv = cw.visitMethod(
             ACC_PUBLIC | ACC_STATIC | (functionNode.isVarArg() ? ACC_VARARGS : 0),
             functionNode.getName(),
-            FunctionSignature.functionSignature(functionNode),
+            new FunctionSignature(functionNode).toString(),
             null,
             null);
 
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
index da7c721..544d8c5 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
@@ -25,8 +25,10 @@
 
 package jdk.nashorn.internal.codegen;
 
+import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.HANDLE_STATIC;
 import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.PRIVATE;
 import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC;
+import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP;
 import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING;
 import static jdk.nashorn.internal.codegen.CompilerConstants.LEAF;
@@ -35,6 +37,7 @@
 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_ARRAY_ARG;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX;
+import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
 import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
@@ -48,6 +51,7 @@
 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT;
 
 import java.io.PrintWriter;
+import java.lang.invoke.MethodHandle;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
@@ -63,8 +67,8 @@
 import jdk.nashorn.internal.codegen.MethodEmitter.Label;
 import jdk.nashorn.internal.codegen.RuntimeCallSite.SpecializedRuntimeNode;
 import jdk.nashorn.internal.codegen.objects.FieldObjectCreator;
-import jdk.nashorn.internal.codegen.objects.FunctionObjectCreator;
 import jdk.nashorn.internal.codegen.objects.MapCreator;
+import jdk.nashorn.internal.codegen.objects.ObjectCreator;
 import jdk.nashorn.internal.codegen.objects.ObjectMapCreator;
 import jdk.nashorn.internal.codegen.types.ArrayType;
 import jdk.nashorn.internal.codegen.types.Type;
@@ -111,11 +115,13 @@
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.Lexer.RegexToken;
 import jdk.nashorn.internal.parser.TokenType;
+import jdk.nashorn.internal.runtime.CodeInstaller;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ECMAException;
 import jdk.nashorn.internal.runtime.PropertyMap;
 import jdk.nashorn.internal.runtime.Scope;
 import jdk.nashorn.internal.runtime.ScriptFunction;
+import jdk.nashorn.internal.runtime.ScriptFunctionData;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 import jdk.nashorn.internal.runtime.Source;
@@ -143,11 +149,17 @@
  */
 public final class CodeGenerator extends NodeOperatorVisitor {
 
-    /** Current compiler */
-    private final Compiler compiler;
+    /** Name of the Global object, cannot be referred to as .class, @see CodeGenerator */
+    private static final String GLOBAL_OBJECT = Compiler.OBJECTS_PACKAGE + '/' + "Global";
 
-    /** Compiler context */
-    private final Context context;
+    /** Name of the ScriptFunctionImpl, cannot be referred to as .class @see FunctionObjectCreator */
+    private static final String SCRIPTFUNCTION_IMPL_OBJECT = Compiler.OBJECTS_PACKAGE + '/' + "ScriptFunctionImpl";
+
+    private static final String SCRIPTFUNCTION_TRAMPOLINE_OBJECT = Compiler.OBJECTS_PACKAGE + '/' + "ScriptFunctionTrampolineImpl";
+
+    /** Constant data & installation. The only reason the compiler keeps this is because it is assigned
+     *  by reflection in class installation */
+    private final Compiler compiler;
 
     /** Call site flags given to the code generator to be used for all generated call sites */
     private final int callSiteFlags;
@@ -168,17 +180,7 @@
      */
     CodeGenerator(final Compiler compiler) {
         this.compiler      = compiler;
-        this.context       = compiler.getContext();
-        this.callSiteFlags = context._callsite_flags;
-    }
-
-    /**
-     * Get the compiler
-     *
-     * @return the compiler used
-     */
-    public Compiler getCompiler() {
-        return compiler;
+        this.callSiteFlags = compiler.getContext()._callsite_flags;
     }
 
     /**
@@ -320,7 +322,7 @@
          */
         final CodeGenerator codegen = this;
 
-        node.accept(new NodeVisitor(compileUnit, method) {
+        node.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
             @Override
             public Node enter(final IdentNode identNode) {
                 loadIdent(identNode);
@@ -534,7 +536,7 @@
         final FunctionNode currentFunction = getCurrentFunctionNode();
         final Block        currentBlock    = getCurrentBlock();
 
-        function.accept(new NodeVisitor(compileUnit, method) {
+        function.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
 
             private void sharedScopeCall(final IdentNode identNode, final int flags) {
                 final Symbol symbol = identNode.getSymbol();
@@ -651,7 +653,7 @@
                 final String signature = new FunctionSignature(true, callee.needsCallee(), callee.getReturnType(), isVarArg ? null : callee.getParameters()).toString();
 
                 if (callee.needsCallee()) {
-                    new FunctionObjectCreator(CodeGenerator.this, callee).makeObject(method);
+                    newFunctionObject(callee);
                 }
 
                 if (callee.isStrictMode()) { // self is undefined
@@ -969,14 +971,18 @@
 
     @Override
     public Node enter(final FunctionNode functionNode) {
+        if (functionNode.isLazy()) {
+            return null;
+        }
+
         if (functionNode.testResolved()) {
             return null;
         }
 
-        compileUnit = functionNode.getCompileUnit();
-        assert compileUnit != null;
+        setCurrentCompileUnit(functionNode.getCompileUnit());
+        assert getCurrentCompileUnit() != null;
 
-        method = compileUnit.getClassEmitter().method(functionNode);
+        method = getCurrentCompileUnit().getClassEmitter().method(functionNode);
         functionNode.setMethodEmitter(method);
         // Mark end for variable tables.
         method.begin();
@@ -1100,18 +1106,18 @@
         final Type elementType = arrayType.getElementType();
 
         if (units != null) {
-            final CompileUnit   savedCompileUnit = compileUnit;
-            final MethodEmitter savedMethod      = method;
+            final CompileUnit   savedCompileUnit = getCurrentCompileUnit();
+            final MethodEmitter savedMethod      = getCurrentMethodEmitter();
 
             try {
                 for (final ArrayUnit unit : units) {
-                    compileUnit = unit.getCompileUnit();
+                    setCurrentCompileUnit(unit.getCompileUnit());
 
-                    final String className = compileUnit.getUnitClassName();
-                    final String name      = compiler.uniqueName(SPLIT_PREFIX.tag());
+                    final String className = getCurrentCompileUnit().getUnitClassName();
+                    final String name      = getCurrentFunctionNode().uniqueName(SPLIT_PREFIX.tag());
                     final String signature = methodDescriptor(type, Object.class, ScriptFunction.class, ScriptObject.class, type);
 
-                    method = compileUnit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature);
+                    method = getCurrentCompileUnit().getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature);
                     method.setFunctionNode(getCurrentFunctionNode());
                     method.begin();
 
@@ -1135,8 +1141,8 @@
                     savedMethod.invokeStatic(className, name, signature);
                 }
             } finally {
-                compileUnit = savedCompileUnit;
-                method      = savedMethod;
+                setCurrentCompileUnit(savedCompileUnit);
+                setCurrentMethodEmitter(savedMethod);
             }
 
             return method;
@@ -1186,8 +1192,8 @@
      * @param string string to load
      */
     public void loadConstant(final String string) {
-        final String       unitClassName = compileUnit.getUnitClassName();
-        final ClassEmitter classEmitter  = compileUnit.getClassEmitter();
+        final String       unitClassName = getCurrentCompileUnit().getUnitClassName();
+        final ClassEmitter classEmitter  = getCurrentCompileUnit().getClassEmitter();
         final int          index         = compiler.getConstantData().add(string);
 
         method.load(index);
@@ -1202,8 +1208,8 @@
      * @param object object to load
      */
     public void loadConstant(final Object object) {
-        final String       unitClassName = compileUnit.getUnitClassName();
-        final ClassEmitter classEmitter  = compileUnit.getClassEmitter();
+        final String       unitClassName = getCurrentCompileUnit().getUnitClassName();
+        final ClassEmitter classEmitter  = getCurrentCompileUnit().getClassEmitter();
         final int          index         = compiler.getConstantData().add(object);
         final Class<?>     cls           = object.getClass();
 
@@ -1272,14 +1278,14 @@
             return loadRegexToken(regexToken);
         }
         // emit field
-        final String       regexName    = compiler.uniqueName(REGEX_PREFIX.tag());
-        final ClassEmitter classEmitter = compileUnit.getClassEmitter();
+        final String       regexName    = getCurrentFunctionNode().uniqueName(REGEX_PREFIX.tag());
+        final ClassEmitter classEmitter = getCurrentCompileUnit().getClassEmitter();
 
         classEmitter.field(EnumSet.of(PRIVATE, STATIC), regexName, Object.class);
         regexFieldCount++;
 
         // get field, if null create new regex, finally clone regex object
-        method.getStatic(compileUnit.getUnitClassName(), regexName, typeDescriptor(Object.class));
+        method.getStatic(getCurrentCompileUnit().getUnitClassName(), regexName, typeDescriptor(Object.class));
         method.dup();
         final Label cachedLabel = new Label("cached");
         method.ifnonnull(cachedLabel);
@@ -1287,7 +1293,7 @@
         method.pop();
         loadRegexToken(regexToken);
         method.dup();
-        method.putStatic(compileUnit.getUnitClassName(), regexName, typeDescriptor(Object.class));
+        method.putStatic(getCurrentCompileUnit().getUnitClassName(), regexName, typeDescriptor(Object.class));
 
         method.label(cachedLabel);
         globalRegExpCopy();
@@ -1409,7 +1415,7 @@
             return null;
         }
 
-        new FunctionObjectCreator(this, referenceNode.getReference()).makeObject(method);
+        newFunctionObject(referenceNode.getReference());
 
         return null;
     }
@@ -1551,6 +1557,10 @@
         return true;
     }
 
+    private static boolean isReducible(final Request request) {
+        return Request.isComparison(request) || request == Request.ADD;
+    }
+
     @Override
     public Node enter(final RuntimeNode runtimeNode) {
         if (runtimeNode.testResolved()) {
@@ -1563,9 +1573,10 @@
          *
          * TODO - remove this - Access Specializer will always know after Attr/Lower
          */
-        if (runtimeNode.isPrimitive() && !runtimeNode.isFinal()) {
+        if (runtimeNode.isPrimitive() && !runtimeNode.isFinal() && isReducible(runtimeNode.getRequest())) {
             final Node lhs = runtimeNode.getArgs().get(0);
-            final Node rhs = runtimeNode.getArgs().size() > 1 ? runtimeNode.getArgs().get(1) : null;
+            assert runtimeNode.getArgs().size() > 1 : runtimeNode + " must have two args";
+            final Node rhs = runtimeNode.getArgs().get(1);
 
             final Type   type   = runtimeNode.getType();
             final Symbol symbol = runtimeNode.getSymbol();
@@ -1709,7 +1720,7 @@
             method.end();
         } catch (final Throwable t) {
             Context.printStackTrace(t);
-            final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + compiler.getSource().getName());
+            final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getCurrentFunctionNode().getSource().getName());
             e.initCause(t);
             throw e;
         }
@@ -1898,7 +1909,7 @@
         method._new(ECMAException.class).dup();
 
         final Node   expression = throwNode.getExpression();
-        final Source source     = compiler.getSource();
+        final Source source     = throwNode.getSource();
         final int    position   = throwNode.position();
         final int    line       = source.getLine(position);
         final int    column     = source.getColumn(position);
@@ -2928,7 +2939,7 @@
         if (scopeCalls.containsKey(scopeCall)) {
             return scopeCalls.get(scopeCall);
         }
-        scopeCall.setClassAndName(compileUnit, compiler);
+        scopeCall.setClassAndName(getCurrentCompileUnit(), getCurrentFunctionNode().uniqueName("scopeCall"));
         scopeCalls.put(scopeCall, scopeCall);
         return scopeCall;
     }
@@ -2947,7 +2958,7 @@
         if (scopeCalls.containsKey(scopeCall)) {
             return scopeCalls.get(scopeCall);
         }
-        scopeCall.setClassAndName(compileUnit, compiler);
+        scopeCall.setClassAndName(getCurrentCompileUnit(), getCurrentFunctionNode().uniqueName("scopeCall"));
         scopeCalls.put(scopeCall, scopeCall);
         return scopeCall;
     }
@@ -2959,12 +2970,12 @@
      * @param ident identifier for block or function where applicable
      */
     private void printSymbols(final Block block, final String ident) {
-        if (!context._print_symbols) {
+        if (!compiler.getContext()._print_symbols) {
             return;
         }
 
         @SuppressWarnings("resource")
-        final PrintWriter out = context.getErr();
+        final PrintWriter out = compiler.getContext().getErr();
         out.println("[BLOCK in '" + ident + "']");
         if (!block.printSymbols(out)) {
             out.println("<no symbols>");
@@ -3057,7 +3068,7 @@
              * on the stack throughout the store and used at the end to execute it
              */
 
-            target.accept(new NodeVisitor(compileUnit, method) {
+            target.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
                 @Override
                 public Node enter(final IdentNode node) {
                     if (targetSymbol.isScope()) {
@@ -3124,7 +3135,7 @@
          * @return the quick symbol
          */
         private Symbol quickSymbol(final Type type, final String prefix) {
-            final String name = compiler.uniqueName(prefix);
+            final String name = getCurrentFunctionNode().uniqueName(prefix);
             final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL, null, null);
 
             symbol.setType(type);
@@ -3166,7 +3177,7 @@
              */
             method.convert(target.getType());
 
-            target.accept(new NodeVisitor(compileUnit, method) {
+            target.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
                 @Override
                 protected Node enterDefault(Node node) {
                     throw new AssertionError("Unexpected node " + node + " in store epilogue");
@@ -3228,42 +3239,83 @@
 
     }
 
+    private void newFunctionObject(final FunctionNode functionNode) {
+        final boolean isLazy = functionNode.isLazy();
+        final Class<?>[] cparams = new Class<?>[] { ScriptFunctionData.class, ScriptObject.class, MethodHandle.class };
+
+        new ObjectCreator(this, new ArrayList<String>(), new ArrayList<Symbol>(), false, false) {
+            @Override
+            public void makeObject(final MethodEmitter method) {
+                final String className = isLazy ? SCRIPTFUNCTION_TRAMPOLINE_OBJECT : SCRIPTFUNCTION_IMPL_OBJECT;
+
+                method._new(className).dup();
+                if (isLazy) {
+                    loadConstant(compiler.getCodeInstaller());
+                    loadConstant(functionNode);
+                } else {
+                    final String signature = new FunctionSignature(true, functionNode.needsCallee(), functionNode.getReturnType(), functionNode.isVarArg() ? null : functionNode.getParameters()).toString();
+                    method.loadHandle(functionNode.getCompileUnit().getUnitClassName(), functionNode.getName(), signature, EnumSet.of(HANDLE_STATIC)); // function
+                }
+                loadConstant(new ScriptFunctionData(functionNode, makeMap()));
+
+                if (isLazy || functionNode.needsParentScope()) {
+                    method.loadScope();
+                } else {
+                    method.loadNull();
+                }
+
+                method.loadHandle(getClassName(), ALLOCATE.tag(), methodDescriptor(ScriptObject.class, PropertyMap.class), EnumSet.of(HANDLE_STATIC));
+
+                final List<Class<?>> cparamList = new ArrayList<>();
+                if (isLazy) {
+                    cparamList.add(CodeInstaller.class);
+                    cparamList.add(FunctionNode.class);
+                } else {
+                    cparamList.add(MethodHandle.class);
+                }
+                cparamList.addAll(Arrays.asList(cparams));
+
+                method.invoke(constructorNoLookup(className, cparamList.toArray(new Class<?>[cparamList.size()])));
+            }
+        }.makeObject(method);
+    }
+
     /*
      * Globals are special. We cannot refer to any Global (or NativeObject) class by .class, as they are different
      * for different contexts. As far as I can tell, the only NativeObject that we need to deal with like this
      * is from the code pipeline is Global
      */
     private MethodEmitter globalInstance() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "instance", "()L" + Compiler.GLOBAL_OBJECT + ';');
+        return method.invokeStatic(GLOBAL_OBJECT, "instance", "()L" + GLOBAL_OBJECT + ';');
     }
 
     private MethodEmitter globalObjectPrototype() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "objectPrototype", methodDescriptor(ScriptObject.class));
+        return method.invokeStatic(GLOBAL_OBJECT, "objectPrototype", methodDescriptor(ScriptObject.class));
     }
 
     private MethodEmitter globalAllocateArguments() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "allocateArguments", methodDescriptor(ScriptObject.class, Object[].class, Object.class, int.class));
+        return method.invokeStatic(GLOBAL_OBJECT, "allocateArguments", methodDescriptor(ScriptObject.class, Object[].class, Object.class, int.class));
     }
 
     private MethodEmitter globalNewRegExp() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "newRegExp", methodDescriptor(Object.class, String.class, String.class));
+        return method.invokeStatic(GLOBAL_OBJECT, "newRegExp", methodDescriptor(Object.class, String.class, String.class));
     }
 
     private MethodEmitter globalRegExpCopy() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "regExpCopy", methodDescriptor(Object.class, Object.class));
+        return method.invokeStatic(GLOBAL_OBJECT, "regExpCopy", methodDescriptor(Object.class, Object.class));
     }
 
     private MethodEmitter globalAllocateArray(final ArrayType type) {
         //make sure the native array is treated as an array type
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "allocate", "(" + type.getDescriptor() + ")Ljdk/nashorn/internal/objects/NativeArray;");
+        return method.invokeStatic(GLOBAL_OBJECT, "allocate", "(" + type.getDescriptor() + ")Ljdk/nashorn/internal/objects/NativeArray;");
     }
 
     private MethodEmitter globalIsEval() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "isEval", methodDescriptor(boolean.class, Object.class));
+        return method.invokeStatic(GLOBAL_OBJECT, "isEval", methodDescriptor(boolean.class, Object.class));
     }
 
     private MethodEmitter globalDirectEval() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "directEval",
+        return method.invokeStatic(GLOBAL_OBJECT, "directEval",
                 methodDescriptor(Object.class, Object.class, Object.class, Object.class, Object.class, Object.class));
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
new file mode 100644
index 0000000..4d41e57
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
@@ -0,0 +1,366 @@
+package jdk.nashorn.internal.codegen;
+
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.ATTR;
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.EMITTED;
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.FINALIZED;
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.EnumSet;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
+import jdk.nashorn.internal.ir.Node;
+import jdk.nashorn.internal.ir.visitor.NodeVisitor;
+import jdk.nashorn.internal.ir.debug.ASTWriter;
+import jdk.nashorn.internal.ir.debug.PrintVisitor;
+import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ECMAErrors;
+
+/**
+ * A compilation phase is a step in the processes of turning a JavaScript FunctionNode
+ * into bytecode. It has an optional return value.
+ */
+enum CompilationPhase {
+
+    /*
+     * Lazy initialization - tag all function nodes not the script as lazy as
+     * default policy. The will get trampolines and only be generated when
+     * called
+     */
+    LAZY_INITIALIZATION_PHASE(EnumSet.of(FunctionNode.CompilationState.INITIALIZED)) {
+        @Override
+        boolean transform(final Compiler compiler, final FunctionNode fn) {
+
+            /*
+             * For lazy compilation, we might be given a node previously marked as lazy
+             * to compile as the outermost function node in the compiler. Unmark it
+             * so it can be compiled and not cause recursion. Make sure the return type
+             * is unknown so it can be correctly deduced. Return types are always
+             * Objects in Lazy nodes as we haven't got a change to generate code for
+             * them and decude its parameter specialization
+             *
+             * TODO: in the future specializations from a callsite will be passed here
+             * so we can generate a better non-lazy version of a function from a trampoline
+             */
+            //compute the signature from the callsite - todo - now just clone object params
+            final FunctionNode outermostFunctionNode = compiler.getFunctionNode();
+            outermostFunctionNode.setIsLazy(false);
+            outermostFunctionNode.setReturnType(Type.UNKNOWN);
+
+            outermostFunctionNode.accept(new NodeVisitor() {
+                @Override
+                public Node enter(final FunctionNode node) {
+                    assert Compiler.LAZY_JIT;
+                    node.setIsLazy(node != outermostFunctionNode);
+                    return node;
+                }
+            });
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "[Lazy JIT Initialization]";
+        }
+    },
+
+    /*
+     * Constant folding pass
+     *   Simple constant folding that will make elementary constructs go away
+     */
+    CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED), CONSTANT_FOLDED) {
+        @Override
+        boolean transform(final Compiler compiler, final FunctionNode fn) {
+            final Context context = compiler.getContext();
+
+            if (context._print_ast) {
+                context.getErr().println(new ASTWriter(fn));
+            }
+
+            if (context._print_parse) {
+                context.getErr().println(new PrintVisitor(fn));
+            }
+
+            fn.accept(new FoldConstants());
+
+            return true;
+        }
+
+        @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, CONSTANT_FOLDED), LOWERED) {
+        @Override
+        boolean transform(final Compiler compiler, final FunctionNode fn) {
+            fn.accept(new Lower());
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "[Control Flow Lowering]";
+        }
+    },
+
+    /*
+     * Attribution
+     *   Assign symbols and types to all nodes.
+     */
+    ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED), ATTR) {
+        @Override
+        boolean transform(final Compiler compiler, final FunctionNode fn) {
+            final Context context = compiler.getContext();
+            try {
+                fn.accept(new Attr(context));
+                return true;
+            } finally {
+                if (context._print_lower_ast) {
+                    context.getErr().println(new ASTWriter(fn));
+                }
+
+                if (context._print_lower_parse) {
+                    context.getErr().println(new PrintVisitor(fn));
+                }
+            }
+        }
+
+        @Override
+        public String toString() {
+            return "[Type Attribution]";
+        }
+    },
+
+    /*
+     * Splitter
+     *   Split the AST into several compile units based on a size heuristic
+     *   Splitter needs attributed AST for weight calculations (e.g. is
+     *   a + b a ScriptRuntime.ADD with call overhead or a dadd with much
+     *   less). Split IR can lead to scope information being changed.
+     */
+    SPLITTING_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED, ATTR), SPLIT) {
+        @Override
+        boolean transform(final Compiler compiler, final FunctionNode fn) {
+            final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName());
+
+            new Splitter(compiler, fn, outermostCompileUnit).split();
+
+            assert fn.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + fn.getCompileUnit() + ") != " + outermostCompileUnit;
+
+            if (fn.isStrictMode()) {
+                assert compiler.getStrictMode();
+                compiler.setStrictMode(true);
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "[Code Splitting]";
+        }
+    },
+
+    /*
+     * FinalizeTypes
+     *
+     *   This pass finalizes the types for nodes. If Attr created wider types than
+     *   known during the first pass, convert nodes are inserted or access nodes
+     *   are specialized where scope accesses.
+     *
+     *   Runtime nodes may be removed and primitivized or reintroduced depending
+     *   on information that was established in Attr.
+     *
+     * Contract: all variables must have slot assignments and scope assignments
+     * before type finalization.
+     */
+    TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT), FINALIZED) {
+        @Override
+        boolean transform(final Compiler compiler, final FunctionNode fn) {
+            fn.accept(new FinalizeTypes());
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "[Type Finalization]";
+        }
+    },
+
+    /*
+     * Bytecode generation:
+     *
+     *   Generate the byte code class(es) resulting from the compiled FunctionNode
+     */
+    BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED), EMITTED) {
+        @Override
+        boolean transform(final Compiler compiler, final FunctionNode fn) {
+            final Context context = compiler.getContext();
+
+            try {
+                final CodeGenerator codegen = new CodeGenerator(compiler);
+                fn.accept(codegen);
+                codegen.generateScopeCalls();
+
+            } catch (final VerifyError e) {
+                if (context._verify_code || context._print_code) {
+                    context.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage());
+                    if (context._dump_on_error) {
+                        e.printStackTrace(context.getErr());
+                    }
+                } else {
+                    throw e;
+                }
+            }
+
+            for (final CompileUnit compileUnit : compiler.getCompileUnits()) {
+                final ClassEmitter classEmitter = compileUnit.getClassEmitter();
+                classEmitter.end();
+
+                final byte[] bytecode = classEmitter.toByteArray();
+                assert bytecode != null;
+
+                final String className = compileUnit.getUnitClassName();
+
+                compiler.addClass(className, bytecode);
+
+                //should could be printed to stderr for generate class?
+                if (context._print_code) {
+                    final StringBuilder sb = new StringBuilder();
+                    sb.append("class: " + className).
+                        append('\n').
+                        append(ClassEmitter.disassemble(bytecode)).
+                        append("=====");
+                    context.getErr().println(sb);
+                }
+
+                //should we verify the generated code?
+                if (context._verify_code) {
+                    context.verify(bytecode);
+                }
+
+                //should code be dumped to disk - only valid in compile_only mode?
+                if (context._dest_dir != null && context._compile_only) {
+                    final String fileName = className.replace('.', File.separatorChar) + ".class";
+                    final int    index    = fileName.lastIndexOf(File.separatorChar);
+
+                    if (index != -1) {
+                        final File dir = new File(fileName.substring(0, index));
+                        try {
+                            if (!dir.exists() && !dir.mkdirs()) {
+                                throw new IOException();
+                            }
+                            final File file = new File(context._dest_dir, fileName);
+                            try (final FileOutputStream fos = new FileOutputStream(file)) {
+                                fos.write(bytecode);
+                            }
+                        } catch (final IOException e) {
+                            Compiler.LOG.warning("Skipping class dump for " + className + ": " + ECMAErrors.getMessage("io.error.cant.write", dir.toString()));
+                        }
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return "[Bytecode Generation]";
+        }
+    };
+
+    private final EnumSet<CompilationState> pre;
+    private final CompilationState post;
+    private long startTime;
+    private long endTime;
+    private boolean isFinished;
+
+    private static final long[] accumulatedTime = new long[CompilationPhase.values().length];
+
+    private CompilationPhase(final EnumSet<CompilationState> pre) {
+        this(pre, null);
+    }
+
+    private CompilationPhase(final EnumSet<CompilationState> pre, final CompilationState post) {
+        this.pre  = pre;
+        this.post = post;
+    }
+
+    boolean isApplicable(final FunctionNode functionNode) {
+        return functionNode.hasState(pre);
+    }
+
+    protected void begin(final FunctionNode functionNode) {
+        if (pre != null) {
+            //check that everything in pre is present
+            for (final CompilationState state : pre) {
+                assert functionNode.hasState(state);
+            }
+            //check that nothing else is present
+            for (final CompilationState state : CompilationState.values()) {
+                assert !(functionNode.hasState(state) && !pre.contains(state));
+            }
+        }
+
+        startTime = System.currentTimeMillis();
+    }
+
+    protected void end(final FunctionNode functionNode) {
+        endTime = System.currentTimeMillis();
+        accumulatedTime[ordinal()] += (endTime - startTime);
+
+        if (post != null) {
+            functionNode.setState(post);
+        }
+
+        isFinished = true;
+    }
+
+    boolean isFinished() {
+        return isFinished;
+    }
+
+    long getStartTime() {
+        return startTime;
+    }
+
+    long getEndTime() {
+        return endTime;
+    }
+
+    public static long getAccumulatedTime(final CompilationPhase phase) {
+        return accumulatedTime[phase.ordinal()];
+    }
+
+    abstract boolean transform(final Compiler compiler, final FunctionNode functionNode);
+
+    final boolean apply(final Compiler compiler, final FunctionNode functionNode) {
+        try {
+            if (!isApplicable(functionNode)) {
+                return false;
+            }
+            begin(functionNode);
+            transform(compiler, functionNode);
+            return true;
+        } finally {
+            end(functionNode);
+        }
+    }
+
+}
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java b/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java
index 727d9e3..5e62116 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java
@@ -51,7 +51,7 @@
      * Add weight to this compile unit
      * @param w weight to add
      */
-    public void addWeight(final long w) {
+    void addWeight(final long w) {
         this.weight += w;
     }
 
@@ -59,7 +59,7 @@
      * Get the current weight of the compile unit.
      * @return the unit's weight
      */
-    public long getWeight() {
+    long getWeight() {
         return weight;
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
index 3b21ab5..23f3874 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
@@ -27,124 +27,377 @@
 
 import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME;
-import static jdk.nashorn.internal.codegen.CompilerConstants.RUN_SCRIPT;
+import static jdk.nashorn.internal.codegen.CompilerConstants.LAZY;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
-
 import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
+import java.util.Arrays;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
-import java.util.TreeMap;
 import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.FunctionNode;
-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.parser.Parser;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
 import jdk.nashorn.internal.runtime.CodeInstaller;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.DebugLogger;
-import jdk.nashorn.internal.runtime.ECMAErrors;
-import jdk.nashorn.internal.runtime.ErrorManager;
 import jdk.nashorn.internal.runtime.Source;
 import jdk.nashorn.internal.runtime.options.Options;
 import org.dynalang.dynalink.support.NameCodec;
 
 /**
  * Responsible for converting JavaScripts to java byte code. Main entry
- * point for code generator
+ * point for code generator. The compiler may also install classes given some
+ * predefined Code installation policy, given to it at construction time.
+ * @see CodeInstaller
  */
 public final class Compiler {
 
-    /** Compiler states available */
-    public enum State {
-        /** compiler is ready */
-        INITIALIZED,
-        /** method has been parsed */
-        PARSED,
-        /** constant folding pass */
-        CONSTANT_FOLDED,
-        /** method has been lowered */
-        LOWERED,
-        /** method hass been attributed */
-        ATTR,
-        /** method has been split */
-        SPLIT,
-        /** method has had its types finalized */
-        FINALIZED,
-        /** method has been emitted to bytecode */
-        EMITTED
-    }
-
-    /** Current context */
-    private final Context context;
-
-    /** Currently compiled source */
-    private final Source source;
-
-    /** Current error manager */
-    private final ErrorManager errors;
-
-    /** Names uniqueName for this compile. */
-    private final Namespace namespace;
-
-    /** Current function node, or null if compiling from source until parsed */
-    private FunctionNode functionNode;
-
-    /** Current compiler state */
-    private final EnumSet<State> state;
-
     /** Name of the scripts package */
     public static final String SCRIPTS_PACKAGE = "jdk/nashorn/internal/scripts";
 
     /** Name of the objects package */
     public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects";
 
-    /** Name of the runtime package */
-    public static final String RUNTIME_PACKAGE = "jdk/nashorn/internal/runtime";
+    static final boolean LAZY_JIT = Options.getBooleanProperty("nashorn.compiler.lazy");
 
-    /** Name of the Global object, cannot be referred to as .class, @see CodeGenerator */
-    public static final String GLOBAL_OBJECT = OBJECTS_PACKAGE + '/' + "Global";
+    static final boolean TIME_COMPILATION = Options.getBooleanProperty("nashorn.compiler.time");
 
-    /** Name of the ScriptFunctionImpl, cannot be referred to as .class @see FunctionObjectCreator */
-    public static final String SCRIPTFUNCTION_IMPL_OBJECT = OBJECTS_PACKAGE + '/' + "ScriptFunctionImpl";
+    private final Map<String, byte[]> bytecode;
 
-    /** Name of the Trampoline, cannot be referred to as .class @see FunctionObjectCreator */
-    public static final String TRAMPOLINE_OBJECT = OBJECTS_PACKAGE + '/' + "Trampoline";
-
-    /** Compile unit (class) table. */
     private final Set<CompileUnit> compileUnits;
 
-    /** All the "complex" constants used in the code. */
     private final ConstantData constantData;
 
+    private final FunctionNode functionNode;
+
+    private final CompilationSequence sequence;
+
+    private final Context context;
+
+    private final String scriptName;
+
+    private boolean strict;
+
+    private CodeInstaller<Context> installer;
+
     static final DebugLogger LOG = new DebugLogger("compiler");
 
-    /** Script name */
-    private String scriptName;
+    /**
+     * This array contains names that need to be reserved at the start
+     * of a compile, to avoid conflict with variable names later introduced.
+     * See {@link CompilerConstants} for special names used for structures
+     * during a compile.
+     */
+    private static String[] RESERVED_NAMES = {
+        SCOPE.tag(),
+        THIS.tag()
+    };
 
-    /** Should we dump classes to disk and compile only? */
-    private final boolean dumpClass;
+    /**
+     * This class makes it possible to do your own compilation sequence
+     * from the code generation package. There are predefined compilation
+     * sequences already
+     */
+    @SuppressWarnings("serial")
+    static class CompilationSequence extends LinkedList<CompilationPhase> {
 
-    /** Code map class name -> byte code for all classes generated from this Source or FunctionNode */
-    private Map<String, byte[]> code;
+        CompilationSequence(final CompilationPhase... phases) {
+            super(Arrays.asList(phases));
+        }
 
-    /** Are we compiling in strict mode? */
-    private boolean strict;
+        CompilationSequence(final CompilationSequence sequence) {
+            this(sequence.toArray(new CompilationPhase[sequence.size()]));
+        }
 
-    /** Is this a lazy compilation - i.e. not from source, but jitting a previously parsed FunctionNode? */
-    private boolean isLazy;
+        CompilationSequence insertAfter(final CompilationPhase phase, final CompilationPhase newPhase) {
+            final CompilationSequence newSeq = new CompilationSequence();
+            for (final CompilationPhase elem : this) {
+                newSeq.add(phase);
+                if (elem.equals(phase)) {
+                    newSeq.add(newPhase);
+                }
+            }
+            assert newSeq.contains(newPhase);
+            return newSeq;
+        }
 
-    /** Lazy jitting is disabled by default */
-    private static final boolean LAZY_JIT = false;
+        CompilationSequence insertBefore(final CompilationPhase phase, final CompilationPhase newPhase) {
+            final CompilationSequence newSeq = new CompilationSequence();
+            for (final CompilationPhase elem : this) {
+                if (elem.equals(phase)) {
+                    newSeq.add(newPhase);
+                }
+                newSeq.add(phase);
+            }
+            assert newSeq.contains(newPhase);
+            return newSeq;
+        }
+
+        CompilationSequence insertFirst(final CompilationPhase phase) {
+            final CompilationSequence newSeq = new CompilationSequence(this);
+            newSeq.addFirst(phase);
+            return newSeq;
+        }
+
+        CompilationSequence insertLast(final CompilationPhase phase) {
+            final CompilationSequence newSeq = new CompilationSequence(this);
+            newSeq.addLast(phase);
+            return newSeq;
+        }
+    }
+
+    /**
+     * Standard (non-lazy) compilation, that basically will take an entire script
+     * and JIT it at once. This can lead to long startup time and fewer type
+     * specializations
+     */
+    final static CompilationSequence SEQUENCE_NORMAL = new CompilationSequence(
+        CompilationPhase.CONSTANT_FOLDING_PHASE,
+        CompilationPhase.LOWERING_PHASE,
+        CompilationPhase.ATTRIBUTION_PHASE,
+        CompilationPhase.SPLITTING_PHASE,
+        CompilationPhase.TYPE_FINALIZATION_PHASE,
+        CompilationPhase.BYTECODE_GENERATION_PHASE);
+
+    final static CompilationSequence SEQUENCE_LAZY =
+        SEQUENCE_NORMAL.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE);
+
+    final static CompilationSequence SEQUENCE_DEFAULT =
+        LAZY_JIT ?
+            SEQUENCE_LAZY :
+            SEQUENCE_NORMAL;
+
+    /**
+     * Constructor
+     *
+     * @param installer    code installer from
+     * @param functionNode function node (in any available {@link CompilationState}) to compile
+     * @param sequence     {@link Compiler#CompilationSequence} of {@link CompilationPhase}s to apply as this compilation
+     * @param strict       should this compilation use strict mode semantics
+     */
+    Compiler(final Context context, final CodeInstaller<Context> installer, final FunctionNode functionNode, final CompilationSequence sequence, final boolean strict) {
+        this.context       = context;
+        this.functionNode  = functionNode;
+        this.sequence      = sequence;
+        this.installer     = installer;
+        this.strict        = strict || functionNode.isStrictMode();
+        this.constantData  = new ConstantData();
+        this.compileUnits  = new HashSet<>();
+        this.bytecode      = new HashMap<>();
+
+        final StringBuilder sb = new StringBuilder();
+        sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.tag())).
+                append('$').
+                append(safeSourceName(functionNode.getSource())).
+                append(functionNode.isLazy() ? LAZY.tag() : "");
+
+        this.scriptName = sb.toString();
+
+        LOG.info("Initializing compiler for scriptName = " + scriptName + ", root function: '" + functionNode.getName() + "'");
+    }
+
+    /**
+     * Constructor
+     *
+     * @param installer    code installer from context
+     * @param functionNode function node (in any available {@link CompilationState}) to compile
+     * @param strict       should this compilation use strict mode semantics
+     */
+    public Compiler(final CodeInstaller<Context> installer, final FunctionNode functionNode, final boolean strict) {
+        this(installer.getOwner(), installer, functionNode, SEQUENCE_DEFAULT, strict);
+    }
+
+    /**
+     * Constructor - compilation will use the same strict semantics as context
+     *
+     * @param installer    code installer from context
+     * @param functionNode function node (in any available {@link CompilationState}) to compile
+     */
+    public Compiler(final CodeInstaller<Context> installer, final FunctionNode functionNode) {
+        this(installer.getOwner(), installer, functionNode, SEQUENCE_DEFAULT, installer.getOwner()._strict);
+    }
+
+    /**
+     * Constructor - compilation needs no installer, but uses a context
+     * Used in "compile only" scenarios
+     * @param context a context
+     * @param functionNode functionNode to compile
+     */
+    public Compiler(final Context context, final FunctionNode functionNode) {
+        this(context, null, functionNode, SEQUENCE_DEFAULT, context._strict);
+    }
+
+    /**
+     * Execute the compilation this Compiler was created with
+     * @return true if compilation succeeds.
+     */
+    public boolean compile() {
+        for (final String reservedName : RESERVED_NAMES) {
+            functionNode.uniqueName(reservedName);
+        }
+
+        for (final CompilationPhase phase : sequence) {
+            LOG.info("Entering compile phase " + phase + " for function '" + functionNode.getName() + "'");
+            if (phase.isApplicable(functionNode)) {
+                if (!phase.apply(this, functionNode)) { //TODO exceptions, error logging
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Install compiled classes into a given loader
+     * @return root script class - if there are several compile units they will also be installed
+     */
+    public Class<?> install() {
+        Class<?> rootClass = null;
+
+        for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
+            final String     className = entry.getKey();
+            LOG.info("Installing class " + className);
+
+            final byte[]     code  = entry.getValue();
+            final Class<?>   clazz = installer.install(Compiler.binaryName(className), code);
+
+            if (rootClass == null && firstCompileUnitName().equals(className)) {
+                rootClass = clazz;
+            }
+
+            try {
+                //use reflection to write source and constants table to installed classes
+                clazz.getField(SOURCE.tag()).set(null, getSource());
+                clazz.getField(CONSTANTS.tag()).set(null, getConstantData().toArray());
+            } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+        }
+
+        LOG.info("Root class: " + rootClass);
+        return rootClass;
+    }
+
+    Set<CompileUnit> getCompileUnits() {
+        return compileUnits;
+    }
+
+    boolean getStrictMode() {
+        return strict;
+    }
+
+    void setStrictMode(final boolean strict) {
+        this.strict = strict;
+    }
+
+    FunctionNode getFunctionNode() {
+        return functionNode;
+    }
+
+    ConstantData getConstantData() {
+        return constantData;
+    }
+
+    CodeInstaller<Context> getCodeInstaller() {
+        return installer;
+    }
+
+    Source getSource() {
+        return functionNode.getSource();
+    }
+
+    void addClass(final String name, final byte[] code) {
+        bytecode.put(name, code);
+    }
+
+    Context getContext() {
+        return this.context;
+    }
+
+    private static String safeSourceName(final Source source) {
+        String baseName = new File(source.getName()).getName();
+
+        final int index = baseName.lastIndexOf(".js");
+        if (index != -1) {
+            baseName = baseName.substring(0, index);
+        }
+
+        baseName = baseName.replace('.', '_').replace('-', '_');
+        final String mangled = NameCodec.encode(baseName);
+
+        return mangled != null ? mangled : baseName;
+    }
+
+    private int nextCompileUnitIndex() {
+        return compileUnits.size() + 1;
+    }
+
+    String firstCompileUnitName() {
+        return SCRIPTS_PACKAGE + '/' + scriptName;
+    }
+
+    private String nextCompileUnitName() {
+        return firstCompileUnitName() + '$' + nextCompileUnitIndex();
+    }
+
+    CompileUnit addCompileUnit(final long initialWeight) {
+        return addCompileUnit(nextCompileUnitName(), initialWeight);
+    }
+
+    CompileUnit addCompileUnit(final String unitClassName) {
+        return addCompileUnit(unitClassName, 0L);
+    }
+
+    private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) {
+        final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight);
+        compileUnits.add(compileUnit);
+        LOG.info("Added compile unit " + compileUnit);
+        return compileUnit;
+    }
+
+    private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) {
+        final ClassEmitter classEmitter = new ClassEmitter(context, functionNode.getSource().getName(), unitClassName, strict);
+        final CompileUnit  compileUnit  = new CompileUnit(unitClassName, classEmitter, initialWeight);
+
+        classEmitter.begin();
+
+        final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE));
+        initMethod.begin();
+        initMethod.load(Type.OBJECT, 0);
+        initMethod.newInstance(jdk.nashorn.internal.scripts.JS$.class);
+        initMethod.returnVoid();
+        initMethod.end();
+
+        return compileUnit;
+    }
+
+    CompileUnit findUnit(final long weight) {
+        for (final CompileUnit unit : compileUnits) {
+            if (unit.canHold(weight)) {
+                unit.addWeight(weight);
+                return unit;
+            }
+        }
+
+        return addCompileUnit(weight);
+    }
+
+    /**
+     * Convert a package/class name to a binary name.
+     *
+     * @param name Package/class name.
+     * @return Binary name.
+     */
+    public static String binaryName(final String name) {
+        return name.replace('/', '.');
+    }
 
     /**
      * Should we use integers for arithmetic operations as well?
@@ -164,573 +417,27 @@
 
     static {
         USE_INT_ARITH  =  Options.getBooleanProperty("nashorn.compiler.intarithmetic");
-
         assert !USE_INT_ARITH : "Integer arithmetic is not enabled";
     }
 
-    /**
-     * Factory method for compiler that should compile from source to bytecode
-     *
-     * @param source  the source
-     * @param context context
-     *
-     * @return compiler instance
-     */
-    public static Compiler compiler(final Source source, final Context context) {
-        return Compiler.compiler(source, context, context.getErrorManager(), context._strict);
-    }
-
-    /**
-     * Factory method to get a compiler that goes from from source to bytecode
-     *
-     * @param source  source code
-     * @param context context
-     * @param errors  error manager
-     * @param strict  compilation in strict mode?
-     *
-     * @return compiler instance
-     */
-    public static Compiler compiler(final Source source, final Context context, final ErrorManager errors, final boolean strict) {
-        return new Compiler(source, context, errors, strict);
-    }
-
-    /**
-     * Factory method to get a compiler that goes from FunctionNode (parsed) to bytecode
-     * Requires previous compiler for state
-     *
-     * @param compiler primordial compiler
-     * @param functionNode functionNode to compile
-     *
-     * @return compiler
-     */
-    public static Compiler compiler(final Compiler compiler, final FunctionNode functionNode) {
-        assert false : "lazy jit - not implemented";
-        final Compiler newCompiler = new Compiler(compiler);
-        newCompiler.state.add(State.PARSED);
-        newCompiler.functionNode = functionNode;
-        newCompiler.isLazy = true;
-        return compiler;
-    }
-
-    private Compiler(final Compiler compiler) {
-        this(compiler.source, compiler.context, compiler.errors, compiler.strict);
-    }
-
-    /**
-     * Constructor
-     *
-     * @param source  the source to compile
-     * @param context context
-     * @param errors  error manager
-     * @param strict  compile in strict mode
-     */
-    private Compiler(final Source source, final Context context, final ErrorManager errors, final boolean strict) {
-        this.source       = source;
-        this.context      = context;
-        this.errors       = errors;
-        this.strict       = strict;
-        this.namespace    = new Namespace(context.getNamespace());
-        this.compileUnits = new HashSet<>();
-        this.constantData = new ConstantData();
-        this.state        = EnumSet.of(State.INITIALIZED);
-        this.dumpClass    = context._compile_only && context._dest_dir != null;
-    }
-
-    private String scriptsPackageName() {
-        return dumpClass ? "" : (SCRIPTS_PACKAGE + '/');
-    }
-
-    private int nextCompileUnitIndex() {
-        return compileUnits.size() + 1;
-    }
-
-    private String firstCompileUnitName() {
-        return scriptsPackageName() + scriptName;
-    }
-
-    private String nextCompileUnitName() {
-        return firstCompileUnitName() + '$' + nextCompileUnitIndex();
-    }
-
-    private CompileUnit addCompileUnit(final long initialWeight) {
-        return addCompileUnit(nextCompileUnitName(), initialWeight);
-    }
-
-    private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) {
-        final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight);
-        compileUnits.add(compileUnit);
-        LOG.info("Added compile unit " + compileUnit);
-        return compileUnit;
-    }
-
-    private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) {
-        final ClassEmitter classEmitter = new ClassEmitter(this, unitClassName, strict);
-        final CompileUnit  compileUnit  = new CompileUnit(unitClassName, classEmitter, initialWeight);
-
-        classEmitter.begin();
-
-        final MethodEmitter initMethod = classEmitter.init(EnumSet.of(Flag.PRIVATE));
-        initMethod.begin();
-        initMethod.load(Type.OBJECT, 0);
-        initMethod.newInstance(jdk.nashorn.internal.scripts.JS$.class);
-        initMethod.returnVoid();
-        initMethod.end();
-
-        return compileUnit;
-    }
-
-    /**
-     * Perform compilation
-     *
-     * @return true if successful, false otherwise - if false check the error manager
-     */
-    public boolean compile() {
-        assert state.contains(State.INITIALIZED);
-
-        /** do we need to parse source? */
-        if (!state.contains(State.PARSED)) {
-            LOG.info("Parsing '" + source + "'");
-
-            assert this.functionNode == null;
-            this.functionNode = new Parser(this, strict).parse(RUN_SCRIPT.tag());
-
-            state.add(State.PARSED);
-            debugPrintParse();
-
-            if (errors.hasErrors() || context._parse_only) {
-                return false;
-            }
-
-            assert !isLazy;
-            //tag lazy nodes for later code generation and trampolines
-            functionNode.accept(new NodeVisitor() {
+    static {
+        if (TIME_COMPILATION) {
+            Runtime.getRuntime().addShutdownHook(new Thread() {
                 @Override
-                public Node enter(final FunctionNode node) {
-                    if (LAZY_JIT) {
-                        node.setIsLazy(!node.isScript());
+                public void run() {
+                    for (final CompilationPhase phase : CompilationPhase.values()) {
+                        final StringBuilder sb = new StringBuilder();
+                        sb.append(phase);
+                        while (sb.length() < 32) {
+                            sb.append(' ');
+                        }
+                        sb.append(CompilationPhase.getAccumulatedTime(phase));
+                        sb.append(' ');
+                        sb.append(" ms");
+                        System.err.println(sb.toString()); //Context err is gone by shutdown TODO
                     }
-                    return node;
-                }
-            });
-        } else {
-            assert isLazy;
-            functionNode.accept(new NodeVisitor() {
-                @Override
-                public Node enter(final FunctionNode node) {
-                    node.setIsLazy(false);
-                    return null; //TODO do we want to do this recursively? then return "node" instead
                 }
             });
         }
-
-        assert functionNode != null;
-        final boolean oldStrict = strict;
-
-        try {
-            strict |= functionNode.isStrictMode();
-
-            /*
-             * These are the compile phases:
-             *
-             * Constant folding pass
-             *   Simple constant folding that will make elementary constructs go away
-             *
-             * 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.
-             *
-             * Attr
-             *   Assign symbols and types to all nodes.
-             *
-             * Splitter
-             *   Split the AST into several compile units based on a size heuristic
-             *   Splitter needs attributed AST for weight calculations (e.g. is
-             *   a + b a ScriptRuntime.ADD with call overhead or a dadd with much
-             *   less). Split IR can lead to scope information being changed.
-             *
-             * Contract: all variables must have slot assignments and scope assignments
-             * before lowering.
-             *
-             * FinalizeTypes
-             *   This pass finalizes the types for nodes. If Attr created wider types than
-             *   known during the first pass, convert nodes are inserted or access nodes
-             *   are specialized where scope accesses.
-             *
-             *   Runtime nodes may be removed and primitivized or reintroduced depending
-             *   on information that was established in Attr.
-             *
-             * CodeGeneration
-             *   Emit bytecode
-             *
-             */
-
-            debugPrintAST();
-
-            if (!state.contains(State.FINALIZED)) {
-                LOG.info("Folding constants in '" + functionNode.getName() + "'");
-                functionNode.accept(new FoldConstants());
-                state.add(State.CONSTANT_FOLDED);
-
-                LOG.info("Lowering '" + functionNode.getName() + "'");
-                functionNode.accept(new Lower(this));
-                state.add(State.LOWERED);
-                debugPrintAST();
-
-                LOG.info("Attributing types '" + functionNode.getName() + "'");
-                functionNode.accept(new Attr(this));
-                state.add(State.ATTR);
-
-                this.scriptName = computeNames();
-
-                // Main script code always goes to this compile unit. Note that since we start this with zero weight
-                // and add script code last this class may end up slightly larger than others, but reserving one class
-                // just for the main script seems wasteful.
-                final CompileUnit scriptCompileUnit = addCompileUnit(firstCompileUnitName(), 0L);
-                LOG.info("Splitting '" + functionNode.getName() + "'");
-                new Splitter(this, functionNode, scriptCompileUnit).split();
-                state.add(State.SPLIT);
-                assert functionNode.getCompileUnit() == scriptCompileUnit;
-
-                assert strict == functionNode.isStrictMode() : "strict == " + strict + " but functionNode == " + functionNode.isStrictMode();
-                if (functionNode.isStrictMode()) {
-                    strict = true;
-                }
-
-                LOG.info("Finalizing types for '" + functionNode.getName() + "'");
-                functionNode.accept(new FinalizeTypes(this));
-                state.add(State.FINALIZED);
-
-                // print ast and parse if --print-lower-ast and/or --print-lower-parse are selected
-                debugPrintAST();
-                debugPrintParse();
-
-                if (errors.hasErrors()) {
-                    return false;
-                }
-            }
-
-            try {
-                LOG.info("Emitting bytecode for '" + functionNode.getName() + "'");
-                final CodeGenerator codegen = new CodeGenerator(this);
-                functionNode.accept(codegen);
-                codegen.generateScopeCalls();
-            } catch (final VerifyError e) {
-                if (context._verify_code || context._print_code) {
-                    context.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage());
-                    if (context._dump_on_error) {
-                        e.printStackTrace(context.getErr());
-                    }
-                } else {
-                    throw e;
-                }
-            }
-
-            state.add(State.EMITTED);
-
-            code = new TreeMap<>();
-            for (final CompileUnit compileUnit : compileUnits) {
-                final ClassEmitter classEmitter = compileUnit.getClassEmitter();
-                classEmitter.end();
-
-                if (!errors.hasErrors()) {
-                    final byte[] bytecode = classEmitter.toByteArray();
-                    if (bytecode != null) {
-                        code.put(compileUnit.getUnitClassName(), bytecode);
-                        debugDisassemble();
-                        debugVerify();
-                    }
-               }
-            }
-
-            if (code.isEmpty()) {
-                return false;
-            }
-
-            try {
-                dumpClassFiles();
-            } catch (final IOException e) {
-                throw new RuntimeException(e);
-            }
-
-            return true;
-        } finally {
-            strict = oldStrict;
-            LOG.info("Done with '" + functionNode.getName() + "'");
-        }
     }
-
-    /**
-     * Install compiled classes into a given loader
-     * @param installer that takes the generated classes and puts them in the system
-     * @return root script class - if there are several compile units they will also be installed
-     */
-    public Class<?> install(final CodeInstaller installer) {
-        assert state.contains(State.EMITTED);
-        assert scriptName != null;
-
-        Class<?> rootClass = null;
-
-        for (final Entry<String, byte[]> entry : code.entrySet()) {
-            final String     className = entry.getKey();
-            LOG.info("Installing class " + className);
-
-            final byte[]     bytecode  = entry.getValue();
-            final Class<?>   clazz     = installer.install(Compiler.binaryName(className), bytecode);
-
-            if (rootClass == null && firstCompileUnitName().equals(className)) {
-                rootClass = clazz;
-            }
-
-            try {
-                //use reflection to write source and constants table to installed classes
-                clazz.getField(SOURCE.tag()).set(null, source);
-                clazz.getField(CONSTANTS.tag()).set(null, constantData.toArray());
-            } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
-                throw new RuntimeException(e);
-            }
-        }
-
-        LOG.info("Root class: " + rootClass);
-
-        return rootClass;
-    }
-
-    /**
-     * Find a unit that will hold a node of the specified weight.
-     *
-     * @param weight Weight of a node
-     * @return Unit to hold node.
-     */
-    CompileUnit findUnit(final long weight) {
-        for (final CompileUnit unit : compileUnits) {
-            if (unit.canHold(weight)) {
-                unit.addWeight(weight);
-                return unit;
-            }
-        }
-
-        return addCompileUnit(weight);
-    }
-
-    /**
-     * Generate a uniqueName name. Public as {@link Parser} is using this to
-     * create symbols in a different package
-     *
-     * @param  name to base unique name on
-     * @return unique name
-     */
-    public String uniqueName(final String name) {
-        return namespace.uniqueName(name);
-    }
-
-    /**
-     * Internal function to compute reserved names and base names for class to
-     * be generated
-     *
-     * @return scriptName
-     */
-    private String computeNames() {
-        // Reserve internally used names.
-        addReservedNames();
-
-        if (dumpClass) {
-            // get source file name and remove ".js"
-            final String baseName = getSource().getName();
-            final int    index    = baseName.lastIndexOf(".js");
-            if (index != -1) {
-                return baseName.substring(0, index);
-            }
-            return baseName;
-        }
-
-        return namespace.getParent().uniqueName(
-            DEFAULT_SCRIPT_NAME.tag() +
-            '$' +
-            safeSourceName(source) +
-            (isLazy ? CompilerConstants.LAZY.tag() : "")
-        );
-    }
-
-    private static String safeSourceName(final Source source) {
-        String baseName = new File(source.getName()).getName();
-        final int index = baseName.lastIndexOf(".js");
-        if (index != -1) {
-            baseName = baseName.substring(0, index);
-        }
-
-        baseName = baseName.replace('.', '_').replace('-', '_');
-        final String mangled = NameCodec.encode(baseName);
-
-        baseName = mangled != null ? mangled : baseName;
-        return baseName;
-    }
-
-    static void verify(final Context context, final byte[] code) {
-        context.verify(code);
-    }
-
-    /**
-     * Fill in the namespace with internally reserved names.
-     */
-    private void addReservedNames() {
-        namespace.uniqueName(SCOPE.tag());
-        namespace.uniqueName(THIS.tag());
-    }
-
-    /**
-     * Get the constant data for this Compiler
-     *
-     * @return the constant data
-     */
-    public ConstantData getConstantData() {
-        return constantData;
-    }
-
-    /**
-     * Get the Context used for Compilation
-     * @see Context
-     * @return the context
-     */
-    public Context getContext() {
-        return context;
-    }
-
-    /**
-     * Get the Source being compiled
-     * @see Source
-     * @return the source
-     */
-    public Source getSource() {
-        return source;
-    }
-
-    /**
-     * Get the error manager used for this compiler
-     * @return the error manager
-     */
-    public ErrorManager getErrors() {
-        return errors;
-    }
-
-    /**
-     * Get the namespace used for this Compiler
-     * @see Namespace
-     * @return the namespace
-     */
-    public Namespace getNamespace() {
-        return namespace;
-    }
-
-    /*
-     * Debugging
-     */
-
-    /**
-     * Print the AST before or after lowering, see --print-ast, --print-lower-ast
-     */
-    private void debugPrintAST() {
-        assert functionNode != null;
-        if (context._print_lower_ast && state.contains(State.LOWERED) ||
-            context._print_ast && !state.contains(State.LOWERED)) {
-            context.getErr().println(new ASTWriter(functionNode));
-        }
-    }
-
-    /**
-     * Print the parsed code before or after lowering, see --print-parse, --print-lower-parse
-     */
-    private boolean debugPrintParse() {
-        if (errors.hasErrors()) {
-            return false;
-        }
-
-        assert functionNode != null;
-
-        if (context._print_lower_parse && state.contains(State.LOWERED) ||
-             context._print_parse && !state.contains(State.LOWERED)) {
-            final PrintVisitor pv = new PrintVisitor();
-            functionNode.accept(pv);
-            context.getErr().print(pv);
-            context.getErr().flush();
-        }
-
-        return true;
-    }
-
-    private void debugDisassemble() {
-        assert code != null;
-        if (context._print_code) {
-            for (final Map.Entry<String, byte[]> entry : code.entrySet()) {
-                context.getErr().println("CLASS: " + entry.getKey());
-                context.getErr().println();
-                ClassEmitter.disassemble(context, entry.getValue());
-                context.getErr().println("======");
-            }
-        }
-    }
-
-    private void debugVerify() {
-        if (context._verify_code) {
-            for (final Map.Entry<String, byte[]> entry : code.entrySet()) {
-                Compiler.verify(context, entry.getValue());
-            }
-        }
-    }
-
-    /**
-     * Implements the "-d" option - dump class files from script to specified output directory
-     *
-     * @throws IOException if classes cannot be written
-     */
-    private void dumpClassFiles() throws IOException {
-        if (context._dest_dir == null) {
-            return;
-        }
-
-        assert code != null;
-
-        for (final Entry<String, byte[]> entry : code.entrySet()) {
-            final String className = entry.getKey();
-            final String fileName  = className.replace('.', File.separatorChar) + ".class";
-            final int    index     = fileName.lastIndexOf(File.separatorChar);
-
-            if (index != -1) {
-                final File dir = new File(fileName.substring(0, index));
-                if (!dir.exists() && !dir.mkdirs()) {
-                    throw new IOException(ECMAErrors.getMessage("io.error.cant.write", dir.toString()));
-                }
-            }
-
-            final byte[] bytecode = entry.getValue();
-            final File outFile = new File(context._dest_dir, fileName);
-            try (final FileOutputStream fos = new FileOutputStream(outFile)) {
-                fos.write(bytecode);
-            }
-        }
-    }
-
-    /**
-     * Convert a package/class name to a binary name.
-     *
-     * @param name Package/class name.
-     * @return Binary name.
-     */
-    public static String binaryName(final String name) {
-        return name.replace('/', '.');
-    }
-
-    /**
-     * Convert a binary name to a package/class name.
-     *
-     * @param name Binary name.
-     * @return Package/class name.
-     */
-    public static String pathName(final String name) {
-        return name.replace('.', '/');
-    }
-
-
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java b/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java
index 0862f1e..631cdb3 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ConstantData.java
@@ -35,7 +35,7 @@
  * Manages constants needed by code generation.  Objects are maintained in an
  * interning maps to remove duplicates.
  */
-public class ConstantData {
+class ConstantData {
     /** Constant table. */
     final List<Object> constants;
 
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java b/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
index b9deb64..ffa4a55 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
@@ -66,7 +66,6 @@
 import jdk.nashorn.internal.runtime.Debug;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.JSType;
-import jdk.nashorn.internal.runtime.Source;
 
 /**
  * Lower to more primitive operations. After lowering, an AST has symbols and
@@ -83,18 +82,9 @@
 
 final class FinalizeTypes extends NodeOperatorVisitor {
 
-    /** Current source. */
-    private final Source source;
-
     private static final DebugLogger LOG = new DebugLogger("finalize");
 
-    /**
-     * Constructor.
-     *
-     * @param compiler the compiler
-     */
-    FinalizeTypes(final Compiler compiler) {
-        this.source = compiler.getSource();
+    FinalizeTypes() {
     }
 
     @Override
@@ -424,16 +414,20 @@
 
     @Override
     public Node enter(final FunctionNode functionNode) {
+        if (functionNode.isLazy()) {
+            return null;
+        }
+
         // If the function doesn't need a callee, we ensure its __callee__ symbol doesn't get a slot. We can't do
         // this earlier, as access to scoped variables, self symbol, etc. in previous phases can all trigger the
         // need for the callee.
-        if(!functionNode.needsCallee()) {
+        if (!functionNode.needsCallee()) {
             functionNode.getCalleeNode().getSymbol().setNeedsSlot(false);
         }
         // Similar reasoning applies to __scope__ symbol: if the function doesn't need either parent scope or its
         // own scope, we ensure it doesn't get a slot, but we can't determine whether it needs a scope earlier than
         // this phase.
-        if(!(functionNode.needsScope() || functionNode.needsParentScope())) {
+        if (!(functionNode.needsScope() || functionNode.needsParentScope())) {
             functionNode.getScopeNode().getSymbol().setNeedsSlot(false);
         }
 
@@ -443,8 +437,7 @@
 
     @Override
     public Node leave(final IfNode ifNode) {
-        final Node test = convert(ifNode.getTest(), Type.BOOLEAN);
-        ifNode.setTest(test);
+        ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN));
         return ifNode;
     }
 
@@ -518,6 +511,7 @@
 
     @Override
     public Node leave(final VarNode varNode) {
+
         final Node rhs = varNode.getInit();
         if (rhs != null) {
             Type destType = specialize(varNode);
@@ -823,7 +817,7 @@
                 setTypeOverride(node, to);
                 return resultNode;
             }
-            resultNode = new UnaryNode(source, Token.recast(node.getToken(), TokenType.CONVERT), node);
+            resultNode = new UnaryNode(node.getSource(), Token.recast(node.getToken(), TokenType.CONVERT), node);
         }
 
         LOG.info("CONVERT('" + node + "', " + to + ") => '" + resultNode + "'");
@@ -836,11 +830,11 @@
         return resultNode;
     }
 
-    private Node discard(final Node node) {
+    private static Node discard(final Node node) {
         node.setDiscard(true);
 
         if (node.getSymbol() != null) {
-            final Node discard = new UnaryNode(source, Token.recast(node.getToken(), TokenType.DISCARD), node);
+            final Node discard = new UnaryNode(node.getSource(), Token.recast(node.getToken(), TokenType.DISCARD), node);
             //discard never has a symbol in the discard node - then it would be a nop
             discard.copyTerminalFlags(node);
             return discard;
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java b/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
index a7e7aa2..4ea53a0 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
@@ -44,10 +44,13 @@
 /**
  * Simple constant folding pass, executed before IR is starting to be lowered.
  */
-public class FoldConstants extends NodeVisitor {
+final class FoldConstants extends NodeVisitor {
 
     private static final DebugLogger LOG = new DebugLogger("fold");
 
+    FoldConstants() {
+    }
+
     @Override
     public Node leave(final UnaryNode unaryNode) {
         final LiteralNode<?> literalNode = new UnaryNodeConstantEvaluator(unaryNode).eval();
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
index 7bb7747..26d040c 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
@@ -25,6 +25,8 @@
 
 package jdk.nashorn.internal.codegen;
 
+import java.lang.invoke.MethodType;
+import java.util.ArrayList;
 import java.util.List;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.FunctionNode;
@@ -32,6 +34,8 @@
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
 
+import static jdk.nashorn.internal.runtime.linker.Lookup.MH;
+
 /**
  * Class that generates function signatures for dynamic calls
  */
@@ -46,6 +50,9 @@
     /** valid Java descriptor string for function */
     private final String descriptor;
 
+    /** {@link MethodType} for function */
+    private final MethodType methodType;
+
     /**
      * Constructor
      *
@@ -126,8 +133,31 @@
             assert false : "isVarArgs cannot be false when argTypes are null";
         }
 
-        returnType = retType;
-        descriptor = Type.getMethodDescriptor(returnType, paramTypes);
+        this.returnType = retType;
+        this.descriptor = Type.getMethodDescriptor(returnType, paramTypes);
+
+        final List<Class<?>> paramTypeList = new ArrayList<>();
+        for (final Type paramType : paramTypes) {
+            paramTypeList.add(paramType.getTypeClass());
+        }
+
+        this.methodType = MH.type(returnType.getTypeClass(), paramTypeList.toArray(new Class[paramTypes.length]));
+    }
+
+    /**
+     * Create a function signature given a function node, using as much
+     * type information for parameters and return types that is availabe
+     *
+     * @param functionNode the function node
+     */
+    public FunctionSignature(final FunctionNode functionNode) {
+        this(
+            true,
+            functionNode.needsCallee(),
+            functionNode.getReturnType(),
+            (functionNode.isVarArg() && !functionNode.isScript()) ?
+                null :
+                functionNode.getParameters());
     }
 
     /**
@@ -165,21 +195,14 @@
     }
 
     /**
-     * Returns the generic signature of the function being compiled.
-     *
-     * @param functionNode function being compiled.
-     * @return function signature.
+     * Return the {@link MethodType} for this function signature
+     * @return the method type
      */
-    public static String functionSignature(final FunctionNode functionNode) {
-        return new FunctionSignature(
-            true,
-            functionNode.needsCallee(),
-            functionNode.getReturnType(),
-            (functionNode.isVarArg() && !functionNode.isScript()) ?
-                null :
-                functionNode.getParameters()).toString();
+    public MethodType getMethodType() {
+        return methodType;
     }
 
+
     private static Type[] objectArgs(final int nArgs) {
         final Type[] array = new Type[nArgs];
         for (int i = 0; i < nArgs; i++) {
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
index 712e076..5f00268 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
@@ -89,10 +89,6 @@
 
 final class Lower extends NodeOperatorVisitor {
 
-    private final Compiler compiler;
-
-    private final Source source;
-
     /**
      * Nesting level stack. Currently just used for loops to avoid the problem
      * with terminal bodies that end with throw/return but still do continues to
@@ -111,9 +107,7 @@
      *
      * @param compiler the compiler
      */
-    Lower(final Compiler compiler) {
-        this.compiler   = compiler;
-        this.source     = compiler.getSource();
+    Lower() {
         this.nesting    = new ArrayDeque<>();
         this.statements = new ArrayList<>();
     }
@@ -204,7 +198,7 @@
         if (getCurrentFunctionNode().isScript()) {
             if (!(expr instanceof Block)) {
                 if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) {
-                    executeNode.setExpression(new BinaryNode(source, Token.recast(executeNode.getToken(), TokenType.ASSIGN),
+                    executeNode.setExpression(new BinaryNode(executeNode.getSource(), Token.recast(executeNode.getToken(), TokenType.ASSIGN),
                             getCurrentFunctionNode().getResultNode(),
                             expr));
                 }
@@ -254,6 +248,11 @@
     public Node enter(final FunctionNode functionNode) {
         LOG.info("START FunctionNode: " + functionNode.getName());
 
+        if (functionNode.isLazy()) {
+            LOG.info("LAZY: " + functionNode.getName());
+            return null;
+        }
+
         initFunctionNode(functionNode);
 
         Node initialEvalResult = LiteralNode.newInstance(functionNode, ScriptRuntime.UNDEFINED);
@@ -299,7 +298,7 @@
             }
 
             if (functionNode.isScript()) {
-                new ExecuteNode(source, functionNode.getFirstToken(), functionNode.getFinish(), initialEvalResult).accept(this);
+                new ExecuteNode(functionNode.getSource(), functionNode.getFirstToken(), functionNode.getFinish(), initialEvalResult).accept(this);
             }
 
             //do the statements - this fills the block with code
@@ -379,6 +378,8 @@
         if (tryNode != null) {
             //we are inside a try block - we don't necessarily have a result node yet. attr will do that.
             if (expr != null) {
+                final Source source = getCurrentFunctionNode().getSource();
+
                 //we need to evaluate the result of the return in case it is complex while
                 //still in the try block, store it in a result value and return it afterwards
                 final long token        = returnNode.getToken();
@@ -518,6 +519,7 @@
          * finally_body_inlined marked (*) will fix it before rethrowing
          * whatever problem there was for identical semantic.
          */
+        final Source source = getCurrentFunctionNode().getSource();
 
         // if try node does not contain a catch we can skip creation of a new
         // try node and just append our synthetic catch to the existing try node.
@@ -559,7 +561,7 @@
         final CatchNode catchAllNode;
         final IdentNode exception;
 
-        exception    = new IdentNode(source, token, finish, compiler.uniqueName("catch_all"));
+        exception    = new IdentNode(source, token, finish, getCurrentFunctionNode().uniqueName("catch_all"));
         catchAllNode = new CatchNode(source, token, finish, new IdentNode(exception), null, catchBody);
         catchAllNode.setIsSyntheticRethrow();
 
@@ -632,7 +634,7 @@
             if (whileNode instanceof DoWhileNode) {
                 setTerminal(whileNode, true);
             } else if (conservativeAlwaysTrue(test)) {
-                node = new ForNode(source, whileNode.getToken(), whileNode.getFinish());
+                node = new ForNode(whileNode.getSource(), whileNode.getToken(), whileNode.getFinish());
                 ((ForNode)node).setBody(body);
                 ((ForNode)node).accept(this);
                 setTerminal(node, !escapes);
@@ -799,7 +801,7 @@
         }
 
         //create a return statement
-        final Node returnNode = new ReturnNode(source, functionNode.getLastToken(), functionNode.getFinish(), resultNode, null);
+        final Node returnNode = new ReturnNode(functionNode.getSource(), functionNode.getLastToken(), functionNode.getFinish(), resultNode, null);
         returnNode.accept(this);
     }
 
@@ -897,7 +899,7 @@
             finallyBody = (Block)finallyBody.clone();
             final boolean hasTerminalFlags = finallyBody.hasTerminalFlags();
 
-            new ExecuteNode(source, finallyBody.getToken(), finallyBody.getFinish(), finallyBody).accept(this);
+            new ExecuteNode(finallyBody.getSource(), finallyBody.getToken(), finallyBody.getFinish(), finallyBody).accept(this);
 
             if (hasTerminalFlags) {
                 getCurrentBlock().copyTerminalFlags(finallyBody);
@@ -951,17 +953,18 @@
      * TODO : only create those that are needed.
      * TODO : make sure slot numbering is not hardcoded in {@link CompilerConstants} - now creation order is significant
      */
-    private void initFunctionNode(final FunctionNode functionNode) {
-        final long token  = functionNode.getToken();
-        final int  finish = functionNode.getFinish();
+    private static void initFunctionNode(final FunctionNode functionNode) {
+        final Source source = functionNode.getSource();
+        final long token    = functionNode.getToken();
+        final int  finish   = functionNode.getFinish();
 
         functionNode.setThisNode(new IdentNode(source, token, finish, THIS.tag()));
         functionNode.setScopeNode(new IdentNode(source, token, finish, SCOPE.tag()));
         functionNode.setResultNode(new IdentNode(source, token, finish, SCRIPT_RETURN.tag()));
         functionNode.setCalleeNode(new IdentNode(source, token, finish, CALLEE.tag()));
-        if(functionNode.isVarArg()) {
+        if (functionNode.isVarArg()) {
             functionNode.setVarArgsNode(new IdentNode(source, token, finish, VARARGS.tag()));
-            if(functionNode.needsArguments()) {
+            if (functionNode.needsArguments()) {
                 functionNode.setArgumentsNode(new IdentNode(source, token, finish, ARGUMENTS.tag()));
             }
         }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/SharedScopeCall.java b/nashorn/src/jdk/nashorn/internal/codegen/SharedScopeCall.java
index 70be108..f8e26996 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/SharedScopeCall.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/SharedScopeCall.java
@@ -106,11 +106,11 @@
     /**
      * Set the compile unit and method name.
      * @param compileUnit the compile unit
-     * @param compiler the compiler to generate a unique method name
+     * @param methodName the method name
      */
-    protected void setClassAndName(final CompileUnit compileUnit, final Compiler compiler) {
+    protected void setClassAndName(final CompileUnit compileUnit, final String methodName) {
         this.compileUnit = compileUnit;
-        this.methodName = compiler.uniqueName("scopeCall");
+        this.methodName  = methodName;
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
index abaab42..d95fd4b 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
@@ -49,13 +49,14 @@
 import jdk.nashorn.internal.ir.SwitchNode;
 import jdk.nashorn.internal.ir.WhileNode;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
+import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.Source;
 import jdk.nashorn.internal.runtime.options.Options;
 
 /**
  * Split the IR into smaller compile units.
  */
-public class Splitter extends NodeVisitor {
+final class Splitter extends NodeVisitor {
     /** Current compiler. */
     private final Compiler compiler;
 
@@ -63,7 +64,7 @@
     private final FunctionNode functionNode;
 
     /** Compile unit for the main script. */
-    private final CompileUnit scriptCompileUnit;
+    private final CompileUnit outermostCompileUnit;
 
     /** Cache for calculated block weights. */
     private final Map<Node, Long> weightCache = new HashMap<>();
@@ -71,27 +72,35 @@
     /** Weight threshold for when to start a split. */
     public static final long SPLIT_THRESHOLD = Options.getIntProperty("nashorn.compiler.splitter.threshold", 32 * 1024);
 
+    private static final DebugLogger LOG = Compiler.LOG;
+
     /**
      * Constructor.
      *
-     * @param compiler           the compiler
-     * @param functionNode       function node to split
-     * @param scriptCompileUnit  script compile unit
+     * @param compiler              the compiler
+     * @param functionNode          function node to split
+     * @param outermostCompileUnit  compile unit for outermost function, if non-lazy this is the script's compile unit
      */
-    public Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit scriptCompileUnit) {
-        this.compiler     = compiler;
-        this.functionNode = functionNode;
-        this.scriptCompileUnit = scriptCompileUnit;
+    public Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit outermostCompileUnit) {
+        this.compiler             = compiler;
+        this.functionNode         = functionNode;
+        this.outermostCompileUnit = outermostCompileUnit;
     }
 
     /**
      * Execute the split
      */
     void split() {
+        if (functionNode.isLazy()) {
+            LOG.info("Postponing split of '" + functionNode.getName() + "' as it's lazy");
+            return;
+        }
+        LOG.info("Initiating split of '" + functionNode.getName() + "'");
+
         long weight = WeighNodes.weigh(functionNode);
 
         if (weight >= SPLIT_THRESHOLD) {
-            Compiler.LOG.info("Splitting '" + functionNode.getName() + "' as its weight " + weight + " exceeds split threshold " + SPLIT_THRESHOLD);
+            LOG.info("Splitting '" + functionNode.getName() + "' as its weight " + weight + " exceeds split threshold " + SPLIT_THRESHOLD);
 
             functionNode.accept(this);
 
@@ -111,20 +120,18 @@
 
         assert functionNode.getCompileUnit() == null : "compile unit already set";
 
-        if (functionNode.isScript()) {
-            assert scriptCompileUnit != null : "script compile unit is null";
+        if (compiler.getFunctionNode() == functionNode) { //functionNode.isScript()) {
+            assert outermostCompileUnit != null : "outermost compile unit is null";
 
-            functionNode.setCompileUnit(scriptCompileUnit);
-            scriptCompileUnit.addWeight(weight + WeighNodes.FUNCTION_WEIGHT);
+            functionNode.setCompileUnit(outermostCompileUnit);
+            outermostCompileUnit.addWeight(weight + WeighNodes.FUNCTION_WEIGHT);
         } else {
             functionNode.setCompileUnit(findUnit(weight));
         }
 
         // Recursively split nested functions
-        final List<FunctionNode> functions = functionNode.getFunctions();
-
-        for (final FunctionNode function : functions) {
-            new Splitter(compiler, function, scriptCompileUnit).split();
+        for (final FunctionNode function : functionNode.getFunctions()) {
+            new Splitter(compiler, function, outermostCompileUnit).split();
         }
     }
 
@@ -192,7 +199,7 @@
         final Source source = parent.getSource();
         final long   token  = parent.getToken();
         final int    finish = parent.getFinish();
-        final String name   = compiler.uniqueName(SPLIT_PREFIX.tag());
+        final String name   = parent.getFunction().uniqueName(SPLIT_PREFIX.tag());
 
         final Block newBlock = new Block(source, token, finish, parent, functionNode);
         newBlock.setFrame(new Frame(parent.getFrame()));
@@ -284,6 +291,10 @@
 
     @Override
     public Node enter(final FunctionNode node) {
+        if (node.isLazy()) {
+            return null;
+        }
+
         final List<Node> statements = node.getStatements();
 
         for (final Node statement : statements) {
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
index 76bf619..006f1e2 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
@@ -66,7 +66,7 @@
  * Computes the "byte code" weight of an AST segment. This is used
  * for Splitting too large class files
  */
-public class WeighNodes extends NodeOperatorVisitor {
+final class WeighNodes extends NodeOperatorVisitor {
     /*
      * Weight constants.
      */
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java b/nashorn/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java
deleted file mode 100644
index 72ed43b..0000000
--- a/nashorn/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (c) 2010, 2013, 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.objects;
-
-import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.HANDLE_STATIC;
-import static jdk.nashorn.internal.codegen.Compiler.SCRIPTFUNCTION_IMPL_OBJECT;
-import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE;
-import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
-import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
-
-import java.lang.invoke.MethodHandle;
-import java.util.ArrayList;
-import java.util.EnumSet;
-
-import jdk.nashorn.internal.codegen.CodeGenerator;
-import jdk.nashorn.internal.codegen.FunctionSignature;
-import jdk.nashorn.internal.codegen.MethodEmitter;
-import jdk.nashorn.internal.ir.FunctionNode;
-import jdk.nashorn.internal.ir.Symbol;
-import jdk.nashorn.internal.runtime.PropertyMap;
-import jdk.nashorn.internal.runtime.ScriptFunctionData;
-import jdk.nashorn.internal.runtime.ScriptObject;
-
-/**
- * Analyze a function object's characteristics for appropriate code
- * generation. This generates code for the instantiation of ScriptFunctions.
- */
-public class FunctionObjectCreator extends ObjectCreator {
-
-    private final FunctionNode functionNode;
-
-    /**
-     * Constructor
-     *
-     * @param codegen      the code generator
-     * @param functionNode the function node to turn into a ScriptFunction implementation
-     */
-    public FunctionObjectCreator(final CodeGenerator codegen, final FunctionNode functionNode) {
-        super(codegen, new ArrayList<String>(), new ArrayList<Symbol>(), false, false);
-        this.functionNode = functionNode;
-    }
-
-    private void loadHandle(final MethodEmitter method, final String signature) {
-        method.loadHandle(functionNode.getCompileUnit().getUnitClassName(), functionNode.getName(), signature, EnumSet.of(HANDLE_STATIC)); // function
-    }
-
-    /**
-     * Emit code for creating the object
-     *
-     * @param method the method emitter
-     */
-    @Override
-    public void makeObject(final MethodEmitter method) {
-
-        final PropertyMap map = makeMap();
-        final String signature = new FunctionSignature(true, functionNode.needsCallee(), functionNode.getReturnType(), functionNode.isVarArg() ? null : functionNode.getParameters()).toString();
-        final ScriptFunctionData scriptFunctionData = new ScriptFunctionData(functionNode, map);
-
-        /*
-         * Instantiate the function object
-         */
-        method._new(SCRIPTFUNCTION_IMPL_OBJECT).dup();
-        codegen.loadConstant(scriptFunctionData);
-        loadHandle(method, signature);
-        if(functionNode.needsParentScope()) {
-            method.loadScope();
-        } else {
-            method.loadNull();
-        }
-        method.loadHandle(getClassName(), ALLOCATE.tag(), methodDescriptor(ScriptObject.class, PropertyMap.class), EnumSet.of(HANDLE_STATIC));
-
-        /*
-         * Invoke the constructor
-         */
-        method.invoke(constructorNoLookup(SCRIPTFUNCTION_IMPL_OBJECT, ScriptFunctionData.class, MethodHandle.class, ScriptObject.class, MethodHandle.class));
-
-    }
-}
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/objects/ObjectClassGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/objects/ObjectClassGenerator.java
index 3c69687..72f9181 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/objects/ObjectClassGenerator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/objects/ObjectClassGenerator.java
@@ -472,11 +472,11 @@
         final byte[] code = classEmitter.toByteArray();
 
         if (context != null && context._print_code) {
-            ClassEmitter.disassemble(context, code);
+            Context.getCurrentErr().println(ClassEmitter.disassemble(code));
         }
 
         if (context != null && context._verify_code) {
-            ClassEmitter.verify(context, code);
+            context.verify(code);
         }
 
         return code;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
index 6d2b3a1..958f7b7 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
@@ -32,15 +32,16 @@
 
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.EnumSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Stack;
+
 import jdk.nashorn.internal.codegen.CompileUnit;
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.Frame;
 import jdk.nashorn.internal.codegen.MethodEmitter;
 import jdk.nashorn.internal.codegen.Namespace;
-import jdk.nashorn.internal.codegen.Splitter;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.annotations.Ignore;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
@@ -67,6 +68,28 @@
         SETTER
     }
 
+    /** Compilation states available */
+    public enum CompilationState {
+        /** compiler is ready */
+        INITIALIZED,
+        /** method has been parsed */
+        PARSED,
+        /** method has been parsed */
+        PARSE_ERROR,
+        /** constant folding pass */
+        CONSTANT_FOLDED,
+        /** method has been lowered */
+        LOWERED,
+        /** method hass been attributed */
+        ATTR,
+        /** method has been split */
+        SPLIT,
+        /** method has had its types finalized */
+        FINALIZED,
+        /** method has been emitted to bytecode */
+        EMITTED
+    }
+
     /** External function identifier. */
     @Ignore
     private IdentNode ident;
@@ -147,6 +170,10 @@
     @Ignore
     private Node selfSymbolInit;
 
+    /** Current compilation state */
+    @Ignore
+    private final EnumSet<CompilationState> compilationState;
+
     /** Function flags. */
     private int flags;
 
@@ -200,16 +227,16 @@
     /**
      * Constructor
      *
-     * @param source   the source
-     * @param token    token
-     * @param finish   finish
-     * @param compiler the compiler
-     * @param parent   the parent block
-     * @param ident    the identifier
-     * @param name     the name of the function
+     * @param source    the source
+     * @param token     token
+     * @param finish    finish
+     * @param namespace the namespace
+     * @param parent    the parent block
+     * @param ident     the identifier
+     * @param name      the name of the function
      */
     @SuppressWarnings("LeakingThisInConstructor")
-    public FunctionNode(final Source source, final long token, final int finish, final Compiler compiler, final Block parent, final IdentNode ident, final String name) {
+    public FunctionNode(final Source source, final long token, final int finish, final Namespace namespace, final Block parent, final IdentNode ident, final String name) {
         super(source, token, finish, parent, null);
 
         this.ident             = ident;
@@ -219,7 +246,7 @@
         this.functions         = new ArrayList<>();
         this.firstToken        = token;
         this.lastToken         = token;
-        this.namespace         = new Namespace(compiler.getNamespace().getParent());
+        this.namespace         = namespace;
         this.labelStack        = new Stack<>();
         this.controlStack      = new Stack<>();
         this.declarations      = new ArrayList<>();
@@ -227,6 +254,7 @@
         // it as such a leak - this is a false positive as we're setting this into a field of the object being
         // constructed, so it can't be seen from other threads.
         this.function          = this;
+        this.compilationState  = EnumSet.of(CompilationState.INITIALIZED);
     }
 
     @SuppressWarnings("LeakingThisInConstructor")
@@ -269,6 +297,8 @@
         // it as such a leak - this is a false positive as we're setting this into a field of the object being
         // constructed, so it can't be seen from other threads.
         this.function = this;
+
+        this.compilationState = EnumSet.copyOf(functionNode.compilationState);
     }
 
     @Override
@@ -344,6 +374,41 @@
         return super.needsScope() || isScript();
     }
 
+    /**
+     * Check whether this FunctionNode has reached a give CompilationState.
+     *
+     * @param state the state to check for
+     * @return true of the node is in the given state
+     */
+    public boolean hasState(final EnumSet<CompilationState> state) {
+        return compilationState.equals(state);
+    }
+
+    /**
+     * Check whether the state of this FunctionNode contains a given compilation
+     * state.
+     *
+     * A node can be in many states at once, e.g. both lowered and initialized.
+     * To check for an exact state, use {FunctionNode{@link #hasState(EnumSet)}
+     *
+     * @param state state to check for
+     * @return true if state is present in the total compilation state of this FunctionNode
+     */
+    public boolean hasState(final CompilationState state) {
+        return compilationState.contains(state);
+    }
+
+    /**
+     * Add a state to the total CompilationState of this node, e.g. if
+     * FunctionNode has been lowered, the compiler will add
+     * {@code CompilationState#LOWERED} to the state vector
+     *
+     * @param state {@link CompilationState} to add
+     */
+    public void setState(final CompilationState state) {
+        compilationState.add(state);
+    }
+
     /*
      * Frame management.
      */
@@ -366,17 +431,6 @@
     }
 
     /**
-     * return a unique name in the scope of the function.
-     *
-     * @param base Base string.
-     *
-     * @return Unique name.
-     */
-    public String uniqueName(final String base) {
-        return namespace.uniqueName(base);
-    }
-
-    /**
      * Create a temporary variable to the current frame.
      *
      * @param currentFrame Frame to add to - defaults to current function frame
@@ -408,6 +462,15 @@
     }
 
     /**
+     * Create a unique name in the namespace of this FunctionNode
+     * @param base prefix for name
+     * @return base if no collision exists, otherwise a name prefix with base
+     */
+    public String uniqueName(final String base) {
+        return namespace.uniqueName(base);
+    }
+
+    /**
      * Add a new temporary variable to the current frame
      *
      * @param type Strong type of symbol
@@ -428,11 +491,11 @@
      */
     public Symbol newLiteral(final LiteralNode<?> literalNode) {
         final String uname = uniqueName(LITERAL_PREFIX.tag());
-        final Symbol sym = new Symbol(uname, IS_CONSTANT, literalNode.getType());
-        sym.setNode(literalNode);
-        literalNode.setSymbol(sym);
+        final Symbol symbol = new Symbol(uname, IS_CONSTANT, literalNode.getType());
+        symbol.setNode(literalNode);
+        literalNode.setSymbol(symbol);
 
-        return sym;
+        return symbol;
     }
 
     @Override
@@ -806,7 +869,6 @@
 
     /**
      * Checks if this function is a sub-function generated by splitting a larger one
-     * @see Splitter
      *
      * @return true if this function is split from a larger one
      */
@@ -816,7 +878,6 @@
 
     /**
      * Flag this function node as being a sub-function generated by the splitter
-     * @see Splitter
      */
     public void setIsSplit() {
         this.flags |= IS_SPLIT;
@@ -1149,7 +1210,6 @@
     /**
      * Get the compile unit used to compile this function
      * @see Compiler
-     * @see Splitter
      * @return the compile unit
      */
     public CompileUnit getCompileUnit() {
@@ -1159,7 +1219,6 @@
     /**
      * Reset the compile unit used to compile this function
      * @see Compiler
-     * @see Splitter
      * @param compileUnit the compile unit
      */
     public void setCompileUnit(final CompileUnit compileUnit) {
diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
index 103670d..79d9349 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
@@ -25,11 +25,8 @@
 
 package jdk.nashorn.internal.ir.debug;
 
-import java.security.AccessController;
-import java.security.PrivilegedAction;
 import java.util.Arrays;
 import java.util.List;
-import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.CompilerConstants;
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.BinaryNode;
@@ -87,8 +84,7 @@
      * @return JSON string representation of AST of the supplied code
      */
     public static String parse(final Context context, final String code, final String name, final boolean includeLoc) {
-        final Compiler     compiler   = Compiler.compiler(new Source(name, code), context, new Context.ThrowErrorManager(), context._strict);
-        final Parser       parser     = new Parser(compiler, context._strict);
+        final Parser       parser     = new Parser(context, new Source(name, code), new Context.ThrowErrorManager(), context._strict);
         final JSONWriter   jsonWriter = new JSONWriter(includeLoc);
         try {
             final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.tag());
diff --git a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
index 63cb5bd..9ce6fd0 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
@@ -70,7 +70,7 @@
     private FunctionNode currentFunctionNode;
 
     /** Current compile unit used for class generation. */
-    protected CompileUnit compileUnit;
+    private CompileUnit compileUnit;
 
     /**
      * Current method visitor used for method generation.
diff --git a/nashorn/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java b/nashorn/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java
index 2b86919..1e9e9d1 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/BoundScriptFunctionImpl.java
@@ -32,7 +32,7 @@
 
 /**
  * A {@code ScriptFunctionImpl} subclass for functions created using {@code Function.prototype.bind}. Such functions
- * must track their {@code [[TargetFunction]] property for purposes of correctly implementing {@code [[HasInstance]]};
+ * must track their {@code [[TargetFunction]]} property for purposes of correctly implementing {@code [[HasInstance]]};
  * see {@link ScriptFunction#isInstance(ScriptObject)}.
  */
 class BoundScriptFunctionImpl extends ScriptFunctionImpl {
diff --git a/nashorn/src/jdk/nashorn/internal/objects/Global.java b/nashorn/src/jdk/nashorn/internal/objects/Global.java
index f1993f7..66316d1 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/Global.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java
@@ -351,6 +351,8 @@
 
     /**
      * Constructor
+     *
+     * @param context the context
      */
     public Global(final Context context) {
         this.context = context;
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java b/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java
index 5fa845d..b63e2f5 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java
@@ -385,6 +385,7 @@
     public static Object extend(final Object self, final Object... types) {
         if(types == null || types.length == 0) {
             typeError("extend.expects.at.least.one.argument");
+            throw new AssertionError(); //circumvent warning for types == null below
         }
         final Class<?>[] stypes = new Class<?>[types.length];
         try {
diff --git a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java
index 3ca57ee..5f559fb 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java
@@ -28,7 +28,7 @@
 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
 
 import java.lang.invoke.MethodHandle;
-import jdk.nashorn.internal.codegen.objects.FunctionObjectCreator;
+
 import jdk.nashorn.internal.runtime.GlobalFunctions;
 import jdk.nashorn.internal.runtime.Property;
 import jdk.nashorn.internal.runtime.PropertyMap;
@@ -94,14 +94,14 @@
 
     /**
      * Constructor called by (compiler) generated code for {@link ScriptObject}s.
-     * Code is generated by {@link FunctionObjectCreator}
+     * Code is generated by {@link jdk.nashorn.internal.codegen.CodeGenerator#newFunctionObject}
      *
      * @param data static function data
      * @param methodHandle handle for invocation
      * @param scope scope object
      * @param allocator instance constructor for function
      */
-    public ScriptFunctionImpl(final ScriptFunctionData data, final MethodHandle methodHandle, final ScriptObject scope, final MethodHandle allocator) {
+    public ScriptFunctionImpl(final MethodHandle methodHandle, final ScriptFunctionData data, final ScriptObject scope, final MethodHandle allocator) {
         super(data, getMap(data.isStrict()), scope);
         // Set method handles in script data
         data.setMethodHandles(methodHandle, allocator);
diff --git a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionTrampolineImpl.java b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionTrampolineImpl.java
new file mode 100644
index 0000000..22dd74a
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionTrampolineImpl.java
@@ -0,0 +1,122 @@
+package jdk.nashorn.internal.objects;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+
+import static jdk.nashorn.internal.runtime.linker.Lookup.MH;
+
+import jdk.nashorn.internal.codegen.Compiler;
+import jdk.nashorn.internal.codegen.FunctionSignature;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.runtime.CodeInstaller;
+import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ScriptFunction;
+import jdk.nashorn.internal.runtime.ScriptFunctionData;
+import jdk.nashorn.internal.runtime.ScriptObject;
+
+/**
+ * A trampoline is a promise to compile a {@link ScriptFunction} later. It just looks like
+ * the call to the script function, but when invoked it will compile the script function
+ * (in a new compile unit) and invoke it
+ */
+public final class ScriptFunctionTrampolineImpl extends ScriptFunctionImpl {
+
+    private CodeInstaller<Context> installer;
+
+    /** Function node to lazily recompile when trampoline is hit */
+    private FunctionNode functionNode;
+
+    /**
+     * Constructor
+     *
+     * @param installer    opaque code installer from context
+     * @param functionNode function node to lazily compile when trampoline is hit
+     * @param data         {@link ScriptFunctionData} for function
+     * @param scope        scope
+     * @param allocator    allocator
+     */
+    public ScriptFunctionTrampolineImpl(final CodeInstaller<Context> installer, final FunctionNode functionNode, final ScriptFunctionData data, final ScriptObject scope, final MethodHandle allocator) {
+        super(null, data, scope, allocator);
+
+        this.installer    = installer;
+        this.functionNode = functionNode;
+
+        data.setMethodHandles(makeTrampoline(), allocator);
+    }
+
+    private final MethodHandle makeTrampoline() {
+        final MethodType mt =
+            new FunctionSignature(
+                true,
+                functionNode.needsCallee(),
+                Type.OBJECT,
+                functionNode.getParameters().size()).
+            getMethodType();
+
+        return
+            MH.bindTo(
+                MH.asCollector(
+                    findOwnMH(
+                        "trampoline",
+                        Object.class,
+                        Object[].class),
+                    Object[].class,
+                    mt.parameterCount()),
+                this);
+    }
+
+    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
+        return MH.findVirtual(MethodHandles.lookup(), ScriptFunctionTrampolineImpl.class, name, MH.type(rtype, types));
+    }
+
+    @Override
+    protected ScriptFunction makeBoundFunction(final ScriptFunctionData data) {
+        //prevent trampoline recompilation cycle if a function is bound before use
+        compile();
+        return super.makeBoundFunction(data);
+    }
+
+    private MethodHandle compile() {
+        final Compiler compiler = new Compiler(installer, functionNode);
+        if (!compiler.compile()) {
+            assert false : "compilation error in trampoline for " + functionNode.getName();
+            return null;
+        }
+
+        final Class<?> clazz = compiler.install();
+        /* compute function signature for lazy method. this can be done first after compilation, as only then do we know
+         * the final state about callees, scopes and specialized parameter types */
+        final FunctionSignature signature = new FunctionSignature(true, functionNode.needsCallee(), Type.OBJECT, functionNode.getParameters().size());
+        final MethodType        mt        = signature.getMethodType();
+
+        MethodHandle mh = MH.findStatic(MethodHandles.publicLookup(), clazz, functionNode.getName(), mt);
+        mh = MH.bindTo(mh, this);
+
+        // now the invoker method looks like the one our superclass is expecting
+        resetInvoker(mh);
+
+        return mh;
+    }
+
+    @SuppressWarnings("unused")
+    private Object trampoline(final Object... args) {
+        /** Create a new compiler for the lazy node, using the same installation policy as the old one */
+
+        MethodHandle mh = compile();
+
+        // spread the array to invididual args of the correct type
+        mh = MH.asSpreader(mh, Object[].class, mh.type().parameterCount());
+
+        try {
+            //invoke the real method the trampoline points to. this only happens once
+            return mh.invoke(args);
+        } catch (final RuntimeException | Error e) {
+            throw e;
+        } catch (final Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+}
diff --git a/nashorn/src/jdk/nashorn/internal/parser/Lexer.java b/nashorn/src/jdk/nashorn/internal/parser/Lexer.java
index 5b890b3..2ae0297 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/Lexer.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/Lexer.java
@@ -289,6 +289,11 @@
         add(type, start, position);
     }
 
+    /**
+     * Return the String of valid whitespace characters for regular
+     * expressions in JavaScript
+     * @return regexp whitespace string
+     */
     public static String getWhitespaceRegExp() {
         return JAVASCRIPT_WHITESPACE_IN_REGEXP;
     }
@@ -959,6 +964,8 @@
                     // Add string token without editing.
                     add(type, stringState.position, stringState.limit);
                     break;
+                default:
+                    break;
                 }
             } else {
                 /// Add string token without editing.
diff --git a/nashorn/src/jdk/nashorn/internal/parser/Parser.java b/nashorn/src/jdk/nashorn/internal/parser/Parser.java
index 4837c9a..1875ee0 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/Parser.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/Parser.java
@@ -39,7 +39,6 @@
 import static jdk.nashorn.internal.parser.TokenType.ELSE;
 import static jdk.nashorn.internal.parser.TokenType.EOF;
 import static jdk.nashorn.internal.parser.TokenType.EOL;
-import static jdk.nashorn.internal.parser.TokenType.EXECSTRING;
 import static jdk.nashorn.internal.parser.TokenType.FINALLY;
 import static jdk.nashorn.internal.parser.TokenType.FUNCTION;
 import static jdk.nashorn.internal.parser.TokenType.IDENT;
@@ -60,7 +59,9 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
-import jdk.nashorn.internal.codegen.Compiler;
+
+import jdk.nashorn.internal.codegen.CompilerConstants;
+import jdk.nashorn.internal.codegen.Namespace;
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
@@ -97,8 +98,10 @@
 import jdk.nashorn.internal.ir.WhileNode;
 import jdk.nashorn.internal.ir.WithNode;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.ErrorManager;
 import jdk.nashorn.internal.runtime.JSErrorType;
 import jdk.nashorn.internal.runtime.ParserException;
+import jdk.nashorn.internal.runtime.Source;
 import jdk.nashorn.internal.runtime.ScriptingFunctions;
 
 /**
@@ -106,49 +109,72 @@
  *
  */
 public class Parser extends AbstractParser {
-    /** Code generator. */
-    private final Compiler compiler;
-
     /** Current context. */
     private final Context context;
 
     /** Is scripting mode. */
     private final boolean scripting;
 
-    /** Top level script being compiled. */
+    /** Top level script being parsed. */
     private FunctionNode script;
 
-    /** Current function being compiled. */
+    /** Current function being parsed. */
     private FunctionNode function;
 
     /** Current parsing block. */
     private Block block;
 
+    /** Namespace for function names where not explicitly given */
+    private final Namespace namespace;
+
     /**
-     * Construct a parser.
-     * @param compiler Compiler state used to parse.
+     * Constructor
+     *
+     * @param context parser context
+     * @param source  source to parse
+     * @param errors  error manager
      */
-    public Parser(final Compiler compiler) {
-        this(compiler, compiler.getContext()._strict);
+    public Parser(final Context context, final Source source, final ErrorManager errors) {
+        this(context, source, errors, context._strict);
     }
 
     /**
      * Construct a parser.
-     * @param compiler Compiler state used to parse.
-     * @param strict parser created with strict mode enabled.
+     *
+     * @param context parser context
+     * @param source  source to parse
+     * @param errors  error manager
+     * @param strict  parser created with strict mode enabled.
      */
-    public Parser(final Compiler compiler, final boolean strict) {
-        super(compiler.getSource(), compiler.getErrors(), strict);
-
-        this.compiler = compiler;
-        this.context  = compiler.getContext();
-        this.scripting = this.context._scripting;
+    public Parser(final Context context, final Source source, final ErrorManager errors, final boolean strict) {
+        super(source, errors, strict);
+        this.context   = context;
+        this.namespace = new Namespace(context.getNamespace());
+        this.scripting = context._scripting;
     }
 
     /**
-     * Parse source content.
-     * @param scriptName file name for script
-     * @return Top level function (script).
+     * Execute parse and return the resulting function node.
+     * Errors will be thrown and the error manager will contain information
+     * if parsing should fail
+     *
+     * This is the default parse call, which will name the function node
+     * "runScript" {@link CompilerConstants#RUN_SCRIPT}
+     *
+     * @return function node resulting from successful parse
+     */
+    public FunctionNode parse() {
+        return parse(RUN_SCRIPT.tag());
+    }
+
+    /**
+     * Execute parse and return the resulting function node.
+     * Errors will be thrown and the error manager will contain information
+     * if parsing should fail
+     *
+     * @param scriptName name for the script, given to the parsed FunctionNode
+     *
+     * @return function node resulting from successful parse
      */
     public FunctionNode parse(final String scriptName) {
         try {
@@ -257,11 +283,11 @@
         }
 
         sb.append(ident != null ? ident.getName() : FUNCTION_PREFIX.tag());
-        final String name = compiler.uniqueName(sb.toString());
+        final String name = namespace.uniqueName(sb.toString());
         assert function != null || name.equals(RUN_SCRIPT.tag())  : "name = " + name;// must not rename runScript().
 
         // Start new block.
-        final FunctionNode functionBlock = new FunctionNode(source, token, Token.descPosition(token), compiler, block, ident, name);
+        final FunctionNode functionBlock = new FunctionNode(source, token, Token.descPosition(token), namespace, block, ident, name);
         block = function = functionBlock;
         function.setStrictMode(isStrictMode);
 
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java b/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java
index 789eb2c..3ec272a 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java
@@ -34,14 +34,21 @@
  * The compiler still retains most of the state around code emission
  * and management internally, so this is to avoid passing around any
  * logic that isn't directly related to installing a class
+ * @param <T> owner class type for this code installer
  *
  */
-public interface CodeInstaller {
+public interface CodeInstaller<T> {
+    /**
+     * Return the owner for the CodeInstaller, e.g. a {@link Context}
+     * @return owner
+     */
+    public T getOwner();
+
     /**
      * Install a class
      * @param className name of the class with / separation
-      * @param bytecode  bytecode
-      * @return the installed class
+     * @param bytecode  bytecode
+     * @return the installed class
      */
     public Class<?> install(final String className, final byte[] bytecode);
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/Context.java b/nashorn/src/jdk/nashorn/internal/runtime/Context.java
index d220e29..8a7507f 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java
@@ -45,12 +45,16 @@
 import java.security.PrivilegedAction;
 import java.util.Locale;
 import java.util.TimeZone;
+
 import jdk.internal.org.objectweb.asm.ClassReader;
 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
 import jdk.nashorn.internal.codegen.ClassEmitter;
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.Namespace;
 import jdk.nashorn.internal.codegen.objects.ObjectClassGenerator;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.debug.PrintVisitor;
+import jdk.nashorn.internal.parser.Parser;
 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
 import jdk.nashorn.internal.runtime.options.KeyValueOption;
@@ -63,6 +67,36 @@
  */
 public final class Context {
 
+    /**
+     * ContextCodeInstaller that has the privilege of installing classes in the Context.
+     * Can only be instantiated from inside the context and is opaque to other classes
+     */
+    public static class ContextCodeInstaller implements CodeInstaller<Context> {
+        private final Context      context;
+        private final ScriptLoader loader;
+        private final CodeSource   codeSource;
+
+        private ContextCodeInstaller(final Context context, final ScriptLoader loader, final CodeSource codeSource) {
+            this.context    = context;
+            this.loader     = loader;
+            this.codeSource = codeSource;
+        }
+
+        /**
+         * Return the context for this installer
+         * @return context
+         */
+        @Override
+        public Context getOwner() {
+            return context;
+        }
+
+        @Override
+        public Class<?> install(final String className, final byte[] bytecode) {
+            return loader.installClass(className, bytecode, codeSource);
+        }
+    }
+
     /** Is Context global debug mode enabled ? */
     public static final boolean DEBUG = Options.getBooleanProperty("nashorn.debug");
 
@@ -751,6 +785,7 @@
     /**
      * Initialize given global scope object.
      *
+     * @param global the global
      * @return the initialized global scope object.
      */
     public ScriptObject initGlobal(final ScriptObject global) {
@@ -877,22 +912,24 @@
             }
         }
 
-        final Compiler compiler = Compiler.compiler(source, this, errMan, strict);
-
-        if (!compiler.compile()) {
+        final FunctionNode functionNode = new Parser(this, source, errMan, strict).parse();
+        if (errors.hasErrors() || _parse_only) {
             return null;
         }
 
-        final URL url = source.getURL();
+        if (_print_lower_parse) {
+            getErr().println(new PrintVisitor(functionNode));
+        }
+
+        final URL          url    = source.getURL();
         final ScriptLoader loader = _loader_per_compile ? createNewLoader() : scriptLoader;
         final CodeSource   cs     = url == null ? null : new CodeSource(url, (CodeSigner[])null);
+        final CodeInstaller<Context> installer = new ContextCodeInstaller(this, loader, cs);
 
-        script = compiler.install(new CodeInstaller() {
-            @Override
-            public Class<?> install(final String className, final byte[] bytecode) {
-                return loader.installClass(className, bytecode, cs);
-            }
-        });
+        final Compiler compiler = new Compiler(installer, functionNode, strict);
+
+        compiler.compile();
+        script = compiler.install();
 
         if (global != null) {
             global.cacheClass(source, script);
@@ -917,15 +954,14 @@
     private ScriptObject newGlobalTrusted() {
         try {
             final Class<?> clazz = Class.forName("jdk.nashorn.internal.objects.Global", true, scriptLoader);
-            final Constructor cstr = clazz.getConstructor(Context.class);
+            final Constructor<?> cstr = clazz.getConstructor(Context.class);
             return (ScriptObject) cstr.newInstance(this);
         } catch (final Exception e) {
             printStackTrace(e);
             if (e instanceof RuntimeException) {
                 throw (RuntimeException)e;
-            } else {
-                throw new RuntimeException(e);
             }
+            throw new RuntimeException(e);
         }
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java b/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java
index dbbaeef..aa7f05d 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java
@@ -73,8 +73,11 @@
      * Get the output writer for the logger. Loggers always default to
      * stderr for output as they are used mainly to output debug info
      *
+     * Can be inherited so this should not be static.
+     *
      * @return print writer for log output.
      */
+    @SuppressWarnings("static-method")
     public PrintWriter getOutputStream() {
         return Context.getCurrentErr();
     }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java b/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
index 7089d1f..2df165a 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
@@ -36,7 +36,7 @@
 public final class FindProperty {
     /** Object where search began. */
     private final ScriptObject self;
-    ;
+
     /** Object where search finish. */
     private final ScriptObject prototype;
 
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java
index d0b24b2..4852505 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java
@@ -78,12 +78,14 @@
     /**
      * Constructor
      *
-     * @param name         function name
-     * @param methodHandle method handle to function (if specializations are present, assumed to be most generic)
-     * @param map          property map
-     * @param scope        scope
-     * @param specs        specialized version of this function - other method handles
-     *
+     * @param name          function name
+     * @param methodHandle  method handle to function (if specializations are present, assumed to be most generic)
+     * @param map           property map
+     * @param scope         scope
+     * @param specs         specialized version of this function - other method handles
+     * @param strict        is this a strict mode function?
+     * @param builtin       is this a built in function?
+     * @param isConstructor is this a constructor?
      */
     protected ScriptFunction(
             final String name,
@@ -240,10 +242,17 @@
      * @param args additional arguments to bind to this function. Can be null or empty to not bind additional arguments.
      * @return a function with the specified self and parameters bound.
      */
-    protected ScriptFunction makeBoundFunction(Object self, Object[] args) {
+    protected ScriptFunction makeBoundFunction(final Object self, final Object[] args) {
         return makeBoundFunction(data.makeBoundFunctionData(this, self, args));
     }
 
+    /**
+     * Create a version of this function as in {@link ScriptFunction#makeBoundFunction(Object, Object[])},
+     * but using a {@link ScriptFunctionData} for the bound data.
+     *
+     * @param boundData ScriptFuntionData for the bound function
+     * @return a function with the bindings performed according to the given data
+     */
     protected abstract ScriptFunction makeBoundFunction(ScriptFunctionData boundData);
 
     @Override
@@ -350,6 +359,15 @@
     }
 
     /**
+     * Reset the invoker handle. This is used by trampolines for
+     * lazy code generation
+     * @param invoker new invoker
+     */
+    protected void resetInvoker(final MethodHandle invoker) {
+        data.resetInvoker(invoker);
+    }
+
+    /**
      * Prototype getter for this ScriptFunction - follows the naming convention
      * used by Nasgen and the code generator
      *
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
index 3366993..37e0246 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
@@ -101,11 +101,13 @@
 
     /**
      * Constructor
+     *
      * @param name the function name
      * @param methodHandle the method handle
      * @param specs array of specialized method handles
      * @param strict strict flag
      * @param builtin builtin flag
+     * @param isConstructor constructor flags
      */
     public ScriptFunctionData(final String name, final MethodHandle methodHandle, final MethodHandle[] specs, final boolean strict, final boolean builtin, final boolean isConstructor) {
         this(name, null, 0L, methodHandle, specs, strict, builtin, isConstructor);
@@ -432,7 +434,7 @@
      * @param invoker the invoker handle
      * @param allocator the allocator handle
      */
-    public void setMethodHandles(MethodHandle invoker, MethodHandle allocator) {
+    public void setMethodHandles(final MethodHandle invoker, final MethodHandle allocator) {
         // We can't make method handle fields final because they're not available during codegen
         // and they're set when first called, so we enforce set-once here.
         if (this.invoker == null) {
@@ -443,6 +445,16 @@
     }
 
     /**
+     * Used by the trampoline. Must not be any wider than package
+     * private
+     * @param invoker new invoker
+     */
+    void resetInvoker(final MethodHandle invoker) {
+        this.invoker     = invoker;
+        this.constructor = null; //delay constructor composition
+    }
+
+    /**
      * Allocates an object using this function's allocator.
      * @return the object allocated using this function's allocator, or null if the function doesn't have an allocator.
      */
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java
index 523179a..2925c8d 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptingFunctions.java
@@ -54,14 +54,21 @@
     /** Handle to implementation of {@link ScriptingFunctions#exec} - Nashorn extension */
     public static final MethodHandle EXEC = findOwnMH("exec",     Object.class, Object.class, Object.class, Object.class);
 
-    /** Names of special properties used by $EXEC API. */
-    public  static final String EXEC_NAME = "$EXEC";
-    public  static final String OUT_NAME  = "$OUT";
-    public  static final String ERR_NAME  = "$ERR";
-    public  static final String EXIT_NAME = "$EXIT";
+    /** EXEC name - special property used by $EXEC API. */
+    public static final String EXEC_NAME = "$EXEC";
+
+    /** OUT name - special property used by $EXEC API. */
+    public static final String OUT_NAME  = "$OUT";
+
+    /** ERR name - special property used by $EXEC API. */
+    public static final String ERR_NAME  = "$ERR";
+
+    /** EXIT name - special property used by $EXEC API. */
+    public static final String EXIT_NAME = "$EXIT";
 
     /** Names of special properties used by $ENV API. */
     public  static final String ENV_NAME  = "$ENV";
+
     private static final String PWD_NAME  = "PWD";
 
     private ScriptingFunctions() {
@@ -114,8 +121,11 @@
      *
      * @param self   self reference
      * @param string string to execute
+     * @param input
      *
      * @return output string from the request
+     * @throws IOException
+     * @throws InterruptedException
      */
     public static Object exec(final Object self, final Object string, final Object input) throws IOException, InterruptedException {
         // Current global is need to fetch additional inputs and for additional results.
diff --git a/nashorn/src/jdk/nashorn/tools/Shell.java b/nashorn/src/jdk/nashorn/tools/Shell.java
index 124e83c..d353c5f 100644
--- a/nashorn/src/jdk/nashorn/tools/Shell.java
+++ b/nashorn/src/jdk/nashorn/tools/Shell.java
@@ -41,6 +41,8 @@
 import java.util.ResourceBundle;
 import jdk.nashorn.api.scripting.NashornException;
 import jdk.nashorn.internal.codegen.Compiler;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.parser.Parser;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ErrorManager;
 import jdk.nashorn.internal.runtime.ScriptFunction;
@@ -241,13 +243,14 @@
 
             // For each file on the command line.
             for (final String fileName : files) {
-                final File file = new File(fileName);
-                final Source source = new Source(fileName, file);
-                final Compiler compiler = Compiler.compiler(source, context);
-                compiler.compile();
+                final FunctionNode functionNode = new Parser(context, new Source(fileName, new File(fileName)), errors).parse();
+
                 if (errors.getNumberOfErrors() != 0) {
                     return COMPILATION_ERROR;
                 }
+
+                //null - pass no code installer - this is compile only
+                new Compiler(context, functionNode).compile();
             }
         } finally {
             context.getOut().flush();
@@ -282,7 +285,7 @@
             // For each file on the command line.
             for (final String fileName : files) {
                 final File file = new File(fileName);
-                ScriptFunction script = context.compileScript(new Source(fileName, file.toURI().toURL()), global);
+                final ScriptFunction script = context.compileScript(new Source(fileName, file.toURI().toURL()), global);
                 if (script == null || errors.getNumberOfErrors() != 0) {
                     return COMPILATION_ERROR;
                 }
diff --git a/nashorn/test/script/trusted/JDK-8006529.js b/nashorn/test/script/trusted/JDK-8006529.js
index f748649..0ad1db3 100644
--- a/nashorn/test/script/trusted/JDK-8006529.js
+++ b/nashorn/test/script/trusted/JDK-8006529.js
@@ -39,17 +39,15 @@
  * and FunctionNode because of package-access check and so reflective calls.
  */
 
+var Parser         = Java.type("jdk.nashorn.internal.parser.Parser")
 var Compiler       = Java.type("jdk.nashorn.internal.codegen.Compiler")
 var Context        = Java.type("jdk.nashorn.internal.runtime.Context")
 var Source         = Java.type("jdk.nashorn.internal.runtime.Source")
 var FunctionNode   = Java.type("jdk.nashorn.internal.ir.FunctionNode")
 
 // Compiler class methods and fields
-
-// Compiler.compile(Source, Context)
-var compilerMethod = Compiler.class.getMethod("compiler", Source.class, Context.class);
-// Compiler.compile()
-var compileMethod  = Compiler.class.getMethod("compile");
+var parseMethod = Parser.class.getMethod("parse");
+var compileMethod = Compiler.class.getMethod("compile");
 
 // NOTE: private field. But this is a trusted test!
 // Compiler.functionNode
@@ -90,10 +88,14 @@
 // source code, returns a jdk.nashorn.internal.ir.FunctionNode object 
 // representing it.
 function compile(source) {
-   var compiler = compilerMethod.invoke(null,
-       new Source("<no name>", source), Context.getContext())
-   compileMethod.invoke(compiler);
-   return getScriptNode(compiler)
+    var source   = new Source("<no name>", source);
+    var parser   = new Parser(Context.getContext(), source, null);
+    var func     = parseMethod.invoke(parser);
+    var compiler = new Compiler(Context.getContext(), func);
+
+    compileMethod.invoke(compiler);
+
+    return getScriptNode(compiler);
 };
 
 var allAssertions = (function() {
diff --git a/nashorn/test/src/jdk/nashorn/internal/parser/ParserTest.java b/nashorn/test/src/jdk/nashorn/internal/parser/ParserTest.java
index 27a637e..08e20df 100644
--- a/nashorn/test/src/jdk/nashorn/internal/parser/ParserTest.java
+++ b/nashorn/test/src/jdk/nashorn/internal/parser/ParserTest.java
@@ -156,10 +156,7 @@
                 Context.setGlobal(global);
             }
             final Source   source   = new Source(file.getAbsolutePath(), buffer);
-            final Compiler compiler = Compiler.compiler(source, context, errors, context._strict);
-
-            final Parser parser = new Parser(compiler);
-            parser.parse(CompilerConstants.RUN_SCRIPT.tag());
+            new Parser(context, source, errors).parse();
             if (errors.getNumberOfErrors() > 0) {
                 log("Parse failed: " + file.getAbsolutePath());
                 failed++;
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/framework/SharedContextEvaluator.java b/nashorn/test/src/jdk/nashorn/internal/test/framework/SharedContextEvaluator.java
index 5855737..4ea77ac 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/framework/SharedContextEvaluator.java
+++ b/nashorn/test/src/jdk/nashorn/internal/test/framework/SharedContextEvaluator.java
@@ -126,6 +126,7 @@
                 }
                 final File file = new File(fileName);
                 ScriptFunction script = context.compileScript(new Source(fileName, file.toURI().toURL()), global);
+
                 if (script == null || errors.getNumberOfErrors() != 0) {
                     return COMPILATION_ERROR;
                 }