Merge
diff --git a/nashorn/bin/verbose_octane.sh b/nashorn/bin/verbose_octane.sh
index e70e9d4..1895afe 100644
--- a/nashorn/bin/verbose_octane.sh
+++ b/nashorn/bin/verbose_octane.sh
@@ -26,7 +26,7 @@
     ITERS=7
 fi
 NASHORN_JAR=dist/nashorn.jar
-JVM_FLAGS="-XX:+UnlockDiagnosticVMOptions -Dnashorn.unstable.relink.threshold=8 -Xms2G -Xmx2G -XX:+TieredCompilation -server -jar ${NASHORN_JAR}"
+JVM_FLAGS="-Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -XX:+UnlockDiagnosticVMOptions -Dnashorn.unstable.relink.threshold=8 -Xms2G -Xmx2G -XX:+TieredCompilation -server -jar ${NASHORN_JAR}"
 JVM_FLAGS7="-Xbootclasspath/p:${NASHORN_JAR} ${JVM_FLAGS}"
 OCTANE_ARGS="--verbose --iterations ${ITERS}"
 
diff --git a/nashorn/make/project.properties b/nashorn/make/project.properties
index bd0d7ec..e2f39bb 100644
--- a/nashorn/make/project.properties
+++ b/nashorn/make/project.properties
@@ -214,7 +214,7 @@
 
 #  -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMethods
 # add '-Dtest.js.outofprocess' to run each test in a new sub-process
-run.test.jvmargs.main=-server -Xmx${run.test.xmx} -XX:-TieredCompilation -esa -ea -Dnashorn.debug=true -Dfile.encoding=UTF-8
+run.test.jvmargs.main=-server -Xmx${run.test.xmx} -XX:-TieredCompilation -ea -Dnashorn.debug=true -Dfile.encoding=UTF-8
 #-XX:+HeapDumpOnOutOfMemoryError -XX:-UseCompressedKlassPointers -XX:+PrintHeapAtGC -XX:ClassMetaspaceSize=300M  
 run.test.jvmargs.octane.main=-Xms${run.test.xms} ${run.test.jvmargs.main}
 
diff --git a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java
index 55967bb..197a6da 100644
--- a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java
+++ b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java
@@ -397,10 +397,7 @@
             }
 
             setContextVariables(ctxt);
-            final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
-            final String fileName = (val != null) ? val.toString() : "<eval>";
-            Object res = ScriptRuntime.apply(script, ctxtGlobal);
-            return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, ctxtGlobal));
+            return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(ScriptRuntime.apply(script, ctxtGlobal), ctxtGlobal));
         } catch (final Exception e) {
             throwAsScriptException(e);
             throw new AssertionError("should not reach here");
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
index 9ecf7c8..373bc51 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
@@ -25,14 +25,16 @@
 
 package jdk.nashorn.internal.codegen;
 
+import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.EXCEPTION_PREFIX;
 import static jdk.nashorn.internal.codegen.CompilerConstants.ITERATOR_PREFIX;
+import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
-import static jdk.nashorn.internal.codegen.CompilerConstants.SCRIPT_RETURN;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SWITCH_TAG_PREFIX;
 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
+import static jdk.nashorn.internal.ir.Symbol.IS_FUNCTION_SELF;
 import static jdk.nashorn.internal.ir.Symbol.IS_GLOBAL;
 import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
 import static jdk.nashorn.internal.ir.Symbol.IS_LET;
@@ -42,18 +44,20 @@
 import static jdk.nashorn.internal.ir.Symbol.IS_VAR;
 import static jdk.nashorn.internal.ir.Symbol.KINDMASK;
 
+import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Deque;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.List;
-import java.util.ListIterator;
+import java.util.Map;
 import java.util.Set;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.CallNode;
-import jdk.nashorn.internal.ir.CallNode.EvalArgs;
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ForNode;
@@ -62,6 +66,7 @@
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.LexicalContext;
+import jdk.nashorn.internal.ir.LexicalContextNode;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
 import jdk.nashorn.internal.ir.Node;
@@ -86,7 +91,6 @@
 import jdk.nashorn.internal.runtime.JSType;
 import jdk.nashorn.internal.runtime.Property;
 import jdk.nashorn.internal.runtime.PropertyMap;
-import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptObject;
 
 /**
@@ -105,21 +109,24 @@
  */
 
 final class Attr extends NodeOperatorVisitor {
+
     /**
      * Local definitions in current block (to discriminate from function
      * declarations always defined in the function scope. This is for
      * "can be undefined" analysis.
      */
-    private Set<String> localDefs;
+    private final Deque<Set<String>> localDefs;
 
     /**
      * Local definitions in current block to guard against cases like
      * NASHORN-467 when things can be undefined as they are used before
      * their local var definition. *sigh* JavaScript...
      */
-    private Set<String> localUses;
+    private final Deque<Set<String>> localUses;
 
-    private final LexicalContext lexicalContext = new LexicalContext();
+    private final Deque<Type> returnTypes;
+
+    private final Map<Symbol, FunctionNode> selfSymbolToFunction = new IdentityHashMap<>();
 
     private static final DebugLogger LOG   = new DebugLogger("attr");
     private static final boolean     DEBUG = LOG.isEnabled();
@@ -128,10 +135,13 @@
      * Constructor.
      */
     Attr() {
+        localDefs = new ArrayDeque<>();
+        localUses = new ArrayDeque<>();
+        returnTypes = new ArrayDeque<>();
     }
 
     @Override
-    protected Node enterDefault(final Node node) {
+    protected boolean enterDefault(final Node node) {
         return start(node);
     }
 
@@ -142,217 +152,44 @@
 
     @Override
     public Node leaveAccessNode(final AccessNode accessNode) {
-        newTemporary(Type.OBJECT, accessNode);  //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this
+        ensureSymbol(Type.OBJECT, accessNode);  //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this
         end(accessNode);
         return accessNode;
     }
 
-    @Override
-    public Node enterBlock(final Block block) {
-        lexicalContext.push(block);
-        start(block);
+    private void enterFunctionBody() {
 
-        final Set<String> savedLocalDefs = localDefs;
-        final Set<String> savedLocalUses = localUses;
-
-        block.setFrame(getCurrentFunctionNode().pushFrame());
-
-        try {
-            // a block starts out by copying the local defs and local uses
-            // from the outer level. But we need the copies, as when we
-            // leave the block the def and use sets given upon entry must
-            // be restored
-            localDefs = new HashSet<>(savedLocalDefs);
-            localUses = new HashSet<>(savedLocalUses);
-
-            block.visitStatements(this);
-        } finally {
-            localDefs = savedLocalDefs;
-            localUses = savedLocalUses;
-
-            getCurrentFunctionNode().popFrame();
-        }
-
-        end(block);
-
-        lexicalContext.pop(block);
-        return null;
-    }
-
-    @Override
-    public Node enterCallNode(final CallNode callNode) {
-        start(callNode);
-
-        callNode.getFunction().accept(this);
-
-        final List<Node> acceptedArgs = new ArrayList<>(callNode.getArgs().size());
-        for (final Node arg : callNode.getArgs()) {
-            LOG.info("Doing call arg " + arg);
-            acceptedArgs.add(arg.accept(this));
-        }
-        callNode.setArgs(acceptedArgs);
-
-        final EvalArgs evalArgs = callNode.getEvalArgs();
-        if (evalArgs != null) {
-            evalArgs.setCode(evalArgs.getCode().accept(this));
-
-            final IdentNode thisNode = new IdentNode(getCurrentFunctionNode().getThisNode());
-            assert thisNode.getSymbol() != null; //should copy attributed symbol and that's it
-            evalArgs.setThis(thisNode);
-        }
-
-        newTemporary(callNode.getType(), callNode); // access specialization in FinalizeTypes may narrow it further later
-
-        end(callNode);
-
-        return null;
-    }
-
-    @Override
-    public Node enterCatchNode(final CatchNode catchNode) {
-        final IdentNode exception = catchNode.getException();
-        final Block     block     = getCurrentBlock();
-
-        start(catchNode);
-
-        // define block-local exception variable
-        final Symbol def = defineSymbol(block, exception.getName(), IS_VAR | IS_LET, exception);
-        newType(def, Type.OBJECT);
-        addLocalDef(exception.getName());
-
-        return catchNode;
-    }
-
-    /**
-     * Declare the definition of a new symbol.
-     *
-     * @param name         Name of symbol.
-     * @param symbolFlags  Symbol flags.
-     * @param node         Defining Node.
-     *
-     * @return Symbol for given name or null for redefinition.
-     */
-    private Symbol defineSymbol(final Block block, final String name, final int symbolFlags, final Node node) {
-        int    flags  = symbolFlags;
-        Symbol symbol = findSymbol(block, name); // Locate symbol.
-
-        if ((flags & KINDMASK) == IS_GLOBAL) {
-            flags |= IS_SCOPE;
-        }
-
-        final FunctionNode function = lexicalContext.getFunction(block);
-        if (symbol != null) {
-            // Symbol was already defined. Check if it needs to be redefined.
-            if ((flags & KINDMASK) == IS_PARAM) {
-                if (!isLocal(function, symbol)) {
-                    // Not defined in this function. Create a new definition.
-                    symbol = null;
-                } else if (symbol.isParam()) {
-                    // Duplicate parameter. Null return will force an error.
-                    assert false : "duplicate parameter";
-                    return null;
-                }
-            } else if ((flags & KINDMASK) == IS_VAR) {
-                if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) {
-                    assert !((flags & IS_LET) == IS_LET && symbol.getBlock() == block) : "duplicate let variable in block";
-                    // Always create a new definition.
-                    symbol = null;
-                } else {
-                    // Not defined in this function. Create a new definition.
-                    if (!isLocal(function, symbol) || symbol.less(IS_VAR)) {
-                        symbol = null;
-                    }
-                }
-            }
-        }
-
-        if (symbol == null) {
-            // If not found, then create a new one.
-            Block symbolBlock;
-
-            // Determine where to create it.
-            if ((flags & Symbol.KINDMASK) == IS_VAR && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) {
-                symbolBlock = block;
-            } else {
-                symbolBlock = function;
-            }
-
-            // Create and add to appropriate block.
-            symbol = new Symbol(name, flags, node, symbolBlock);
-            symbolBlock.putSymbol(name, symbol);
-
-            if ((flags & Symbol.KINDMASK) != IS_GLOBAL) {
-                symbolBlock.getFrame().addSymbol(symbol);
-                symbol.setNeedsSlot(true);
-            }
-        } else if (symbol.less(flags)) {
-            symbol.setFlags(flags);
-        }
-
-        if (node != null) {
-            node.setSymbol(symbol);
-        }
-
-        return symbol;
-    }
-
-    @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
-        start(functionNode, false);
-        if (functionNode.isLazy()) {
-            LOG.info("LAZY: " + functionNode.getName() + " => Promoting to OBJECT");
-            newTemporary(lexicalContext.getCurrentFunction(), Type.OBJECT, functionNode);
-            functionNode.setReturnType(Type.OBJECT);
-            end(functionNode);
-            return null;
-        }
-
-        lexicalContext.push(functionNode);
-
-        clearLocalDefs();
-        clearLocalUses();
-
-        functionNode.setFrame(functionNode.pushFrame());
-
-        initCallee(functionNode);
-        initThis(functionNode);
+        final FunctionNode functionNode = getLexicalContext().getCurrentFunction();
+        final Block body = getLexicalContext().getCurrentBlock();
+        initCallee(body);
+        initThis(body);
         if (functionNode.isVarArg()) {
-            initVarArg(functionNode);
+            initVarArg(body, functionNode.needsArguments());
         }
 
-        initParameters(functionNode);
-        initScope(functionNode);
-        initReturn(functionNode);
-
-        // Add all nested declared functions as symbols in this function
-        for (final FunctionNode nestedFunction : functionNode.getDeclaredFunctions()) {
-            final IdentNode ident = nestedFunction.getIdent();
-            if (ident != null) {
-                assert nestedFunction.isDeclared();
-                final Symbol functionSymbol = defineSymbol(functionNode, ident.getName(), IS_VAR, nestedFunction);
-                newType(functionSymbol, Type.typeFor(ScriptFunction.class));
-            }
-        }
+        initParameters(functionNode, body);
+        initScope(body);
+        initReturn(body);
 
         if (functionNode.isProgram()) {
-            initFromPropertyMap(functionNode);
+            initFromPropertyMap(body);
         }
 
         // Add function name as local symbol
         if (!functionNode.isDeclared() && !functionNode.isProgram()) {
-            if(functionNode.getSymbol() != null) {
+            if (functionNode.getSymbol() != null) {
                 // a temporary left over from an earlier pass when the function was lazy
                 assert functionNode.getSymbol().isTemp();
                 // remove it
                 functionNode.setSymbol(null);
             }
             final Symbol selfSymbol;
-            if(functionNode.isAnonymous()) {
-                selfSymbol = newTemporary(functionNode, Type.OBJECT, functionNode);
+            if (functionNode.isAnonymous()) {
+                selfSymbol = ensureSymbol(functionNode, Type.OBJECT, functionNode);
             } else {
-                selfSymbol = defineSymbol(functionNode, functionNode.getIdent().getName(), IS_VAR, functionNode);
+                selfSymbol = defineSymbol(body, functionNode.getIdent().getName(), IS_VAR | IS_FUNCTION_SELF, functionNode);
                 newType(selfSymbol, Type.OBJECT);
-                selfSymbol.setNode(functionNode);
+                selfSymbolToFunction.put(selfSymbol, functionNode);
             }
         }
 
@@ -373,73 +210,243 @@
          * @see NASHORN-73
          */
 
-        final List<Symbol> declaredSymbols = new ArrayList<>();
         // This visitor will assign symbol to all declared variables, except function declarations (which are taken care
         // in a separate step above) and "var" declarations in for loop initializers.
-        functionNode.accept(new NodeOperatorVisitor() {
+        body.accept(new NodeOperatorVisitor() {
             @Override
-            public Node enterFunctionNode(FunctionNode nestedFn) {
-                // Don't descend into nested functions
-                return nestedFn == functionNode ? nestedFn : null;
+            public boolean enterFunctionNode(final FunctionNode nestedFn) {
+                return false;
             }
+
             @Override
-            public Node enterVarNode(VarNode varNode) {
-                if(varNode.isStatement() && !varNode.isFunctionDeclaration()) {
+            public boolean enterVarNode(final VarNode varNode) {
+
+                // any declared symbols that aren't visited need to be typed as well, hence the list
+
+                if (varNode.isStatement()) {
+
                     final IdentNode ident = varNode.getName();
-                    // any declared symbols that aren't visited need to be typed as well, hence the list
-                    declaredSymbols.add(defineSymbol(functionNode, ident.getName(), IS_VAR, new IdentNode(ident)));
+                    final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR, new IdentNode(ident));
+                    functionNode.addDeclaredSymbol(symbol);
+                    if (varNode.isFunctionDeclaration()) {
+                        newType(symbol, FunctionNode.FUNCTION_TYPE);
+                    }
                 }
-                return null;
+                return false;
             }
         });
+    }
 
-        visitFunctionStatements(functionNode);
+    @Override
+    public boolean enterBlock(final Block block) {
+        start(block);
+
+        if (getLexicalContext().isFunctionBody()) {
+            enterFunctionBody();
+        }
+        pushLocalsBlock();
+
+        return true;
+    }
+
+    @Override
+    public Node leaveBlock(final Block block) {
+        popLocals();
+        return end(block);
+    }
+
+    @Override
+    public Node leaveCallNode(final CallNode callNode) {
+        ensureSymbol(callNode.getType(), callNode);
+        return end(callNode);
+    }
+
+    @Override
+    public boolean enterCallNode(final CallNode callNode) {
+        return start(callNode);
+    }
+
+    @Override
+    public boolean enterCatchNode(final CatchNode catchNode) {
+        final IdentNode exception = catchNode.getException();
+        final Block     block     = getLexicalContext().getCurrentBlock();
+
+        start(catchNode);
+
+        // define block-local exception variable
+        final Symbol def = defineSymbol(block, exception.getName(), IS_VAR | IS_LET, exception);
+        newType(def, Type.OBJECT);
+        addLocalDef(exception.getName());
+
+        return true;
+    }
+
+    /**
+     * Declare the definition of a new symbol.
+     *
+     * @param name         Name of symbol.
+     * @param symbolFlags  Symbol flags.
+     * @param node         Defining Node.
+     *
+     * @return Symbol for given name or null for redefinition.
+     */
+    private Symbol defineSymbol(final Block block, final String name, final int symbolFlags, final Node node) {
+        int    flags  = symbolFlags;
+        Symbol symbol = findSymbol(block, name); // Locate symbol.
+
+        if ((flags & KINDMASK) == IS_GLOBAL) {
+            flags |= IS_SCOPE;
+        }
+
+        final FunctionNode function = getLexicalContext().getFunction(block);
+        if (symbol != null) {
+            // Symbol was already defined. Check if it needs to be redefined.
+            if ((flags & KINDMASK) == IS_PARAM) {
+                if (!isLocal(function, symbol)) {
+                    // Not defined in this function. Create a new definition.
+                    symbol = null;
+                } else if (symbol.isParam()) {
+                    // Duplicate parameter. Null return will force an error.
+                    assert false : "duplicate parameter";
+                    return null;
+                }
+            } else if ((flags & KINDMASK) == IS_VAR) {
+                if ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET) {
+                    // Always create a new definition.
+                    symbol = null;
+                } else {
+                    // Not defined in this function. Create a new definition.
+                    if (!isLocal(function, symbol) || symbol.less(IS_VAR)) {
+                        symbol = null;
+                    }
+                }
+            }
+        }
+
+        if (symbol == null) {
+            // If not found, then create a new one.
+            Block symbolBlock;
+
+            // Determine where to create it.
+            if ((flags & Symbol.KINDMASK) == IS_VAR && ((flags & IS_INTERNAL) == IS_INTERNAL || (flags & IS_LET) == IS_LET)) {
+                symbolBlock = block; //internal vars are always defined in the block closest to them
+            } else {
+                symbolBlock = getLexicalContext().getFunctionBody(function);
+            }
+
+            // Create and add to appropriate block.
+            symbol = new Symbol(name, flags);
+            symbolBlock.putSymbol(name, symbol);
+
+            if ((flags & Symbol.KINDMASK) != IS_GLOBAL) {
+                symbol.setNeedsSlot(true);
+            }
+        } else if (symbol.less(flags)) {
+            symbol.setFlags(flags);
+        }
+
+        if (node != null) {
+            node.setSymbol(symbol);
+        }
+
+        return symbol;
+    }
+
+    @Override
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
+        start(functionNode, false);
+
+        if (functionNode.isDeclared()) {
+            final Iterator<Block> blocks = getLexicalContext().getBlocks();
+            if (blocks.hasNext()) {
+                defineSymbol(
+                    blocks.next(),
+                    functionNode.getIdent().getName(),
+                    IS_VAR,
+                    functionNode);
+            } else {
+                // Q: What's an outermost function in a lexical context that is not a program?
+                // A: It's a function being compiled lazily!
+                assert getLexicalContext().getOutermostFunction() == functionNode && !functionNode.isProgram();
+            }
+        }
+
+        if (functionNode.isLazy()) {
+            LOG.info("LAZY: ", functionNode.getName(), " => Promoting to OBJECT");
+            ensureSymbol(getLexicalContext().getCurrentFunction(), Type.OBJECT, functionNode);
+            end(functionNode);
+            return false;
+        }
+
+        returnTypes.push(functionNode.getReturnType());
+        pushLocalsFunction();
+        return true;
+    }
+
+    @Override
+    public Node leaveFunctionNode(final FunctionNode functionNode) {
+        FunctionNode newFunctionNode = functionNode;
+
+        final LexicalContext lc = getLexicalContext();
 
         //unknown parameters are promoted to object type.
-        finalizeParameters(functionNode);
-        finalizeTypes(functionNode);
-        for (final Symbol symbol : declaredSymbols) {
+        finalizeParameters(newFunctionNode);
+        finalizeTypes(newFunctionNode);
+        for (final Symbol symbol : newFunctionNode.getDeclaredSymbols()) {
             if (symbol.getSymbolType().isUnknown()) {
                 symbol.setType(Type.OBJECT);
                 symbol.setCanBeUndefined();
             }
         }
 
-        if (functionNode.getReturnType().isUnknown()) {
-            LOG.info("Unknown return type promoted to object");
-            functionNode.setReturnType(Type.OBJECT);
+        final Block body = newFunctionNode.getBody();
+
+        if (newFunctionNode.hasLazyChildren()) {
+            //the final body has already been assigned as we have left the function node block body by now
+            objectifySymbols(body);
         }
 
-        if (functionNode.getSelfSymbolInit() != null) {
-            LOG.info("Accepting self symbol init " + functionNode.getSelfSymbolInit() + " for " + functionNode.getName());
-            final Node init = functionNode.getSelfSymbolInit();
+        if (body.getFlag(Block.NEEDS_SELF_SYMBOL)) {
+            final IdentNode callee = compilerConstant(CALLEE);
+            final VarNode selfInit =
+                new VarNode(
+                    newFunctionNode.getSource(),
+                    newFunctionNode.getToken(),
+                    newFunctionNode.getFinish(),
+                    newFunctionNode.getIdent(),
+                    callee);
+
+            LOG.info("Accepting self symbol init ", selfInit, " for ", newFunctionNode.getName());
+
             final List<Node> newStatements = new ArrayList<>();
-            newStatements.add(init);
-            newStatements.addAll(functionNode.getStatements());
-            functionNode.setStatements(newStatements);
-            functionNode.setNeedsSelfSymbol(functionNode.getSelfSymbolInit().accept(this));
+            newStatements.add(selfInit);
+            assert callee.getSymbol() != null && callee.getSymbol().hasSlot();
+
+            final IdentNode name       = selfInit.getName();
+            final Symbol    nameSymbol = body.getExistingSymbol(name.getName());
+
+            assert nameSymbol != null;
+
+            name.setSymbol(nameSymbol);
+            selfInit.setSymbol(nameSymbol);
+
+            newStatements.addAll(body.getStatements());
+            newFunctionNode = newFunctionNode.setBody(lc, body.setStatements(lc, newStatements));
         }
 
-        if (functionNode.hasLazyChildren()) {
-            objectifySymbols(functionNode);
+        if (returnTypes.peek().isUnknown()) {
+            LOG.info("Unknown return type promoted to object");
+            newFunctionNode = newFunctionNode.setReturnType(lc, Type.OBJECT);
         }
+        final Type returnType = returnTypes.pop();
+        newFunctionNode = newFunctionNode.setReturnType(lc, returnType.isUnknown() ? Type.OBJECT : returnType);
+        newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR);
 
-        functionNode.popFrame();
+        popLocals();
 
-        functionNode.setState(CompilationState.ATTR);
+        end(newFunctionNode, false);
 
-        end(functionNode, false);
-        lexicalContext.pop(functionNode);
-
-        return null;
-    }
-
-    private void visitFunctionStatements(final FunctionNode functionNode) {
-        final List<Node> newStatements = new ArrayList<>(functionNode.getStatements());
-        for(ListIterator<Node> stmts = newStatements.listIterator(); stmts.hasNext();) {
-            stmts.set(stmts.next().accept(this));
-        }
-        functionNode.setStatements(newStatements);
+        return newFunctionNode; //.setFlag(lc, lc.getFlags(functionNode));
     }
 
     @Override
@@ -450,7 +457,7 @@
     }
 
     @Override
-    public Node enterIdentNode(final IdentNode identNode) {
+    public boolean enterIdentNode(final IdentNode identNode) {
         final String name = identNode.getName();
 
         start(identNode);
@@ -458,31 +465,28 @@
         if (identNode.isPropertyName()) {
             // assign a pseudo symbol to property name
             final Symbol pseudoSymbol = pseudoSymbol(name);
-            LOG.info("IdentNode is property name -> assigning pseudo symbol " + pseudoSymbol);
+            LOG.info("IdentNode is property name -> assigning pseudo symbol ", pseudoSymbol);
             LOG.unindent();
             identNode.setSymbol(pseudoSymbol);
-            return null;
+            return false;
         }
 
-        final Block  block     = getCurrentBlock();
-        final Symbol oldSymbol = identNode.getSymbol();
+        final LexicalContext lc        = getLexicalContext();
+        final Block          block     = lc.getCurrentBlock();
+        final Symbol         oldSymbol = identNode.getSymbol();
 
         Symbol symbol = findSymbol(block, name);
 
         //If an existing symbol with the name is found, use that otherwise, declare a new one
         if (symbol != null) {
-            LOG.info("Existing symbol = " + symbol);
-            if (isFunctionExpressionSelfReference(symbol)) {
-                final FunctionNode functionNode = (FunctionNode)symbol.getNode();
-                assert functionNode.getCalleeNode() != null;
-
-                final VarNode var = new VarNode(functionNode.getSource(), functionNode.getToken(), functionNode.getFinish(), functionNode.getIdent(), functionNode.getCalleeNode());
-                //newTemporary(Type.OBJECT, var); //ScriptFunction? TODO
-
-                functionNode.setNeedsSelfSymbol(var);
-            }
-
-            if (!identNode.isInitializedHere()) { // NASHORN-448
+            LOG.info("Existing symbol = ", symbol);
+            if (symbol.isFunctionSelf()) {
+                final FunctionNode functionNode = lc.getDefiningFunction(symbol);
+                assert functionNode != null;
+                assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null;
+                lc.setFlag(functionNode.getBody(), Block.NEEDS_SELF_SYMBOL);
+                newType(symbol, FunctionNode.FUNCTION_TYPE);
+            } else if (!identNode.isInitializedHere()) { // NASHORN-448
                 // here is a use outside the local def scope
                 if (!isLocalDef(name)) {
                     newType(symbol, Type.OBJECT);
@@ -492,25 +496,19 @@
 
             identNode.setSymbol(symbol);
             // non-local: we need to put symbol in scope (if it isn't already)
-            if (!isLocal(getCurrentFunctionNode(), symbol) && !symbol.isScope()) {
-                symbol.setIsScope();
+            if (!isLocal(lc.getCurrentFunction(), symbol) && !symbol.isScope()) {
+                Symbol.setSymbolIsScope(lc, symbol);
             }
         } else {
-            LOG.info("No symbol exists. Declare undefined: " + symbol);
-            symbol = useSymbol(block, name, identNode);
+            LOG.info("No symbol exists. Declare undefined: ", symbol);
+            symbol = defineSymbol(block, name, IS_GLOBAL, identNode);
             // we have never seen this before, it can be undefined
             newType(symbol, Type.OBJECT); // TODO unknown -we have explicit casts anyway?
             symbol.setCanBeUndefined();
-            symbol.setIsScope();
+            Symbol.setSymbolIsScope(lc, symbol);
         }
 
-        assert symbol != null;
-        if(symbol.isGlobal()) {
-            setUsesGlobalSymbol();
-        } else if(symbol.isScope()) {
-            final Iterator<Block> blocks = lexicalContext.getBlocks();
-            blocks.next().setUsesScopeSymbol(symbol, blocks);
-        }
+        setBlockScope(name, symbol);
 
         if (symbol != oldSymbol && !identNode.isInitializedHere()) {
             symbol.increaseUseCount();
@@ -519,7 +517,37 @@
 
         end(identNode);
 
-        return null;
+        return false;
+    }
+
+    private void setBlockScope(final String name, final Symbol symbol) {
+        assert symbol != null;
+        if (symbol.isGlobal()) {
+            setUsesGlobalSymbol();
+            return;
+        }
+
+        if (symbol.isScope()) {
+            final LexicalContext lc = getLexicalContext();
+
+            Block scopeBlock = null;
+            for (final Iterator<LexicalContextNode> contextNodeIter = getLexicalContext().getAllNodes(); contextNodeIter.hasNext(); ) {
+                final LexicalContextNode node = contextNodeIter.next();
+                if (node instanceof Block) {
+                    if (((Block)node).getExistingSymbol(name) != null) {
+                        scopeBlock = (Block)node;
+                        break;
+                    }
+                } else if (node instanceof FunctionNode) {
+                    lc.setFlag(node, FunctionNode.USES_ANCESTOR_SCOPE);
+                }
+            }
+
+            if (scopeBlock != null) {
+                assert getLexicalContext().contains(scopeBlock);
+                lc.setFlag(scopeBlock, Block.NEEDS_SCOPE);
+            }
+        }
     }
 
     /**
@@ -528,35 +556,12 @@
      * @see #needsParentScope()
      */
     private void setUsesGlobalSymbol() {
-        for(final Iterator<FunctionNode> fns = lexicalContext.getFunctions(); fns.hasNext();) {
-            fns.next().setUsesAncestorScope();
+        for (final Iterator<FunctionNode> fns = getLexicalContext().getFunctions(); fns.hasNext();) {
+            getLexicalContext().setFlag(fns.next(), FunctionNode.USES_ANCESTOR_SCOPE);
         }
     }
 
     /**
-     * Declare the use of a symbol in a block.
-     *
-     * @param block block in which the symbol is used
-     * @param name Name of symbol.
-     * @param node Using node
-     *
-     * @return Symbol for given name.
-     */
-    private Symbol useSymbol(final Block block, final String name, final Node node) {
-        Symbol symbol = findSymbol(block, name);
-
-        if (symbol == null) {
-            // If not found, declare as a free var.
-            symbol = defineSymbol(block, name, IS_GLOBAL, node);
-        } else {
-            node.setSymbol(symbol);
-        }
-
-        return symbol;
-    }
-
-
-    /**
      * Search for symbol in the lexical context starting from the given block.
      * @param name Symbol name.
      * @return Found symbol or null if not found.
@@ -564,7 +569,7 @@
     private Symbol findSymbol(final Block block, final String name) {
         // Search up block chain to locate symbol.
 
-        for(final Iterator<Block> blocks = lexicalContext.getBlocks(block); blocks.hasNext();) {
+        for(final Iterator<Block> blocks = getLexicalContext().getBlocks(block); blocks.hasNext();) {
             // Find name.
             final Symbol symbol = blocks.next().getExistingSymbol(name);
             // If found then we are good.
@@ -577,13 +582,13 @@
 
     @Override
     public Node leaveIndexNode(final IndexNode indexNode) {
-        newTemporary(Type.OBJECT, indexNode); //TODO
+        ensureSymbol(Type.OBJECT, indexNode); //TODO
         return indexNode;
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enterLiteralNode(final LiteralNode literalNode) {
+    public boolean enterLiteralNode(final LiteralNode literalNode) {
         try {
             start(literalNode);
             assert !literalNode.isTokenType(TokenType.THIS) : "tokentype for " + literalNode + " is this"; //guard against old dead code case. literal nodes should never inherit tokens
@@ -604,26 +609,33 @@
                 assert !(literalNode.getValue() instanceof Node) : "literals with Node values not supported";
             }
 
-            getCurrentFunctionNode().newLiteral(literalNode);
+            getLexicalContext().getCurrentFunction().newLiteral(literalNode);
         } finally {
             end(literalNode);
         }
-        return null;
+
+        return false;
+    }
+
+    @Override
+    public boolean enterObjectNode(final ObjectNode objectNode) {
+        return start(objectNode);
     }
 
     @Override
     public Node leaveObjectNode(final ObjectNode objectNode) {
-        newTemporary(Type.OBJECT, objectNode);
-        end(objectNode);
-        return objectNode;
+        ensureSymbol(Type.OBJECT, objectNode);
+        return end(objectNode);
     }
 
+    //TODO is this correct why not leave?
     @Override
-    public Node enterPropertyNode(final PropertyNode propertyNode) {
+    public boolean enterPropertyNode(final PropertyNode propertyNode) {
         // assign a pseudo symbol to property name, see NASHORN-710
+        start(propertyNode);
         propertyNode.setSymbol(new Symbol(propertyNode.getKeyName(), 0, Type.OBJECT));
         end(propertyNode);
-        return propertyNode;
+        return true;
     }
 
     @Override
@@ -636,8 +648,10 @@
             if (expr.getType().isUnknown() && symbol.isParam()) {
                 symbol.setType(Type.OBJECT);
             }
-            getCurrentFunctionNode().setReturnType(Type.widest(getCurrentFunctionNode().getReturnType(), symbol.getSymbolType()));
-            LOG.info("Returntype is now " + getCurrentFunctionNode().getReturnType());
+
+            final Type returnType = Type.widest(returnTypes.pop(), symbol.getSymbolType());
+            returnTypes.push(returnType);
+            LOG.info("Returntype is now ", returnType);
         }
 
         end(returnNode);
@@ -649,25 +663,29 @@
     public Node leaveSwitchNode(final SwitchNode switchNode) {
         Type type = Type.UNKNOWN;
 
+        final List<CaseNode> newCases = new ArrayList<>();
         for (final CaseNode caseNode : switchNode.getCases()) {
             final Node test = caseNode.getTest();
+
+            CaseNode newCaseNode = caseNode;
             if (test != null) {
                 if (test instanceof LiteralNode) {
                     //go down to integers if we can
                     final LiteralNode<?> lit = (LiteralNode<?>)test;
                     if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
                         if (JSType.isRepresentableAsInt(lit.getNumber())) {
-                            caseNode.setTest(LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
+                            newCaseNode = caseNode.setTest(LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
                         }
                     }
                 } else {
                     // the "all integer" case that CodeGenerator optimizes for currently assumes literals only
                     type = Type.OBJECT;
-                    break;
                 }
 
-                type = Type.widest(type, caseNode.getTest().getType());
+                type = Type.widest(type, newCaseNode.getTest().getType());
             }
+
+            newCases.add(newCaseNode);
         }
 
         //only optimize for all integers
@@ -675,11 +693,11 @@
             type = Type.OBJECT;
         }
 
-        switchNode.setTag(newInternal(getCurrentFunctionNode().uniqueName(SWITCH_TAG_PREFIX.tag()), type));
+        switchNode.setTag(newInternal(getLexicalContext().getCurrentFunction().uniqueName(SWITCH_TAG_PREFIX.symbolName()), type));
 
         end(switchNode);
 
-        return switchNode;
+        return switchNode.setCases(getLexicalContext(), newCases);
     }
 
     @Override
@@ -696,25 +714,25 @@
     }
 
     @Override
-    public Node enterVarNode(final VarNode varNode) {
+    public boolean enterVarNode(final VarNode varNode) {
         start(varNode);
 
         final IdentNode ident = varNode.getName();
         final String    name  = ident.getName();
 
-        final Symbol symbol = defineSymbol(getCurrentBlock(), name, IS_VAR, ident);
+        final Symbol symbol = defineSymbol(getLexicalContext().getCurrentBlock(), name, IS_VAR, ident);
         assert symbol != null;
 
-        LOG.info("VarNode " + varNode + " set symbol " + symbol);
+        LOG.info("VarNode ", varNode, " set symbol ", symbol);
         varNode.setSymbol(symbol);
 
         // NASHORN-467 - use before definition of vars - conservative
-        if (localUses.contains(ident.getName())) {
+        if (isLocalUse(ident.getName())) {
             newType(symbol, Type.OBJECT);
             symbol.setCanBeUndefined();
         }
 
-        return varNode;
+        return true;
     }
 
     @Override
@@ -734,7 +752,7 @@
         addLocalDef(name);
 
         final Symbol  symbol   = varNode.getSymbol();
-        final boolean isScript = lexicalContext.getFunction(symbol.getBlock()).isProgram(); //see NASHORN-56
+        final boolean isScript = getLexicalContext().getDefiningFunction(symbol).isProgram(); //see NASHORN-56
         if ((init.getType().isNumeric() || init.getType().isBoolean()) && !isScript) {
             // Forbid integers as local vars for now as we have no way to treat them as undefined
             newType(symbol, init.getType());
@@ -751,14 +769,14 @@
 
     @Override
     public Node leaveADD(final UnaryNode unaryNode) {
-        newTemporary(arithType(), unaryNode);
+        ensureSymbol(arithType(), unaryNode);
         end(unaryNode);
         return unaryNode;
     }
 
     @Override
     public Node leaveBIT_NOT(final UnaryNode unaryNode) {
-        newTemporary(Type.INT, unaryNode);
+        ensureSymbol(Type.INT, unaryNode);
         end(unaryNode);
         return unaryNode;
     }
@@ -766,30 +784,29 @@
     @Override
     public Node leaveDECINC(final UnaryNode unaryNode) {
         // @see assignOffset
-        ensureAssignmentSlots(getCurrentFunctionNode(), unaryNode.rhs());
+        ensureAssignmentSlots(getLexicalContext().getCurrentFunction(), unaryNode.rhs());
         final Type type = arithType();
         newType(unaryNode.rhs().getSymbol(), type);
-        newTemporary(type, unaryNode);
+        ensureSymbol(type, unaryNode);
         end(unaryNode);
         return unaryNode;
     }
 
     @Override
     public Node leaveDELETE(final UnaryNode unaryNode) {
-        final FunctionNode   currentFunctionNode = getCurrentFunctionNode();
-        final boolean        strictMode          = currentFunctionNode.isStrictMode();
+        final FunctionNode   currentFunctionNode = getLexicalContext().getCurrentFunction();
+        final boolean        strictMode          = currentFunctionNode.isStrict();
         final Node           rhs                 = unaryNode.rhs();
         final Node           strictFlagNode      = LiteralNode.newInstance(unaryNode, strictMode).accept(this);
 
         Request request = Request.DELETE;
-        final RuntimeNode runtimeNode;
         final List<Node> args = new ArrayList<>();
 
         if (rhs instanceof IdentNode) {
             // If this is a declared variable or a function parameter, delete always fails (except for globals).
             final String name = ((IdentNode)rhs).getName();
 
-            final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !rhs.getSymbol().isTopLevel());
+            final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !isProgramLevelSymbol(name));
 
             if (failDelete && rhs.getSymbol().isThis()) {
                 return LiteralNode.newInstance(unaryNode, true).accept(this);
@@ -797,7 +814,7 @@
             final Node literalNode = LiteralNode.newInstance(unaryNode, name).accept(this);
 
             if (!failDelete) {
-                args.add(currentFunctionNode.getScopeNode());
+                args.add(compilerConstant(SCOPE));
             }
             args.add(literalNode);
             args.add(strictFlagNode);
@@ -825,42 +842,62 @@
             return LiteralNode.newInstance(unaryNode, true).accept(this);
         }
 
-        runtimeNode = new RuntimeNode(unaryNode, request, args);
-        assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //clone constructor should do this
+        final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, request, args);
+        assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //unary parent constructor should do this
 
         return leaveRuntimeNode(runtimeNode);
     }
 
+    /**
+     * Is the symbol denoted by the specified name in the current lexical context defined in the program level
+     * @param name the name of the symbol
+     * @return true if the symbol denoted by the specified name in the current lexical context defined in the program level.
+     */
+    private boolean isProgramLevelSymbol(final String name) {
+        for(final Iterator<Block> it = getLexicalContext().getBlocks(); it.hasNext();) {
+            final Block next = it.next();
+            if(next.getExistingSymbol(name) != null) {
+                return next == getLexicalContext().getFunctionBody(getLexicalContext().getOutermostFunction());
+            }
+        }
+        throw new AssertionError("Couldn't find symbol " + name + " in the context");
+    }
+
     @Override
     public Node leaveNEW(final UnaryNode unaryNode) {
-        newTemporary(Type.OBJECT, unaryNode);
+        ensureSymbol(Type.OBJECT, unaryNode);
         end(unaryNode);
         return unaryNode;
     }
 
     @Override
     public Node leaveNOT(final UnaryNode unaryNode) {
-        newTemporary(Type.BOOLEAN, unaryNode);
+        ensureSymbol(Type.BOOLEAN, unaryNode);
         end(unaryNode);
         return unaryNode;
     }
 
+    private IdentNode compilerConstant(CompilerConstants cc) {
+        final FunctionNode functionNode = getLexicalContext().getCurrentFunction();
+        final IdentNode node = new IdentNode(functionNode.getSource(), functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
+        node.setSymbol(functionNode.compilerConstant(cc));
+        return node;
+    }
+
     @Override
     public Node leaveTYPEOF(final UnaryNode unaryNode) {
-        final Node rhs    = unaryNode.rhs();
-
-        RuntimeNode runtimeNode;
+        final Node rhs = unaryNode.rhs();
 
         List<Node> args = new ArrayList<>();
         if (rhs instanceof IdentNode && !rhs.getSymbol().isParam() && !rhs.getSymbol().isVar()) {
-            args.add(getCurrentFunctionNode().getScopeNode());
+            args.add(compilerConstant(SCOPE));
             args.add(LiteralNode.newInstance(rhs, ((IdentNode)rhs).getName()).accept(this)); //null
         } else {
             args.add(rhs);
             args.add(LiteralNode.newInstance(unaryNode).accept(this)); //null, do not reuse token of identifier rhs, it can be e.g. 'this'
         }
 
-        runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
+        RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
         assert runtimeNode.getSymbol() == unaryNode.getSymbol();
 
         runtimeNode = (RuntimeNode)leaveRuntimeNode(runtimeNode);
@@ -872,21 +909,20 @@
 
     @Override
     public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
-        newTemporary(runtimeNode.getRequest().getReturnType(), runtimeNode);
+        ensureSymbol(runtimeNode.getRequest().getReturnType(), runtimeNode);
         return runtimeNode;
     }
 
     @Override
     public Node leaveSUB(final UnaryNode unaryNode) {
-        newTemporary(arithType(), unaryNode);
+        ensureSymbol(arithType(), unaryNode);
         end(unaryNode);
         return unaryNode;
     }
 
     @Override
     public Node leaveVOID(final UnaryNode unaryNode) {
-        final RuntimeNode runtimeNode = new RuntimeNode(unaryNode, Request.VOID);
-        runtimeNode.accept(this);
+        final RuntimeNode runtimeNode = (RuntimeNode)new RuntimeNode(unaryNode, Request.VOID).accept(this);
         assert runtimeNode.getSymbol().getSymbolType().isObject();
         end(unaryNode);
         return runtimeNode;
@@ -903,7 +939,7 @@
 
         ensureTypeNotUnknown(lhs);
         ensureTypeNotUnknown(rhs);
-        newTemporary(Type.widest(lhs.getType(), rhs.getType()), binaryNode);
+        ensureSymbol(Type.widest(lhs.getType(), rhs.getType()), binaryNode);
 
         end(binaryNode);
 
@@ -912,7 +948,7 @@
 
     @Override
     public Node leaveAND(final BinaryNode binaryNode) {
-        newTemporary(Type.OBJECT, binaryNode);
+        ensureSymbol(Type.OBJECT, binaryNode);
         end(binaryNode);
         return binaryNode;
     }
@@ -921,39 +957,40 @@
      * This is a helper called before an assignment.
      * @param binaryNode assignment node
      */
-    private Node enterAssignmentNode(final BinaryNode binaryNode) {
+    private boolean enterAssignmentNode(final BinaryNode binaryNode) {
         start(binaryNode);
 
         final Node lhs = binaryNode.lhs();
 
         if (lhs instanceof IdentNode) {
-            final Block     block = getCurrentBlock();
-            final IdentNode ident = (IdentNode)lhs;
-            final String    name  = ident.getName();
+            final LexicalContext lc    = getLexicalContext();
+            final Block          block = lc.getCurrentBlock();
+            final IdentNode      ident = (IdentNode)lhs;
+            final String         name  = ident.getName();
 
-            Symbol symbol = findSymbol(getCurrentBlock(), name);
+            Symbol symbol = findSymbol(block, name);
 
             if (symbol == null) {
                 symbol = defineSymbol(block, name, IS_GLOBAL, ident);
                 binaryNode.setSymbol(symbol);
-            } else if (!isLocal(getCurrentFunctionNode(), symbol)) {
-                symbol.setIsScope();
+            } else if (!isLocal(lc.getCurrentFunction(), symbol)) {
+                Symbol.setSymbolIsScope(lc, symbol);
             }
 
             addLocalDef(name);
         }
 
-        return binaryNode;
+        return true;
     }
 
     private boolean isLocal(FunctionNode function, Symbol symbol) {
-        final Block block = symbol.getBlock();
-        // some temp symbols have no block, so can be assumed local
-        return block == null || lexicalContext.getFunction(block) == function;
+        final FunctionNode definingFn = getLexicalContext().getDefiningFunction(symbol);
+        // Temp symbols are not assigned to a block, so their defining fn is null; those can be assumed local
+        return definingFn == null || definingFn == function;
     }
 
     @Override
-    public Node enterASSIGN(final BinaryNode binaryNode) {
+    public boolean enterASSIGN(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -963,7 +1000,7 @@
     }
 
     @Override
-    public Node enterASSIGN_ADD(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_ADD(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -978,7 +1015,7 @@
     }
 
     @Override
-    public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -988,7 +1025,7 @@
     }
 
     @Override
-    public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -998,7 +1035,7 @@
     }
 
     @Override
-    public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1008,7 +1045,7 @@
     }
 
     @Override
-    public Node enterASSIGN_DIV(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_DIV(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1018,7 +1055,7 @@
     }
 
     @Override
-    public Node enterASSIGN_MOD(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_MOD(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1028,7 +1065,7 @@
     }
 
     @Override
-    public Node enterASSIGN_MUL(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_MUL(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1038,7 +1075,7 @@
     }
 
     @Override
-    public Node enterASSIGN_SAR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SAR(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1048,7 +1085,7 @@
     }
 
     @Override
-    public Node enterASSIGN_SHL(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SHL(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1058,7 +1095,7 @@
     }
 
     @Override
-    public Node enterASSIGN_SHR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SHR(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1068,7 +1105,7 @@
     }
 
     @Override
-    public Node enterASSIGN_SUB(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SUB(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
     }
 
@@ -1094,13 +1131,13 @@
 
     @Override
     public Node leaveCOMMARIGHT(final BinaryNode binaryNode) {
-        newTemporary(binaryNode.rhs().getType(), binaryNode);
+        ensureSymbol(binaryNode.rhs().getType(), binaryNode);
         return binaryNode;
     }
 
     @Override
     public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
-        newTemporary(binaryNode.lhs().getType(), binaryNode);
+        ensureSymbol(binaryNode.lhs().getType(), binaryNode);
         return binaryNode;
     }
 
@@ -1113,7 +1150,7 @@
         final Node lhs = binaryNode.lhs();
         final Node rhs = binaryNode.rhs();
 
-        newTemporary(Type.BOOLEAN, binaryNode);
+        ensureSymbol(Type.BOOLEAN, binaryNode);
         ensureTypeNotUnknown(lhs);
         ensureTypeNotUnknown(rhs);
 
@@ -1131,7 +1168,7 @@
 
         //newType(binaryNode.lhs().getSymbol(), operandType);
         //newType(binaryNode.rhs().getSymbol(), operandType);
-        newTemporary(destType, binaryNode);
+        ensureSymbol(destType, binaryNode);
         return binaryNode;
     }
 
@@ -1216,7 +1253,7 @@
 
     @Override
     public Node leaveOR(final BinaryNode binaryNode) {
-        newTemporary(Type.OBJECT, binaryNode);
+        ensureSymbol(Type.OBJECT, binaryNode);
         end(binaryNode);
         return binaryNode;
     }
@@ -1244,7 +1281,7 @@
     @Override
     public Node leaveForNode(final ForNode forNode) {
         if (forNode.isForIn()) {
-            forNode.setIterator(newInternal(getCurrentFunctionNode().uniqueName(ITERATOR_PREFIX.tag()), Type.OBJECT)); //NASHORN-73
+            forNode.setIterator(newInternal(getLexicalContext().getCurrentFunction().uniqueName(ITERATOR_PREFIX.symbolName()), 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
@@ -1267,65 +1304,50 @@
         ensureTypeNotUnknown(rhs);
 
         final Type type = Type.widest(lhs.getType(), rhs.getType());
-        newTemporary(type, ternaryNode);
+        ensureSymbol(type, ternaryNode);
 
         end(ternaryNode);
+        assert ternaryNode.getSymbol() != null;
 
         return ternaryNode;
     }
 
-    private void initThis(final FunctionNode functionNode) {
-        final Symbol thisSymbol = defineSymbol(functionNode, THIS.tag(), IS_PARAM | IS_THIS, null);
+    private void initThis(final Block block) {
+        final Symbol thisSymbol = defineSymbol(block, THIS.symbolName(), IS_PARAM | IS_THIS, null);
         newType(thisSymbol, Type.OBJECT);
         thisSymbol.setNeedsSlot(true);
-        functionNode.getThisNode().setSymbol(thisSymbol);
-        LOG.info("Initialized scope symbol: " + thisSymbol);
     }
 
-    private void initScope(final FunctionNode functionNode) {
-        final Symbol scopeSymbol = defineSymbol(functionNode, SCOPE.tag(), IS_VAR | IS_INTERNAL, null);
+    private void initScope(final Block block) {
+        final Symbol scopeSymbol = defineSymbol(block, SCOPE.symbolName(), IS_VAR | IS_INTERNAL, null);
         newType(scopeSymbol, Type.typeFor(ScriptObject.class));
         scopeSymbol.setNeedsSlot(true);
-        functionNode.getScopeNode().setSymbol(scopeSymbol);
-        LOG.info("Initialized scope symbol: " + scopeSymbol);
     }
 
-    private void initReturn(final FunctionNode functionNode) {
-        final Symbol returnSymbol = defineSymbol(functionNode, SCRIPT_RETURN.tag(), IS_VAR | IS_INTERNAL, null);
+    private void initReturn(final Block block) {
+        final Symbol returnSymbol = defineSymbol(block, RETURN.symbolName(), IS_VAR | IS_INTERNAL, null);
         newType(returnSymbol, Type.OBJECT);
         returnSymbol.setNeedsSlot(true);
-        functionNode.getResultNode().setSymbol(returnSymbol);
-        LOG.info("Initialized return symbol: " + returnSymbol);
         //return symbol is always object as it's the __return__ thing. What returnType is is another matter though
     }
 
-    private void initVarArg(final FunctionNode functionNode) {
-        if (functionNode.isVarArg()) {
-            final Symbol varArgsSymbol = defineSymbol(functionNode, VARARGS.tag(), IS_PARAM | IS_INTERNAL, null);
-            varArgsSymbol.setTypeOverride(Type.OBJECT_ARRAY);
-            varArgsSymbol.setNeedsSlot(true);
-            functionNode.getVarArgsNode().setSymbol(varArgsSymbol);
-            LOG.info("Initialized varargs symbol: " + varArgsSymbol);
+    private void initVarArg(final Block block, final boolean needsArguments) {
+        final Symbol varArgsSymbol = defineSymbol(block, VARARGS.symbolName(), IS_PARAM | IS_INTERNAL, null);
+        varArgsSymbol.setTypeOverride(Type.OBJECT_ARRAY);
+        varArgsSymbol.setNeedsSlot(true);
 
-            if (functionNode.needsArguments()) {
-                final String    argumentsName   = functionNode.getArgumentsNode().getName();
-                final Symbol    argumentsSymbol = defineSymbol(functionNode, argumentsName, IS_VAR | IS_INTERNAL, null);
-                newType(argumentsSymbol, Type.typeFor(ScriptObject.class));
-                argumentsSymbol.setNeedsSlot(true);
-                functionNode.getArgumentsNode().setSymbol(argumentsSymbol);
-                addLocalDef(argumentsName);
-                LOG.info("Initialized vararg varArgsSymbol=" + varArgsSymbol + " argumentsSymbol=" + argumentsSymbol);
-            }
+        if (needsArguments) {
+            final Symbol    argumentsSymbol = defineSymbol(block, ARGUMENTS.symbolName(), IS_VAR | IS_INTERNAL, null);
+            newType(argumentsSymbol, Type.typeFor(ScriptObject.class));
+            argumentsSymbol.setNeedsSlot(true);
+            addLocalDef(ARGUMENTS.symbolName());
         }
     }
 
-    private void initCallee(final FunctionNode functionNode) {
-        assert functionNode.getCalleeNode() != null : functionNode + " has no callee";
-        final Symbol calleeSymbol = defineSymbol(functionNode, CALLEE.tag(), IS_PARAM | IS_INTERNAL, null);
-        newType(calleeSymbol, Type.typeFor(ScriptFunction.class));
+    private void initCallee(final Block block) {
+        final Symbol calleeSymbol = defineSymbol(block, CALLEE.symbolName(), IS_PARAM | IS_INTERNAL, null);
+        newType(calleeSymbol, FunctionNode.FUNCTION_TYPE);
         calleeSymbol.setNeedsSlot(true);
-        functionNode.getCalleeNode().setSymbol(calleeSymbol);
-        LOG.info("Initialized callee symbol " + calleeSymbol);
     }
 
     /**
@@ -1334,25 +1356,19 @@
      *
      * @param functionNode the function node
      */
-    private void initParameters(final FunctionNode functionNode) {
-        //If a function is specialized, we don't need to tag either it return
-        // type or its parameters with the widest (OBJECT) type for safety.
-        functionNode.setReturnType(Type.UNKNOWN);
-
+    private void initParameters(final FunctionNode functionNode, final Block body) {
         for (final IdentNode param : functionNode.getParameters()) {
             addLocalDef(param.getName());
-            final Symbol paramSymbol = defineSymbol(functionNode, param.getName(), IS_PARAM, param);
+            final Symbol paramSymbol = defineSymbol(body, param.getName(), IS_PARAM, param);
             if (paramSymbol != null) {
                 final Type callSiteParamType = functionNode.getSpecializedType(param);
                 if (callSiteParamType != null) {
-                    LOG.info("Param " + paramSymbol + " has a callsite type " + callSiteParamType + ". Using that.");
-
-                    System.err.println("Param " + param + " has a callsite type " + callSiteParamType + ". Using that.");
+                    LOG.info("Param ", paramSymbol, " has a callsite type ", callSiteParamType, ". Using that.");
                 }
                 newType(paramSymbol, callSiteParamType == null ? Type.UNKNOWN : callSiteParamType);
             }
 
-            LOG.info("Initialized param " + paramSymbol);
+            LOG.info("Initialized param ", paramSymbol);
         }
     }
 
@@ -1378,7 +1394,7 @@
             // this function, we can tell the runtime system that no matter what the
             // call site is, use this information. TODO
             if (!paramSymbol.getSymbolType().isObject()) {
-                LOG.finest("Parameter " + ident + " could profit from specialization to " + paramSymbol.getSymbolType());
+                LOG.finest("Parameter ", ident, " could profit from specialization to ", paramSymbol.getSymbolType());
             }
 
             newType(paramSymbol, Type.widest(type, paramSymbol.getSymbolType()));
@@ -1392,19 +1408,18 @@
 
     /**
      * Move any properties from a global map into the scope of this method
-     * @param functionNode the function node for which to init scope vars
+     * @param block the function node body for which to init scope vars
      */
-    private void initFromPropertyMap(final FunctionNode functionNode) {
+    private void initFromPropertyMap(final Block block) {
         // For a script, add scope symbols as defined in the property map
-        assert functionNode.isProgram();
 
         final PropertyMap map = Context.getGlobalMap();
 
         for (final Property property : map.getProperties()) {
             final String key    = property.getKey();
-            final Symbol symbol = defineSymbol(functionNode, key, IS_GLOBAL, null);
+            final Symbol symbol = defineSymbol(block, key, IS_GLOBAL, null);
             newType(symbol, Type.OBJECT);
-            LOG.info("Added global symbol from property map " + symbol);
+            LOG.info("Added global symbol from property map ", symbol);
         }
     }
 
@@ -1412,7 +1427,7 @@
 
         final Symbol symbol = node.getSymbol();
 
-        LOG.info("Ensure type not unknown for: " + symbol);
+        LOG.info("Ensure type not unknown for: ", symbol);
 
         /*
          * Note that not just unknowns, but params need to be blown
@@ -1452,7 +1467,7 @@
     }
 
     private Symbol exceptionSymbol() {
-        return newInternal(getCurrentFunctionNode().uniqueName(EXCEPTION_PREFIX.tag()), Type.typeFor(ECMAException.class));
+        return newInternal(getLexicalContext().getCurrentFunction().uniqueName(EXCEPTION_PREFIX.symbolName()), Type.typeFor(ECMAException.class));
     }
 
     /**
@@ -1512,15 +1527,15 @@
                     }
                     Type from = node.getType();
                     if (!Type.areEquivalent(from, to) && Type.widest(from, to) == to) {
-                        LOG.fine("Had to post pass widen '" + node + "' " + Debug.id(node) + " from " + node.getType() + " to " + to);
+                        LOG.fine("Had to post pass widen '", node, "' " + Debug.id(node), " from ", node.getType(), " to ", to);
                         newType(node.getSymbol(), to);
                         changed.add(node);
                     }
                 }
 
                 @Override
-                public Node enterFunctionNode(final FunctionNode node) {
-                    return node.isLazy() ? null : node;
+                public boolean enterFunctionNode(final FunctionNode node) {
+                    return !node.isLazy();
                 }
 
                 /**
@@ -1574,7 +1589,7 @@
         } else {
             type = Type.OBJECT; //force lhs to be an object if not numeric assignment, e.g. strings too.
         }
-        newTemporary(type, binaryNode);
+        ensureSymbol(type, binaryNode);
         newType(lhs.getSymbol(), type);
         end(binaryNode);
         return binaryNode;
@@ -1589,32 +1604,25 @@
         final Node lhs = binaryNode.lhs();
 
         newType(lhs.getSymbol(), destType); //may not narrow if dest is already wider than destType
-        newTemporary(destType, binaryNode); //for OP= nodes, the node can carry a narrower types than its lhs rhs. This is perfectly fine
+        ensureSymbol(destType, binaryNode); //for OP= nodes, the node can carry a narrower types than its lhs rhs. This is perfectly fine
 
-        ensureAssignmentSlots(getCurrentFunctionNode(), binaryNode);
+        ensureAssignmentSlots(getLexicalContext().getCurrentFunction(), binaryNode);
 
         end(binaryNode);
         return binaryNode;
     }
 
-    private static boolean isFunctionExpressionSelfReference(final Symbol symbol) {
-        if (symbol.isVar() && symbol.getNode() == symbol.getBlock() && symbol.getNode() instanceof FunctionNode) {
-            return ((FunctionNode)symbol.getNode()).getIdent().getName().equals(symbol.getName());
-        }
-        return false;
+    private Symbol ensureSymbol(final FunctionNode functionNode, final Type type, final Node node) {
+        LOG.info("New TEMPORARY added to ", functionNode.getName(), " type=", type);
+        return functionNode.ensureSymbol(getLexicalContext().getCurrentBlock(), type, node);
     }
 
-    private static Symbol newTemporary(final FunctionNode functionNode, final Type type, final Node node) {
-        LOG.info("New TEMPORARY added to " + functionNode.getName() + " type=" + type);
-        return functionNode.newTemporary(type, node);
-    }
-
-    private Symbol newTemporary(final Type type, final Node node) {
-        return newTemporary(getCurrentFunctionNode(), type, node);
+    private Symbol ensureSymbol(final Type type, final Node node) {
+        return ensureSymbol(getLexicalContext().getCurrentFunction(), type, node);
     }
 
     private Symbol newInternal(final String name, final Type type) {
-        final Symbol iter = defineSymbol(getCurrentFunctionNode(), name, IS_VAR | IS_INTERNAL, null);
+        final Symbol iter = defineSymbol(getLexicalContext().getCurrentBlock(), name, IS_VAR | IS_INTERNAL, null);
         iter.setType(type); // NASHORN-73
         return iter;
     }
@@ -1624,40 +1632,51 @@
         symbol.setType(type);
 
         if (symbol.getSymbolType() != oldType) {
-            LOG.info("New TYPE " + type + " for " + symbol + " (was " + oldType + ")");
+            LOG.info("New TYPE ", type, " for ", symbol," (was ", oldType, ")");
         }
 
         if (symbol.isParam()) {
             symbol.setType(type);
-            LOG.info("Param type change " + symbol);
+            LOG.info("Param type change ", symbol);
         }
     }
 
-    private void clearLocalDefs() {
-        localDefs = new HashSet<>();
+    private void pushLocalsFunction() {
+        localDefs.push(new HashSet<String>());
+        localUses.push(new HashSet<String>());
+    }
+
+    private void pushLocalsBlock() {
+        localDefs.push(localDefs.isEmpty() ? new HashSet<String>() : new HashSet<>(localDefs.peek()));
+        localUses.push(localUses.isEmpty() ? new HashSet<String>() : new HashSet<>(localUses.peek()));
+    }
+
+    private void popLocals() {
+        localDefs.pop();
+        localUses.pop();
     }
 
     private boolean isLocalDef(final String name) {
-        return localDefs.contains(name);
+        return localDefs.peek().contains(name);
     }
 
     private void addLocalDef(final String name) {
-        LOG.info("Adding local def of symbol: '" + name + "'");
-        localDefs.add(name);
+        LOG.info("Adding local def of symbol: '", name, "'");
+        localDefs.peek().add(name);
     }
 
     private void removeLocalDef(final String name) {
-        LOG.info("Removing local def of symbol: '" + name + "'");
-        localDefs.remove(name);
+        LOG.info("Removing local def of symbol: '", name, "'");
+        localDefs.peek().remove(name);
     }
 
-    private void clearLocalUses() {
-        localUses = new HashSet<>();
+    private boolean isLocalUse(final String name) {
+        return localUses.peek().contains(name);
     }
 
     private void addLocalUse(final String name) {
-        LOG.info("Adding local use of symbol: '" + name + "'");
-        localUses.add(name);
+        LOG.info("Adding local use of symbol: '", name, "'");
+        localUses.peek().add(name);
     }
 
     /**
@@ -1665,30 +1684,28 @@
      * This is done when the function contains unevaluated black boxes such as
      * lazy sub-function nodes that have not been compiled.
      *
-     * @param functionNode function node in whose scope symbols should conservatively be made objects
+     * @param body body for the function node we are leaving
      */
-    private static void objectifySymbols(final FunctionNode functionNode) {
-        functionNode.accept(new NodeVisitor() {
+    private static void objectifySymbols(final Block body) {
+        body.accept(new NodeVisitor() {
             private void toObject(final Block block) {
                 for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext();) {
                     final Symbol symbol = iter.next();
-                    newType(symbol, Type.OBJECT);
+                    if (!symbol.isTemp()) {
+                        newType(symbol, Type.OBJECT);
+                    }
                 }
             }
 
             @Override
-            public Node enterBlock(final Block block) {
+            public boolean enterBlock(final Block block) {
                 toObject(block);
-                return block;
+                return true;
             }
 
             @Override
-            public Node enterFunctionNode(final FunctionNode node) {
-                toObject(node);
-                if (node.isLazy()) {
-                    return null;
-                }
-                return node;
+            public boolean enterFunctionNode(final FunctionNode node) {
+                return false;
             }
         });
     }
@@ -1702,11 +1719,11 @@
         return cn.substring(lastDot + 1);
     }
 
-    private Node start(final Node node) {
+    private boolean start(final Node node) {
         return start(node, true);
     }
 
-    private Node start(final Node node, final boolean printNode) {
+    private boolean start(final Node node, final boolean printNode) {
         if (DEBUG) {
             final StringBuilder sb = new StringBuilder();
 
@@ -1715,13 +1732,13 @@
                 append("] ").
                 append(printNode ? node.toString() : "").
                 append(" in '").
-                append(getCurrentFunctionNode().getName()).
+                append(getLexicalContext().getCurrentFunction().getName()).
                 append("'");
-            LOG.info(sb.toString());
+            LOG.info(sb);
             LOG.indent();
         }
 
-        return node;
+        return true;
     }
 
     private Node end(final Node node) {
@@ -1737,7 +1754,7 @@
                 append("] ").
                 append(printNode ? node.toString() : "").
                 append(" in '").
-                append(getCurrentFunctionNode().getName());
+                append(getLexicalContext().getCurrentFunction().getName());
 
             if (node.getSymbol() == null) {
                 sb.append(" <NO SYMBOL>");
@@ -1746,7 +1763,7 @@
             }
 
             LOG.unindent();
-            LOG.info(sb.toString());
+            LOG.info(sb);
         }
 
         return node;
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
index 7ca7f99..2d37918 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
@@ -58,12 +58,14 @@
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Set;
+
 import jdk.internal.org.objectweb.asm.ClassReader;
 import jdk.internal.org.objectweb.asm.ClassWriter;
 import jdk.internal.org.objectweb.asm.MethodVisitor;
 import jdk.internal.org.objectweb.asm.util.TraceClassVisitor;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.SplitNode;
 import jdk.nashorn.internal.runtime.PropertyMap;
 import jdk.nashorn.internal.runtime.ScriptEnvironment;
 import jdk.nashorn.internal.runtime.ScriptObject;
@@ -219,14 +221,14 @@
     private void defineCommonStatics(final boolean strictMode) {
         // source - used to store the source data (text) for this script.  Shared across
         // compile units.  Set externally by the compiler.
-        field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), SOURCE.tag(), Source.class);
+        field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), SOURCE.symbolName(), Source.class);
 
         // constants - used to the constants array for this script.  Shared across
         // compile units.  Set externally by the compiler.
-        field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), CONSTANTS.tag(), Object[].class);
+        field(EnumSet.of(Flag.PRIVATE, Flag.STATIC), CONSTANTS.symbolName(), Object[].class);
 
         // strictMode - was this script compiled in strict mode.  Set externally by the compiler.
-        field(EnumSet.of(Flag.PUBLIC, Flag.STATIC, Flag.FINAL), STRICT_MODE.tag(), boolean.class, strictMode);
+        field(EnumSet.of(Flag.PUBLIC, Flag.STATIC, Flag.FINAL), STRICT_MODE.symbolName(), boolean.class, strictMode);
     }
 
     /**
@@ -238,9 +240,9 @@
 
         if (constantMethodNeeded.contains(String.class)) {
             // $getString - get the ith entry from the constants table and cast to String.
-            final MethodEmitter getStringMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), GET_STRING.tag(), String.class, int.class);
+            final MethodEmitter getStringMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), GET_STRING.symbolName(), String.class, int.class);
             getStringMethod.begin();
-            getStringMethod.getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor())
+            getStringMethod.getStatic(unitClassName, CONSTANTS.symbolName(), CONSTANTS.descriptor())
                         .load(Type.INT, 0)
                         .arrayload()
                         .checkcast(String.class)
@@ -250,7 +252,7 @@
 
         if (constantMethodNeeded.contains(PropertyMap.class)) {
             // $getMap - get the ith entry from the constants table and cast to PropertyMap.
-            final MethodEmitter getMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), GET_MAP.tag(), PropertyMap.class, int.class);
+            final MethodEmitter getMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), GET_MAP.symbolName(), PropertyMap.class, int.class);
             getMapMethod.begin();
             getMapMethod.loadConstants()
                         .load(Type.INT, 0)
@@ -260,7 +262,7 @@
             getMapMethod.end();
 
             // $setMap - overwrite an existing map.
-            final MethodEmitter setMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SET_MAP.tag(), void.class, int.class, PropertyMap.class);
+            final MethodEmitter setMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SET_MAP.symbolName(), void.class, int.class, PropertyMap.class);
             setMapMethod.begin();
             setMapMethod.loadConstants()
                         .load(Type.INT, 0)
@@ -289,7 +291,7 @@
         final MethodEmitter getArrayMethod = method(EnumSet.of(Flag.PRIVATE, Flag.STATIC), methodName, cls, int.class);
 
         getArrayMethod.begin();
-        getArrayMethod.getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor())
+        getArrayMethod.getStatic(unitClassName, CONSTANTS.symbolName(), CONSTANTS.descriptor())
                       .load(Type.INT, 0)
                       .arrayload()
                       .checkcast(cls)
@@ -307,7 +309,7 @@
      */
     static String getArrayMethodName(final Class<?> cls) {
         assert cls.isArray();
-        return GET_ARRAY_PREFIX.tag() + cls.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.tag();
+        return GET_ARRAY_PREFIX.symbolName() + cls.getComponentType().getSimpleName() + GET_ARRAY_SUFFIX.symbolName();
     }
 
     /**
@@ -409,6 +411,10 @@
         methodsStarted.remove(method);
     }
 
+    SplitMethodEmitter method(final SplitNode splitNode, final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
+        return new SplitMethodEmitter(this, methodVisitor(EnumSet.of(Flag.PUBLIC, Flag.STATIC), methodName, rtype, ptypes), splitNode);
+    }
+
     /**
      * Add a new method to the class - defaults to public method
      *
@@ -433,7 +439,7 @@
      * @return method emitter to use for weaving this method
      */
     MethodEmitter method(final EnumSet<Flag> methodFlags, final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
-        return new MethodEmitter(this, cw.visitMethod(Flag.getValue(methodFlags), methodName, methodDescriptor(rtype, ptypes), null, null));
+        return new MethodEmitter(this, methodVisitor(methodFlags, methodName, rtype, ptypes));
     }
 
     /**
@@ -484,7 +490,7 @@
      * @return method emitter to use for weaving <clinit>
      */
     MethodEmitter clinit() {
-        return method(EnumSet.of(Flag.STATIC), CLINIT.tag(), void.class);
+        return method(EnumSet.of(Flag.STATIC), CLINIT.symbolName(), void.class);
     }
 
     /**
@@ -493,7 +499,7 @@
      * @return method emitter to use for weaving <init>()V
      */
     MethodEmitter init() {
-        return method(INIT.tag(), void.class);
+        return method(INIT.symbolName(), void.class);
     }
 
     /**
@@ -503,7 +509,7 @@
      * @return method emitter to use for weaving <init>()V
      */
     MethodEmitter init(final Class<?>... ptypes) {
-        return method(INIT.tag(), void.class, ptypes);
+        return method(INIT.symbolName(), void.class, ptypes);
     }
 
     /**
@@ -515,7 +521,7 @@
      * @return method emitter to use for weaving <init>(...)V
      */
     MethodEmitter init(final EnumSet<Flag> flags, final Class<?>... ptypes) {
-        return method(flags, INIT.tag(), void.class, ptypes);
+        return method(flags, INIT.symbolName(), void.class, ptypes);
     }
 
     /**
@@ -628,4 +634,9 @@
             return v;
         }
     }
+
+    private MethodVisitor methodVisitor(EnumSet<Flag> flags, final String methodName, final Class<?> rtype, final Class<?>... ptypes) {
+        return cw.visitMethod(Flag.getValue(flags), methodName, methodDescriptor(rtype, ptypes), null, null);
+    }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
index 1d09e9c..15631ad 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
@@ -27,14 +27,18 @@
 
 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.ARGUMENTS;
+import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
 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;
 import static jdk.nashorn.internal.codegen.CompilerConstants.QUICK_PREFIX;
 import static jdk.nashorn.internal.codegen.CompilerConstants.REGEX_PREFIX;
+import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
 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.THIS;
+import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.interfaceCallNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
@@ -48,8 +52,10 @@
 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT;
 
 import java.io.PrintWriter;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Deque;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -67,11 +73,11 @@
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.BreakNode;
+import jdk.nashorn.internal.ir.BreakableNode;
 import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.EmptyNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
@@ -85,6 +91,7 @@
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
+import jdk.nashorn.internal.ir.LoopNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ObjectNode;
 import jdk.nashorn.internal.ir.PropertyNode;
@@ -107,6 +114,7 @@
 import jdk.nashorn.internal.parser.Lexer.RegexToken;
 import jdk.nashorn.internal.parser.TokenType;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.Debug;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.ECMAException;
 import jdk.nashorn.internal.runtime.Property;
@@ -157,14 +165,29 @@
     /** How many regexp fields have been emitted */
     private int regexFieldCount;
 
-    /** Used for temporary signaling between enterCallNode and enterFunctionNode to handle the special case of calling
-     * a just-defined anonymous function expression. */
-    private boolean functionNodeIsCallee;
-
     /** Map of shared scope call sites */
     private final Map<SharedScopeCall, SharedScopeCall> scopeCalls = new HashMap<>();
 
-    private final LexicalContext lexicalContext = new LexicalContext();
+    /** Compile unit stack - every time we start a sub method (e.g. a split) we push one */
+    private final Deque<CompileUnit> compileUnits = new ArrayDeque<>();
+
+    /** Method emitter stack - every time we start a sub method (e.g. a split) we push one */
+    private final Deque<MethodEmitter> methodEmitters = new ArrayDeque<>();
+
+    /** The discard stack - whenever we enter a discard node we keep track of its return value status -
+     *  i.e. should we keep it or throw it away */
+    private final Deque<Node> discard = new ArrayDeque<>();
+
+    // A stack tracking the next free local variable slot in the blocks. There's one entry for every block
+    // currently on the lexical context stack.
+    private int[] nextFreeSlots = new int[16];
+    private int nextFreeSlotsSize = 0;
+
+    /** Current method emitter */
+    private MethodEmitter method;
+
+    /** Current compile unit */
+    private CompileUnit unit;
 
     /** When should we stop caching regexp expressions in fields to limit bytecode size? */
     private static final int MAX_REGEX_FIELDS = 2 * 1024;
@@ -188,7 +211,37 @@
      * @return the correct flags for a call site in the current function
      */
     int getCallSiteFlags() {
-        return getCurrentFunctionNode().isStrictMode() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags;
+        return getLexicalContext().getCurrentFunction().isStrict() ? callSiteFlags | CALLSITE_STRICT : callSiteFlags;
+    }
+
+    private void pushMethodEmitter(final MethodEmitter newMethod) {
+        methodEmitters.push(newMethod);
+        this.method = newMethod;
+    }
+
+    private void popMethodEmitter(final MethodEmitter oldMethod) {
+        assert methodEmitters.peek() == oldMethod;
+        methodEmitters.pop();
+        if (!methodEmitters.isEmpty()) {
+            this.method = methodEmitters.peek();
+        } else {
+            this.method = null;
+        }
+    }
+
+    private void push(final CompileUnit newUnit) {
+        compileUnits.push(newUnit);
+        this.unit = newUnit;
+    }
+
+    private void pop(final CompileUnit oldUnit) {
+        assert compileUnits.peek() == oldUnit;
+        compileUnits.pop();
+        if (!compileUnits.isEmpty()) {
+            this.unit = compileUnits.peek();
+        } else {
+            this.unit = null;
+        }
     }
 
     /**
@@ -217,7 +270,7 @@
             assert identNode.getSymbol().isScope() : identNode + " is not in scope!";
 
             final int flags = CALLSITE_SCOPE | getCallSiteFlags();
-            method.loadScope();
+            method.loadCompilerConstant(SCOPE);
 
             if (isFastScope(symbol)) {
                 // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope.
@@ -237,11 +290,11 @@
      * @return true if fast scope
      */
     private boolean isFastScope(final Symbol symbol) {
-        if (!symbol.isScope() || !symbol.getBlock().needsScope()) {
+        if (!symbol.isScope() || !getLexicalContext().getDefiningBlock(symbol).needsScope()) {
             return false;
         }
         // Allow fast scope access if no function contains with or eval
-        for(final Iterator<FunctionNode> it = lexicalContext.getFunctions(getCurrentFunctionNode()); it.hasNext();) {
+        for (final Iterator<FunctionNode> it = getLexicalContext().getFunctions(); it.hasNext();) {
             final FunctionNode func = it.next();
             if (func.hasWith() || func.hasEval()) {
                 return false;
@@ -251,7 +304,7 @@
     }
 
     private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) {
-        method.load(isFastScope(symbol) ? getScopeProtoDepth(getCurrentBlock(), symbol) : -1);
+        method.load(isFastScope(symbol) ? getScopeProtoDepth(getLexicalContext().getCurrentBlock(), symbol) : -1);
         final SharedScopeCall scopeCall = getScopeGet(valueType, symbol, flags | CALLSITE_FAST_SCOPE);
         scopeCall.generateInvoke(method);
         return method;
@@ -271,10 +324,10 @@
 
     private int getScopeProtoDepth(final Block startingBlock, final Symbol symbol) {
         int depth = 0;
-        final Block definingBlock = symbol.getBlock();
-        for(final Iterator<Block> blocks = lexicalContext.getBlocks(startingBlock); blocks.hasNext();) {
+        final String name = symbol.getName();
+        for(final Iterator<Block> blocks = getLexicalContext().getBlocks(startingBlock); blocks.hasNext();) {
             final Block currentBlock = blocks.next();
-            if (currentBlock == definingBlock) {
+            if (currentBlock.getExistingSymbol(name) == symbol) {
                 return depth;
             }
             if (currentBlock.needsScope()) {
@@ -285,9 +338,9 @@
     }
 
     private void loadFastScopeProto(final Symbol symbol, final boolean swap) {
-        final int depth = getScopeProtoDepth(getCurrentBlock(), symbol);
+        final int depth = getScopeProtoDepth(getLexicalContext().getCurrentBlock(), symbol);
         assert depth != -1;
-        if(depth > 0) {
+        if (depth > 0) {
             if (swap) {
                 method.swap();
             }
@@ -328,46 +381,46 @@
          */
         final CodeGenerator codegen = this;
 
-        node.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
+        node.accept(new NodeVisitor() {
             @Override
-            public Node enterIdentNode(final IdentNode identNode) {
+            public boolean enterIdentNode(final IdentNode identNode) {
                 loadIdent(identNode);
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterAccessNode(final AccessNode accessNode) {
+            public boolean enterAccessNode(final AccessNode accessNode) {
                 if (!baseAlreadyOnStack) {
                     load(accessNode.getBase()).convert(Type.OBJECT);
                 }
                 assert method.peekType().isObject();
                 method.dynamicGet(node.getType(), accessNode.getProperty().getName(), getCallSiteFlags(), accessNode.isFunction());
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterIndexNode(final IndexNode indexNode) {
+            public boolean enterIndexNode(final IndexNode indexNode) {
                 if (!baseAlreadyOnStack) {
                     load(indexNode.getBase()).convert(Type.OBJECT);
                     load(indexNode.getIndex());
                 }
                 method.dynamicGetIndex(node.getType(), getCallSiteFlags(), indexNode.isFunction());
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterFunctionNode(FunctionNode functionNode) {
+            public boolean enterFunctionNode(FunctionNode functionNode) {
                 // function nodes will always leave a constructed function object on stack, no need to load the symbol
                 // separately as in enterDefault()
                 functionNode.accept(codegen);
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterDefault(final Node otherNode) {
+            public boolean enterDefault(final Node otherNode) {
                 otherNode.accept(codegen); // generate code for whatever we are looking at.
                 method.load(symbol); // load the final symbol to the stack (or nop if no slot, then result is already there)
-                return null;
+                return false;
             }
         });
 
@@ -375,14 +428,9 @@
     }
 
     @Override
-    public Node enterAccessNode(final AccessNode accessNode) {
-        if (accessNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterAccessNode(final AccessNode accessNode) {
         load(accessNode);
-
-        return null;
+        return false;
     }
 
     /**
@@ -407,7 +455,7 @@
             final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis() || !symbol.canBeUndefined();
 
             if (symbol.hasSlot() && !isInternal) {
-                assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + getCurrentFunctionNode();
+                assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + getLexicalContext().getCurrentFunction();
                 if (symbol.getSymbolType().isNumber()) {
                     numbers.add(symbol);
                 } else if (symbol.getSymbolType().isObject()) {
@@ -441,22 +489,20 @@
      * @param block block containing symbols.
      */
     private void symbolInfo(final Block block) {
-        for (final Symbol symbol : block.getFrame().getSymbols()) {
-            method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel());
+        for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext(); ) {
+            final Symbol symbol = iter.next();
+            if (symbol.hasSlot()) {
+                method.localVariable(symbol, block.getEntryLabel(), block.getBreakLabel());
+            }
         }
     }
 
     @Override
-    public Node enterBlock(final Block block) {
-        if (block.testResolved()) {
-            return null;
-        }
-        lexicalContext.push(block);
-
+    public boolean enterBlock(final Block block) {
         method.label(block.getEntryLabel());
         initLocals(block);
 
-        return block;
+        return true;
     }
 
     @Override
@@ -464,10 +510,10 @@
         method.label(block.getBreakLabel());
         symbolInfo(block);
 
-        if (block.needsScope()) {
+        if (block.needsScope() && !block.isTerminal()) {
             popBlockScope(block);
         }
-        lexicalContext.pop(block);
+        --nextFreeSlotsSize;
         return block;
     }
 
@@ -477,34 +523,30 @@
         final Label skipLabel     = new Label("skip_catch");
 
         /* pop scope a la try-finally */
-        method.loadScope();
+        method.loadCompilerConstant(SCOPE);
         method.invoke(ScriptObject.GET_PROTO);
-        method.storeScope();
+        method.storeCompilerConstant(SCOPE);
         method._goto(skipLabel);
         method.label(exitLabel);
 
         method._catch(recoveryLabel);
-        method.loadScope();
+        method.loadCompilerConstant(SCOPE);
         method.invoke(ScriptObject.GET_PROTO);
-        method.storeScope();
+        method.storeCompilerConstant(SCOPE);
         method.athrow();
         method.label(skipLabel);
         method._try(block.getEntryLabel(), exitLabel, recoveryLabel, Throwable.class);
     }
 
     @Override
-    public Node enterBreakNode(final BreakNode breakNode) {
-        if (breakNode.testResolved()) {
-            return null;
-        }
-
-        for (int i = 0; i < breakNode.getScopeNestingLevel(); i++) {
+    public boolean enterBreakNode(final BreakNode breakNode) {
+        final BreakableNode breakFrom = getLexicalContext().getBreakable(breakNode.getLabel());
+        for (int i = 0; i < getLexicalContext().getScopeNestingLevelTo(breakFrom); i++) {
             closeWith();
         }
+        method.splitAwareGoto(getLexicalContext(), breakFrom.getBreakLabel());
 
-        method.splitAwareGoto(breakNode.getTargetLabel());
-
-        return null;
+        return false;
     }
 
     private int loadArgs(final List<Node> args) {
@@ -541,21 +583,17 @@
     }
 
     @Override
-    public Node enterCallNode(final CallNode callNode) {
-        if (callNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterCallNode(final CallNode callNode) {
         final List<Node>   args            = callNode.getArgs();
         final Node         function        = callNode.getFunction();
-        final Block        currentBlock    = getCurrentBlock();
+        final Block        currentBlock    = getLexicalContext().getCurrentBlock();
 
-        function.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
+        function.accept(new NodeVisitor() {
 
             private void sharedScopeCall(final IdentNode identNode, final int flags) {
                 final Symbol symbol = identNode.getSymbol();
                 int    scopeCallFlags = flags;
-                method.loadScope();
+                method.loadCompilerConstant(SCOPE);
                 if (isFastScope(symbol)) {
                     method.load(getScopeProtoDepth(currentBlock, symbol));
                     scopeCallFlags |= CALLSITE_FAST_SCOPE;
@@ -591,7 +629,7 @@
                 // We don't need ScriptFunction object for 'eval'
                 method.pop();
 
-                method.loadScope(); // Load up self (scope).
+                method.loadCompilerConstant(SCOPE); // Load up self (scope).
 
                 final CallNode.EvalArgs evalArgs = callNode.getEvalArgs();
                 // load evaluated code
@@ -618,7 +656,7 @@
             }
 
             @Override
-            public Node enterIdentNode(final IdentNode node) {
+            public boolean enterIdentNode(final IdentNode node) {
                 final Symbol symbol = node.getSymbol();
 
                 if (symbol.isScope()) {
@@ -637,16 +675,16 @@
                     } else {
                         sharedScopeCall(node, flags);
                     }
-                    assert method.peekType().equals(callNode.getType());
+                    assert method.peekType().equals(callNode.getType()) : method.peekType() + "!=" + callNode.getType();
                 } else {
                     enterDefault(node);
                 }
 
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterAccessNode(final AccessNode node) {
+            public boolean enterAccessNode(final AccessNode node) {
                 load(node.getBase());
                 method.convert(Type.OBJECT);
                 method.dup();
@@ -655,35 +693,34 @@
                 method.dynamicCall(callNode.getType(), 2 + loadArgs(args), getCallSiteFlags());
                 assert method.peekType().equals(callNode.getType());
 
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterFunctionNode(final FunctionNode callee) {
+            public boolean enterFunctionNode(final FunctionNode origCallee) {
+                // NOTE: visiting the callee will leave a constructed ScriptFunction object on the stack if
+                // callee.needsCallee() == true
+                final FunctionNode callee = (FunctionNode)origCallee.accept(CodeGenerator.this);
+
                 final boolean      isVarArg = callee.isVarArg();
                 final int          argCount = isVarArg ? -1 : callee.getParameters().size();
 
                 final String signature = new FunctionSignature(true, callee.needsCallee(), callee.getReturnType(), isVarArg ? null : callee.getParameters()).toString();
 
-                if (callee.needsCallee()) {
-                    newFunctionObject(callee);
-                }
-
-                if (callee.isStrictMode()) { // self is undefined
+                if (callee.isStrict()) { // self is undefined
                     method.loadUndefined(Type.OBJECT);
                 } else { // get global from scope (which is the self)
                     globalInstance();
                 }
                 loadArgs(args, signature, isVarArg, argCount);
+                assert callee.getCompileUnit() != null : "no compile unit for " + callee.getName() + " " + Debug.id(callee) + " " + callNode;
                 method.invokestatic(callee.getCompileUnit().getUnitClassName(), callee.getName(), signature);
                 assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType();
-                functionNodeIsCallee = true;
-                callee.accept(CodeGenerator.this);
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterIndexNode(final IndexNode node) {
+            public boolean enterIndexNode(final IndexNode node) {
                 load(node.getBase());
                 method.convert(Type.OBJECT);
                 method.dup();
@@ -697,11 +734,11 @@
                 method.dynamicCall(callNode.getType(), 2 + loadArgs(args), getCallSiteFlags());
                 assert method.peekType().equals(callNode.getType());
 
-                return null;
+                return false;
             }
 
             @Override
-            protected Node enterDefault(final Node node) {
+            protected boolean enterDefault(final Node node) {
                 // Load up function.
                 load(function);
                 method.convert(Type.OBJECT); //TODO, e.g. booleans can be used as functions
@@ -709,58 +746,41 @@
                 method.dynamicCall(callNode.getType(), 2 + loadArgs(args), getCallSiteFlags() | CALLSITE_SCOPE);
                 assert method.peekType().equals(callNode.getType());
 
-                return null;
+                return false;
             }
         });
 
         method.store(callNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterContinueNode(final ContinueNode continueNode) {
-        if (continueNode.testResolved()) {
-            return null;
-        }
-
-        for (int i = 0; i < continueNode.getScopeNestingLevel(); i++) {
+    public boolean enterContinueNode(final ContinueNode continueNode) {
+        final LoopNode continueTo = getLexicalContext().getContinueTo(continueNode.getLabel());
+        for (int i = 0; i < getLexicalContext().getScopeNestingLevelTo(continueTo); i++) {
             closeWith();
         }
+        method.splitAwareGoto(getLexicalContext(), continueTo.getContinueLabel());
 
-        method.splitAwareGoto(continueNode.getTargetLabel());
-
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
-        return enterWhileNode(doWhileNode);
+    public boolean enterEmptyNode(final EmptyNode emptyNode) {
+        return false;
     }
 
     @Override
-    public Node enterEmptyNode(final EmptyNode emptyNode) {
-        return null;
-    }
-
-    @Override
-    public Node enterExecuteNode(final ExecuteNode executeNode) {
-        if (executeNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterExecuteNode(final ExecuteNode executeNode) {
         final Node expression = executeNode.getExpression();
         expression.accept(this);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterForNode(final ForNode forNode) {
-        if (forNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterForNode(final ForNode forNode) {
         final Node  test   = forNode.getTest();
         final Block body   = forNode.getBody();
         final Node  modify = forNode.getModify();
@@ -790,6 +810,10 @@
 
             new Store<Node>(init) {
                 @Override
+                protected void storeNonDiscard() {
+                    return;
+                }
+                @Override
                 protected void evaluate() {
                     method.load(iter);
                     method.invoke(interfaceCallNoLookup(Iterator.class, "next", Object.class));
@@ -829,7 +853,19 @@
             method.label(breakLabel);
         }
 
-        return null;
+        return false;
+    }
+
+    private static int assignSlots(final Block block, final int firstSlot) {
+        int nextSlot = firstSlot;
+        for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext(); ) {
+            final Symbol next = iter.next();
+            if (next.hasSlot()) {
+                next.setSlot(nextSlot);
+                nextSlot += next.slotCount();
+            }
+        }
+        return nextSlot;
     }
 
     /**
@@ -838,21 +874,26 @@
      * @param block block with local vars.
      */
     private void initLocals(final Block block) {
-        final FunctionNode function       = lexicalContext.getFunction(block);
-        final boolean      isFunctionNode = block == function;
+        final boolean isFunctionBody = getLexicalContext().isFunctionBody();
 
-        /*
-         * Get the symbols from the frame and realign the frame so that all
-         * slots get correct numbers. The slot numbering is not fixed until
-         * after initLocals has been run
-         */
-        final Frame        frame   = block.getFrame();
-        final List<Symbol> symbols = frame.getSymbols();
+        final int nextFreeSlot;
+        if (isFunctionBody) {
+            // On entry to function, start with slot 0
+            nextFreeSlot = 0;
+        } else {
+            // Otherwise, continue from previous block's first free slot
+            nextFreeSlot = nextFreeSlots[nextFreeSlotsSize - 1];
+        }
+        if(nextFreeSlotsSize == nextFreeSlots.length) {
+            final int[] newNextFreeSlots = new int[nextFreeSlotsSize * 2];
+            System.arraycopy(nextFreeSlots, 0, newNextFreeSlots, 0, nextFreeSlotsSize);
+            nextFreeSlots = newNextFreeSlots;
+        }
+        nextFreeSlots[nextFreeSlotsSize++] = assignSlots(block, nextFreeSlot);
 
-        /* Fix the predefined slots so they have numbers >= 0, like varargs. */
-        frame.realign();
-
-        if (isFunctionNode) {
+        final FunctionNode function = getLexicalContext().getCurrentFunction();
+        if (isFunctionBody) {
+            /* Fix the predefined slots so they have numbers >= 0, like varargs. */
             if (function.needsParentScope()) {
                 initParentScope();
             }
@@ -876,14 +917,18 @@
             final List<String> nameList = new ArrayList<>();
             final List<Symbol> locals   = new ArrayList<>();
 
-
             // Initalize symbols and values
             final List<Symbol> newSymbols = new ArrayList<>();
             final List<Symbol> values     = new ArrayList<>();
 
             final boolean hasArguments = function.needsArguments();
-            for (final Symbol symbol : symbols) {
-                if (symbol.isInternal() || symbol.isThis()) {
+
+            final Iterator<Symbol> symbols = block.symbolIterator();
+
+            while (symbols.hasNext()) {
+                final Symbol symbol = symbols.next();
+
+                if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) {
                     continue;
                 }
 
@@ -907,9 +952,6 @@
                 }
             }
 
-            /* Correct slot numbering again */
-            frame.realign();
-
             // we may have locals that need to be initialized
             initSymbols(locals);
 
@@ -931,7 +973,7 @@
                 @Override
                 protected void loadScope(MethodEmitter m) {
                     if(function.needsParentScope()) {
-                        m.loadScope();
+                        m.loadCompilerConstant(SCOPE);
                     } else {
                         m.loadNull();
                     }
@@ -940,118 +982,102 @@
             foc.makeObject(method);
 
             // runScript(): merge scope into global
-            if (isFunctionNode && function.isProgram()) {
+            if (isFunctionBody && function.isProgram()) {
                 method.invoke(ScriptRuntime.MERGE_SCOPE);
             }
 
-            method.storeScope();
+            method.storeCompilerConstant(SCOPE);
         } else {
             // Since we don't have a scope, parameters didn't get assigned array indices by the FieldObjectCreator, so
             // we need to assign them separately here.
             int nextParam = 0;
-            if (isFunctionNode && function.isVarArg()) {
+            if (isFunctionBody && function.isVarArg()) {
                 for (final IdentNode param : function.getParameters()) {
                     param.getSymbol().setFieldIndex(nextParam++);
                 }
             }
+
+            final Iterator<Symbol> iter = block.symbolIterator();
+            final List<Symbol> symbols = new ArrayList<>();
+            while (iter.hasNext()) {
+                symbols.add(iter.next());
+            }
             initSymbols(symbols);
         }
 
         // Debugging: print symbols? @see --print-symbols flag
-        printSymbols(block, (isFunctionNode ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName()));
+        printSymbols(block, (isFunctionBody ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName()));
     }
 
     private void initArguments(final FunctionNode function) {
-        method.loadVarArgs();
+        method.loadCompilerConstant(VARARGS);
         if(function.needsCallee()) {
-            method.loadCallee();
+            method.loadCompilerConstant(CALLEE);
         } else {
             // If function is strict mode, "arguments.callee" is not populated, so we don't necessarily need the
             // caller.
-            assert function.isStrictMode();
+            assert function.isStrict();
             method.loadNull();
         }
         method.load(function.getParameters().size());
         globalAllocateArguments();
-        method.storeArguments();
+        method.storeCompilerConstant(ARGUMENTS);
     }
 
     private void initParentScope() {
-        method.loadCallee();
+        method.loadCompilerConstant(CALLEE);
         method.invoke(ScriptFunction.GET_SCOPE);
-        method.storeScope();
+        method.storeCompilerConstant(SCOPE);
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
-        final boolean isCallee = functionNodeIsCallee;
-        functionNodeIsCallee = false;
-
-        if (functionNode.testResolved()) {
-            return null;
-        }
-
-        if(!(isCallee || functionNode == compiler.getFunctionNode())) {
-            newFunctionObject(functionNode);
-        }
-
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
         if (functionNode.isLazy()) {
-            return null;
+            // Must do it now; can't postpone it until leaveFunctionNode()
+            newFunctionObject(functionNode, functionNode);
+            return false;
         }
 
-        LOG.info("=== BEGIN " + functionNode.getName());
-        lexicalContext.push(functionNode);
+        LOG.info("=== BEGIN ", functionNode.getName());
 
-        setCurrentCompileUnit(functionNode.getCompileUnit());
-        assert getCurrentCompileUnit() != null;
+        assert functionNode.getCompileUnit() != null : "no compile unit for " + functionNode.getName() + " " + Debug.id(functionNode);
+        push(functionNode.getCompileUnit());
+        assert !compileUnits.isEmpty();
 
-        setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(functionNode));
-        functionNode.setMethodEmitter(method);
+        pushMethodEmitter(unit.getClassEmitter().method(functionNode));
         // Mark end for variable tables.
         method.begin();
-        method.label(functionNode.getEntryLabel());
 
-        initLocals(functionNode);
-        functionNode.setState(CompilationState.EMITTED);
-
-        return functionNode;
+        return true;
     }
 
     @Override
     public Node leaveFunctionNode(final FunctionNode functionNode) {
-        // Mark end for variable tables.
-        method.label(functionNode.getBreakLabel());
-
-        if (!functionNode.needsScope()) {
-            method.markerVariable(LEAF.tag(), functionNode.getEntryLabel(), functionNode.getBreakLabel());
-        }
-
-        symbolInfo(functionNode);
         try {
             method.end(); // wrap up this method
+            pop(functionNode.getCompileUnit());
+            popMethodEmitter(method);
+            LOG.info("=== END ", functionNode.getName());
+
+            final FunctionNode newFunctionNode = functionNode.setState(getLexicalContext(), CompilationState.EMITTED);
+
+            newFunctionObject(newFunctionNode, functionNode);
+            return newFunctionNode;
         } catch (final Throwable t) {
             Context.printStackTrace(t);
             final VerifyError e = new VerifyError("Code generation bug in \"" + functionNode.getName() + "\": likely stack misaligned: " + t + " " + functionNode.getSource().getName());
             e.initCause(t);
             throw e;
         }
-
-        lexicalContext.pop(functionNode);
-        LOG.info("=== END " + functionNode.getName());
-        return functionNode;
     }
 
     @Override
-    public Node enterIdentNode(final IdentNode identNode) {
-        return null;
+    public boolean enterIdentNode(final IdentNode identNode) {
+        return false;
     }
 
     @Override
-    public Node enterIfNode(final IfNode ifNode) {
-        if (ifNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterIfNode(final IfNode ifNode) {
         final Node  test = ifNode.getTest();
         final Block pass = ifNode.getPass();
         final Block fail = ifNode.getFail();
@@ -1082,30 +1108,21 @@
             method.label(afterLabel);
         }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterIndexNode(final IndexNode indexNode) {
-        if (indexNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterIndexNode(final IndexNode indexNode) {
         load(indexNode);
-
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
-        if (lineNumberNode.testResolved()) {
-            return null;
-        }
-
-        final Label label = new Label("line:" + lineNumberNode.getLineNumber() + " (" + getCurrentFunctionNode().getName() + ")");
+    public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) {
+        final Label label = new Label("line:" + lineNumberNode.getLineNumber() + " (" + getLexicalContext().getCurrentFunction().getName() + ")");
         method.label(label);
         method.lineNumber(lineNumberNode.getLineNumber(), label);
-        return null;
+        return false;
     }
 
     /**
@@ -1131,43 +1148,43 @@
         final Type elementType = arrayType.getElementType();
 
         if (units != null) {
-            final CompileUnit   savedCompileUnit = getCurrentCompileUnit();
-            final MethodEmitter savedMethod      = getCurrentMethodEmitter();
+            final MethodEmitter savedMethod = method;
 
-            try {
-                for (final ArrayUnit unit : units) {
-                    setCurrentCompileUnit(unit.getCompileUnit());
+            for (final ArrayUnit arrayUnit : units) {
+                push(arrayUnit.getCompileUnit());
 
-                    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);
+                final String className = unit.getUnitClassName();
+                final String name      = getLexicalContext().getCurrentFunction().uniqueName(SPLIT_PREFIX.symbolName());
+                final String signature = methodDescriptor(type, Object.class, ScriptFunction.class, ScriptObject.class, type);
 
-                    setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature));
-                    method.setFunctionNode(getCurrentFunctionNode());
-                    method.begin();
+                final MethodEmitter me = unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature);
+                pushMethodEmitter(me);
 
-                    fixScopeSlot();
+                method.setFunctionNode(getLexicalContext().getCurrentFunction());
+                method.begin();
 
-                    method.load(arrayType, SPLIT_ARRAY_ARG.slot());
+                fixScopeSlot();
 
-                    for (int i = unit.getLo(); i < unit.getHi(); i++) {
-                        storeElement(nodes, elementType, postsets[i]);
-                    }
+                method.load(arrayType, SPLIT_ARRAY_ARG.slot());
 
-                    method._return();
-                    method.end();
-
-                    savedMethod.loadThis();
-                    savedMethod.swap();
-                    savedMethod.loadCallee();
-                    savedMethod.swap();
-                    savedMethod.loadScope();
-                    savedMethod.swap();
-                    savedMethod.invokestatic(className, name, signature);
+                for (int i = arrayUnit.getLo(); i < arrayUnit.getHi(); i++) {
+                    storeElement(nodes, elementType, postsets[i]);
                 }
-            } finally {
-                setCurrentCompileUnit(savedCompileUnit);
-                setCurrentMethodEmitter(savedMethod);
+
+                method._return();
+                method.end();
+                popMethodEmitter(me);
+
+                assert method == savedMethod;
+                method.loadCompilerConstant(THIS);
+                method.swap();
+                method.loadCompilerConstant(CALLEE);
+                method.swap();
+                method.loadCompilerConstant(SCOPE);
+                method.swap();
+                method.invokestatic(className, name, signature);
+
+                pop(unit);
             }
 
             return method;
@@ -1217,12 +1234,12 @@
      * @param string string to load
      */
     void loadConstant(final String string) {
-        final String       unitClassName = getCurrentCompileUnit().getUnitClassName();
-        final ClassEmitter classEmitter  = getCurrentCompileUnit().getClassEmitter();
+        final String       unitClassName = unit.getUnitClassName();
+        final ClassEmitter classEmitter  = unit.getClassEmitter();
         final int          index         = compiler.getConstantData().add(string);
 
         method.load(index);
-        method.invokestatic(unitClassName, GET_STRING.tag(), methodDescriptor(String.class, int.class));
+        method.invokestatic(unitClassName, GET_STRING.symbolName(), methodDescriptor(String.class, int.class));
         classEmitter.needGetConstantMethod(String.class);
     }
 
@@ -1233,14 +1250,14 @@
      * @param object object to load
      */
     void loadConstant(final Object object) {
-        final String       unitClassName = getCurrentCompileUnit().getUnitClassName();
-        final ClassEmitter classEmitter  = getCurrentCompileUnit().getClassEmitter();
+        final String       unitClassName = unit.getUnitClassName();
+        final ClassEmitter classEmitter  = unit.getClassEmitter();
         final int          index         = compiler.getConstantData().add(object);
         final Class<?>     cls           = object.getClass();
 
         if (cls == PropertyMap.class) {
             method.load(index);
-            method.invokestatic(unitClassName, GET_MAP.tag(), methodDescriptor(PropertyMap.class, int.class));
+            method.invokestatic(unitClassName, GET_MAP.symbolName(), methodDescriptor(PropertyMap.class, int.class));
             classEmitter.needGetConstantMethod(PropertyMap.class);
         } else if (cls.isArray()) {
             method.load(index);
@@ -1303,14 +1320,14 @@
             return loadRegexToken(regexToken);
         }
         // emit field
-        final String       regexName    = getCurrentFunctionNode().uniqueName(REGEX_PREFIX.tag());
-        final ClassEmitter classEmitter = getCurrentCompileUnit().getClassEmitter();
+        final String       regexName    = getLexicalContext().getCurrentFunction().uniqueName(REGEX_PREFIX.symbolName());
+        final ClassEmitter classEmitter = unit.getClassEmitter();
 
         classEmitter.field(EnumSet.of(PRIVATE, STATIC), regexName, Object.class);
         regexFieldCount++;
 
         // get field, if null create new regex, finally clone regex object
-        method.getStatic(getCurrentCompileUnit().getUnitClassName(), regexName, typeDescriptor(Object.class));
+        method.getStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class));
         method.dup();
         final Label cachedLabel = new Label("cached");
         method.ifnonnull(cachedLabel);
@@ -1318,7 +1335,7 @@
         method.pop();
         loadRegexToken(regexToken);
         method.dup();
-        method.putStatic(getCurrentCompileUnit().getUnitClassName(), regexName, typeDescriptor(Object.class));
+        method.putStatic(unit.getUnitClassName(), regexName, typeDescriptor(Object.class));
 
         method.label(cachedLabel);
         globalRegExpCopy();
@@ -1328,18 +1345,14 @@
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enterLiteralNode(final LiteralNode literalNode) {
+    public boolean enterLiteralNode(final LiteralNode literalNode) {
         assert literalNode.getSymbol() != null : literalNode + " has no symbol";
         load(literalNode).store(literalNode.getSymbol());
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterObjectNode(final ObjectNode objectNode) {
-        if (objectNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterObjectNode(final ObjectNode objectNode) {
         final List<Node> elements = objectNode.getElements();
         final int        size     = elements.size();
 
@@ -1404,14 +1417,14 @@
 
         if (!hasGettersSetters) {
             method.store(objectNode.getSymbol());
-            return null;
+            return false;
         }
 
         for (final Node element : elements) {
             final PropertyNode propertyNode = (PropertyNode)element;
             final Object       key          = propertyNode.getKey();
-            final FunctionNode getter       = (FunctionNode)propertyNode.getGetter();
-            final FunctionNode setter       = (FunctionNode)propertyNode.getSetter();
+            final FunctionNode getter       = propertyNode.getGetter();
+            final FunctionNode setter       = propertyNode.getSetter();
 
             if (getter == null && setter == null) {
                 continue;
@@ -1436,35 +1449,25 @@
 
         method.store(objectNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterReturnNode(final ReturnNode returnNode) {
-        if (returnNode.testResolved()) {
-            return null;
-        }
+    public boolean enterReturnNode(final ReturnNode returnNode) {
+        method.registerReturn();
 
-        // Set the split return flag in the scope if this is a split method fragment.
-        if (method.getSplitNode() != null) {
-            assert method.getSplitNode().hasReturn() : "unexpected return in split node";
-
-            method.loadScope();
-            method.checkcast(Scope.class);
-            method.load(0);
-            method.invoke(Scope.SET_SPLIT_STATE);
-        }
+        final Type returnType = getLexicalContext().getCurrentFunction().getReturnType();
 
         final Node expression = returnNode.getExpression();
         if (expression != null) {
             load(expression);
         } else {
-            method.loadUndefined(getCurrentFunctionNode().getReturnType());
+            method.loadUndefined(returnType);
         }
 
-        method._return(getCurrentFunctionNode().getReturnType());
+        method._return(returnType);
 
-        return null;
+        return false;
     }
 
     private static boolean isNullLiteral(final Node node) {
@@ -1542,19 +1545,20 @@
         }
 
         assert args.size() == 2;
-        final Node lhs = args.get(0);
-        final Node rhs = args.get(1);
-
         final Type returnType = node.getType();
-        load(lhs);
-        load(rhs);
+
+        load(args.get(0));
+        load(args.get(1));
 
         Request finalRequest = request;
 
+        //if the request is a comparison, i.e. one that can be reversed
+        //it keeps its semantic, but make sure that the object comes in
+        //last
         final Request reverse = Request.reverse(request);
-        if (method.peekType().isObject() && reverse != null) {
-            if (!method.peekType(1).isObject()) {
-                method.swap();
+        if (method.peekType().isObject() && reverse != null) { //rhs is object
+            if (!method.peekType(1).isObject()) { //lhs is not object
+                method.swap(); //prefer object as lhs
                 finalRequest = reverse;
             }
         }
@@ -1581,11 +1585,7 @@
     }
 
     @Override
-    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
-        if (runtimeNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
         /*
          * First check if this should be something other than a runtime node
          * AccessSpecializer might have changed the type
@@ -1624,7 +1624,7 @@
                 method.add();
                 method.convert(type);
                 method.store(symbol);
-                return null;
+                return false;
             default:
                 // it's ok to send this one on with only primitive arguments, maybe INSTANCEOF(true, true) or similar
                 // assert false : runtimeNode + " has all primitive arguments. This is an inconsistent state";
@@ -1636,11 +1636,11 @@
         final List<Node> args = runtimeNode.getArgs();
 
         if (nullCheck(runtimeNode, args, new FunctionSignature(false, false, runtimeNode.getType(), args).toString())) {
-            return null;
+            return false;
         }
 
         if (!runtimeNode.isFinal() && specializationCheck(runtimeNode.getRequest(), runtimeNode, args)) {
-            return null;
+            return false;
         }
 
         for (final Node arg : runtimeNode.getArgs()) {
@@ -1658,129 +1658,146 @@
         method.convert(runtimeNode.getType());
         method.store(runtimeNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterSplitNode(final SplitNode splitNode) {
-        if (splitNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterSplitNode(final SplitNode splitNode) {
         final CompileUnit splitCompileUnit = splitNode.getCompileUnit();
 
-        final FunctionNode fn   = getCurrentFunctionNode();
+        final FunctionNode fn   = getLexicalContext().getCurrentFunction();
         final String className  = splitCompileUnit.getUnitClassName();
         final String name       = splitNode.getName();
 
-        final Class<?>   rtype  = fn.getReturnType().getTypeClass();
-        final boolean needsArguments = fn.needsArguments();
-        final Class<?>[] ptypes = needsArguments ?
+        final Class<?>   rtype          = fn.getReturnType().getTypeClass();
+        final boolean    needsArguments = fn.needsArguments();
+        final Class<?>[] ptypes         = needsArguments ?
                 new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class, Object.class} :
                 new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class};
 
-        setCurrentCompileUnit(splitCompileUnit);
-        splitNode.setCompileUnit(splitCompileUnit);
+        final MethodEmitter caller = method;
+        push(splitCompileUnit);
 
         final Call splitCall = staticCallNoLookup(
             className,
             name,
             methodDescriptor(rtype, ptypes));
 
-        setCurrentMethodEmitter(
-            splitCompileUnit.getClassEmitter().method(
-                EnumSet.of(Flag.PUBLIC, Flag.STATIC),
-                name,
-                rtype,
-                ptypes));
+        final MethodEmitter splitEmitter =
+                splitCompileUnit.getClassEmitter().method(
+                        splitNode,
+                        name,
+                        rtype,
+                        ptypes);
+
+        pushMethodEmitter(splitEmitter);
 
         method.setFunctionNode(fn);
-        method.setSplitNode(splitNode);
-        splitNode.setMethodEmitter(method);
 
-        final MethodEmitter caller = splitNode.getCaller();
-        if(fn.needsCallee()) {
-            caller.loadCallee();
+        if (fn.needsCallee()) {
+            caller.loadCompilerConstant(CALLEE);
         } else {
             caller.loadNull();
         }
-        caller.loadThis();
-        caller.loadScope();
+        caller.loadCompilerConstant(THIS);
+        caller.loadCompilerConstant(SCOPE);
         if (needsArguments) {
-            caller.loadArguments();
+            caller.loadCompilerConstant(ARGUMENTS);
         }
         caller.invoke(splitCall);
-        caller.storeResult();
+        caller.storeCompilerConstant(RETURN);
 
         method.begin();
 
         method.loadUndefined(fn.getReturnType());
-        method.storeResult();
+        method.storeCompilerConstant(RETURN);
 
         fixScopeSlot();
 
-        return splitNode;
+        return true;
     }
 
     private void fixScopeSlot() {
-        if (getCurrentFunctionNode().getScopeNode().getSymbol().getSlot() != SCOPE.slot()) {
+        if (getLexicalContext().getCurrentFunction().compilerConstant(SCOPE).getSlot() != SCOPE.slot()) {
             // TODO hack to move the scope to the expected slot (that's needed because split methods reuse the same slots as the root method)
             method.load(Type.typeFor(ScriptObject.class), SCOPE.slot());
-            method.storeScope();
+            method.storeCompilerConstant(SCOPE);
         }
     }
 
     @Override
     public Node leaveSplitNode(final SplitNode splitNode) {
+        assert method instanceof SplitMethodEmitter;
+        final boolean     hasReturn = method.hasReturn();
+        final List<Label> targets   = method.getExternalTargets();
+
         try {
             // Wrap up this method.
-            method.loadResult();
-            method._return(getCurrentFunctionNode().getReturnType());
+
+            method.loadCompilerConstant(RETURN);
+            method._return(getLexicalContext().getCurrentFunction().getReturnType());
             method.end();
+
+            pop(splitNode.getCompileUnit());
+            popMethodEmitter(method);
+
         } catch (final Throwable t) {
             Context.printStackTrace(t);
-            final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getCurrentFunctionNode().getSource().getName());
+            final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getLexicalContext().getCurrentFunction().getSource().getName());
             e.initCause(t);
             throw e;
         }
 
         // Handle return from split method if there was one.
-        final MethodEmitter caller      = splitNode.getCaller();
-        final List<Label>   targets     = splitNode.getExternalTargets();
-        final int           targetCount = targets.size();
+        final MethodEmitter caller = method;
+        final int     targetCount = targets.size();
 
-        if (splitNode.hasReturn() || targetCount > 0) {
+        //no external jump targets or return in switch node
+        if (!hasReturn && targets.isEmpty()) {
+            return splitNode;
+        }
 
-            caller.loadScope();
-            caller.checkcast(Scope.class);
-            caller.invoke(Scope.GET_SPLIT_STATE);
+        caller.loadCompilerConstant(SCOPE);
+        caller.checkcast(Scope.class);
+        caller.invoke(Scope.GET_SPLIT_STATE);
 
-            // Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue
-            final Label   breakLabel = new Label("no_split_state");
-            final int     low        = splitNode.hasReturn() ? 0 : 1;
-            final int     labelCount = targetCount + 1 - low;
-            final Label[] labels     = new Label[labelCount];
+        final Label breakLabel = new Label("no_split_state");
+        // Split state is -1 for no split state, 0 for return, 1..n+1 for break/continue
+
+        //the common case is that we don't need a switch
+        if (targetCount == 0) {
+            assert hasReturn;
+            caller.ifne(breakLabel);
+            //has to be zero
+            caller.label(new Label("split_return"));
+            method.loadCompilerConstant(RETURN);
+            caller._return(getLexicalContext().getCurrentFunction().getReturnType());
+            caller.label(breakLabel);
+        } else {
+            assert !targets.isEmpty();
+
+            final int     low         = hasReturn ? 0 : 1;
+            final int     labelCount  = targetCount + 1 - low;
+            final Label[] labels      = new Label[labelCount];
 
             for (int i = 0; i < labelCount; i++) {
-                labels[i] = new Label("split_state_" + i);
+                labels[i] = new Label(i == 0 ? "split_return" : "split_" + targets.get(i - 1));
             }
-
             caller.tableswitch(low, targetCount, breakLabel, labels);
             for (int i = low; i <= targetCount; i++) {
                 caller.label(labels[i - low]);
                 if (i == 0) {
-                    caller.loadResult();
-                    caller._return(getCurrentFunctionNode().getReturnType());
+                    caller.loadCompilerConstant(RETURN);
+                    caller._return(getLexicalContext().getCurrentFunction().getReturnType());
                 } else {
                     // Clear split state.
-                    caller.loadScope();
+                    caller.loadCompilerConstant(SCOPE);
                     caller.checkcast(Scope.class);
                     caller.load(-1);
                     caller.invoke(Scope.SET_SPLIT_STATE);
-                    caller.splitAwareGoto(targets.get(i - 1));
+                    caller.splitAwareGoto(getLexicalContext(), targets.get(i - 1));
                 }
             }
-
             caller.label(breakLabel);
         }
 
@@ -1788,11 +1805,7 @@
     }
 
     @Override
-    public Node enterSwitchNode(final SwitchNode switchNode) {
-        if (switchNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterSwitchNode(final SwitchNode switchNode) {
         final Node           expression  = switchNode.getExpression();
         final Symbol         tag         = switchNode.getTag();
         final boolean        allInteger  = tag.getSymbolType().isInteger();
@@ -1810,7 +1823,7 @@
 
         if (cases.isEmpty()) {
             method.label(breakLabel);
-            return null;
+            return false;
         }
 
         if (allInteger) {
@@ -1916,15 +1929,11 @@
             method.label(breakLabel);
         }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterThrowNode(final ThrowNode throwNode) {
-        if (throwNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterThrowNode(final ThrowNode throwNode) {
         method._new(ECMAException.class).dup();
 
         final Node   expression = throwNode.getExpression();
@@ -1943,15 +1952,11 @@
 
         method.athrow();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterTryNode(final TryNode tryNode) {
-        if (tryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterTryNode(final TryNode tryNode) {
         final Block       body        = tryNode.getBody();
         final List<Block> catchBlocks = tryNode.getCatchBlocks();
         final Symbol      symbol      = tryNode.getException();
@@ -1974,74 +1979,68 @@
         method.store(symbol);
 
         for (int i = 0; i < catchBlocks.size(); i++) {
-            final Block saveBlock = getCurrentBlock();
             final Block catchBlock = catchBlocks.get(i);
 
-            setCurrentBlock(catchBlock);
+            //TODO this is very ugly - try not to call enter/leave methods directly
+            //better to use the implicit lexical context scoping given by the visitor's
+            //accept method.
+            getLexicalContext().push(catchBlock);
+            enterBlock(catchBlock);
 
-            try {
-                enterBlock(catchBlock);
+            final CatchNode catchNode          = (CatchNode)catchBlocks.get(i).getStatements().get(0);
+            final IdentNode exception          = catchNode.getException();
+            final Node      exceptionCondition = catchNode.getExceptionCondition();
+            final Block     catchBody          = catchNode.getBody();
 
-                final CatchNode catchNode          = (CatchNode)catchBlocks.get(i).getStatements().get(0);
-                final IdentNode exception          = catchNode.getException();
-                final Node      exceptionCondition = catchNode.getExceptionCondition();
-                final Block     catchBody          = catchNode.getBody();
-
-                if (catchNode.isSyntheticRethrow()) {
-                    // Generate catch body (inlined finally) and rethrow exception
-                    catchBody.accept(this);
-                    method.load(symbol).athrow();
-                    lexicalContext.pop(catchBlock);
-                    continue;
+            new Store<IdentNode>(exception) {
+                @Override
+                protected void storeNonDiscard() {
+                    return;
                 }
+                @Override
+                protected void evaluate() {
+                    /*
+                     * If caught object is an instance of ECMAException, then
+                     * bind obj.thrown to the script catch var. Or else bind the
+                     * caught object itself to the script catch var.
+                     */
+                    final Label notEcmaException = new Label("no_ecma_exception");
 
-                new Store<IdentNode>(exception) {
-                    @Override
-                    protected void evaluate() {
-                        /*
-                         * If caught object is an instance of ECMAException, then
-                         * bind obj.thrown to the script catch var. Or else bind the
-                         * caught object itself to the script catch var.
-                         */
-                        final Label notEcmaException = new Label("no_ecma_exception");
-
-                        method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException);
-                        method.checkcast(ECMAException.class); //TODO is this necessary?
-                        method.getField(ECMAException.THROWN);
-                        method.label(notEcmaException);
-                    }
-                }.store();
-
-                final Label next;
-
-                if (exceptionCondition != null) {
-                    next = new Label("next");
-                    load(exceptionCondition).convert(Type.BOOLEAN).ifeq(next);
-                } else {
-                    next = null;
+                    method.load(symbol).dup()._instanceof(ECMAException.class).ifeq(notEcmaException);
+                    method.checkcast(ECMAException.class); //TODO is this necessary?
+                    method.getField(ECMAException.THROWN);
+                    method.label(notEcmaException);
                 }
+            }.store();
 
-                catchBody.accept(this);
+            final Label next;
 
-                if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) {
-                    method._goto(skip);
-                }
-
-                if (next != null) {
-                    if (i + 1 == catchBlocks.size()) {
-                        // no next catch block - rethrow if condition failed
-                        method._goto(skip);
-                        method.label(next);
-                        method.load(symbol).athrow();
-                    } else {
-                        method.label(next);
-                    }
-                }
-
-                leaveBlock(catchBlock);
-            } finally {
-                setCurrentBlock(saveBlock);
+            if (exceptionCondition != null) {
+                next = new Label("next");
+                load(exceptionCondition).convert(Type.BOOLEAN).ifeq(next);
+            } else {
+                next = null;
             }
+
+            catchBody.accept(this);
+
+            if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) {
+                method._goto(skip);
+            }
+
+            if (next != null) {
+                if (i + 1 == catchBlocks.size()) {
+                    // no next catch block - rethrow if condition failed
+                    method._goto(skip);
+                    method.label(next);
+                    method.load(symbol).athrow();
+                } else {
+                    method.label(next);
+                }
+            }
+
+            leaveBlock(catchBlock);
+            getLexicalContext().pop(catchBlock);
         }
 
         method.label(skip);
@@ -2049,15 +2048,15 @@
 
         // Finally body is always inlined elsewhere so it doesn't need to be emitted
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterVarNode(final VarNode varNode) {
+    public boolean enterVarNode(final VarNode varNode) {
         final Node init = varNode.getInit();
 
-        if (varNode.testResolved() || init == null) {
-            return null;
+        if (init == null) {
+            return false;
         }
 
         final Symbol varSymbol = varNode.getSymbol();
@@ -2067,7 +2066,7 @@
 
         final boolean needsScope = varSymbol.isScope();
         if (needsScope) {
-            method.loadScope();
+            method.loadCompilerConstant(SCOPE);
         }
         load(init);
 
@@ -2087,22 +2086,18 @@
             method.store(varSymbol);
         }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterWhileNode(final WhileNode whileNode) {
-        if (whileNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterWhileNode(final WhileNode whileNode) {
         final Node  test          = whileNode.getTest();
         final Block body          = whileNode.getBody();
         final Label breakLabel    = whileNode.getBreakLabel();
         final Label continueLabel = whileNode.getContinueLabel();
         final Label loopLabel     = new Label("loop");
 
-        if (!(whileNode instanceof DoWhileNode)) {
+        if (!whileNode.isDoWhile()) {
             method._goto(continueLabel);
         }
 
@@ -2114,21 +2109,17 @@
             method.label(breakLabel);
         }
 
-        return null;
+        return false;
     }
 
     private void closeWith() {
-        method.loadScope();
+        method.loadCompilerConstant(SCOPE);
         method.invoke(ScriptRuntime.CLOSE_WITH);
-        method.storeScope();
+        method.storeCompilerConstant(SCOPE);
     }
 
     @Override
-    public Node enterWithNode(final WithNode withNode) {
-        if (withNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterWithNode(final WithNode withNode) {
         final Node expression = withNode.getExpression();
         final Node body       = withNode.getBody();
 
@@ -2139,13 +2130,13 @@
 
         method.label(tryLabel);
 
-        method.loadScope();
+        method.loadCompilerConstant(SCOPE);
         load(expression);
 
         assert expression.getType().isObject() : "with expression needs to be object: " + expression;
 
         method.invoke(ScriptRuntime.OPEN_WITH);
-        method.storeScope();
+        method.storeCompilerConstant(SCOPE);
 
         body.accept(this);
 
@@ -2164,40 +2155,27 @@
 
         method._try(tryLabel, endLabel, catchLabel);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterADD(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterADD(final UnaryNode unaryNode) {
         load(unaryNode.rhs());
         assert unaryNode.rhs().getType().isNumber();
         method.store(unaryNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterBIT_NOT(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterBIT_NOT(final UnaryNode unaryNode) {
         load(unaryNode.rhs()).convert(Type.INT).load(-1).xor().store(unaryNode.getSymbol());
-
-        return null;
+        return false;
     }
 
     // do this better with convert calls to method. TODO
     @Override
-    public Node enterCONVERT(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterCONVERT(final UnaryNode unaryNode) {
         final Node rhs = unaryNode.rhs();
         final Type to  = unaryNode.getType();
 
@@ -2230,15 +2208,11 @@
 
         method.store(unaryNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterDECINC(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterDECINC(final UnaryNode unaryNode) {
         final Node      rhs         = unaryNode.rhs();
         final Type      type        = unaryNode.getType();
         final TokenType tokenType   = unaryNode.tokenType();
@@ -2282,32 +2256,28 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterDISCARD(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterDISCARD(final UnaryNode unaryNode) {
         final Node rhs = unaryNode.rhs();
 
+       // System.err.println("**** Enter discard " + unaryNode);
+        discard.push(rhs);
         load(rhs);
 
-        if (rhs.shouldDiscard()) {
+        if (discard.peek() == rhs) {
+            assert !rhs.isAssignment();
             method.pop();
+            discard.pop();
         }
-
-        return null;
+       // System.err.println("**** Leave discard " + unaryNode);
+        return false;
     }
 
     @Override
-    public Node enterNEW(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterNEW(final UnaryNode unaryNode) {
         final CallNode callNode = (CallNode)unaryNode.rhs();
         final List<Node> args   = callNode.getArgs();
 
@@ -2317,15 +2287,11 @@
         method.dynamicNew(1 + loadArgs(args), getCallSiteFlags());
         method.store(unaryNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterNOT(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterNOT(final UnaryNode unaryNode) {
         final Node rhs = unaryNode.rhs();
 
         load(rhs);
@@ -2342,18 +2308,14 @@
         method.label(afterLabel);
         method.store(unaryNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterSUB(final UnaryNode unaryNode) {
-        if (unaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterSUB(final UnaryNode unaryNode) {
         load(unaryNode.rhs()).neg().store(unaryNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     private Node enterNumericAdd(final Node lhs, final Node rhs, final Type type, final Symbol symbol) {
@@ -2366,11 +2328,7 @@
     }
 
     @Override
-    public Node enterADD(final BinaryNode binaryNode) {
-        if (binaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterADD(final BinaryNode binaryNode) {
         final Node lhs = binaryNode.lhs();
         final Node rhs = binaryNode.rhs();
 
@@ -2384,14 +2342,10 @@
             method.store(binaryNode.getSymbol());
         }
 
-        return null;
+        return false;
     }
 
-    private Node enterAND_OR(final BinaryNode binaryNode) {
-        if (binaryNode.testResolved()) {
-            return null;
-        }
-
+    private boolean enterAND_OR(final BinaryNode binaryNode) {
         final Node lhs = binaryNode.lhs();
         final Node rhs = binaryNode.rhs();
 
@@ -2410,20 +2364,16 @@
         method.label(skip);
         method.store(binaryNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterAND(final BinaryNode binaryNode) {
+    public boolean enterAND(final BinaryNode binaryNode) {
         return enterAND_OR(binaryNode);
     }
 
     @Override
-    public Node enterASSIGN(final BinaryNode binaryNode) {
-        if (binaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterASSIGN(final BinaryNode binaryNode) {
         final Node lhs = binaryNode.lhs();
         final Node rhs = binaryNode.rhs();
 
@@ -2442,7 +2392,7 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     /**
@@ -2473,14 +2423,6 @@
             this.opType = opType;
         }
 
-        @Override
-        public void store() {
-            if (assignNode.testResolved()) {
-                return;
-            }
-            super.store();
-        }
-
         protected abstract void op();
 
         @Override
@@ -2493,35 +2435,43 @@
     }
 
     @Override
-    public Node enterASSIGN_ADD(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_ADD(final BinaryNode binaryNode) {
         assert RuntimeNode.Request.ADD.canSpecialize();
+        final Type lhsType = binaryNode.lhs().getType();
+        final Type rhsType = binaryNode.rhs().getType();
         final boolean specialize = binaryNode.getType() == Type.OBJECT;
 
         new AssignOp(binaryNode) {
-            @Override
-            protected boolean isSelfModifying() {
-                return !specialize;
-            }
 
             @Override
             protected void op() {
-                method.add();
+                if (specialize) {
+                    method.dynamicRuntimeCall(
+                            new SpecializedRuntimeNode(
+                                Request.ADD,
+                                new Type[] {
+                                    lhsType,
+                                    rhsType,
+                                },
+                                Type.OBJECT).getInitialName(),
+                            Type.OBJECT,
+                            Request.ADD);
+                } else {
+                    method.add();
+                }
             }
 
             @Override
             protected void evaluate() {
-                if (specialize && specializationCheck(Request.ADD, assignNode, Arrays.asList(assignNode.lhs(), assignNode.rhs()))) {
-                    return;
-                }
                 super.evaluate();
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
         new AssignOp(Type.INT, binaryNode) {
             @Override
             protected void op() {
@@ -2529,11 +2479,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
         new AssignOp(Type.INT, binaryNode) {
             @Override
             protected void op() {
@@ -2541,11 +2491,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
         new AssignOp(Type.INT, binaryNode) {
             @Override
             protected void op() {
@@ -2553,11 +2503,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_DIV(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_DIV(final BinaryNode binaryNode) {
         new AssignOp(binaryNode) {
             @Override
             protected void op() {
@@ -2565,11 +2515,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_MOD(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_MOD(final BinaryNode binaryNode) {
         new AssignOp(binaryNode) {
             @Override
             protected void op() {
@@ -2577,11 +2527,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_MUL(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_MUL(final BinaryNode binaryNode) {
         new AssignOp(binaryNode) {
             @Override
             protected void op() {
@@ -2589,11 +2539,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_SAR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SAR(final BinaryNode binaryNode) {
         new AssignOp(Type.INT, binaryNode) {
             @Override
             protected void op() {
@@ -2601,11 +2551,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_SHL(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SHL(final BinaryNode binaryNode) {
         new AssignOp(Type.INT, binaryNode) {
             @Override
             protected void op() {
@@ -2613,11 +2563,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_SHR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SHR(final BinaryNode binaryNode) {
         new AssignOp(Type.INT, binaryNode) {
             @Override
             protected void op() {
@@ -2626,11 +2576,11 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterASSIGN_SUB(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SUB(final BinaryNode binaryNode) {
         new AssignOp(binaryNode) {
             @Override
             protected void op() {
@@ -2638,7 +2588,7 @@
             }
         }.store();
 
-        return null;
+        return false;
     }
 
     /**
@@ -2649,9 +2599,6 @@
         protected abstract void op();
 
         protected void evaluate(final BinaryNode node) {
-            if (node.testResolved()) {
-                return;
-            }
             load(node.lhs());
             load(node.rhs());
             op();
@@ -2660,7 +2607,7 @@
     }
 
     @Override
-    public Node enterBIT_AND(final BinaryNode binaryNode) {
+    public boolean enterBIT_AND(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2668,11 +2615,11 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterBIT_OR(final BinaryNode binaryNode) {
+    public boolean enterBIT_OR(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2680,11 +2627,11 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterBIT_XOR(final BinaryNode binaryNode) {
+    public boolean enterBIT_XOR(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2692,14 +2639,10 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
-    private Node enterComma(final BinaryNode binaryNode) {
-        if (binaryNode.testResolved()) {
-            return null;
-        }
-
+    private boolean enterComma(final BinaryNode binaryNode) {
         final Node lhs = binaryNode.lhs();
         final Node rhs = binaryNode.rhs();
 
@@ -2707,21 +2650,21 @@
         load(rhs);
         method.store(binaryNode.getSymbol());
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterCOMMARIGHT(final BinaryNode binaryNode) {
+    public boolean enterCOMMARIGHT(final BinaryNode binaryNode) {
         return enterComma(binaryNode);
     }
 
     @Override
-    public Node enterCOMMALEFT(final BinaryNode binaryNode) {
+    public boolean enterCOMMALEFT(final BinaryNode binaryNode) {
         return enterComma(binaryNode);
     }
 
     @Override
-    public Node enterDIV(final BinaryNode binaryNode) {
+    public boolean enterDIV(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2729,10 +2672,10 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
-    private Node enterCmp(final Node lhs, final Node rhs, final Condition cond, final Type type, final Symbol symbol) {
+    private boolean enterCmp(final Node lhs, final Node rhs, final Condition cond, final Type type, final Symbol symbol) {
         final Type lhsType = lhs.getType();
         final Type rhsType = rhs.getType();
 
@@ -2758,48 +2701,45 @@
         method.convert(type);
         method.store(symbol);
 
-        return null;
+        return false;
     }
 
-    private Node enterCmp(final BinaryNode binaryNode, final Condition cond) {
-        if (binaryNode.testResolved()) {
-            return null;
-        }
+    private boolean enterCmp(final BinaryNode binaryNode, final Condition cond) {
         return enterCmp(binaryNode.lhs(), binaryNode.rhs(), cond, binaryNode.getType(), binaryNode.getSymbol());
     }
 
     @Override
-    public Node enterEQ(final BinaryNode binaryNode) {
+    public boolean enterEQ(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.EQ);
     }
 
     @Override
-    public Node enterEQ_STRICT(final BinaryNode binaryNode) {
+    public boolean enterEQ_STRICT(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.EQ);
     }
 
     @Override
-    public Node enterGE(final BinaryNode binaryNode) {
+    public boolean enterGE(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.GE);
     }
 
     @Override
-    public Node enterGT(final BinaryNode binaryNode) {
+    public boolean enterGT(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.GT);
     }
 
     @Override
-    public Node enterLE(final BinaryNode binaryNode) {
+    public boolean enterLE(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.LE);
     }
 
     @Override
-    public Node enterLT(final BinaryNode binaryNode) {
+    public boolean enterLT(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.LT);
     }
 
     @Override
-    public Node enterMOD(final BinaryNode binaryNode) {
+    public boolean enterMOD(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2807,11 +2747,11 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterMUL(final BinaryNode binaryNode) {
+    public boolean enterMUL(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2819,26 +2759,26 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterNE(final BinaryNode binaryNode) {
+    public boolean enterNE(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.NE);
     }
 
     @Override
-    public Node enterNE_STRICT(final BinaryNode binaryNode) {
+    public boolean enterNE_STRICT(final BinaryNode binaryNode) {
         return enterCmp(binaryNode, Condition.NE);
     }
 
     @Override
-    public Node enterOR(final BinaryNode binaryNode) {
+    public boolean enterOR(final BinaryNode binaryNode) {
         return enterAND_OR(binaryNode);
     }
 
     @Override
-    public Node enterSAR(final BinaryNode binaryNode) {
+    public boolean enterSAR(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2846,11 +2786,11 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterSHL(final BinaryNode binaryNode) {
+    public boolean enterSHL(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2858,11 +2798,11 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterSHR(final BinaryNode binaryNode) {
+    public boolean enterSHR(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2871,11 +2811,11 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterSUB(final BinaryNode binaryNode) {
+    public boolean enterSUB(final BinaryNode binaryNode) {
         new BinaryArith() {
             @Override
             protected void op() {
@@ -2883,18 +2823,11 @@
             }
         }.evaluate(binaryNode);
 
-        return null;
+        return false;
     }
 
-    /*
-     * Ternary visits.
-     */
     @Override
-    public Node enterTernaryNode(final TernaryNode ternaryNode) {
-        if (ternaryNode.testResolved()) {
-            return null;
-        }
-
+    public boolean enterTernaryNode(final TernaryNode ternaryNode) {
         final Node lhs   = ternaryNode.lhs();
         final Node rhs   = ternaryNode.rhs();
         final Node third = ternaryNode.third();
@@ -2926,7 +2859,7 @@
         method.label(exitLabel);
         method.store(symbol);
 
-        return null;
+        return false;
     }
 
     /**
@@ -2955,7 +2888,7 @@
         if (scopeCalls.containsKey(scopeCall)) {
             return scopeCalls.get(scopeCall);
         }
-        scopeCall.setClassAndName(getCurrentCompileUnit(), getCurrentFunctionNode().uniqueName("scopeCall"));
+        scopeCall.setClassAndName(unit, getLexicalContext().getCurrentFunction().uniqueName("scopeCall"));
         scopeCalls.put(scopeCall, scopeCall);
         return scopeCall;
     }
@@ -2974,7 +2907,7 @@
         if (scopeCalls.containsKey(scopeCall)) {
             return scopeCalls.get(scopeCall);
         }
-        scopeCall.setClassAndName(getCurrentCompileUnit(), getCurrentFunctionNode().uniqueName("scopeCall"));
+        scopeCall.setClassAndName(unit, getLexicalContext().getCurrentFunction().uniqueName("scopeCall"));
         scopeCalls.put(scopeCall, scopeCall);
         return scopeCall;
     }
@@ -3037,9 +2970,6 @@
         /** The target node to store to, e.g. x */
         private final Node target;
 
-        /** Should the result always be discarded, no matter what? */
-        private final boolean alwaysDiscard;
-
         /** How deep on the stack do the arguments go if this generates an indy call */
         private int depth;
 
@@ -3055,7 +2985,6 @@
         protected Store(final T assignNode, final Node target) {
             this.assignNode = assignNode;
             this.target = target;
-            this.alwaysDiscard = assignNode == target;
         }
 
         /**
@@ -3077,21 +3006,21 @@
 
         private void prologue() {
             final Symbol targetSymbol = target.getSymbol();
-            final Symbol scopeSymbol  = getCurrentFunctionNode().getScopeNode().getSymbol();
+            final Symbol scopeSymbol  = getLexicalContext().getCurrentFunction().compilerConstant(SCOPE);
 
             /**
              * This loads the parts of the target, e.g base and index. they are kept
              * on the stack throughout the store and used at the end to execute it
              */
 
-            target.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
+            target.accept(new NodeVisitor() {
                 @Override
-                public Node enterIdentNode(final IdentNode node) {
+                public boolean enterIdentNode(final IdentNode node) {
                     if (targetSymbol.isScope()) {
                         method.load(scopeSymbol);
                         depth++;
                     }
-                    return null;
+                    return false;
                 }
 
                 private void enterBaseNode() {
@@ -3109,13 +3038,13 @@
                 }
 
                 @Override
-                public Node enterAccessNode(final AccessNode node) {
+                public boolean enterAccessNode(final AccessNode node) {
                     enterBaseNode();
-                    return null;
+                    return false;
                 }
 
                 @Override
-                public Node enterIndexNode(final IndexNode node) {
+                public boolean enterIndexNode(final IndexNode node) {
                     enterBaseNode();
 
                     final Node index = node.getIndex();
@@ -3131,14 +3060,14 @@
                         method.dup(1);
                     }
 
-                    return null;
+                    return false;
                 }
 
             });
         }
 
         private Symbol quickSymbol(final Type type) {
-            return quickSymbol(type, QUICK_PREFIX.tag());
+            return quickSymbol(type, QUICK_PREFIX.symbolName());
         }
 
         /**
@@ -3151,22 +3080,28 @@
          * @return the quick symbol
          */
         private Symbol quickSymbol(final Type type, final String prefix) {
-            final String name = getCurrentFunctionNode().uniqueName(prefix);
-            final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL, null, null);
+            final String name = getLexicalContext().getCurrentFunction().uniqueName(prefix);
+            final Symbol symbol = new Symbol(name, IS_TEMP | IS_INTERNAL);
 
             symbol.setType(type);
-            symbol.setSlot(getCurrentBlock().getFrame().getSlotCount());
+            final int quickSlot = nextFreeSlots[nextFreeSlotsSize - 1];
+            nextFreeSlots[nextFreeSlotsSize - 1] = quickSlot + symbol.slotCount();
+            symbol.setSlot(quickSlot);
 
             return symbol;
         }
 
         // store the result that "lives on" after the op, e.g. "i" in i++ postfix.
         protected void storeNonDiscard() {
-            if (assignNode.shouldDiscard() || alwaysDiscard) {
-                assignNode.setDiscard(false);
+            if (discard.peek() == assignNode) {
+                assert assignNode.isAssignment();
+                discard.pop();
                 return;
             }
 
+            //System.err.println("Store with out discard that shouldn't just return " + assignNode);
+            //new Throwable().printStackTrace();
+
             final Symbol symbol = assignNode.getSymbol();
             if (symbol.hasSlot()) {
                 method.dup().store(symbol);
@@ -3191,22 +3126,22 @@
              */
             method.convert(target.getType());
 
-            target.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
+            target.accept(new NodeVisitor() {
                 @Override
-                protected Node enterDefault(Node node) {
+                protected boolean enterDefault(Node node) {
                     throw new AssertionError("Unexpected node " + node + " in store epilogue");
                 }
 
                 @Override
-                public Node enterUnaryNode(final UnaryNode node) {
-                    if(node.tokenType() == TokenType.CONVERT && node.getSymbol() != null) {
+                public boolean enterUnaryNode(final UnaryNode node) {
+                    if (node.tokenType() == TokenType.CONVERT && node.getSymbol() != null) {
                         method.convert(node.rhs().getType());
                     }
-                    return node;
+                    return true;
                 }
 
                 @Override
-                public Node enterIdentNode(final IdentNode node) {
+                public boolean enterIdentNode(final IdentNode node) {
                     final Symbol symbol = node.getSymbol();
                     assert symbol != null;
                     if (symbol.isScope()) {
@@ -3218,20 +3153,20 @@
                     } else {
                         method.store(symbol);
                     }
-                    return null;
+                    return false;
 
                 }
 
                 @Override
-                public Node enterAccessNode(final AccessNode node) {
+                public boolean enterAccessNode(final AccessNode node) {
                     method.dynamicSet(node.getProperty().getType(), node.getProperty().getName(), getCallSiteFlags());
-                    return null;
+                    return false;
                 }
 
                 @Override
-                public Node enterIndexNode(final IndexNode node) {
+                public boolean enterIndexNode(final IndexNode node) {
                     method.dynamicSetIndex(getCallSiteFlags());
-                    return null;
+                    return false;
                 }
             });
 
@@ -3250,10 +3185,23 @@
                 method.load(quick);
             }
         }
-
     }
 
-    private void newFunctionObject(final FunctionNode functionNode) {
+    private void newFunctionObject(final FunctionNode functionNode, final FunctionNode originalFunctionNode) {
+        final LexicalContext lc = getLexicalContext();
+        assert lc.peek() == functionNode;
+        // We don't emit a ScriptFunction on stack for:
+        // 1. the outermost compiled function (as there's no code being generated in its outer context that'd need it
+        //    as a callee), and
+        // 2. for functions that are immediately called upon definition and they don't need a callee, e.g. (function(){})().
+        //    Such immediately-called functions are invoked using INVOKESTATIC (see enterFunctionNode() of the embedded
+        //    visitor of enterCallNode() for details), and if they don't need a callee, they don't have it on their
+        //    static method's parameter list.
+        if(lc.getOutermostFunction() == functionNode ||
+                (!functionNode.needsCallee()) && lc.isFunctionDefinedInCurrentCall(originalFunctionNode)) {
+            return;
+        }
+
         final boolean isLazy  = functionNode.isLazy();
 
         new ObjectCreator(this, new ArrayList<String>(), new ArrayList<Symbol>(), false, false) {
@@ -3265,7 +3213,7 @@
                 loadConstant(new RecompilableScriptFunctionData(functionNode, compiler.getCodeInstaller(), Compiler.binaryName(getClassName()), makeMap()));
 
                 if (isLazy || functionNode.needsParentScope()) {
-                    m.loadScope();
+                    m.loadCompilerConstant(SCOPE);
                 } else {
                     m.loadNull();
                 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
index 8b905f8..884a68b 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
@@ -15,6 +15,7 @@
 import java.util.HashSet;
 import java.util.Set;
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.FunctionNode;
 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
@@ -29,8 +30,8 @@
 import jdk.nashorn.internal.runtime.Timing;
 
 /**
- * A compilation phase is a step in the processes of turning a JavaScript FunctionNode
- * into bytecode. It has an optional return value.
+ * A compilation phase is a step in the processes of turning a JavaScript
+ * FunctionNode into bytecode. It has an optional return value.
  */
 enum CompilationPhase {
 
@@ -41,77 +42,87 @@
      */
     LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
         @Override
-        void transform(final Compiler compiler, final FunctionNode fn) {
+        FunctionNode transform(final Compiler compiler, final FunctionNode fn0) {
 
             /*
-             * 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
+             * 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
+             * 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);
+            assert outermostFunctionNode == fn0;
 
             final Set<FunctionNode> neverLazy = new HashSet<>();
-            final Set<FunctionNode> lazy = new HashSet<>();
+            final Set<FunctionNode> lazy      = new HashSet<>();
 
-            outermostFunctionNode.accept(new NodeVisitor() {
-                // self references are done with invokestatic and thus cannot have trampolines - never lazy
+            FunctionNode newFunctionNode = outermostFunctionNode;
+
+            newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor() {
+                // self references are done with invokestatic and thus cannot
+                // have trampolines - never lazy
                 @Override
-                public Node enterCallNode(final CallNode node) {
+                public boolean enterCallNode(final CallNode node) {
                     final Node callee = node.getFunction();
                     if (callee instanceof FunctionNode) {
                         neverLazy.add(((FunctionNode)callee));
-                        return null;
+                        return false;
                     }
-                    return node;
+                    return true;
                 }
 
+                //any function that isn't the outermost one must be marked as lazy
                 @Override
-                public Node enterFunctionNode(final FunctionNode node) {
-                    if (node == outermostFunctionNode) {
-                        return node;
-                    }
+                public boolean enterFunctionNode(final FunctionNode node) {
                     assert compiler.isLazy();
                     lazy.add(node);
-
-                    //also needs scope, potentially needs arguments etc etc
-
-                    return node;
+                    return true;
                 }
             });
 
+            //at least one method is non lazy - the outermost one
+            neverLazy.add(newFunctionNode);
+
             for (final FunctionNode node : neverLazy) {
-                Compiler.LOG.fine("Marking " + node.getName() + " as non lazy, as it's a self reference");
-                node.setIsLazy(false);
+                Compiler.LOG.fine(
+                        "Marking ",
+                        node.getName(),
+                        " as non lazy, as it's a self reference");
                 lazy.remove(node);
             }
 
-            outermostFunctionNode.accept(new NodeOperatorVisitor() {
-                private final LexicalContext lexicalContext = new LexicalContext();
+            newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeOperatorVisitor() {
                 @Override
-                public Node enterFunctionNode(FunctionNode functionNode) {
-                    lexicalContext.push(functionNode);
-                    if(lazy.contains(functionNode)) {
-                        Compiler.LOG.fine("Marking " + functionNode.getName() + " as lazy");
-                        functionNode.setIsLazy(true);
-                        lexicalContext.getParentFunction(functionNode).setHasLazyChildren();
+                public Node leaveFunctionNode(final FunctionNode functionNode) {
+                    final LexicalContext lc = getLexicalContext();
+                    if (lazy.contains(functionNode)) {
+                        Compiler.LOG.fine(
+                                "Marking ",
+                                functionNode.getName(),
+                                " as lazy");
+                        final FunctionNode parent = lc.getParentFunction(functionNode);
+                        assert parent != null;
+                        lc.setFlag(parent, FunctionNode.HAS_LAZY_CHILDREN);
+                        lc.setFlag(parent.getBody(), Block.NEEDS_SCOPE);
+                        lc.setFlag(functionNode, FunctionNode.IS_LAZY);
+                        return functionNode;
                     }
-                    return functionNode;
-                }
-                @Override
-                public Node leaveFunctionNode(FunctionNode functionNode) {
-                    lexicalContext.pop(functionNode);
-                    return functionNode;
+
+                    return functionNode.
+                        clearFlag(lc, FunctionNode.IS_LAZY).
+                        setReturnType(lc, Type.UNKNOWN);
                 }
             });
+
+            return newFunctionNode;
         }
 
         @Override
@@ -121,13 +132,13 @@
     },
 
     /*
-     * Constant folding pass
-     *   Simple constant folding that will make elementary constructs go away
+     * Constant folding pass Simple constant folding that will make elementary
+     * constructs go away
      */
     CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
         @Override
-        void transform(final Compiler compiler, final FunctionNode fn) {
-            fn.accept(new FoldConstants());
+        FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
+            return (FunctionNode)fn.accept(new FoldConstants());
         }
 
         @Override
@@ -137,18 +148,16 @@
     },
 
     /*
-     * 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.
-     *
+     * Lower (Control flow pass) Finalizes the control flow. Clones blocks for
+     * finally constructs and similar things. Establishes termination criteria
+     * for nodes Guarantee return instructions to method making sure control
+     * flow cannot fall off the end. Replacing high level nodes with lower such
+     * as runtime nodes where applicable.
      */
     LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) {
         @Override
-        void transform(final Compiler compiler, final FunctionNode fn) {
-            fn.accept(new Lower());
+        FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
+            return (FunctionNode)fn.accept(new Lower());
         }
 
         @Override
@@ -158,13 +167,27 @@
     },
 
     /*
-     * Attribution
-     *   Assign symbols and types to all nodes.
+     * Attribution Assign symbols and types to all nodes.
      */
     ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) {
         @Override
-        void transform(final Compiler compiler, final FunctionNode fn) {
-            fn.accept(new Attr());
+        FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
+            return (FunctionNode)initReturnTypes(fn).accept(new Attr());
+        }
+
+        /**
+         * Pessimistically set all lazy functions' return types to Object
+         * @param functionNode node where to start iterating
+         */
+        private FunctionNode initReturnTypes(final FunctionNode functionNode) {
+            return (FunctionNode)functionNode.accept(new NodeVisitor() {
+                @Override
+                public Node leaveFunctionNode(final FunctionNode node) {
+                    return node.isLazy() ?
+                           node.setReturnType(getLexicalContext(), Type.OBJECT) :
+                           node.setReturnType(getLexicalContext(), Type.UNKNOWN);
+                }
+            });
         }
 
         @Override
@@ -174,25 +197,35 @@
     },
 
     /*
-     * 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.
+     * 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, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
         @Override
-        void transform(final Compiler compiler, final FunctionNode fn) {
+        FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
             final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName());
 
-            new Splitter(compiler, fn, outermostCompileUnit).split();
+            final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn);
 
-            assert fn.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + fn.getCompileUnit() + ") != " + outermostCompileUnit;
+            assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit;
 
-            if (fn.isStrictMode()) {
+            if (newFunctionNode.isStrict()) {
                 assert compiler.getStrictMode();
                 compiler.setStrictMode(true);
             }
+
+            /*
+            newFunctionNode.accept(new NodeVisitor() {
+                @Override
+                public boolean enterFunctionNode(final FunctionNode functionNode) {
+                    assert functionNode.getCompileUnit() != null : functionNode.getName() + " " + Debug.id(functionNode) + " has no compile unit";
+                    return true;
+                }
+            });*/
+
+            return newFunctionNode;
         }
 
         @Override
@@ -204,30 +237,32 @@
     /*
      * 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.
+     * 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.
+     * 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, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT)) {
         @Override
-        void transform(final Compiler compiler, final FunctionNode fn) {
+        FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
             final ScriptEnvironment env = compiler.getEnv();
 
-            fn.accept(new FinalizeTypes());
+            final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes());
 
             if (env._print_lower_ast) {
-                env.getErr().println(new ASTWriter(fn));
+                env.getErr().println(new ASTWriter(newFunctionNode));
             }
 
             if (env._print_lower_parse) {
-                env.getErr().println(new PrintVisitor(fn));
-           }
+                env.getErr().println(new PrintVisitor(newFunctionNode));
+            }
+
+            return newFunctionNode;
         }
 
         @Override
@@ -239,31 +274,21 @@
     /*
      * Bytecode generation:
      *
-     *   Generate the byte code class(es) resulting from the compiled FunctionNode
+     * Generate the byte code class(es) resulting from the compiled FunctionNode
      */
     BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED)) {
         @Override
-        void transform(final Compiler compiler, final FunctionNode fn) {
+        FunctionNode transform(final Compiler compiler, final FunctionNode fn) {
             final ScriptEnvironment env = compiler.getEnv();
+            FunctionNode newFunctionNode = fn;
 
             try {
                 final CodeGenerator codegen = new CodeGenerator(compiler);
-                fn.accept(codegen);
+                newFunctionNode = (FunctionNode)newFunctionNode.accept(codegen);
                 codegen.generateScopeCalls();
-                fn.accept(new NodeOperatorVisitor() {
-                    @Override
-                    public Node enterFunctionNode(FunctionNode functionNode) {
-                        if(functionNode.isLazy()) {
-                            functionNode.resetResolved();
-                            return null;
-                        }
-                        return fn;
-                    }
-                });
-
             } catch (final VerifyError e) {
                 if (env._verify_code || env._print_code) {
-                    env.getErr().println(e.getClass().getSimpleName() + ": " + e.getMessage());
+                    env.getErr().println(e.getClass().getSimpleName() + ": "  + e.getMessage());
                     if (env._dump_on_error) {
                         e.printStackTrace(env.getErr());
                     }
@@ -283,25 +308,25 @@
 
                 compiler.addClass(className, bytecode);
 
-                //should could be printed to stderr for generate class?
+                // should could be printed to stderr for generate class?
                 if (env._print_code) {
                     final StringBuilder sb = new StringBuilder();
-                    sb.append("class: " + className).
-                        append('\n').
-                        append(ClassEmitter.disassemble(bytecode)).
-                        append("=====");
+                    sb.append("class: " + className).append('\n')
+                            .append(ClassEmitter.disassemble(bytecode))
+                            .append("=====");
                     env.getErr().println(sb);
                 }
 
-                //should we verify the generated code?
+                // should we verify the generated code?
                 if (env._verify_code) {
                     compiler.getCodeInstaller().verify(bytecode);
                 }
 
-                //should code be dumped to disk - only valid in compile_only mode?
+                // should code be dumped to disk - only valid in compile_only
+                // mode?
                 if (env._dest_dir != null && env._compile_only) {
                     final String fileName = className.replace('.', File.separatorChar) + ".class";
-                    final int    index    = fileName.lastIndexOf(File.separatorChar);
+                    final int index = fileName.lastIndexOf(File.separatorChar);
 
                     if (index != -1) {
                         final File dir = new File(fileName.substring(0, index));
@@ -314,11 +339,18 @@
                                 fos.write(bytecode);
                             }
                         } catch (final IOException e) {
-                            Compiler.LOG.warning("Skipping class dump for " + className + ": " + ECMAErrors.getMessage("io.error.cant.write", dir.toString()));
+                            Compiler.LOG.warning("Skipping class dump for ",
+                                    className,
+                                    ": ",
+                                    ECMAErrors.getMessage(
+                                        "io.error.cant.write",
+                                        dir.toString()));
                         }
                     }
                 }
             }
+
+            return newFunctionNode;
         }
 
         @Override
@@ -340,26 +372,28 @@
         return functionNode.hasState(pre);
     }
 
-    protected void begin(final FunctionNode functionNode) {
+    protected FunctionNode begin(final FunctionNode functionNode) {
         if (pre != null) {
-            //check that everything in pre is present
+            // check that everything in pre is present
             for (final CompilationState state : pre) {
                 assert functionNode.hasState(state);
             }
-            //check that nothing else is present
+            // check that nothing else is present
             for (final CompilationState state : CompilationState.values()) {
                 assert !(functionNode.hasState(state) && !pre.contains(state));
             }
         }
 
         startTime = System.currentTimeMillis();
+        return functionNode;
     }
 
-    protected void end(final FunctionNode functionNode) {
+    protected FunctionNode end(final FunctionNode functionNode) {
         endTime = System.currentTimeMillis();
         Timing.accumulateTime(toString(), endTime - startTime);
 
         isFinished = true;
+        return functionNode;
     }
 
     boolean isFinished() {
@@ -374,15 +408,13 @@
         return endTime;
     }
 
-    abstract void transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException;
+    abstract FunctionNode transform(final Compiler compiler, final FunctionNode functionNode) throws CompilationException;
 
-    final void apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException {
+    final FunctionNode apply(final Compiler compiler, final FunctionNode functionNode) throws CompilationException {
         if (!isApplicable(functionNode)) {
-            throw new CompilationException("compile phase not applicable: " + this);
+            throw new CompilationException("compile phase not applicable: " + this + " to " + functionNode.getName() + " state=" + functionNode.getState());
         }
-        begin(functionNode);
-        transform(compiler, functionNode);
-        end(functionNode);
+        return end(transform(compiler, begin(functionNode)));
     }
 
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
index 397f39a..b1e47d0 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
@@ -25,12 +25,16 @@
 
 package jdk.nashorn.internal.codegen;
 
+import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
+import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
 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.LAZY;
+import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
 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 static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -46,13 +50,12 @@
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.logging.Level;
+
 import jdk.internal.dynalink.support.NameCodec;
 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.FunctionNode.CompilationState;
-import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.CodeInstaller;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.ScriptEnvironment;
@@ -80,8 +83,6 @@
 
     private final ConstantData constantData;
 
-    private final FunctionNode functionNode;
-
     private final CompilationSequence sequence;
 
     private final ScriptEnvironment env;
@@ -90,6 +91,8 @@
 
     private boolean strict;
 
+    private FunctionNode functionNode;
+
     private CodeInstaller<ScriptEnvironment> installer;
 
     /** logger for compiler, trampolines, splits and related code generation events
@@ -103,8 +106,12 @@
      * during a compile.
      */
     private static String[] RESERVED_NAMES = {
-        SCOPE.tag(),
-        THIS.tag()
+        SCOPE.symbolName(),
+        THIS.symbolName(),
+        RETURN.symbolName(),
+        CALLEE.symbolName(),
+        VARARGS.symbolName(),
+        ARGUMENTS.symbolName()
     };
 
     /**
@@ -186,7 +193,7 @@
 
     private static String lazyTag(final FunctionNode functionNode) {
         if (functionNode.isLazy()) {
-            return '$' + LAZY.tag() + '$' + functionNode.getName();
+            return '$' + LAZY.symbolName() + '$' + functionNode.getName();
         }
         return "";
     }
@@ -205,13 +212,13 @@
         this.functionNode  = functionNode;
         this.sequence      = sequence;
         this.installer     = installer;
-        this.strict        = strict || functionNode.isStrictMode();
+        this.strict        = strict || functionNode.isStrict();
         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() + lazyTag(functionNode))).
+        sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.symbolName() + lazyTag(functionNode))).
                 append('$').
                 append(safeSourceName(functionNode.getSource()));
 
@@ -253,9 +260,9 @@
      * Execute the compilation this Compiler was created with
      * @params param types if known, for specialization
      * @throws CompilationException if something goes wrong
-     * @return this compiler, for possible chaining
+     * @return function node that results from code transforms
      */
-    public Compiler compile() throws CompilationException {
+    public FunctionNode compile() throws CompilationException {
         return compile(null);
     }
 
@@ -263,9 +270,9 @@
      * Execute the compilation this Compiler was created with
      * @param paramTypes param types if known, for specialization
      * @throws CompilationException if something goes wrong
-     * @return this compiler, for possible chaining
+     * @return function node that results from code transforms
      */
-    public Compiler compile(final Class<?> paramTypes) throws CompilationException {
+    public FunctionNode compile(final Class<?> paramTypes) throws CompilationException {
         for (final String reservedName : RESERVED_NAMES) {
             functionNode.uniqueName(reservedName);
         }
@@ -276,7 +283,7 @@
         long time = 0L;
 
         for (final CompilationPhase phase : sequence) {
-            phase.apply(this, functionNode);
+            this.functionNode = phase.apply(this, functionNode);
 
             final long duration = Timing.isEnabled() ? (phase.getEndTime() - phase.getStartTime()) : 0L;
             time += duration;
@@ -295,7 +302,7 @@
                         append(" ms ");
                 }
 
-                LOG.fine(sb.toString());
+                LOG.fine(sb);
             }
         }
 
@@ -311,14 +318,14 @@
                     append(" ms");
             }
 
-            LOG.info(sb.toString());
+            LOG.info(sb);
         }
 
-        return this;
+        return functionNode;
     }
 
     private Class<?> install(final String className, final byte[] code) {
-        LOG.fine("Installing class " + className);
+        LOG.fine("Installing class ", className);
 
         final Class<?> clazz = installer.install(Compiler.binaryName(className), code);
 
@@ -330,8 +337,8 @@
                 @Override
                 public Void run() throws Exception {
                     //use reflection to write source and constants table to installed classes
-                    final Field sourceField    = clazz.getDeclaredField(SOURCE.tag());
-                    final Field constantsField = clazz.getDeclaredField(CONSTANTS.tag());
+                    final Field sourceField    = clazz.getDeclaredField(SOURCE.symbolName());
+                    final Field constantsField = clazz.getDeclaredField(CONSTANTS.symbolName());
                     sourceField.setAccessible(true);
                     constantsField.setAccessible(true);
                     sourceField.set(null, source);
@@ -380,17 +387,6 @@
             unit.setCode(installedClasses.get(unit.getUnitClassName()));
         }
 
-        functionNode.accept(new NodeVisitor() {
-            @Override
-            public Node enterFunctionNode(final FunctionNode node) {
-                if (node.isLazy()) {
-                    return null;
-                }
-                node.setState(CompilationState.INSTALLED);
-                return node;
-            }
-        });
-
         final StringBuilder sb;
         if (LOG.isEnabled()) {
             sb = new StringBuilder();
@@ -416,7 +412,7 @@
         }
 
         if (sb != null) {
-            LOG.info(sb.toString());
+            LOG.info(sb);
         }
 
         return rootClass;
@@ -495,7 +491,7 @@
     private CompileUnit addCompileUnit(final String unitClassName, final long initialWeight) {
         final CompileUnit compileUnit = initCompileUnit(unitClassName, initialWeight);
         compileUnits.add(compileUnit);
-        LOG.fine("Added compile unit " + compileUnit);
+        LOG.fine("Added compile unit ", compileUnit);
         return compileUnit;
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java b/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java
index 1d978d9..809a245 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilerConstants.java
@@ -52,9 +52,6 @@
     /** lazy prefix for classes of jitted methods */
     LAZY("Lazy"),
 
-    /** leaf tag used for functions that require no scope */
-    LEAF("__leaf__"),
-
     /** constructor name */
     INIT("<init>"),
 
@@ -96,7 +93,7 @@
     SCOPE("__scope__", ScriptObject.class, 2),
 
     /** the return value variable name were intermediate results are stored for scripts */
-    SCRIPT_RETURN("__return__"),
+    RETURN("__return__"),
 
     /** the callee value variable when necessary */
     CALLEE("__callee__", ScriptFunction.class),
@@ -167,30 +164,30 @@
     /** get array suffix */
     GET_ARRAY_SUFFIX("$array");
 
-    private final String tag;
+    private final String symbolName;
     private final Class<?> type;
     private final int slot;
 
     private CompilerConstants() {
-        this.tag = name();
+        this.symbolName = name();
         this.type = null;
         this.slot = -1;
     }
 
-    private CompilerConstants(final String tag) {
-        this(tag, -1);
+    private CompilerConstants(final String symbolName) {
+        this(symbolName, -1);
     }
 
-    private CompilerConstants(final String tag, final int slot) {
-        this(tag, null, slot);
+    private CompilerConstants(final String symbolName, final int slot) {
+        this(symbolName, null, slot);
     }
 
-    private CompilerConstants(final String tag, final Class<?> type) {
-        this(tag, type, -1);
+    private CompilerConstants(final String symbolName, final Class<?> type) {
+        this(symbolName, type, -1);
     }
 
-    private CompilerConstants(final String tag, final Class<?> type, final int slot) {
-        this.tag  = tag;
+    private CompilerConstants(final String symbolName, final Class<?> type, final int slot) {
+        this.symbolName  = symbolName;
         this.type = type;
         this.slot = slot;
     }
@@ -202,8 +199,8 @@
      *
      * @return the tag
      */
-    public final String tag() {
-        return tag;
+    public final String symbolName() {
+        return symbolName;
     }
 
     /**
@@ -277,7 +274,7 @@
      * @return Call representing void constructor for type
      */
     public static Call constructorNoLookup(final Class<?> clazz) {
-        return specialCallNoLookup(clazz, INIT.tag(), void.class);
+        return specialCallNoLookup(clazz, INIT.symbolName(), void.class);
     }
 
     /**
@@ -290,7 +287,7 @@
      * @return Call representing constructor for type
      */
     public static Call constructorNoLookup(final String className, final Class<?>... ptypes) {
-        return specialCallNoLookup(className, INIT.tag(), methodDescriptor(void.class, ptypes));
+        return specialCallNoLookup(className, INIT.symbolName(), methodDescriptor(void.class, ptypes));
     }
 
     /**
@@ -303,7 +300,7 @@
      * @return Call representing constructor for type
      */
     public static Call constructorNoLookup(final Class<?> clazz, final Class<?>... ptypes) {
-        return specialCallNoLookup(clazz, INIT.tag(), void.class, ptypes);
+        return specialCallNoLookup(clazz, INIT.symbolName(), void.class, ptypes);
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java b/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
index 83e75cc..57b8b38 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java
@@ -26,6 +26,7 @@
 package jdk.nashorn.internal.codegen;
 
 import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
+import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor;
 import static jdk.nashorn.internal.codegen.types.Type.OBJECT;
@@ -86,7 +87,7 @@
      * @param method the method emitter to use
      */
     protected void loadScope(final MethodEmitter method) {
-        method.loadScope();
+        method.loadCompilerConstant(SCOPE);
     }
 
     /**
@@ -105,7 +106,7 @@
             loadScope(method);
 
             if (hasArguments()) {
-                method.loadArguments();
+                method.loadCompilerConstant(ARGUMENTS);
                 method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class, ARGUMENTS.type()));
             } else {
                 method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class));
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java b/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
index cd18524..2c995ab 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
@@ -25,18 +25,22 @@
 
 package jdk.nashorn.internal.codegen;
 
+import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
+import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
+
+import java.util.ArrayList;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
+
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.Assignment;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.CallNode;
-import jdk.nashorn.internal.ir.CallNode.EvalArgs;
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
@@ -85,18 +89,11 @@
 
     private static final DebugLogger LOG = new DebugLogger("finalize");
 
-    private final LexicalContext lexicalContext = new LexicalContext();
-
     FinalizeTypes() {
     }
 
     @Override
     public Node leaveCallNode(final CallNode callNode) {
-        final EvalArgs evalArgs = callNode.getEvalArgs();
-        if (evalArgs != null) {
-            evalArgs.setCode(evalArgs.getCode().accept(this));
-        }
-
         // AccessSpecializer - call return type may change the access for this location
         final Node function = callNode.getFunction();
         if (function instanceof FunctionNode) {
@@ -133,8 +130,7 @@
     @Override
     public Node leaveNEW(final UnaryNode unaryNode) {
         assert unaryNode.getSymbol() != null && unaryNode.getSymbol().getSymbolType().isObject();
-        ((CallNode)unaryNode.rhs()).setIsNew();
-        return unaryNode;
+        return unaryNode.setRHS(((CallNode)unaryNode.rhs()).setIsNew());
     }
 
     @Override
@@ -254,7 +250,7 @@
     @Override
     public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
         assert binaryNode.getSymbol() != null;
-        final BinaryNode newBinaryNode = (BinaryNode)binaryNode.setRHS(discard(binaryNode.rhs()));
+        final BinaryNode newBinaryNode = binaryNode.setRHS(discard(binaryNode.rhs()));
         // AccessSpecializer - the type of lhs, which is the remaining value of this node may have changed
         // in that case, update the node type as well
         propagateType(newBinaryNode, newBinaryNode.lhs().getType());
@@ -354,41 +350,30 @@
     }
 
     @Override
-    public Node enterBlock(final Block block) {
-        lexicalContext.push(block);
+    public boolean enterBlock(final Block block) {
         updateSymbols(block);
-        return block;
+        return true;
     }
 
+    /*
     @Override
-    public Node leaveBlock(Block block) {
-        lexicalContext.pop(block);
-        return super.leaveBlock(block);
-    }
+    public Node leaveBlock(final Block block) {
+        final LexicalContext lc = getLexicalContext();
+        return block;//.setFlag(lc, lc.getFlags(block));
+    }*/
 
     @Override
     public Node leaveCatchNode(final CatchNode catchNode) {
         final Node exceptionCondition = catchNode.getExceptionCondition();
         if (exceptionCondition != null) {
-            catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN));
+            return catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN));
         }
         return catchNode;
     }
 
     @Override
-    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
-        return enterWhileNode(doWhileNode);
-    }
-
-    @Override
-    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
-        return leaveWhileNode(doWhileNode);
-    }
-
-    @Override
     public Node leaveExecuteNode(final ExecuteNode executeNode) {
-        executeNode.setExpression(discard(executeNode.getExpression()));
-        return executeNode;
+        return executeNode.setExpression(discard(executeNode.getExpression()));
     }
 
     @Override
@@ -397,69 +382,54 @@
         final Node test   = forNode.getTest();
         final Node modify = forNode.getModify();
 
+        final LexicalContext lc = getLexicalContext();
+
         if (forNode.isForIn()) {
-            forNode.setModify(convert(forNode.getModify(), Type.OBJECT)); // NASHORN-400
-            return forNode;
+            return forNode.setModify(lc, convert(forNode.getModify(), Type.OBJECT)); // NASHORN-400
         }
+        assert test != null || forNode.hasGoto() : "forNode " + forNode + " needs goto and is missing it in " + getLexicalContext().getCurrentFunction();
 
-        if (init != null) {
-            forNode.setInit(discard(init));
-        }
-
-        if (test != null) {
-            forNode.setTest(convert(test, Type.BOOLEAN));
-        } else {
-            assert forNode.hasGoto() : "forNode " + forNode + " needs goto and is missing it in " + getCurrentFunctionNode();
-        }
-
-        if (modify != null) {
-            forNode.setModify(discard(modify));
-        }
-
-        return forNode;
+        return forNode.
+            setInit(lc, init == null ? null : discard(init)).
+            setTest(lc, test == null ? null : convert(test, Type.BOOLEAN)).
+            setModify(lc, modify == null ? null : discard(modify));
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
         if (functionNode.isLazy()) {
-            return null;
+            return false;
         }
 
-        lexicalContext.push(functionNode);
         // 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()) {
-            functionNode.getCalleeNode().getSymbol().setNeedsSlot(false);
+            functionNode.compilerConstant(CALLEE).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())) {
-            functionNode.getScopeNode().getSymbol().setNeedsSlot(false);
+        if (!(functionNode.getBody().needsScope() || functionNode.needsParentScope())) {
+            functionNode.compilerConstant(SCOPE).setNeedsSlot(false);
         }
 
-        updateSymbols(functionNode);
-        functionNode.setState(CompilationState.FINALIZED);
-
-        return functionNode;
+        return true;
     }
 
     @Override
-    public Node leaveFunctionNode(FunctionNode functionNode) {
-        lexicalContext.pop(functionNode);
-        return super.leaveFunctionNode(functionNode);
+    public Node leaveFunctionNode(final FunctionNode functionNode) {
+        return functionNode.setState(getLexicalContext(), CompilationState.FINALIZED);
     }
 
     @Override
     public Node leaveIfNode(final IfNode ifNode) {
-        ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN));
-        return ifNode;
+        return ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN));
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enterLiteralNode(final LiteralNode literalNode) {
+    public boolean enterLiteralNode(final LiteralNode literalNode) {
         if (literalNode instanceof ArrayLiteralNode) {
             final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode)literalNode;
             final Node[]           array            = arrayLiteralNode.getValue();
@@ -473,14 +443,14 @@
             }
         }
 
-        return null;
+        return false;
     }
 
     @Override
     public Node leaveReturnNode(final ReturnNode returnNode) {
         final Node expr = returnNode.getExpression();
         if (expr != null) {
-            returnNode.setExpression(convert(expr, getCurrentFunctionNode().getReturnType()));
+            return returnNode.setExpression(convert(expr, getLexicalContext().getCurrentFunction().getReturnType()));
         }
         return returnNode;
     }
@@ -496,21 +466,24 @@
 
     @Override
     public Node leaveSwitchNode(final SwitchNode switchNode) {
-        final Node           expression  = switchNode.getExpression();
-        final List<CaseNode> cases       = switchNode.getCases();
-        final boolean        allInteger  = switchNode.getTag().getSymbolType().isInteger();
+        final boolean allInteger = switchNode.getTag().getSymbolType().isInteger();
 
-        if (!allInteger) {
-            switchNode.setExpression(convert(expression, Type.OBJECT));
-            for (final CaseNode caseNode : cases) {
-                final Node test = caseNode.getTest();
-                if (test != null) {
-                    caseNode.setTest(convert(test, Type.OBJECT));
-                }
-            }
+        if (allInteger) {
+            return switchNode;
         }
 
-        return switchNode;
+        final Node           expression  = switchNode.getExpression();
+        final List<CaseNode> cases       = switchNode.getCases();
+        final List<CaseNode> newCases    = new ArrayList<>();
+
+        for (final CaseNode caseNode : cases) {
+            final Node test = caseNode.getTest();
+            newCases.add(test != null ? caseNode.setTest(convert(test, Type.OBJECT)) : caseNode);
+        }
+
+        return switchNode.
+            setExpression(getLexicalContext(), convert(expression, Type.OBJECT)).
+            setCases(getLexicalContext(), newCases);
     }
 
     @Override
@@ -520,8 +493,7 @@
 
     @Override
     public Node leaveThrowNode(final ThrowNode throwNode) {
-        throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT));
-        return throwNode;
+        return throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT));
     }
 
     @Override
@@ -544,23 +516,24 @@
     public Node leaveWhileNode(final WhileNode whileNode) {
         final Node test = whileNode.getTest();
         if (test != null) {
-            whileNode.setTest(convert(test, Type.BOOLEAN));
+            return whileNode.setTest(getLexicalContext(), convert(test, Type.BOOLEAN));
         }
         return whileNode;
     }
 
     @Override
     public Node leaveWithNode(final WithNode withNode) {
-        withNode.setExpression(convert(withNode.getExpression(), Type.OBJECT));
-        return withNode;
+        return withNode.setExpression(getLexicalContext(), convert(withNode.getExpression(), Type.OBJECT));
     }
 
     private static void updateSymbolsLog(final FunctionNode functionNode, final Symbol symbol, final boolean loseSlot) {
-        if (!symbol.isScope()) {
-            LOG.finest("updateSymbols: " + symbol + " => scope, because all vars in " + functionNode.getName() + " are in scope");
-        }
-        if (loseSlot && symbol.hasSlot()) {
-            LOG.finest("updateSymbols: " + symbol + " => no slot, because all vars in " + functionNode.getName() + " are in scope");
+        if (LOG.isEnabled()) {
+            if (!symbol.isScope()) {
+                LOG.finest("updateSymbols: ", symbol, " => scope, because all vars in ", functionNode.getName(), " are in scope");
+            }
+            if (loseSlot && symbol.hasSlot()) {
+                LOG.finest("updateSymbols: ", symbol, " => no slot, because all vars in ", functionNode.getName(), " are in scope");
+            }
         }
     }
 
@@ -574,29 +547,28 @@
             return; // nothing to do
         }
 
-        final FunctionNode functionNode = lexicalContext.getFunction(block);
-        assert !(block instanceof FunctionNode) || functionNode == block;
+        final LexicalContext lc             = getLexicalContext();
+        final FunctionNode   functionNode   = lc.getFunction(block);
+        final boolean        allVarsInScope = functionNode.allVarsInScope();
+        final boolean        isVarArg       = functionNode.isVarArg();
 
-        final List<Symbol> symbols        = block.getFrame().getSymbols();
-        final boolean      allVarsInScope = functionNode.allVarsInScope();
-        final boolean      isVarArg       = functionNode.isVarArg();
-
-        for (final Symbol symbol : symbols) {
-            if (symbol.isInternal() || symbol.isThis()) {
+        for (final Iterator<Symbol> iter = block.symbolIterator(); iter.hasNext(); ) {
+            final Symbol symbol = iter.next();
+            if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) {
                 continue;
             }
 
             if (symbol.isVar()) {
                 if (allVarsInScope || symbol.isScope()) {
                     updateSymbolsLog(functionNode, symbol, true);
-                    symbol.setIsScope();
+                    Symbol.setSymbolIsScope(lc, symbol);
                     symbol.setNeedsSlot(false);
                 } else {
                     assert symbol.hasSlot() : symbol + " should have a slot only, no scope";
                 }
             } else if (symbol.isParam() && (allVarsInScope || isVarArg || symbol.isScope())) {
                 updateSymbolsLog(functionNode, symbol, isVarArg);
-                symbol.setIsScope();
+                Symbol.setSymbolIsScope(lc, symbol);
                 symbol.setNeedsSlot(!isVarArg);
             }
         }
@@ -636,11 +608,7 @@
             //fallthru
         default:
             if (newRuntimeNode || widest.isObject()) {
-                final RuntimeNode runtimeNode = new RuntimeNode(binaryNode, request);
-                if (finalized) {
-                    runtimeNode.setIsFinal();
-                }
-                return runtimeNode;
+                return new RuntimeNode(binaryNode, request).setIsFinal(finalized);
             }
             break;
         }
@@ -667,7 +635,8 @@
     }
 
     private Node leaveBinary(final BinaryNode binaryNode, final Type lhsType, final Type rhsType) {
-        return binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)).setRHS(convert(binaryNode.rhs(), rhsType));
+        Node b =  binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)).setRHS(convert(binaryNode.rhs(), rhsType));
+        return b;
     }
 
     /**
@@ -683,28 +652,28 @@
 
         node.accept(new NodeVisitor() {
             private void setCanBePrimitive(final Symbol symbol) {
-                LOG.info("*** can be primitive symbol " + symbol + " " + Debug.id(symbol));
+                LOG.info("*** can be primitive symbol ", symbol, " ", Debug.id(symbol));
                 symbol.setCanBePrimitive(to);
             }
 
             @Override
-            public Node enterIdentNode(final IdentNode identNode) {
+            public boolean enterIdentNode(final IdentNode identNode) {
                 if (!exclude.contains(identNode)) {
                     setCanBePrimitive(identNode.getSymbol());
                 }
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterAccessNode(final AccessNode accessNode) {
+            public boolean enterAccessNode(final AccessNode accessNode) {
                 setCanBePrimitive(accessNode.getProperty().getSymbol());
-                return null;
+                return false;
             }
 
             @Override
-            public Node enterIndexNode(final IndexNode indexNode) {
+            public boolean enterIndexNode(final IndexNode indexNode) {
                 exclude.add(indexNode.getBase()); //prevent array base node to be flagged as primitive, but k in a[k++] is fine
-                return indexNode;
+                return true;
             }
         });
     }
@@ -785,12 +754,12 @@
     private static <T extends Node> T setTypeOverride(final T node, final Type to) {
         final Type from = node.getType();
         if (!node.getType().equals(to)) {
-            LOG.info("Changing call override type for '" + node + "' from " + node.getType() + " to " + to);
+            LOG.info("Changing call override type for '", node, "' from ", node.getType(), " to ", to);
             if (!to.isObject() && from.isObject()) {
                 setCanBePrimitive(node, to);
             }
         }
-        LOG.info("Type override for lhs in '" + node + "' => " + to);
+        LOG.info("Type override for lhs in '", node, "' => ", to);
         return ((TypeOverride<T>)node).setType(to);
     }
 
@@ -814,8 +783,8 @@
     private Node convert(final Node node, final Type to) {
         assert !to.isUnknown() : "unknown type for " + node + " class=" + node.getClass();
         assert node != null : "node is null";
-        assert node.getSymbol() != null : "node " + node + " has no symbol!";
-        assert node.tokenType() != TokenType.CONVERT : "assert convert in convert " + node + " in " + getCurrentFunctionNode();
+        assert node.getSymbol() != null : "node " + node + " " + node.getClass() + " has no symbol! " + getLexicalContext().getCurrentFunction() + " " + node.getSource();
+        assert node.tokenType() != TokenType.CONVERT : "assert convert in convert " + node + " in " + getLexicalContext().getCurrentFunction();
 
         final Type from = node.getType();
 
@@ -842,23 +811,23 @@
             resultNode = new UnaryNode(node.getSource(), Token.recast(node.getToken(), TokenType.CONVERT), node);
         }
 
-        LOG.info("CONVERT('" + node + "', " + to + ") => '" + resultNode + "'");
+        LOG.info("CONVERT('", node, "', ", to, ") => '", resultNode, "'");
 
+        final LexicalContext lc = getLexicalContext();
         //This is the only place in this file that can create new temporaries
         //FinalizeTypes may not introduce ANY node that is not a conversion.
-        getCurrentFunctionNode().newTemporary(getCurrentBlock().getFrame(), to, resultNode);
-        resultNode.copyTerminalFlags(node);
+        lc.getCurrentFunction().ensureSymbol(lc.getCurrentBlock(), to, resultNode);
+
+        assert !node.isTerminal();
 
         return resultNode;
     }
 
     private static Node discard(final Node node) {
-        node.setDiscard(true);
-
         if (node.getSymbol() != null) {
             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);
+            assert !node.isTerminal();
             return discard;
         }
 
@@ -883,7 +852,7 @@
         final Symbol symbol = node.getSymbol();
         if (symbol.isTemp()) {
             symbol.setTypeOverride(to);
-            LOG.info("Type override for temporary in '" + node + "' => " + to);
+            LOG.info("Type override for temporary in '", node, "' => ", to);
         }
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java b/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
index fbc6264..8614066 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
@@ -57,7 +57,7 @@
     public Node leaveUnaryNode(final UnaryNode unaryNode) {
         final LiteralNode<?> literalNode = new UnaryNodeConstantEvaluator(unaryNode).eval();
         if (literalNode != null) {
-            LOG.info("Unary constant folded " + unaryNode + " to " + literalNode);
+            LOG.info("Unary constant folded ", unaryNode, " to ", literalNode);
             return literalNode;
         }
         return unaryNode;
@@ -67,24 +67,20 @@
     public Node leaveBinaryNode(final BinaryNode binaryNode) {
         final LiteralNode<?> literalNode = new BinaryNodeConstantEvaluator(binaryNode).eval();
         if (literalNode != null) {
-            LOG.info("Binary constant folded " + binaryNode + " to " + literalNode);
+            LOG.info("Binary constant folded ", binaryNode, " to ", literalNode);
             return literalNode;
         }
         return binaryNode;
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
-        if (functionNode.isLazy()) {
-            return null;
-        }
-        return functionNode;
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
+        return !functionNode.isLazy();
     }
 
     @Override
     public Node leaveFunctionNode(final FunctionNode functionNode) {
-        functionNode.setState(CompilationState.CONSTANT_FOLDED);
-        return functionNode;
+        return functionNode.setState(getLexicalContext(), CompilationState.CONSTANT_FOLDED);
     }
 
     @Override
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Frame.java b/nashorn/src/jdk/nashorn/internal/codegen/Frame.java
deleted file mode 100644
index b72456b..0000000
--- a/nashorn/src/jdk/nashorn/internal/codegen/Frame.java
+++ /dev/null
@@ -1,196 +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;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import jdk.nashorn.internal.ir.Symbol;
-
-/**
- * Tracks the variable area state.
- *
- */
-public final class Frame {
-    /** Previous frame. */
-    private Frame previous;
-
-    /** Current variables. */
-    private final ArrayList<Symbol> symbols;
-
-    /** Number of slots in previous frame. */
-    private int baseCount;
-
-    /** Number of slots in this frame. */
-    private int count;
-
-    /**
-     * Constructor.
-     *
-     * @param previous frame, the parent variable frame
-     */
-    public Frame(final Frame previous) {
-        this.previous  = previous;
-        this.symbols   = new ArrayList<>();
-        this.baseCount = getBaseCount();
-        this.count     = 0;
-    }
-
-    /**
-     * Copy constructor
-     * @param frame
-     * @param symbols
-     */
-    private Frame(final Frame frame, final List<Symbol> symbols) {
-        this.previous  = frame.getPrevious() == null ? null : new Frame(frame.getPrevious(), frame.getPrevious().getSymbols());
-        this.symbols   = new ArrayList<>(frame.getSymbols());
-        this.baseCount = frame.getBaseCount();
-        this.count     = frame.getCount();
-    }
-
-    /**
-     * Copy the frame
-     *
-     * @return a new frame with the identical contents
-     */
-    public Frame copy() {
-        return new Frame(this, getSymbols());
-    }
-
-    /**
-     * Add a new variable to the frame.
-     * @param symbol Symbol representing variable.
-     */
-    public void addSymbol(final Symbol symbol) {
-        final int slot = symbol.getSlot();
-        if (slot < 0) {
-            symbols.add(symbol);
-            count += symbol.slotCount();
-        }
-    }
-
-    /**
-     * Realign slot numbering prior to code generation.
-     * @return Number of slots in frame.
-     */
-    public int realign() {
-        baseCount = getBaseCount();
-        count     = 0;
-
-        for (final Symbol symbol : symbols) {
-            if (symbol.hasSlot()) {
-                symbol.setSlot(baseCount + count);
-                count += symbol.slotCount();
-            }
-        }
-
-        return count;
-    }
-
-    /**
-     * Return the slot count of previous frames.
-     * @return Number of slots in previous frames.
-     */
-    private int getBaseCount() {
-        return previous != null ? previous.getSlotCount() : 0;
-    }
-
-    /**
-     * Determine the number of slots to top of frame.
-     * @return Number of slots in total.
-     */
-    public int getSlotCount() {
-        return baseCount + count;
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-        Frame f = this;
-        boolean hasPrev = false;
-        int pos = 0;
-
-        do {
-            if (hasPrev) {
-                sb.append("\n");
-            }
-
-            sb.append("#").
-                append(pos++).
-                append(" {baseCount:").
-                append(baseCount).
-                append(", ").
-                append("count:").
-                append(count).
-                append("} ");
-
-            for (final Symbol var : f.getSymbols()) {
-                sb.append('[').
-                    append(var.toString()).
-                    append(' ').
-                    append(var.hashCode()).
-                    append("] ");
-            }
-
-            f = f.getPrevious();
-            hasPrev = true;
-        } while (f != null);
-
-        return sb.toString();
-    }
-
-    /**
-     * Get variable count for this frame
-     * @return variable count
-     */
-    public int getCount() {
-        return count;
-    }
-
-    /**
-     * Get previous frame
-     * @return previous frame
-     */
-    public Frame getPrevious() {
-        return previous;
-    }
-
-    /**
-     * Set previous frame
-     * @param previous previous frame
-     */
-    public void setPrevious(final Frame previous) {
-        this.previous = previous;
-    }
-
-    /**
-     * Get symbols in frame
-     * @return a list of symbols in this frame
-     */
-    public List<Symbol> getSymbols() {
-        return Collections.unmodifiableList(symbols);
-    }
- }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
index 715ddc6..d6209e4 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
@@ -25,29 +25,21 @@
 
 package jdk.nashorn.internal.codegen;
 
-import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
-import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL;
-import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
-import static jdk.nashorn.internal.codegen.CompilerConstants.SCRIPT_RETURN;
+import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
-import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
 
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Deque;
-import java.util.Iterator;
 import java.util.List;
 import jdk.nashorn.internal.ir.BaseNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
+import jdk.nashorn.internal.ir.BlockLexicalContext;
 import jdk.nashorn.internal.ir.BreakNode;
 import jdk.nashorn.internal.ir.CallNode;
-import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.EmptyNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
@@ -56,10 +48,10 @@
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.LabelNode;
-import jdk.nashorn.internal.ir.LabeledNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LineNumberNode;
 import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.LoopNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.SwitchNode;
@@ -90,356 +82,167 @@
 
 final class Lower extends NodeOperatorVisitor {
 
-    /**
-     * 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
-     * outer loops or same loop.
-     */
-    private final Deque<Node> nesting;
-
     private static final DebugLogger LOG = new DebugLogger("lower");
 
-    private Node lastStatement;
-
-    private List<Node> statements;
-
-    private LexicalContext lexicalContext = new LexicalContext();
-
     /**
      * Constructor.
      *
      * @param compiler the compiler
      */
     Lower() {
-        this.nesting    = new ArrayDeque<>();
-        this.statements = new ArrayList<>();
+        super(new BlockLexicalContext() {
+
+            @Override
+            public List<Node> popStatements() {
+                List<Node> newStatements = new ArrayList<>();
+                boolean terminated = false;
+
+                final List<Node> statements = super.popStatements();
+                for (final Node statement : statements) {
+                    if (!terminated) {
+                        newStatements.add(statement);
+                        if (statement.isTerminal()) {
+                            terminated = true;
+                        }
+                    } else {
+                        if (statement instanceof VarNode) {
+                            newStatements.add(((VarNode)statement).setInit(null));
+                        }
+                    }
+                }
+                return newStatements;
+            }
+        });
     }
 
     @Override
-    public Node enterBlock(final Block block) {
-        final Node       savedLastStatement = lastStatement;
-        final List<Node> savedStatements    = statements;
-        lexicalContext.push(block);
-        try {
-            this.statements = new ArrayList<>();
-            NodeVisitor visitor = this;
-            for (final Node statement : block.getStatements()) {
-                statement.accept(visitor);
-                /*
-                 * This is slightly unsound, for example if we have a loop with
-                 * a guarded statement like if (x) continue in the body and the
-                 * body ends with TERMINAL, e.g. return; we removed the continue
-                 * before we had the loop stack, as all we cared about was a
-                 * return last in the loop.
-                 *
-                 * @see NASHORN-285
-                 */
-                if (lastStatement != null && lastStatement.isTerminal()) {
-                    copyTerminal(block, lastStatement);
-                    visitor = new DeadCodeVarDeclarationVisitor();
-                }
-            }
-            block.setStatements(statements);
+    public boolean enterBlock(final Block block) {
+        final LexicalContext lc = getLexicalContext();
+        if (lc.isFunctionBody() && lc.getCurrentFunction().isProgram() && !lc.getCurrentFunction().hasDeclaredFunctions()) {
+            new ExecuteNode(block.getSource(), block.getToken(), block.getFinish(), LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED)).accept(this);
+        }
+        return true;
+    }
 
-        } finally {
-            this.statements = savedStatements;
-            this.lastStatement = savedLastStatement;
-            lexicalContext.pop(block);
+    @Override
+    public Node leaveBlock(final Block block) {
+        //now we have committed the entire statement list to the block, but we need to truncate
+        //whatever is after the last terminal. block append won't append past it
+
+        final BlockLexicalContext lc = (BlockLexicalContext)getLexicalContext();
+
+        Node last = lc.getLastStatement();
+
+        if (lc.isFunctionBody()) {
+            final FunctionNode currentFunction = getLexicalContext().getCurrentFunction();
+            final boolean isProgram = currentFunction.isProgram();
+            final ReturnNode returnNode = new ReturnNode(
+                currentFunction.getSource(),
+                currentFunction.getToken(),
+                currentFunction.getFinish(),
+                isProgram ?
+                    compilerConstant(RETURN) :
+                    LiteralNode.newInstance(block, ScriptRuntime.UNDEFINED));
+
+            last = returnNode.accept(this);
         }
 
-        return null;
+        if (last != null && last.isTerminal()) {
+            return block.setIsTerminal(lc, true);
+        }
+
+        return block;
     }
 
     @Override
-    public Node enterBreakNode(final BreakNode breakNode) {
-        return enterBreakOrContinue(breakNode);
+    public boolean enterBreakNode(final BreakNode breakNode) {
+        addStatement(breakNode);
+        return false;
     }
 
     @Override
-    public Node enterCallNode(final CallNode callNode) {
-        final Node function = markerFunction(callNode.getFunction());
-        callNode.setFunction(function);
-        checkEval(callNode); //check if this is an eval call and store the information
-        return callNode;
-    }
-
-    @Override
-    public Node leaveCaseNode(final CaseNode caseNode) {
-        caseNode.copyTerminalFlags(caseNode.getBody());
-        return caseNode;
+    public Node leaveCallNode(final CallNode callNode) {
+        return checkEval(callNode.setFunction(markerFunction(callNode.getFunction())));
     }
 
     @Override
     public Node leaveCatchNode(final CatchNode catchNode) {
-        catchNode.copyTerminalFlags(catchNode.getBody());
-        addStatement(catchNode);
-        return catchNode;
+        return addStatement(catchNode);
     }
 
     @Override
-    public Node enterContinueNode(final ContinueNode continueNode) {
-        return enterBreakOrContinue(continueNode);
+    public boolean enterContinueNode(final ContinueNode continueNode) {
+        addStatement(continueNode);
+        return false;
     }
 
     @Override
-    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
-        return enterWhileNode(doWhileNode);
-    }
-
-    @Override
-    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
-        return leaveWhileNode(doWhileNode);
-    }
-
-    @Override
-    public Node enterEmptyNode(final EmptyNode emptyNode) {
-        return null;
+    public boolean enterEmptyNode(final EmptyNode emptyNode) {
+        return false;
     }
 
     @Override
     public Node leaveExecuteNode(final ExecuteNode executeNode) {
         final Node expr = executeNode.getExpression();
+        ExecuteNode node = executeNode;
 
-        if (getCurrentFunctionNode().isProgram()) {
+        final FunctionNode currentFunction = getLexicalContext().getCurrentFunction();
+
+        if (currentFunction.isProgram()) {
             if (!(expr instanceof Block) || expr instanceof FunctionNode) { // it's not a block, but can be a function
                 if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) {
-                    executeNode.setExpression(new BinaryNode(executeNode.getSource(), Token.recast(executeNode.getToken(), TokenType.ASSIGN),
-                            getCurrentFunctionNode().getResultNode(),
-                            expr));
+                    node = executeNode.setExpression(
+                        new BinaryNode(
+                            executeNode.getSource(),
+                            Token.recast(
+                                executeNode.getToken(),
+                                TokenType.ASSIGN),
+                            compilerConstant(RETURN),
+                        expr));
                 }
             }
         }
 
-        copyTerminal(executeNode, executeNode.getExpression());
-        addStatement(executeNode);
-
-        return executeNode;
-    }
-
-    @Override
-    public Node enterForNode(final ForNode forNode) {
-        nest(forNode);
-        return forNode;
+        return addStatement(node);
     }
 
     @Override
     public Node leaveForNode(final ForNode forNode) {
+        ForNode newForNode = forNode;
+
         final Node  test = forNode.getTest();
-        final Block body = forNode.getBody();
-
-        if (!forNode.isForIn() && test == null) {
-            setHasGoto(forNode);
-        }
-
-        final boolean escapes = controlFlowEscapes(body);
-        if (escapes) {
-            setTerminal(body, false);
-        }
-
-        // pop the loop from the loop context
-        unnest(forNode);
-
         if (!forNode.isForIn() && conservativeAlwaysTrue(test)) {
-            forNode.setTest(null);
-            setHasGoto(forNode);
-            setTerminal(forNode, !escapes);
+            newForNode = forNode.setTest(getLexicalContext(), null);
         }
 
-        addStatement(forNode);
-
-        return forNode;
+        return addStatement(checkEscape(newForNode));
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
-        LOG.info("START FunctionNode: " + functionNode.getName());
-
-        if (functionNode.isLazy()) {
-            LOG.info("LAZY: " + functionNode.getName());
-            return null;
-        }
-        lexicalContext.push(functionNode);
-        initFunctionNode(functionNode);
-
-        nest(functionNode);
-
-        /*
-         * As we are evaluating a nested structure, we need to store the
-         * statement list for the surrounding block and restore it when the
-         * function is done
-         */
-        final List<Node> savedStatements = statements;
-        final Node savedLastStatement = lastStatement;
-
-        statements    = new ArrayList<>();
-        lastStatement = null;
-
-        if (functionNode.needsSelfSymbol()) {
-            //function needs to start with var funcIdent = __callee_;
-            statements.add(functionNode.getSelfSymbolInit().accept(this));
-        }
-
-        NodeVisitor visitor = this;
-        try {
-            //do the statements - this fills the block with code
-            boolean needsInitialEvalResult = functionNode.isProgram();
-            for (final Node statement : functionNode.getStatements()) {
-                // If this function is a program, then insert an assignment to the initial eval result after all
-                // function declarations.
-                if(needsInitialEvalResult && !(statement instanceof LineNumberNode || (statement instanceof VarNode && ((VarNode)statement).isFunctionDeclaration()))) {
-                    addInitialEvalResult(functionNode);
-                    needsInitialEvalResult = false;
-                }
-                statement.accept(visitor);
-                //If there are unused terminated endpoints in the function, we need
-                // to add a "return undefined" in those places for correct semantics
-                LOG.info("Checking lastStatement="+lastStatement+" for terminal flags");
-                if (lastStatement != null && lastStatement.hasTerminalFlags()) {
-                    copyTerminal(functionNode, lastStatement);
-                    assert !needsInitialEvalResult;
-                    visitor = new DeadCodeVarDeclarationVisitor();
-                }
-            }
-            if(needsInitialEvalResult) {
-                addInitialEvalResult(functionNode);
-            }
-            functionNode.setStatements(statements);
-
-            if (!functionNode.isTerminal()) {
-                guaranteeReturn(functionNode);
-            }
-        } finally {
-            statements    = savedStatements;
-            lastStatement = savedLastStatement;
-        }
-
-        LOG.info("END FunctionNode: " + functionNode.getName());
-        unnest(functionNode);
-        lexicalContext.pop(functionNode);
-
-        functionNode.setState(CompilationState.LOWERED);
-
-        return null;
-    }
-
-    /**
-     * This visitor is used to go over statements after a terminal statement. Those statements are dead code, but the
-     * var declarations in them still have the effect of declaring a local variable on the function level. Therefore,
-     * they aren't really dead code and must be preserved. Note that they're only preserved as no-op declarations; their
-     * initializers are wiped out as those are, in fact, dead code.
-     */
-    private class DeadCodeVarDeclarationVisitor extends NodeOperatorVisitor {
-        DeadCodeVarDeclarationVisitor() {
-        }
-
-        @Override
-        public Node enterVarNode(VarNode varNode) {
-            // Can't ever see a function declaration, as this visitor is only ever used after a terminal statement was
-            // encountered, and all function declarations precede any terminal statements.
-            assert !varNode.isFunctionDeclaration();
-            if(varNode.getInit() == null) {
-                // No initializer, just pass it to Lower.
-                return varNode.accept(Lower.this);
-            }
-            // Wipe out the initializer and then pass it to Lower.
-            return varNode.setInit(null).accept(Lower.this);
-        }
-    }
-
-    private void addInitialEvalResult(final FunctionNode functionNode) {
-        new ExecuteNode(functionNode.getSource(), functionNode.getFirstToken(), functionNode.getFinish(),
-                getInitialEvalResult(functionNode)).accept(this);
-    }
-
-    /**
-     * Result of initial result of evaluating a particular program, which is either the last function it declares, or
-     * undefined if it doesn't declare any functions.
-     * @param program
-     * @return the initial result of evaluating the program
-     */
-    private static Node getInitialEvalResult(final FunctionNode program) {
-        IdentNode lastFnName = null;
-        for (final FunctionNode fn : program.getDeclaredFunctions()) {
-            assert fn.isDeclared();
-            final IdentNode fnName = fn.getIdent();
-            if(fnName != null) {
-                lastFnName = fnName;
-            }
-        }
-        return lastFnName != null ? new IdentNode(lastFnName) : LiteralNode.newInstance(program, ScriptRuntime.UNDEFINED);
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
+        return !functionNode.isLazy();
     }
 
     @Override
-    public Node enterIfNode(final IfNode ifNode) {
-        return nest(ifNode);
+    public Node leaveFunctionNode(final FunctionNode functionNode) {
+        LOG.info("END FunctionNode: ", functionNode.getName());
+        return functionNode.setState(getLexicalContext(), CompilationState.LOWERED);
     }
 
     @Override
     public Node leaveIfNode(final IfNode ifNode) {
-        final Node pass = ifNode.getPass();
-        final Node fail = ifNode.getFail();
-
-        if (pass.isTerminal() && fail != null && fail.isTerminal()) {
-            setTerminal(ifNode,  true);
-        }
-
-        addStatement(ifNode);
-        unnest(ifNode);
-
-        return ifNode;
+        return addStatement(ifNode);
     }
 
     @Override
-    public Node enterLabelNode(LabelNode labelNode) {
-        final Block body = labelNode.getBody();
-        body.accept(this);
-        copyTerminal(labelNode, body);
-        addStatement(labelNode);
-        return null;
+    public Node leaveLabelNode(final LabelNode labelNode) {
+        return addStatement(labelNode);
     }
 
     @Override
-    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
-        addStatement(lineNumberNode, false); // don't put it in lastStatement cache
-        return null;
-    }
-
-    @Override
-    public Node enterReturnNode(final ReturnNode returnNode) {
-        final TryNode tryNode = returnNode.getTryChain();
-        final Node    expr    = returnNode.getExpression();
-
-        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();
-                final Node resultNode   = new IdentNode(getCurrentFunctionNode().getResultNode());
-                final Node assignResult = new BinaryNode(source, Token.recast(token, TokenType.ASSIGN), resultNode, expr);
-
-                //add return_in_try = expr; to try block
-                new ExecuteNode(source, token, Token.descPosition(token), assignResult).accept(this);
-
-                //splice in the finally code, inlining it here
-                if (copyFinally(tryNode, null)) {
-                    return null;
-                }
-
-                //make sure that the return node now returns 'return_in_try'
-                returnNode.setExpression(resultNode);
-            } else if (copyFinally(tryNode, null)) {
-                return null;
-            }
-        } else if (expr != null) {
-            returnNode.setExpression(expr.accept(this));
-        }
-
-        addStatement(returnNode);
-
-        return null;
+    public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) {
+        addStatement(lineNumberNode); // don't put it in lastStatement cache
+        return false;
     }
 
     @Override
@@ -448,31 +251,10 @@
         return returnNode;
     }
 
-    @Override
-    public Node enterSwitchNode(final SwitchNode switchNode) {
-        nest(switchNode);
-        return switchNode;
-    }
 
     @Override
     public Node leaveSwitchNode(final SwitchNode switchNode) {
-        unnest(switchNode);
-
-        final List<CaseNode> cases       = switchNode.getCases();
-        final CaseNode       defaultCase = switchNode.getDefaultCase();
-
-        boolean allTerminal = !cases.isEmpty();
-        for (final CaseNode caseNode : switchNode.getCases()) {
-            allTerminal &= caseNode.isTerminal();
-        }
-
-        if (allTerminal && defaultCase != null && defaultCase.isTerminal()) {
-            setTerminal(switchNode, true);
-        }
-
-        addStatement(switchNode);
-
-        return switchNode;
+        return addStatement(switchNode);
     }
 
     @Override
@@ -481,208 +263,234 @@
         return throwNode;
     }
 
-    @Override
-    public Node enterTryNode(final TryNode tryNode) {
-        final Block  finallyBody = tryNode.getFinallyBody();
-        final long   token       = tryNode.getToken();
-        final int    finish      = tryNode.getFinish();
+    private static Node ensureUniqueLabelsIn(final Node node) {
+        return node.accept(new NodeVisitor() {
+           @Override
+           public Node leaveDefault(final Node labelledNode) {
+               return labelledNode.ensureUniqueLabels(getLexicalContext());
+           }
+        });
+    }
 
-        nest(tryNode);
+    private static List<Node> copyFinally(final Block finallyBody) {
+        final List<Node> newStatements = new ArrayList<>();
+        for (final Node statement : finallyBody.getStatements()) {
+            newStatements.add(ensureUniqueLabelsIn(statement));
+            if (statement.hasTerminalFlags()) {
+                return newStatements;
+            }
+        }
+        return newStatements;
+    }
 
-        if (finallyBody == null) {
-            //do nothing if no finally exists
-            return tryNode;
+    private Block catchAllBlock(final TryNode tryNode) {
+        final Source source = tryNode.getSource();
+        final long   token  = tryNode.getToken();
+        final int    finish = tryNode.getFinish();
+
+        final IdentNode exception = new IdentNode(source, token, finish, getLexicalContext().getCurrentFunction().uniqueName("catch_all"));
+
+        final Block catchBody = new Block(source, token, finish, new ThrowNode(source, token, finish, new IdentNode(exception))).
+                setIsTerminal(getLexicalContext(), true); //ends with throw, so terminal
+
+        final CatchNode catchAllNode  = new CatchNode(source, token, finish, new IdentNode(exception), null, catchBody);
+        final Block     catchAllBlock = new Block(source, token, finish, catchAllNode);
+
+        //catchallblock -> catchallnode (catchnode) -> exception -> throw
+
+        return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower
+    }
+
+    private IdentNode compilerConstant(final CompilerConstants cc) {
+        final FunctionNode functionNode = getLexicalContext().getCurrentFunction();
+        return new IdentNode(functionNode.getSource(), functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
+    }
+
+    private static boolean isTerminal(final List<Node> statements) {
+        return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
+    }
+
+    /**
+     * Splice finally code into all endpoints of a trynode
+     * @param tryNode the try node
+     * @param list of rethrowing throw nodes from synthetic catch blocks
+     * @param finallyBody the code in the original finally block
+     * @return new try node after splicing finally code (same if nop)
+     */
+    private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
+        final Source source = tryNode.getSource();
+        final int    finish = tryNode.getFinish();
+
+        assert tryNode.getFinallyBody() == null;
+
+        final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor() {
+            final List<Node> insideTry = new ArrayList<>();
+
+            @Override
+            public boolean enterDefault(final Node node) {
+                insideTry.add(node);
+                return true;
+            }
+
+            @Override
+            public boolean enterFunctionNode(final FunctionNode functionNode) {
+                // do not enter function nodes - finally code should not be inlined into them
+                return false;
+            }
+
+            @Override
+            public Node leaveThrowNode(final ThrowNode throwNode) {
+                if (rethrows.contains(throwNode)) {
+                    final List<Node> newStatements = copyFinally(finallyBody);
+                    if (!isTerminal(newStatements)) {
+                        newStatements.add(throwNode);
+                    }
+                    return new Block(source, throwNode.getToken(), throwNode.getFinish(), newStatements);
+                }
+                return throwNode;
+            }
+
+            @Override
+            public Node leaveBreakNode(final BreakNode breakNode) {
+                return copy(breakNode, Lower.this.getLexicalContext().getBreakable(breakNode.getLabel()));
+            }
+
+            @Override
+            public Node leaveContinueNode(final ContinueNode continueNode) {
+                return copy(continueNode, Lower.this.getLexicalContext().getContinueTo(continueNode.getLabel()));
+            }
+
+            @Override
+            public Node leaveReturnNode(final ReturnNode returnNode) {
+                final Node  expr  = returnNode.getExpression();
+                final List<Node> newStatements = new ArrayList<>();
+
+                final Node resultNode;
+                if (expr != null) {
+                    //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
+                    resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
+                    newStatements.add(new ExecuteNode(new BinaryNode(source, Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
+                } else {
+                    resultNode = null;
+                }
+
+                newStatements.addAll(copyFinally(finallyBody));
+                if (!isTerminal(newStatements)) {
+                    newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
+                }
+
+                return new ExecuteNode(new Block(source, returnNode.getToken(), getLexicalContext().getCurrentBlock().getFinish(), newStatements));
+            }
+
+            private Node copy(final Node endpoint, final Node targetNode) {
+                if (!insideTry.contains(targetNode)) {
+                    final List<Node> newStatements = copyFinally(finallyBody);
+                    if (!isTerminal(newStatements)) {
+                        newStatements.add(endpoint);
+                    }
+                    return new ExecuteNode(new Block(source, endpoint.getToken(), finish, newStatements));
+                }
+                return endpoint;
+            }
+        });
+
+        addStatement(newTryNode);
+        for (final Node statement : finallyBody.getStatements()) {
+            addStatement(statement);
         }
 
-        /*
-         * We have a finally clause.
-         *
-         * Transform to do finally tail duplication as follows:
-         *
-         * <pre>
-         *  try {
-         *    try_body
-         *  } catch e1 {
-         *    catchbody_1
-         *  }
-         *  ...
-         *  } catch en {
-         *    catchbody_n
-         *  } finally {
-         *    finally_body
-         *  }
-         *
-         *  (where e1 ... en are optional)
-         *
-         *  turns into
-         *
-         *  try {
-         *    try {
-         *      try_body
-         *    } catch e1 {
-         *      catchbody1
-         *      //nothing inlined explicitly here, return, break other
-         *      //terminals may inline the finally body
-         *      ...
-         *    } catch en {
-         *      catchbody2
-         *      //nothing inlined explicitly here, return, break other
-         *      //terminals may inline the finally body
-         *    }
-         *  } catch all ex {
-         *      finally_body_inlined
-         *      rethrow ex
-         *  }
-         *  finally_body_inlined
-         * </pre>
-         *
-         * If tries are catches are terminal, visitors for return, break &
-         * continue will handle the tail duplications. Throw needs to be
-         * treated specially with the catchall as described in the above
-         * ASCII art.
-         *
-         * If the try isn't terminal we do the finally_body_inlined at the
-         * end. If the try is terminated with continue/break/return the
-         * existing visitor logic will inline the finally before that
-         * operation. if the try is terminated with a throw, the catches e1
-         * ... en will have a chance to process the exception. If the
-         * appropriate catch e1..en is non terminal we fall through to the
-         * last finally_body_inlined. if the catch e1...en IS terminal with
-         * continue/break/return existing visitor logic will fix it. If they
-         * are terminal with another throw it goes to the catchall and the
-         * 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.
-        if (!tryNode.getCatchBlocks().isEmpty()) {
-            // insert an intermediate try-catch* node, where we move the body and all catch blocks.
-            // the original try node become a try-finally container for the new try-catch* node.
-            // because we don't clone (to avoid deep copy), we have to fix the block chain in the end.
-            final TryNode innerTryNode;
-            innerTryNode = new TryNode(source, token, finish, tryNode.getNext());
-            innerTryNode.setBody(tryNode.getBody());
-            innerTryNode.setCatchBlocks(tryNode.getCatchBlocks());
-
-            // set outer tryNode's body to innerTryNode
-            final Block outerBody;
-            outerBody = new Block(source, token, finish);
-            outerBody.setStatements(new ArrayList<Node>(Arrays.asList(innerTryNode)));
-            tryNode.setBody(outerBody);
-            tryNode.setCatchBlocks(null);
-        }
-
-        // create a catch-all that inlines finally and rethrows
-
-        final Block catchBlock      = new Block(source, token, finish);
-        //this catch block should get define symbol
-
-        final Block catchBody       = new Block(source, token, finish);
-        final Node  catchAllFinally = finallyBody.copy();
-
-        catchBody.addStatement(new ExecuteNode(source, finallyBody.getToken(), finallyBody.getFinish(), catchAllFinally));
-        setTerminal(catchBody, true);
-
-        final CatchNode catchAllNode;
-        final IdentNode exception;
-
-        exception    = new IdentNode(source, token, finish, getCurrentFunctionNode().uniqueName("catch_all"));
-        catchAllNode = new CatchNode(source, token, finish, new IdentNode(exception), null, catchBody);
-        catchAllNode.setIsSyntheticRethrow();
-
-        catchBlock.addStatement(catchAllNode);
-
-        // replace all catches of outer tryNode with the catch-all
-        tryNode.setCatchBlocks(new ArrayList<>(Arrays.asList(catchBlock)));
-
-        /*
-         * We leave the finally block for the original try in place for now
-         * so that children visitations will work. It is removed and placed
-         * afterwards in the else case below, after all children are visited
-         */
-
-        return tryNode;
+        return newTryNode;
     }
 
     @Override
     public Node leaveTryNode(final TryNode tryNode) {
-        final Block finallyBody   = tryNode.getFinallyBody();
+        final Block finallyBody = tryNode.getFinallyBody();
 
-        boolean allTerminal = tryNode.getBody().isTerminal() && (finallyBody == null || finallyBody.isTerminal());
-
-        for (final Block catchBlock : tryNode.getCatchBlocks()) {
-            allTerminal &= catchBlock.isTerminal();
+        if (finallyBody == null) {
+            return addStatement(tryNode);
         }
 
-        tryNode.setIsTerminal(allTerminal);
+        /*
+         * create a new trynode
+         *    if we have catches:
+         *
+         *    try            try
+         *       x              try
+         *    catch               x
+         *       y              catch
+         *    finally z           y
+         *                   catchall
+         *                        rethrow
+         *
+         *   otheriwse
+         *
+         *   try              try
+         *      x               x
+         *   finally          catchall
+         *      y               rethrow
+         *
+         *
+         *   now splice in finally code wherever needed
+         *
+         */
+        TryNode newTryNode;
 
-        addStatement(tryNode);
-        unnest(tryNode);
+        final Block catchAll = catchAllBlock(tryNode);
 
-        // if finally body is present, place it after the tryNode
-        if (finallyBody != null) {
-            tryNode.setFinallyBody(null);
-            addStatement(finallyBody);
+        final List<ThrowNode> rethrows = new ArrayList<>();
+        catchAll.accept(new NodeVisitor() {
+            @Override
+            public boolean enterThrowNode(final ThrowNode throwNode) {
+                rethrows.add(throwNode);
+                return true;
+            }
+        });
+        assert rethrows.size() == 1;
+
+        if (tryNode.getCatchBlocks().isEmpty()) {
+            newTryNode = tryNode.setFinallyBody(null);
+        } else {
+            Block outerBody = new Block(tryNode.getSource(), tryNode.getToken(), tryNode.getFinish(), new ArrayList<Node>(Arrays.asList(tryNode.setFinallyBody(null))));
+            newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
         }
 
-        return tryNode;
+        newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
+
+        /*
+         * Now that the transform is done, we have to go into the try and splice
+         * the finally block in front of any statement that is outside the try
+         */
+        return spliceFinally(newTryNode, rethrows, finallyBody);
     }
 
     @Override
     public Node leaveVarNode(final VarNode varNode) {
         addStatement(varNode);
+        if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && getLexicalContext().getCurrentFunction().isProgram()) {
+            new ExecuteNode(varNode.getSource(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this);
+        }
         return varNode;
     }
 
     @Override
-    public Node enterWhileNode(final WhileNode whileNode) {
-        return nest(whileNode);
-    }
-
-    @Override
     public Node leaveWhileNode(final WhileNode whileNode) {
         final Node test = whileNode.getTest();
+        final Block body = whileNode.getBody();
 
-        if (test == null) {
-            setHasGoto(whileNode);
+        if (conservativeAlwaysTrue(test)) {
+            //turn it into a for node without a test.
+            final ForNode forNode = (ForNode)new ForNode(whileNode.getSource(), whileNode.getToken(), whileNode.getFinish(), null, null, body, null, ForNode.IS_FOR).accept(this);
+            getLexicalContext().replace(whileNode, forNode);
+            return forNode;
         }
 
-        final Block   body    = whileNode.getBody();
-        final boolean escapes = controlFlowEscapes(body);
-        if (escapes) {
-            setTerminal(body, false);
-        }
-
-        Node node = whileNode;
-
-        if (body.isTerminal()) {
-            if (whileNode instanceof DoWhileNode) {
-                setTerminal(whileNode, true);
-            } else if (conservativeAlwaysTrue(test)) {
-                node = new ForNode(whileNode.getSource(), whileNode.getToken(), whileNode.getFinish());
-                ((ForNode)node).setBody(body);
-                node.accept(this);
-                setTerminal(node, !escapes);
-            }
-        }
-
-        // pop the loop from the loop context
-        unnest(whileNode);
-        addStatement(node);
-
-        return node;
+         return addStatement(checkEscape(whileNode));
     }
 
     @Override
     public Node leaveWithNode(final WithNode withNode) {
-        if (withNode.getBody().isTerminal()) {
-            setTerminal(withNode,  true);
-        }
-        addStatement(withNode);
-
-        return withNode;
+        return addStatement(withNode);
     }
 
     @Override
@@ -741,23 +549,25 @@
      *
      * @param callNode call node to check if it's an eval
      */
-    private void checkEval(final CallNode callNode) {
+    private CallNode checkEval(final CallNode callNode) {
         if (callNode.getFunction() instanceof IdentNode) {
 
             final List<Node> args   = callNode.getArgs();
             final IdentNode  callee = (IdentNode)callNode.getFunction();
 
             // 'eval' call with at least one argument
-            if (args.size() >= 1 && EVAL.tag().equals(callee.getName())) {
-                final CallNode.EvalArgs evalArgs =
+            if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) {
+                final FunctionNode currentFunction = getLexicalContext().getCurrentFunction();
+                return callNode.setEvalArgs(
                     new CallNode.EvalArgs(
-                        args.get(0).copy().accept(this), //clone as we use this for the "is eval case". original evaluated separately for "is not eval case"
-                        getCurrentFunctionNode().getThisNode(),
+                        ensureUniqueLabelsIn(args.get(0)).accept(this),
+                        compilerConstant(THIS),
                         evalLocation(callee),
-                        getCurrentFunctionNode().isStrictMode());
-                callNode.setEvalArgs(evalArgs);
+                        currentFunction.isStrict()));
             }
         }
+
+        return callNode;
     }
 
     private static boolean conservativeAlwaysTrue(final Node node) {
@@ -773,7 +583,7 @@
      * @param loopBody the loop body to check
      * @return true if control flow may escape the loop
      */
-    private boolean controlFlowEscapes(final Node loopBody) {
+    private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) {
         final List<Node> escapes = new ArrayList<>();
 
         loopBody.accept(new NodeVisitor() {
@@ -786,7 +596,7 @@
             @Override
             public Node leaveContinueNode(final ContinueNode node) {
                 // all inner loops have been popped.
-                if (nesting.contains(node.getTargetNode())) {
+                if (lex.contains(lex.getContinueTo(node.getLabel()))) {
                     escapes.add(node);
                 }
                 return node;
@@ -796,135 +606,23 @@
         return !escapes.isEmpty();
     }
 
-    private void guaranteeReturn(final FunctionNode functionNode) {
-        Node resultNode;
-
-        if (functionNode.isProgram()) {
-            resultNode = functionNode.getResultNode(); // the eval result, symbol assigned in Attr
-        } else {
-            if (lastStatement != null && lastStatement.isTerminal() || lastStatement instanceof ReturnNode) {
-                return; //already in place or not needed, as it should be for a non-undefined returning function
-            }
-            resultNode = LiteralNode.newInstance(functionNode, ScriptRuntime.UNDEFINED);
+    private LoopNode checkEscape(final LoopNode loopNode) {
+        final LexicalContext lc = getLexicalContext();
+        final boolean escapes = controlFlowEscapes(lc, loopNode.getBody());
+        if (escapes) {
+            return loopNode.
+                setBody(lc, loopNode.getBody().setIsTerminal(lc, false)).
+                setControlFlowEscapes(lc, escapes);
         }
-
-        //create a return statement
-        final Node returnNode = new ReturnNode(functionNode.getSource(), functionNode.getLastToken(), functionNode.getFinish(), resultNode, null);
-        returnNode.accept(this);
+        return loopNode;
     }
 
 
-    private Node nest(final Node node) {
-        LOG.info("Nesting: " + node);
-        LOG.indent();
-        nesting.push(node);
-        return node;
+    private Node addStatement(final Node statement) {
+        ((BlockLexicalContext)getLexicalContext()).appendStatement(statement);
+        return statement;
     }
 
-    private void unnest(final Node node) {
-        LOG.unindent();
-        assert nesting.getFirst() == node : "inconsistent nesting order : " + nesting.getFirst() + " != " + node;
-        LOG.info("Unnesting: " + nesting);
-        nesting.pop();
-    }
-
-    private static void setTerminal(final Node node, final boolean isTerminal) {
-        LOG.info("terminal = " + isTerminal + " for " + node);
-        node.setIsTerminal(isTerminal);
-    }
-
-    private static void setHasGoto(final Node node) { //, final boolean hasGoto) {
-        LOG.info("hasGoto = true for " + node);
-        node.setHasGoto();
-    }
-
-    private static void copyTerminal(final Node node, final Node sourceNode) {
-        LOG.info("copy terminal flags " + sourceNode + " -> " + node);
-        node.copyTerminalFlags(sourceNode);
-    }
-
-    private void addStatement(final Node statement, final boolean storeInLastStatement) {
-        LOG.info("add statement = " + statement + " (lastStatement = " + lastStatement + ")");
-        statements.add(statement);
-        if (storeInLastStatement) {
-            lastStatement = statement;
-        }
-    }
-
-    private void addStatement(final Node statement) {
-        addStatement(statement, true);
-    }
-
-    /**
-     * Determine if Try block is inside target block.
-     *
-     * @param tryNode Try node to test.
-     * @param target  Target block.
-     *
-     * @return true if try block is inside the target, false otherwise.
-     */
-    private boolean isNestedTry(final TryNode tryNode, final Block target) {
-        for(Iterator<Block> blocks = lexicalContext.getBlocks(getCurrentBlock()); blocks.hasNext();) {
-            final Block block = blocks.next();
-            if(block == target) {
-                return false;
-            }
-            if(tryNode.isChildBlock(block)) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Clones the body of the try finallys up to the target block.
-     *
-     * @param node       first try node in the chain.
-     * @param targetNode target block of the break/continue statement or null for return
-     *
-     * @return true if terminates.
-     */
-    private boolean copyFinally(final TryNode node, final Node targetNode) {
-        Block target = null;
-
-        if (targetNode instanceof Block) {
-            target = (Block)targetNode;
-        }
-
-        for (TryNode tryNode = node; tryNode != null; tryNode = tryNode.getNext()) {
-            if (target != null && !isNestedTry(tryNode, target)) {
-                return false;
-            }
-
-            Block finallyBody = tryNode.getFinallyBody();
-            if (finallyBody == null) {
-                continue;
-            }
-
-            finallyBody = (Block)finallyBody.copy();
-            final boolean hasTerminalFlags = finallyBody.hasTerminalFlags();
-
-            new ExecuteNode(finallyBody.getSource(), finallyBody.getToken(), finallyBody.getFinish(), finallyBody).accept(this);
-
-            if (hasTerminalFlags) {
-                getCurrentBlock().copyTerminalFlags(finallyBody);
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private Node enterBreakOrContinue(final LabeledNode labeledNode) {
-        final TryNode tryNode = labeledNode.getTryChain();
-        if (tryNode != null && copyFinally(tryNode, labeledNode.getTargetNode())) {
-            return null;
-        }
-        addStatement(labeledNode);
-        return null;
-    }
-
-
     /**
      * An internal expression has a symbol that is tagged internal. Check if
      * this is such a node
@@ -939,40 +637,21 @@
 
     /**
      * Is this an assignment to the special variable that hosts scripting eval
-     * results?
+     * results, i.e. __return__?
      *
      * @param expression expression to check whether it is $evalresult = X
      * @return true if an assignment to eval result, false otherwise
      */
-    private boolean isEvalResultAssignment(final Node expression) {
+    private static boolean isEvalResultAssignment(final Node expression) {
         Node e = expression;
-        if (e.tokenType() == TokenType.DISCARD) {
-            e = ((UnaryNode)expression).rhs();
-        }
-        final Node resultNode = getCurrentFunctionNode().getResultNode();
-        return e instanceof BinaryNode && ((BinaryNode)e).lhs().equals(resultNode);
-    }
-
-    /**
-     * Prepare special function nodes.
-     * TODO : only create those that are needed.
-     * TODO : make sure slot numbering is not hardcoded in {@link CompilerConstants} - now creation order is significant
-     */
-    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()) {
-            functionNode.setVarArgsNode(new IdentNode(source, token, finish, VARARGS.tag()));
-            if (functionNode.needsArguments()) {
-                functionNode.setArgumentsNode(new IdentNode(source, token, finish, ARGUMENTS.tag()));
+        assert e.tokenType() != TokenType.DISCARD; //there are no discards this early anymore
+        if (e instanceof BinaryNode) {
+            final Node lhs = ((BinaryNode)e).lhs();
+            if (lhs instanceof IdentNode) {
+                return ((IdentNode)lhs).getName().equals(RETURN.symbolName());
             }
         }
+        return false;
     }
 
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java b/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java
index ae40ed3..6ffdd2c 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java
@@ -53,9 +53,12 @@
 import static jdk.internal.org.objectweb.asm.Opcodes.PUTFIELD;
 import static jdk.internal.org.objectweb.asm.Opcodes.PUTSTATIC;
 import static jdk.internal.org.objectweb.asm.Opcodes.RETURN;
+import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
+import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.THIS_DEBUGGER;
+import static jdk.nashorn.internal.codegen.CompilerConstants.VARARGS;
 import static jdk.nashorn.internal.codegen.CompilerConstants.className;
 import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
 import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
@@ -67,6 +70,8 @@
 import java.util.ArrayDeque;
 import java.util.EnumSet;
 import java.util.Iterator;
+import java.util.List;
+
 import jdk.internal.dynalink.support.NameCodec;
 import jdk.internal.org.objectweb.asm.Handle;
 import jdk.internal.org.objectweb.asm.MethodVisitor;
@@ -79,14 +84,14 @@
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.FunctionNode;
 import jdk.nashorn.internal.ir.IdentNode;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
-import jdk.nashorn.internal.ir.SplitNode;
 import jdk.nashorn.internal.ir.Symbol;
 import jdk.nashorn.internal.runtime.ArgumentSetter;
+import jdk.nashorn.internal.runtime.Debug;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.JSType;
-import jdk.nashorn.internal.runtime.Scope;
 import jdk.nashorn.internal.runtime.ScriptEnvironment;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.linker.Bootstrap;
@@ -116,10 +121,10 @@
     private final ClassEmitter classEmitter;
 
     /** FunctionNode representing this method, or null if none exists */
-    private FunctionNode functionNode;
+    protected FunctionNode functionNode;
 
-    /** SplitNode representing the current split, or null if none exists */
-    private SplitNode splitNode;
+    /** Check whether this emitter ever has a function return point */
+    private boolean hasReturn;
 
     /** The script environment */
     private final ScriptEnvironment env;
@@ -203,7 +208,7 @@
 
     @Override
     public String toString() {
-        return "methodEmitter: " + (functionNode == null ? method : functionNode.getName()).toString() + ' ' + stack;
+        return "methodEmitter: " + (functionNode == null ? method : functionNode.getName()).toString() + ' ' + Debug.id(this);
     }
 
     /**
@@ -476,8 +481,8 @@
 
         String name = symbol.getName();
 
-        if (name.equals(THIS.tag())) {
-            name = THIS_DEBUGGER.tag();
+        if (name.equals(THIS.symbolName())) {
+            name = THIS_DEBUGGER.symbolName();
         }
 
         method.visitLocalVariable(name, symbol.getSymbolType().getDescriptor(), null, start, end, symbol.getSlot());
@@ -654,7 +659,7 @@
      * @return this method emitter
      */
     MethodEmitter loadConstants() {
-        getStatic(classEmitter.getUnitClassName(), CONSTANTS.tag(), CONSTANTS.descriptor());
+        getStatic(classEmitter.getUnitClassName(), CONSTANTS.symbolName(), CONSTANTS.descriptor());
         assert peekType().isArray() : peekType();
         return this;
     }
@@ -835,13 +840,13 @@
             if (functionNode.needsArguments()) {
                 // ScriptObject.getArgument(int) on arguments
                 debug("load symbol", symbol.getName(), " arguments index=", index);
-                loadArguments();
+                loadCompilerConstant(ARGUMENTS);
                 load(index);
                 ScriptObject.GET_ARGUMENT.invoke(this);
             } else {
                 // array load from __varargs__
                 debug("load symbol", symbol.getName(), " array index=", index);
-                loadVarArgs();
+                loadCompilerConstant(VARARGS);
                 load(symbol.getFieldIndex());
                 arrayload();
             }
@@ -870,48 +875,13 @@
         if(functionNode == null) {
             return slot == CompilerConstants.JAVA_THIS.slot();
         }
-        final int thisSlot = functionNode.getThisNode().getSymbol().getSlot();
+        final int thisSlot = compilerConstant(THIS).getSlot();
         assert !functionNode.needsCallee() || thisSlot == 1; // needsCallee -> thisSlot == 1
         assert functionNode.needsCallee() || thisSlot == 0; // !needsCallee -> thisSlot == 0
         return slot == thisSlot;
     }
 
     /**
-     * Push the this object to the stack.
-     *
-     * @return the method emitter
-     */
-    MethodEmitter loadThis() {
-        load(functionNode.getThisNode().getSymbol());
-        return this;
-    }
-
-    /**
-     * Push the scope object to the stack.
-     *
-     * @return the method emitter
-     */
-    MethodEmitter loadScope() {
-        if (peekType() == Type.SCOPE) {
-            dup();
-            return this;
-        }
-        load(functionNode.getScopeNode().getSymbol());
-        return this;
-    }
-
-    /**
-     * Push the return object to the stack.
-     *
-     * @return the method emitter
-     */
-    MethodEmitter loadResult() {
-        load(functionNode.getResultNode().getSymbol());
-        return this;
-    }
-
-
-    /**
      * Push a method handle to the stack
      *
      * @param className  class name
@@ -927,62 +897,24 @@
         return this;
     }
 
-    /**
-     * Push the varargs object to the stack
-     *
-     * @return the method emitter
-     */
-    MethodEmitter loadVarArgs() {
-        debug("load var args " + functionNode.getVarArgsNode().getSymbol());
-        return load(functionNode.getVarArgsNode().getSymbol());
+    private Symbol compilerConstant(final CompilerConstants cc) {
+        return functionNode.getBody().getExistingSymbol(cc.symbolName());
     }
 
-    /**
-     * Push the arguments array to the stack
-     *
-     * @return the method emitter
-     */
-    MethodEmitter loadArguments() {
-        debug("load arguments ", functionNode.getArgumentsNode().getSymbol());
-        assert functionNode.getArgumentsNode().getSymbol().getSlot() != 0;
-        return load(functionNode.getArgumentsNode().getSymbol());
+    MethodEmitter loadCompilerConstant(final CompilerConstants cc) {
+        final Symbol symbol = compilerConstant(cc);
+        if (cc == SCOPE && peekType() == Type.SCOPE) {
+            dup();
+            return this;
+        }
+        debug("load compiler constant ", symbol);
+        return load(symbol);
     }
 
-    /**
-     * Push the callee object to the stack
-     *
-     * @return the method emitter
-     */
-    MethodEmitter loadCallee() {
-        final Symbol calleeSymbol = functionNode.getCalleeNode().getSymbol();
-        debug("load callee ", calleeSymbol);
-        assert calleeSymbol.getSlot() == 0 : "callee has wrong slot " + calleeSymbol.getSlot() + " in " + functionNode.getName();
-
-        return load(calleeSymbol);
-    }
-
-    /**
-     * Pop the scope from the stack and store it in its predefined slot
-     */
-    void storeScope() {
-        debug("store scope");
-        store(functionNode.getScopeNode().getSymbol());
-    }
-
-    /**
-     * Pop the return from the stack and store it in its predefined slot
-     */
-    void storeResult() {
-        debug("store result");
-        store(functionNode.getResultNode().getSymbol());
-    }
-
-    /**
-     * Pop the arguments array from the stack and store it in its predefined slot
-     */
-    void storeArguments() {
-        debug("store arguments");
-        store(functionNode.getArgumentsNode().getSymbol());
+    void storeCompilerConstant(final CompilerConstants cc) {
+        final Symbol symbol = compilerConstant(cc);
+        debug("store compiler constant ", symbol);
+        store(symbol);
     }
 
     /**
@@ -1030,13 +962,13 @@
             final int index = symbol.getFieldIndex();
             if (functionNode.needsArguments()) {
                 debug("store symbol", symbol.getName(), " arguments index=", index);
-                loadArguments();
+                loadCompilerConstant(ARGUMENTS);
                 load(index);
                 ArgumentSetter.SET_ARGUMENT.invoke(this);
             } else {
                 // varargs without arguments object - just do array store to __varargs__
                 debug("store symbol", symbol.getName(), " array index=", index);
-                loadVarArgs();
+                loadCompilerConstant(VARARGS);
                 load(index);
                 ArgumentSetter.SET_ARRAY_ELEMENT.invoke(this);
             }
@@ -1345,6 +1277,11 @@
         }
     }
 
+    MethodEmitter registerReturn() {
+        this.hasReturn = true;
+        return this;
+    }
+
     /**
      * Perform a non void return, popping the type from the stack
      *
@@ -1385,22 +1322,7 @@
      *
      * @param label destination label
      */
-    void splitAwareGoto(final Label label) {
-
-        if (splitNode != null) {
-            final int index = splitNode.getExternalTargets().indexOf(label);
-
-            if (index > -1) {
-                loadScope();
-                checkcast(Scope.class);
-                load(index + 1);
-                invoke(Scope.SET_SPLIT_STATE);
-                loadUndefined(Type.OBJECT);
-                _return(functionNode.getReturnType());
-                return;
-            }
-        }
-
+    void splitAwareGoto(final LexicalContext lc, final Label label) {
         _goto(label);
     }
 
@@ -2237,7 +2159,7 @@
             }
 
             if (env != null) { //early bootstrap code doesn't have inited context yet
-                LOG.info(sb.toString());
+                LOG.info(sb);
                 if (DEBUG_TRACE_LINE == linePrefix) {
                     new Throwable().printStackTrace(LOG.getOutputStream());
                 }
@@ -2254,21 +2176,12 @@
         this.functionNode = functionNode;
     }
 
-    /**
-     * Get the split node for this method emitter, if this is code
-     * generation due to splitting large methods
-     *
-     * @return split node
-     */
-    SplitNode getSplitNode() {
-        return splitNode;
+    boolean hasReturn() {
+        return hasReturn;
     }
 
-    /**
-     * Set the split node for this method emitter
-     * @param splitNode split node
-     */
-    void setSplitNode(final SplitNode splitNode) {
-        this.splitNode = splitNode;
+    List<Label> getExternalTargets() {
+        return null;
     }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Namespace.java b/nashorn/src/jdk/nashorn/internal/codegen/Namespace.java
index 02fdb94..5de2fdf 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Namespace.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Namespace.java
@@ -53,7 +53,7 @@
      */
     public Namespace(final Namespace parent) {
         this.parent    = parent;
-        directory = new HashMap<>();
+        this.directory = new HashMap<>();
     }
 
     /**
@@ -65,10 +65,6 @@
         return parent;
     }
 
-    private HashMap<String, Integer> getDirectory() {
-        return directory;
-    }
-
     /**
      * Create a uniqueName name in the namespace in the form base$n where n varies
      * .
@@ -78,7 +74,7 @@
      */
     public String uniqueName(final String base) {
         for (Namespace namespace = this; namespace != null; namespace = namespace.getParent()) {
-            final HashMap<String, Integer> namespaceDirectory = namespace.getDirectory();
+            final HashMap<String, Integer> namespaceDirectory = namespace.directory;
             final Integer                  counter            = namespaceDirectory.get(base);
 
             if (counter != null) {
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java
index 6e032e2..b82e26a 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java
@@ -204,8 +204,8 @@
      * @return The class name.
      */
     public static String getClassName(final int fieldCount) {
-        return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.tag() + fieldCount :
-                                 SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.tag();
+        return fieldCount != 0 ? SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName() + fieldCount :
+                                 SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName();
     }
 
     /**
@@ -218,7 +218,7 @@
      * @return The class name.
      */
     public static String getClassName(final int fieldCount, final int paramCount) {
-        return SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.tag() + fieldCount + SCOPE_MARKER + paramCount;
+        return SCRIPTS_PACKAGE + '/' + JS_OBJECT_PREFIX.symbolName() + fieldCount + SCOPE_MARKER + paramCount;
     }
 
     /**
@@ -449,7 +449,7 @@
      * @param className    Name of JavaScript class.
      */
     private static void newAllocate(final ClassEmitter classEmitter, final String className) {
-        final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.tag(), ScriptObject.class, PropertyMap.class);
+        final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.symbolName(), ScriptObject.class, PropertyMap.class);
         allocate.begin();
         allocate._new(className);
         allocate.dup();
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/ObjectCreator.java b/nashorn/src/jdk/nashorn/internal/codegen/ObjectCreator.java
index e4e7b7d..a9c494c 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/ObjectCreator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ObjectCreator.java
@@ -36,7 +36,7 @@
 public abstract class ObjectCreator {
 
     /** Compile unit for this ObjectCreator, see CompileUnit */
-    protected final CompileUnit   compileUnit;
+    //protected final CompileUnit   compileUnit;
 
     /** List of keys to initiate in this ObjectCreator */
     protected final List<String>  keys;
@@ -66,7 +66,6 @@
      */
     protected ObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final boolean isScope, final boolean hasArguments) {
         this.codegen       = codegen;
-        this.compileUnit   = codegen.getCurrentCompileUnit();
         this.keys          = keys;
         this.symbols       = symbols;
         this.isScope       = isScope;
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java b/nashorn/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java
new file mode 100644
index 0000000..5fa4486
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
+
+import jdk.internal.org.objectweb.asm.MethodVisitor;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.LexicalContext;
+import jdk.nashorn.internal.ir.SplitNode;
+import jdk.nashorn.internal.runtime.Scope;
+
+/**
+ * Emitter used for splitting methods. Needs to keep track of if there are jump targets
+ * outside the current split node. All external jump targets encountered at method
+ * emission are logged, and {@code CodeGenerator#leaveSplitNode(SplitNode)} creates
+ * an appropriate jump table when the SplitNode has been iterated through
+ */
+public class SplitMethodEmitter extends MethodEmitter {
+
+    private final SplitNode splitNode;
+
+    private final List<Label> externalTargets = new ArrayList<>();
+
+    SplitMethodEmitter(final ClassEmitter classEmitter, final MethodVisitor mv, SplitNode splitNode) {
+        super(classEmitter, mv);
+        this.splitNode = splitNode;
+    }
+
+    @Override
+    void splitAwareGoto(final LexicalContext lc, final Label label) {
+        assert splitNode != null;
+        final int index = findExternalTarget(lc, label);
+        if (index >= 0) {
+            loadCompilerConstant(SCOPE);
+            checkcast(Scope.class);
+            load(index + 1);
+            invoke(Scope.SET_SPLIT_STATE);
+            loadUndefined(Type.OBJECT);
+            _return(functionNode.getReturnType());
+            return;
+        }
+        super.splitAwareGoto(lc, label);
+    }
+
+    private int findExternalTarget(final LexicalContext lc, final Label label) {
+        final int index = externalTargets.indexOf(label);
+
+        if (index >= 0) {
+            return index;
+        }
+
+        if (lc.isExternalTarget(splitNode, label)) {
+             externalTargets.add(label);
+             return externalTargets.size() - 1;
+         }
+         return -1;
+    }
+
+    @Override
+    MethodEmitter registerReturn() {
+        super.registerReturn();
+        loadCompilerConstant(SCOPE);
+        checkcast(Scope.class);
+        load(0);
+        invoke(Scope.SET_SPLIT_STATE);
+        return this;
+    }
+
+    @Override
+    final List<Label> getExternalTargets() {
+        return externalTargets;
+    }
+}
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
index f9a84f9..f482e4d 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
@@ -28,29 +28,18 @@
 import static jdk.nashorn.internal.codegen.CompilerConstants.SPLIT_PREFIX;
 
 import java.util.ArrayList;
-import java.util.Deque;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import jdk.nashorn.internal.ir.Block;
-import jdk.nashorn.internal.ir.BreakNode;
-import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
-import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
-import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
 import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.SplitNode;
-import jdk.nashorn.internal.ir.SwitchNode;
-import jdk.nashorn.internal.ir.WhileNode;
-import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.Source;
@@ -64,7 +53,7 @@
     private final Compiler compiler;
 
     /** IR to be broken down. */
-    private final FunctionNode functionNode;
+    private FunctionNode outermost;
 
     /** Compile unit for the main script. */
     private final CompileUnit outermostCompileUnit;
@@ -72,8 +61,6 @@
     /** Cache for calculated block weights. */
     private final Map<Node, Long> weightCache = new HashMap<>();
 
-    private final LexicalContext lexicalContext = new LexicalContext();
-
     /** Weight threshold for when to start a split. */
     public static final long SPLIT_THRESHOLD = Options.getIntProperty("nashorn.compiler.splitter.threshold", 32 * 1024);
 
@@ -88,70 +75,92 @@
      */
     public Splitter(final Compiler compiler, final FunctionNode functionNode, final CompileUnit outermostCompileUnit) {
         this.compiler             = compiler;
-        this.functionNode         = functionNode;
+        this.outermost = functionNode;
         this.outermostCompileUnit = outermostCompileUnit;
     }
 
     /**
      * Execute the split
      */
-    void split() {
+    FunctionNode split(final FunctionNode fn) {
+        FunctionNode functionNode = fn;
+
         if (functionNode.isLazy()) {
-            LOG.finest("Postponing split of '" + functionNode.getName() + "' as it's lazy");
-            return;
+            LOG.finest("Postponing split of '", functionNode.getName(), "' as it's lazy");
+            return functionNode;
         }
 
-        LOG.finest("Initiating split of '" + functionNode.getName() + "'");
+        LOG.finest("Initiating split of '", functionNode.getName(), "'");
+
+        final LexicalContext lc = getLexicalContext();
 
         long weight = WeighNodes.weigh(functionNode);
+        final boolean top = compiler.getFunctionNode() == outermost;
 
         if (weight >= SPLIT_THRESHOLD) {
-            LOG.finest("Splitting '" + functionNode.getName() + "' as its weight " + weight + " exceeds split threshold " + SPLIT_THRESHOLD);
-
-            functionNode.accept(this);
+            LOG.finest("Splitting '", functionNode.getName(), "' as its weight ", weight, " exceeds split threshold ", SPLIT_THRESHOLD);
+            functionNode = (FunctionNode)functionNode.accept(this);
 
             if (functionNode.isSplit()) {
                 // Weight has changed so weigh again, this time using block weight cache
                 weight = WeighNodes.weigh(functionNode, weightCache);
+                functionNode = functionNode.setBody(lc, functionNode.getBody().setNeedsScope(lc));
             }
 
             if (weight >= SPLIT_THRESHOLD) {
-                weight = splitBlock(functionNode, functionNode);
-            }
-
-            if (functionNode.isSplit()) {
-                functionNode.accept(new SplitFlowAnalyzer());
+                functionNode = functionNode.setBody(lc, splitBlock(functionNode.getBody(), functionNode));
+                weight = WeighNodes.weigh(functionNode.getBody(), weightCache);
             }
         }
 
-        assert functionNode.getCompileUnit() == null : "compile unit already set";
+        assert functionNode.getCompileUnit() == null : "compile unit already set for " + functionNode.getName();
 
-        if (compiler.getFunctionNode() == functionNode) { //functionNode.isScript()) {
+        if (top) {
             assert outermostCompileUnit != null : "outermost compile unit is null";
-
-            functionNode.setCompileUnit(outermostCompileUnit);
+            functionNode = functionNode.setCompileUnit(lc, outermostCompileUnit);
             outermostCompileUnit.addWeight(weight + WeighNodes.FUNCTION_WEIGHT);
         } else {
-            functionNode.setCompileUnit(findUnit(weight));
+            functionNode = functionNode.setCompileUnit(lc, findUnit(weight));
         }
 
-        // Recursively split nested functions
-        functionNode.accept(new NodeOperatorVisitor() {
-            @Override
-            public Node enterFunctionNode(FunctionNode function) {
-                if(function == functionNode) {
-                    // Don't process outermost function (it was already processed) but descend into it to find nested
-                    // functions.
-                    return function;
+        final Block body = functionNode.getBody();
+        final List<FunctionNode> dc = directChildren(functionNode);
+
+        final Block newBody = (Block)body.accept(new NodeVisitor() {
+                @Override
+                public boolean enterFunctionNode(final FunctionNode nestedFunction) {
+                    return dc.contains(nestedFunction);
                 }
-                // Process a nested function
-                new Splitter(compiler, function, outermostCompileUnit).split();
-                // Don't descend into a a nested function; Splitter.split() has taken care of nested-in-nested functions.
-                return null;
+
+                @Override
+                public Node leaveFunctionNode(final FunctionNode nestedFunction) {
+                    FunctionNode split = new Splitter(compiler, nestedFunction, outermostCompileUnit).split(nestedFunction);
+                    getLexicalContext().replace(nestedFunction, split);
+                    return split;
+                }
+            });
+        functionNode = functionNode.setBody(lc, newBody);
+
+        assert functionNode.getCompileUnit() != null;
+
+        return functionNode.setState(lc, CompilationState.SPLIT);
+    }
+
+    private static List<FunctionNode> directChildren(final FunctionNode functionNode) {
+        final List<FunctionNode> dc = new ArrayList<>();
+        functionNode.accept(new NodeVisitor() {
+            @Override
+            public boolean enterFunctionNode(final FunctionNode child) {
+                if (child == functionNode) {
+                    return true;
+                }
+                if (getLexicalContext().getParentFunction(child) == functionNode) {
+                    dc.add(child);
+                }
+                return false;
             }
         });
-
-        functionNode.setState(CompilationState.SPLIT);
+        return dc;
     }
 
     /**
@@ -170,8 +179,8 @@
      *
      * @return new weight for the resulting block.
      */
-    private long splitBlock(final Block block, final FunctionNode function) {
-        functionNode.setIsSplit();
+    private Block splitBlock(final Block block, final FunctionNode function) {
+        getLexicalContext().setFlag(getLexicalContext().getCurrentFunction(), FunctionNode.IS_SPLIT);
 
         final List<Node> splits = new ArrayList<>();
         List<Node> statements = new ArrayList<>();
@@ -186,7 +195,6 @@
                     statements = new ArrayList<>();
                     statementsWeight = 0;
                 }
-
             }
 
             if (statement.isTerminal()) {
@@ -201,9 +209,7 @@
             splits.add(createBlockSplitNode(block, function, statements, statementsWeight));
         }
 
-        block.setStatements(splits);
-
-        return WeighNodes.weigh(block, weightCache);
+        return block.setStatements(getLexicalContext(), splits);
     }
 
     /**
@@ -218,51 +224,44 @@
         final Source source = parent.getSource();
         final long   token  = parent.getToken();
         final int    finish = parent.getFinish();
-        final String name   = function.uniqueName(SPLIT_PREFIX.tag());
+        final String name   = function.uniqueName(SPLIT_PREFIX.symbolName());
 
-        final Block newBlock = new Block(source, token, finish);
-        newBlock.setFrame(new Frame(parent.getFrame()));
-        newBlock.setStatements(statements);
+        final Block newBlock = new Block(source, token, finish, statements);
 
-        final SplitNode splitNode = new SplitNode(name, functionNode, newBlock);
-
-        splitNode.setCompileUnit(compiler.findUnit(weight + WeighNodes.FUNCTION_WEIGHT));
-
-        return splitNode;
+        return new SplitNode(name, newBlock, compiler.findUnit(weight + WeighNodes.FUNCTION_WEIGHT));
     }
 
     @Override
-    public Node enterBlock(final Block block) {
+    public boolean enterBlock(final Block block) {
         if (block.isCatchBlock()) {
-            return null;
+            return false;
         }
-        lexicalContext.push(block);
 
         final long weight = WeighNodes.weigh(block, weightCache);
 
         if (weight < SPLIT_THRESHOLD) {
             weightCache.put(block, weight);
-            lexicalContext.pop(block);
-            return null;
+            return false;
         }
 
-        return block;
+        return true;
     }
 
     @Override
     public Node leaveBlock(final Block block) {
         assert !block.isCatchBlock();
 
+        Block newBlock = block;
+
         // Block was heavier than SLIT_THRESHOLD in enter, but a sub-block may have
         // been split already, so weigh again before splitting.
         long weight = WeighNodes.weigh(block, weightCache);
         if (weight >= SPLIT_THRESHOLD) {
-            weight = splitBlock(block, lexicalContext.getFunction(block));
+            newBlock = splitBlock(block, getLexicalContext().getFunction(block));
+            weight   = WeighNodes.weigh(newBlock, weightCache);
         }
-        weightCache.put(block, weight);
-
-        lexicalContext.pop(block);
-        return block;
+        weightCache.put(newBlock, weight);
+        return newBlock;
     }
 
     @SuppressWarnings("rawtypes")
@@ -274,7 +273,7 @@
             return literal;
         }
 
-        functionNode.setIsSplit();
+        getLexicalContext().setFlag(getLexicalContext().getCurrentFunction(), FunctionNode.IS_SPLIT);
 
         if (literal instanceof ArrayLiteralNode) {
             final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode) literal;
@@ -312,123 +311,12 @@
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode node) {
-        if(node == functionNode && !node.isLazy()) {
-            lexicalContext.push(node);
-            node.visitStatements(this);
-            lexicalContext.pop(node);
+    public boolean enterFunctionNode(final FunctionNode node) {
+        //only go into the function node for this splitter. any subfunctions are rejected
+        if (node == outermost && !node.isLazy()) {
+            return true;
         }
-        return null;
-    }
-
-    static class SplitFlowAnalyzer extends NodeVisitor {
-
-        /** Stack of visited Split nodes, deepest node first. */
-        private final Deque<SplitNode> splitStack;
-
-        /** Map of possible jump targets to containing split node */
-        private final Map<Node,SplitNode> targetNodes = new HashMap<>();
-
-        SplitFlowAnalyzer() {
-            this.splitStack = new LinkedList<>();
-        }
-
-        @Override
-        public Node enterLabelNode(final LabelNode labelNode) {
-            registerJumpTarget(labelNode.getBreakNode());
-            registerJumpTarget(labelNode.getContinueNode());
-            return labelNode;
-        }
-
-        @Override
-        public Node enterWhileNode(final WhileNode whileNode) {
-            registerJumpTarget(whileNode);
-            return whileNode;
-        }
-
-        @Override
-        public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
-            registerJumpTarget(doWhileNode);
-            return doWhileNode;
-        }
-
-        @Override
-        public Node enterForNode(final ForNode forNode) {
-            registerJumpTarget(forNode);
-            return forNode;
-        }
-
-        @Override
-        public Node enterSwitchNode(final SwitchNode switchNode) {
-            registerJumpTarget(switchNode);
-            return switchNode;
-        }
-
-        @Override
-        public Node enterReturnNode(final ReturnNode returnNode) {
-            for (final SplitNode split : splitStack) {
-                split.setHasReturn(true);
-            }
-            return returnNode;
-        }
-
-        @Override
-        public Node enterContinueNode(final ContinueNode continueNode) {
-            searchJumpTarget(continueNode.getTargetNode(), continueNode.getTargetLabel());
-            return continueNode;
-        }
-
-        @Override
-        public Node enterBreakNode(final BreakNode breakNode) {
-            searchJumpTarget(breakNode.getTargetNode(), breakNode.getTargetLabel());
-            return breakNode;
-        }
-
-        @Override
-        public Node enterSplitNode(final SplitNode splitNode) {
-            splitStack.addFirst(splitNode);
-            return splitNode;
-        }
-
-        @Override
-        public Node leaveSplitNode(final SplitNode splitNode) {
-            assert splitNode == splitStack.peekFirst();
-            splitStack.removeFirst();
-            return splitNode;
-        }
-
-        /**
-         * Register the split node containing a potential jump target.
-         * @param targetNode a potential target node.
-         */
-        private void registerJumpTarget(final Node targetNode) {
-            final SplitNode splitNode = splitStack.peekFirst();
-            if (splitNode != null) {
-                targetNodes.put(targetNode, splitNode);
-            }
-        }
-
-        /**
-         * Check if a jump target is outside the current split node and its parent split nodes.
-         * @param targetNode the jump target node.
-         * @param targetLabel the jump target label.
-         */
-        private void searchJumpTarget(final Node targetNode, final Label targetLabel) {
-
-            final SplitNode targetSplit = targetNodes.get(targetNode);
-            // Note that targetSplit may be null, indicating that targetNode is in top level method.
-            // In this case we have to add the external jump target to all split nodes.
-
-            for (final SplitNode split : splitStack) {
-                if (split == targetSplit) {
-                    break;
-                }
-                final List<Label> externalTargets = split.getExternalTargets();
-                if (!externalTargets.contains(targetLabel)) {
-                    split.addExternalTarget(targetLabel);
-                }
-            }
-        }
+        return false;
     }
 }
 
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
index 18bd955..002cc90 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
@@ -35,7 +35,6 @@
 import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
@@ -101,7 +100,7 @@
      * @param weightCache cache of already calculated block weights
      */
     private WeighNodes(FunctionNode topFunction, final Map<Node, Long> weightCache) {
-        super(null, null);
+        super();
         this.topFunction = topFunction;
         this.weightCache = weightCache;
     }
@@ -123,13 +122,13 @@
     }
 
     @Override
-    public Node enterBlock(final Block block) {
+    public boolean enterBlock(final Block block) {
         if (weightCache != null && weightCache.containsKey(block)) {
             weight += weightCache.get(block);
-            return null;
+            return false;
         }
 
-        return block;
+        return true;
     }
 
     @Override
@@ -157,12 +156,6 @@
     }
 
     @Override
-    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
-        weight += LOOP_WEIGHT;
-        return doWhileNode;
-    }
-
-    @Override
     public Node leaveExecuteNode(final ExecuteNode executeNode) {
         return executeNode;
     }
@@ -174,15 +167,15 @@
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
-        if(functionNode == topFunction) {
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
+        if (functionNode == topFunction) {
             // the function being weighted; descend into its statements
-            functionNode.visitStatements(this);
-        } else {
-            // just a reference to inner function from outer function
-            weight += FUNC_EXPR_WEIGHT;
+            return true;
+//            functionNode.visitStatements(this);
         }
-        return null;
+        // just a reference to inner function from outer function
+        weight += FUNC_EXPR_WEIGHT;
+        return false;
     }
 
     @Override
@@ -205,7 +198,7 @@
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enterLiteralNode(final LiteralNode literalNode) {
+    public boolean enterLiteralNode(final LiteralNode literalNode) {
         weight += LITERAL_WEIGHT;
 
         if (literalNode instanceof ArrayLiteralNode) {
@@ -224,10 +217,10 @@
                 }
             }
 
-            return null;
+            return false;
         }
 
-        return literalNode;
+        return true;
     }
 
     @Override
@@ -249,9 +242,9 @@
     }
 
     @Override
-    public Node enterSplitNode(final SplitNode splitNode) {
+    public boolean enterSplitNode(final SplitNode splitNode) {
         weight += SPLIT_WEIGHT;
-        return null;
+        return false;
     }
 
     @Override
diff --git a/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java b/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java
index b7b7668..2f739bf 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java
@@ -25,23 +25,18 @@
 
 package jdk.nashorn.internal.ir;
 
-import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS;
-
-import jdk.nashorn.internal.codegen.ObjectClassGenerator;
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of a property access (period operator.)
- *
  */
-public class AccessNode extends BaseNode implements TypeOverride<AccessNode> {
+@Immutable
+public final class AccessNode extends BaseNode {
     /** Property ident. */
-    private IdentNode property;
-
-    /** Does this node have a type override */
-    private boolean hasCallSiteType;
+    private final IdentNode property;
 
     /**
      * Constructor
@@ -53,49 +48,13 @@
      * @param property  property
      */
     public AccessNode(final Source source, final long token, final int finish, final Node base, final IdentNode property) {
-        super(source, token, finish, base);
-
-        this.start    = base.getStart();
+        super(source, token, finish, base, false, false);
         this.property = property.setIsPropertyName();
     }
 
-    /**
-     * Copy constructor
-     *
-     * @param accessNode  source node
-     */
-    public AccessNode(final AccessNode accessNode) {
-        this(accessNode, new CopyState());
-    }
-
-    /**
-     * Internal copy constructor
-     *
-     * @param accessNode  source node
-     * @param cs          copy state
-     */
-    protected AccessNode(final AccessNode accessNode, final CopyState cs) {
-        super(accessNode, cs);
-        this.property = (IdentNode)cs.existingOrCopy(accessNode.getProperty());
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new AccessNode(this, cs);
-    }
-
-    @Override
-    public boolean equals(final Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-        final AccessNode accessNode = (AccessNode)other;
-        return property.equals(accessNode.getProperty());
-    }
-
-    @Override
-    public int hashCode() {
-        return super.hashCode() ^ property.hashCode();
+    private AccessNode(final AccessNode accessNode, final Node base, final IdentNode property, final boolean isFunction, final boolean hasCallSiteType) {
+        super(accessNode, base, isFunction, hasCallSiteType);
+        this.property = property;
     }
 
     /**
@@ -104,12 +63,11 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterAccessNode(this) != null) {
-            base = base.accept(visitor);
-            property = (IdentNode)property.accept(visitor);
-            return visitor.leaveAccessNode(this);
+        if (visitor.enterAccessNode(this)) {
+            return visitor.leaveAccessNode(
+                setBase(base.accept(visitor)).
+                setProperty((IdentNode)property.accept(visitor)));
         }
-
         return this;
     }
 
@@ -117,7 +75,7 @@
     public void toString(final StringBuilder sb) {
         final boolean needsParen = tokenType().needsParens(getBase().tokenType(), true);
 
-        if (hasCallSiteType) {
+        if (hasCallSiteType()) {
             sb.append('{');
             final String desc = getType().getDescriptor();
             sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor());
@@ -147,19 +105,34 @@
         return property;
     }
 
-    @Override
-    public AccessNode setType(final Type type) {
-        if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
-            ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType());
+    private AccessNode setBase(final Node base) {
+        if (this.base == base) {
+            return this;
         }
-        property = property.setType(type);
-        getSymbol().setTypeOverride(type); //always a temp so this is fine.
-        hasCallSiteType = true;
-        return this;
+        return new AccessNode(this, base, property, isFunction(), hasCallSiteType());
+    }
+
+
+    private AccessNode setProperty(final IdentNode property) {
+        if (this.property == property) {
+            return this;
+        }
+        return new AccessNode(this, base, property, isFunction(), hasCallSiteType());
     }
 
     @Override
-    public boolean canHaveCallSiteType() {
-        return true; //carried by the symbol and always the same nodetype==symboltype
+    public AccessNode setType(final Type type) {
+        logTypeChange(type);
+        getSymbol().setTypeOverride(type); //always a temp so this is fine.
+        return new AccessNode(this, base, property.setType(type), isFunction(), hasCallSiteType());
     }
+
+    @Override
+    public BaseNode setIsFunction() {
+        if (isFunction()) {
+            return this;
+        }
+        return new AccessNode(this, base, property, true, hasCallSiteType());
+    }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java b/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java
index 26a2836..5e5bfb1 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java
@@ -25,6 +25,10 @@
 
 package jdk.nashorn.internal.ir;
 
+import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS;
+import jdk.nashorn.internal.codegen.ObjectClassGenerator;
+import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
@@ -33,12 +37,15 @@
  * @see AccessNode
  * @see IndexNode
  */
-public abstract class BaseNode extends Node implements FunctionCall {
+@Immutable
+public abstract class BaseNode extends Node implements FunctionCall, TypeOverride<BaseNode> {
 
     /** Base Node. */
-    protected Node base;
+    protected final Node base;
 
-    private boolean function;
+    private final boolean isFunction;
+
+    private final boolean hasCallSiteType;
 
     /**
      * Constructor
@@ -47,37 +54,28 @@
      * @param token  token
      * @param finish finish
      * @param base   base node
+     * @param isFunction is this a function
+     * @param hasCallSiteType does this access have a callsite type
      */
-    public BaseNode(final Source source, final long token, final int finish, final Node base) {
-        super(source, token, finish);
-        this.base = base;
-        setStart(base.getStart());
+    public BaseNode(final Source source, final long token, final int finish, final Node base, final boolean isFunction, final boolean hasCallSiteType) {
+        super(source, token, base.getStart(), finish);
+        this.base            = base;
+        this.isFunction      = isFunction;
+        this.hasCallSiteType = hasCallSiteType;
     }
 
     /**
-     * Copy constructor
-     *
-     * @param baseNode the base node
-     * @param cs       a copy state
+     * Copy constructor for immutable nodes
+     * @param baseNode node to inherit from
+     * @param base base
+     * @param isFunction is this a function
+     * @param hasCallSiteType does this access have a callsite type
      */
-    protected BaseNode(final BaseNode baseNode, final CopyState cs) {
+    protected BaseNode(final BaseNode baseNode, final Node base, final boolean isFunction, final boolean hasCallSiteType) {
         super(baseNode);
-        this.base = cs.existingOrCopy(baseNode.getBase());
-        setStart(base.getStart());
-    }
-
-    @Override
-    public boolean equals(final Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-        final BaseNode baseNode = (BaseNode)other;
-        return base.equals(baseNode.getBase());
-    }
-
-    @Override
-    public int hashCode() {
-        return base.hashCode();
+        this.base            = base;
+        this.isFunction      = isFunction;
+        this.hasCallSiteType = hasCallSiteType;
     }
 
     /**
@@ -88,25 +86,37 @@
         return base;
     }
 
-    /**
-     * Reset the base node for this access
-     * @param base new base node
-     */
-    public void setBase(final Node base) {
-        this.base = base;
-    }
-
     @Override
     public boolean isFunction() {
-        return function;
+        return isFunction;
     }
 
     /**
      * Mark this node as being the callee operand of a {@link CallNode}.
      * @return a base node identical to this one in all aspects except with its function flag set.
      */
-    public BaseNode setIsFunction() {
-        function = true;
-        return this;
+    public abstract BaseNode setIsFunction();
+
+    @Override
+    public boolean canHaveCallSiteType() {
+        return true; //carried by the symbol and always the same nodetype==symboltype
+    }
+
+    /**
+     * Does the access have a call site type override?
+     * @return true if overridden
+     */
+    protected boolean hasCallSiteType() {
+        return hasCallSiteType;
+    }
+
+    /**
+     * Debug type change
+     * @param type new type
+     */
+    protected final void logTypeChange(final Type type) {
+        if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
+            ObjectClassGenerator.LOG.info(getClass().getName(), " ", this, " => ", type, " instead of ", getType());
+        }
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java b/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java
index 42c7a6c..28df8ed 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java
@@ -26,6 +26,7 @@
 package jdk.nashorn.internal.ir;
 
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.TokenType;
 import jdk.nashorn.internal.runtime.Source;
@@ -33,9 +34,12 @@
 /**
  * BinaryNode nodes represent two operand operations.
  */
-public class BinaryNode extends UnaryNode {
+@Immutable
+public final class BinaryNode extends Node implements Assignment<Node> {
     /** Left hand side argument. */
-    private Node lhs;
+    private final Node lhs;
+
+    private final Node rhs;
 
     /**
      * Constructor
@@ -46,28 +50,15 @@
      * @param rhs    right hand side
      */
     public BinaryNode(final Source source, final long token, final Node lhs, final Node rhs) {
-        super(source, token, rhs);
+        super(source, token, lhs.getStart(), rhs.getFinish());
+        this.lhs   = lhs;
+        this.rhs   = rhs;
+    }
 
-        start  = lhs.getStart();
-        finish = rhs.getFinish();
-
+    private BinaryNode(final BinaryNode binaryNode, final Node lhs, final Node rhs) {
+        super(binaryNode);
         this.lhs = lhs;
-    }
-
-    /**
-     * Copy constructor
-     *
-     * @param binaryNode the binary node
-     * @param cs         copy state
-     */
-    protected BinaryNode(final BinaryNode binaryNode, final CopyState cs) {
-        super(binaryNode, cs);
-        lhs = cs.existingOrCopy(binaryNode.lhs);
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new BinaryNode(this, cs);
+        this.rhs = rhs;
     }
 
     /**
@@ -149,28 +140,14 @@
         return rhs();
     }
 
-    @Override
-    public boolean equals(final Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-        return lhs.equals(((BinaryNode)other).lhs());
-    }
-
-    @Override
-    public int hashCode() {
-        return super.hashCode() ^ lhs().hashCode();
-    }
-
     /**
      * Assist in IR navigation.
      * @param visitor IR navigating visitor.
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterBinaryNode(this) != null) {
-            // TODO: good cause for a separate visitMembers: we could delegate to UnaryNode.visitMembers
-            return visitor.leaveBinaryNode((BinaryNode)setLHS(lhs.accept(visitor)).setRHS(rhs().accept(visitor)));
+        if (visitor.enterBinaryNode(this)) {
+            return visitor.leaveBinaryNode(setLHS(lhs.accept(visitor)).setRHS(rhs.accept(visitor)));
         }
 
         return this;
@@ -231,14 +208,35 @@
     }
 
     /**
+     * Get the right hand side expression for this node
+     * @return the left hand side expression
+     */
+    public Node rhs() {
+        return rhs;
+    }
+
+    /**
      * Set the left hand side expression for this node
      * @param lhs new left hand side expression
      * @return a node equivalent to this one except for the requested change.
      */
     public BinaryNode setLHS(final Node lhs) {
-        if(this.lhs == lhs) return this;
-        final BinaryNode n = (BinaryNode)clone();
-        n.lhs = lhs;
-        return n;
+        if (this.lhs == lhs) {
+            return this;
+        }
+        return new BinaryNode(this, lhs, rhs);
     }
+
+    /**
+     * Set the right hand side expression for this node
+     * @param rhs new left hand side expression
+     * @return a node equivalent to this one except for the requested change.
+     */
+    public BinaryNode setRHS(final Node rhs) {
+        if (this.rhs == rhs) {
+            return this;
+        }
+        return new BinaryNode(this, lhs, rhs);
+    }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Block.java b/nashorn/src/jdk/nashorn/internal/ir/Block.java
index 6f138f8..48b9ed6 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Block.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Block.java
@@ -27,14 +27,15 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.ListIterator;
-import jdk.nashorn.internal.codegen.Frame;
+import java.util.Map;
 import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
@@ -42,97 +43,79 @@
  * IR representation for a list of statements and functions. All provides the
  * basis for script body.
  */
-public class Block extends Node {
+@Immutable
+public class Block extends BreakableNode implements Flags<Block> {
     /** List of statements */
-    protected List<Node> statements;
+    protected final List<Node> statements;
 
-    /** Symbol table. */
-    protected final HashMap<String, Symbol> symbols;
-
-    /** Variable frame. */
-    protected Frame frame;
+    /** Symbol table - keys must be returned in the order they were put in. */
+    protected final Map<String, Symbol> symbols;
 
     /** Entry label. */
     protected final Label entryLabel;
 
-    /** Break label. */
-    protected final Label breakLabel;
-
     /** Does the block/function need a new scope? */
-    protected boolean needsScope;
+    protected final int flags;
+
+    /** Flag indicating that this block needs scope */
+    public static final int NEEDS_SCOPE = 1 << 0;
+
+    /**
+     * Flag indicating whether this block needs
+     * self symbol assignment at the start. This is used only for
+     * blocks that are the bodies of function nodes who refer to themselves
+     * by name. It causes codegen to insert a var [fn_name] = __callee__
+     * at the start of the body
+     */
+    public static final int NEEDS_SELF_SYMBOL = 1 << 1;
+
+    /**
+     * Is this block tagged as terminal based on its contents
+     * (usually the last statement)
+     */
+    public static final int IS_TERMINAL = 1 << 2;
 
     /**
      * Constructor
      *
-     * @param source   source code
-     * @param token    token
-     * @param finish   finish
+     * @param source     source code
+     * @param token      token
+     * @param finish     finish
+     * @param statements statements
      */
-    public Block(final Source source, final long token, final int finish) {
-        super(source, token, finish);
+    public Block(final Source source, final long token, final int finish, final Node... statements) {
+        super(source, token, finish, new Label("block_break"));
 
-        this.statements = new ArrayList<>();
-        this.symbols    = new HashMap<>();
+        this.statements = Arrays.asList(statements);
+        this.symbols    = new LinkedHashMap<>();
         this.entryLabel = new Label("block_entry");
-        this.breakLabel = new Label("block_break");
+        this.flags     =  0;
     }
 
     /**
-     * Internal copy constructor
+     * Constructor
      *
-     * @param block the source block
-     * @param cs    the copy state
+     * @param source     source code
+     * @param token      token
+     * @param finish     finish
+     * @param statements statements
      */
-    protected Block(final Block block, final CopyState cs) {
+    public Block(final Source source, final long token, final int finish, final List<Node> statements) {
+        this(source, token, finish, statements.toArray(new Node[statements.size()]));
+    }
+
+    private Block(final Block block, final int finish, final List<Node> statements, final int flags) {
         super(block);
-
-        this.statements = new ArrayList<>();
-        for (final Node statement : block.getStatements()) {
-            statements.add(cs.existingOrCopy(statement));
-        }
-        this.symbols    = new HashMap<>();
-        this.frame      = block.frame == null ? null : block.frame.copy();
+        this.statements = statements;
+        this.flags      = flags;
+        this.symbols    = block.symbols; //todo - symbols have no dependencies on any IR node and can as far as we understand it be shallow copied now
         this.entryLabel = new Label(block.entryLabel);
-        this.breakLabel = new Label(block.breakLabel);
-
-        assert block.symbols.isEmpty() : "must not clone with symbols";
+        this.finish = finish;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new Block(this, cs);
-    }
-
-    /**
-     * Add a new statement to the statement list.
-     *
-     * @param statement Statement node to add.
-     */
-    public void addStatement(final Node statement) {
-        if (statement != null) {
-            statements.add(statement);
-            if (getFinish() < statement.getFinish()) {
-                setFinish(statement.getFinish());
-            }
-        }
-    }
-
-    /**
-     * Prepend statements to the statement list
-     *
-     * @param prepended statement to add
-     */
-    public void prependStatements(final List<Node> prepended) {
-        statements.addAll(0, prepended);
-    }
-
-    /**
-     * Add a list of statements to the statement list.
-     *
-     * @param statementList Statement nodes to add.
-     */
-    public void addStatements(final List<Node> statementList) {
-        statements.addAll(statementList);
+    public Node ensureUniqueLabels(final LexicalContext lc) {
+        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags));
     }
 
     /**
@@ -142,19 +125,9 @@
      * @return new or same node
      */
     @Override
-    public Node accept(final NodeVisitor visitor) {
-        final Block saveBlock = visitor.getCurrentBlock();
-        visitor.setCurrentBlock(this);
-
-        try {
-            // Ignore parent to avoid recursion.
-
-            if (visitor.enterBlock(this) != null) {
-                visitStatements(visitor);
-                return visitor.leaveBlock(this);
-            }
-        } finally {
-            visitor.setCurrentBlock(saveBlock);
+    public Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterBlock(this)) {
+            return visitor.leaveBlock(setStatements(lc, Node.accept(visitor, Node.class, statements)));
         }
 
         return this;
@@ -222,11 +195,18 @@
     }
 
     /**
-     * Get the break label for this block
-     * @return the break label
+     * Tag block as terminal or non terminal
+     * @param lc          lexical context
+     * @param isTerminal is block terminal
+     * @return same block, or new if flag changed
      */
-    public Label getBreakLabel() {
-        return breakLabel;
+    public Block setIsTerminal(final LexicalContext lc, final boolean isTerminal) {
+        return isTerminal ? setFlag(lc, IS_TERMINAL) : clearFlag(lc, IS_TERMINAL);
+    }
+
+    @Override
+    public boolean isTerminal() {
+        return getFlag(IS_TERMINAL);
     }
 
     /**
@@ -238,23 +218,6 @@
     }
 
     /**
-     * Get the frame for this block
-     * @return the frame
-     */
-    public Frame getFrame() {
-        return frame;
-    }
-
-    /**
-     * Reset the frame for this block
-     *
-     * @param frame  the new frame
-     */
-    public void setFrame(final Frame frame) {
-        this.frame = frame;
-    }
-
-    /**
      * Get the list of statements in this block
      *
      * @return a list of statements
@@ -264,21 +227,21 @@
     }
 
     /**
-     * Applies the specified visitor to all statements in the block.
-     * @param visitor the visitor.
-     */
-    public void visitStatements(NodeVisitor visitor) {
-        for (ListIterator<Node> stmts = statements.listIterator(); stmts.hasNext();) {
-            stmts.set(stmts.next().accept(visitor));
-        }
-    }
-    /**
      * Reset the statement list for this block
      *
-     * @param statements  new statement list
+     * @param lc lexical context
+     * @param statements new statement list
+     * @return new block if statements changed, identity of statements == block.statements
      */
-    public void setStatements(final List<Node> statements) {
-        this.statements = statements;
+    public Block setStatements(final LexicalContext lc, final List<Node> statements) {
+        if (this.statements == statements) {
+            return this;
+        }
+        int lastFinish = 0;
+        if (!statements.isEmpty()) {
+            lastFinish = statements.get(statements.size() - 1).getFinish();
+        }
+        return Node.replaceInLexicalContext(lc, this, new Block(this, Math.max(finish, lastFinish), statements, flags));
     }
 
     /**
@@ -297,39 +260,65 @@
      * @return true if this function needs a scope
      */
     public boolean needsScope() {
-        return needsScope;
+        return (flags & NEEDS_SCOPE) == NEEDS_SCOPE;
+    }
+
+    @Override
+    public Block setFlags(final LexicalContext lc, int flags) {
+        if (this.flags == flags) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags));
+    }
+
+    @Override
+    public Block clearFlag(final LexicalContext lc, int flag) {
+        return setFlags(lc, flags & ~flag);
+    }
+
+    @Override
+    public Block setFlag(final LexicalContext lc, int flag) {
+        return setFlags(lc, flags | flag);
+    }
+
+    @Override
+    public boolean getFlag(final int flag) {
+        return (flags & flag) == flag;
     }
 
     /**
      * Set the needs scope flag.
+     * @param lc lexicalContext
+     * @return new block if state changed, otherwise this
      */
-    public void setNeedsScope() {
-        needsScope = true;
+    public Block setNeedsScope(final LexicalContext lc) {
+        if (needsScope()) {
+            return this;
+        }
+
+        return Node.replaceInLexicalContext(lc, this, new Block(this, finish, statements, flags | NEEDS_SCOPE));
     }
 
     /**
-     * Marks this block as using a specified scoped symbol. The block and its parent blocks up to but not
-     * including the block defining the symbol will be marked as needing parent scope. The block defining the symbol
-     * will be marked as one that needs to have its own scope.
-     * @param symbol the symbol being used.
-     * @param ancestors the iterator over block's containing lexical context
+     * Computationally determine the next slot for this block,
+     * indexed from 0. Use this as a relative base when computing
+     * frames
+     * @return next slot
      */
-    public void setUsesScopeSymbol(final Symbol symbol, Iterator<Block> ancestors) {
-        if(symbol.getBlock() == this) {
-            setNeedsScope();
-        } else {
-            setUsesParentScopeSymbol(symbol, ancestors);
+    public int nextSlot() {
+        final Iterator<Symbol> iter = symbolIterator();
+        int next = 0;
+        while (iter.hasNext()) {
+        final Symbol symbol = iter.next();
+        if (symbol.hasSlot()) {
+            next += symbol.slotCount();
         }
+        }
+        return next;
     }
 
-    /**
-     * Invoked when this block uses a scope symbol defined in one of its ancestors.
-     * @param symbol the scope symbol being used
-     * @param ancestors iterator over ancestor blocks
-     */
-    void setUsesParentScopeSymbol(final Symbol symbol, Iterator<Block> ancestors) {
-        if(ancestors.hasNext()) {
-            ancestors.next().setUsesScopeSymbol(symbol, ancestors);
-        }
+    @Override
+    protected boolean isBreakableWithoutLabel() {
+        return false;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BlockLexicalContext.java b/nashorn/src/jdk/nashorn/internal/ir/BlockLexicalContext.java
new file mode 100644
index 0000000..38186f56
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/ir/BlockLexicalContext.java
@@ -0,0 +1,120 @@
+/*
+ * 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.ir;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.ListIterator;
+
+/**
+ * This is a subclass of lexical context used for filling
+ * blocks (and function nodes) with statements. When popping
+ * a block from the lexical context, any statements that have
+ * been generated in it are commited to the block. This saves
+ * unnecessary object mutations and lexical context replacement
+ */
+public class BlockLexicalContext extends LexicalContext {
+    /** statement stack, each block on the lexical context maintains one of these, which is
+     *  committed to the block on pop */
+    private Deque<List<Node>> sstack = new ArrayDeque<>();
+
+    /** Last non debug statement emitted in this context */
+    protected Node lastStatement;
+
+    @Override
+    public <T extends LexicalContextNode> T push(final T node) {
+        T pushed = super.push(node);
+        if (node instanceof Block) {
+            sstack.push(new ArrayList<Node>());
+        }
+        return pushed;
+    }
+
+    /**
+     * Get the statement list from the stack, possibly filtered
+     * @return statement list
+     */
+    protected List<Node> popStatements() {
+        return sstack.pop();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public <T extends LexicalContextNode> T pop(final T node) {
+        T expected = node;
+        if (node instanceof Block) {
+            final List<Node> newStatements = popStatements();
+            expected = (T)((Block)node).setStatements(this, newStatements);
+            if (!sstack.isEmpty()) {
+                lastStatement = lastStatement(sstack.peek());
+            }
+        }
+        return super.pop(expected);
+    }
+
+    /**
+     * Append a statement to the block being generated
+     * @param statement statement to add
+     */
+    public void appendStatement(final Node statement) {
+        assert statement != null;
+        sstack.peek().add(statement);
+        if (!statement.isDebug()) {
+            lastStatement = statement;
+        }
+    }
+
+    /**
+     * Prepend a statement to the block being generated
+     * @param statement statement to prepend
+     * @return the prepended statement
+     */
+    public Node prependStatement(final Node statement) {
+        assert statement != null;
+        sstack.peek().add(0, statement);
+        return statement;
+    }
+
+    /**
+     * Get the last (non debug) statement that was emitted into a block
+     * @return the last statement emitted
+     */
+    public Node getLastStatement() {
+        return lastStatement;
+    }
+
+    private static Node lastStatement(final List<Node> statements) {
+        for (final ListIterator<Node> iter = statements.listIterator(statements.size()); iter.hasPrevious(); ) {
+            final Node node = iter.previous();
+            if (!node.isDebug()) {
+                return node;
+            }
+        }
+        return null;
+    }
+}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java b/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java
index 7ad0dc6..f0966b4 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java
@@ -25,37 +25,34 @@
 
 package jdk.nashorn.internal.ir;
 
-import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for {@code break} statements.
  */
-public class BreakNode extends LabeledNode {
+@Immutable
+public final class BreakNode extends Node {
 
-     /**
+    private final IdentNode label;
+
+    /**
      * Constructor
      *
-     * @param source     source code
-     * @param token      token
-     * @param finish     finish
-     * @param labelNode  break label
-     * @param targetNode node to break to
-     * @param tryChain   surrounding try chain
+     * @param source source code
+     * @param token  token
+     * @param finish finish
+     * @param label  label for break or null if none
      */
-    public BreakNode(final Source source, final long token, final int finish, final LabelNode labelNode, final Node targetNode, final TryNode tryChain) {
-        super(source, token, finish, labelNode, targetNode, tryChain);
-        setHasGoto();
-    }
-
-    private BreakNode(final BreakNode breakNode, final CopyState cs) {
-        super(breakNode, cs);
+    public BreakNode(final Source source, final long token, final int finish, final IdentNode label) {
+        super(source, token, finish);
+        this.label = label;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new BreakNode(this, cs);
+    public boolean hasGoto() {
+        return true;
     }
 
     /**
@@ -64,7 +61,7 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterBreakNode(this) != null) {
+        if (visitor.enterBreakNode(this)) {
             return visitor.leaveBreakNode(this);
         }
 
@@ -72,26 +69,20 @@
     }
 
     /**
-     * Return the target label of this break node.
-     * @return the target label.
+     * Get the label for this break node
+     * @return label, or null if none
      */
-    public Label getTargetLabel() {
-        if (targetNode instanceof BreakableNode) {
-            return ((BreakableNode)targetNode).getBreakLabel();
-        } else if (targetNode instanceof Block) {
-            return ((Block)targetNode).getBreakLabel();
-        }
-
-        throw new AssertionError("Invalid break target " + targetNode.getClass());
+    public IdentNode getLabel() {
+        return label;
     }
 
     @Override
     public void toString(final StringBuilder sb) {
         sb.append("break");
 
-        if (labelNode != null) {
+        if (label != null) {
             sb.append(' ');
-            labelNode.getLabel().toString(sb);
+            label.toString(sb);
         }
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BreakableNode.java b/nashorn/src/jdk/nashorn/internal/ir/BreakableNode.java
index d15b756..3662bfa 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/BreakableNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/BreakableNode.java
@@ -25,27 +25,34 @@
 
 package jdk.nashorn.internal.ir;
 
+import java.util.Arrays;
+import java.util.List;
+
 import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * This class represents a node from which control flow can execute
  * a {@code break} statement
  */
-public abstract class BreakableNode extends Node {
+@Immutable
+public abstract class BreakableNode extends LexicalContextNode {
 
     /** break label. */
-    protected Label breakLabel;
+    protected final Label breakLabel;
 
     /**
      * Constructor
      *
-     * @param source   source code
-     * @param token    token
-     * @param finish   finish
+     * @param source     source code
+     * @param token      token
+     * @param finish     finish
+     * @param breakLabel break label
      */
-    public BreakableNode(final Source source, final long token, final int finish) {
+    protected BreakableNode(final Source source, final long token, final int finish, final Label breakLabel) {
         super(source, token, finish);
+        this.breakLabel = breakLabel;
     }
 
     /**
@@ -55,6 +62,19 @@
      */
     protected BreakableNode(final BreakableNode breakableNode) {
         super(breakableNode);
+        this.breakLabel = new Label(breakableNode.getBreakLabel());
+    }
+
+    @Override
+    public abstract Node ensureUniqueLabels(final LexicalContext lc);
+
+    /**
+     * Check whether this can be broken out from without using a label,
+     * e.g. everything but Blocks, basically
+     * @return true if breakable without label
+     */
+    protected boolean isBreakableWithoutLabel() {
+        return true;
     }
 
     /**
@@ -65,4 +85,14 @@
         return breakLabel;
     }
 
+    /**
+     * Return the labels associated with this node. Breakable nodes that
+     * aren't LoopNodes only have a break label -> the location immediately
+     * afterwards the node in code
+     * @return list of labels representing locations around this node
+     */
+    public List<Label> getLabels() {
+        return Arrays.asList(breakLabel);
+    }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/CallNode.java b/nashorn/src/jdk/nashorn/internal/ir/CallNode.java
index 3410709..4dfbbb5 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/CallNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/CallNode.java
@@ -25,49 +25,51 @@
 
 package jdk.nashorn.internal.ir;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.annotations.Ignore;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for a function call.
- *
  */
-public class CallNode extends Node implements TypeOverride<CallNode> {
+@Immutable
+public final class CallNode extends LexicalContextNode implements TypeOverride<CallNode> {
 
-    private Type type;
+    private final Type type;
 
     /** Function identifier or function body. */
-    private Node function;
+    private final Node function;
 
     /** Call arguments. */
-    private List<Node> args;
+    private final List<Node> args;
 
-    /** flag - is new expression */
-    private boolean isNew;
+    /** Is this a "new" operation */
+    public static final int IS_NEW        = 0x1;
 
-    /** flag - is in with block */
-    private boolean inWithBlock;
+    /** Is this call tagged as inside a with block */
+    public static final int IN_WITH_BLOCK = 0x2;
+
+    private final int flags;
 
     /**
      * Arguments to be passed to builtin {@code eval} function
      */
     public static class EvalArgs {
         /** evaluated code */
-        private Node code;
+        private final Node code;
 
         /** 'this' passed to evaluated code */
-        private IdentNode evalThis;
+        private final IdentNode evalThis;
 
         /** location string for the eval call */
-        final private String location;
+        private final String location;
 
         /** is this call from a strict context? */
-        final private boolean strictMode;
+        private final boolean strictMode;
 
         /**
          * Constructor
@@ -92,12 +94,11 @@
             return code;
         }
 
-        /**
-         * Set the code that is to be eval.ed by this eval function
-         * @param code the code as an AST node
-         */
-        public void setCode(final Node code) {
-            this.code = code;
+        private EvalArgs setCode(final Node code) {
+            if (this.code == code) {
+                return this;
+            }
+            return new EvalArgs(code, evalThis, location, strictMode);
         }
 
         /**
@@ -108,12 +109,11 @@
             return this.evalThis;
         }
 
-        /**
-         * Set the {@code this} symbol used to invoke this eval call
-         * @param evalThis the {@code this} symbol
-         */
-        public void setThis(final IdentNode evalThis) {
-            this.evalThis = evalThis;
+        private EvalArgs setThis(final IdentNode evalThis) {
+            if (this.evalThis == evalThis) {
+                return this;
+            }
+            return new EvalArgs(code, evalThis, location, strictMode);
         }
 
         /**
@@ -135,7 +135,7 @@
 
     /** arguments for 'eval' call. Non-null only if this call node is 'eval' */
     @Ignore
-    private EvalArgs evalArgs;
+    private final EvalArgs evalArgs;
 
     /**
      * Constructors
@@ -145,32 +145,27 @@
      * @param finish   finish
      * @param function the function to call
      * @param args     args to the call
+     * @param flags    flags
      */
-    public CallNode(final Source source, final long token, final int finish, final Node function, final List<Node> args) {
+    public CallNode(final Source source, final long token, final int finish, final Node function, final List<Node> args, final int flags) {
         super(source, token, finish);
 
-        setStart(function.getStart());
-
-        this.function     = function;
-        this.args         = args;
+        this.function = function;
+        this.args     = args;
+        this.flags    = flags;
+        this.type     = null;
+        this.evalArgs = null;
     }
 
-    private CallNode(final CallNode callNode, final CopyState cs) {
+    private CallNode(final CallNode callNode, final Node function, final List<Node> args, final int flags, final Type type, final EvalArgs evalArgs) {
         super(callNode);
-
-        final List<Node> newArgs = new ArrayList<>();
-
-        for (final Node arg : callNode.args) {
-            newArgs.add(cs.existingOrCopy(arg));
-        }
-
-        this.function     = cs.existingOrCopy(callNode.function);     //TODO existing or same?
-        this.args         = newArgs;
-        this.isNew        = callNode.isNew;
-        this.inWithBlock  = callNode.inWithBlock;
+        this.function = function;
+        this.args = args;
+        this.flags = flags;
+        this.type = type;
+        this.evalArgs = evalArgs;
     }
 
-
     @Override
     public Type getType() {
         if (hasCallSiteType()) {
@@ -181,8 +176,10 @@
 
     @Override
     public CallNode setType(final Type type) {
-        this.type = type;
-        return this;
+        if (this.type == type) {
+            return this;
+        }
+        return new CallNode(this, function, args, flags, type, evalArgs);
     }
 
     private boolean hasCallSiteType() {
@@ -194,11 +191,6 @@
         return true;
     }
 
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new CallNode(this, cs);
-    }
-
     /**
      * Assist in IR navigation.
      *
@@ -207,15 +199,22 @@
      * @return node or replacement
      */
     @Override
-    public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterCallNode(this) != null) {
-            function = function.accept(visitor);
-
-            for (int i = 0, count = args.size(); i < count; i++) {
-                args.set(i, args.get(i).accept(visitor));
+    public Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterCallNode(this)) {
+            final CallNode newCallNode = (CallNode)visitor.leaveCallNode(
+                    setFunction(function.accept(visitor)).
+                    setArgs(Node.accept(visitor, Node.class, args)).
+                    setFlags(flags).
+                    setType(type).
+                    setEvalArgs(evalArgs == null ?
+                            null :
+                            evalArgs.setCode(evalArgs.getCode().accept(visitor)).
+                                setThis((IdentNode)evalArgs.getThis().accept(visitor))));
+            // Theoretically, we'd need to instead pass lc to every setter and do a replacement on each. In practice,
+            // setType from TypeOverride can't accept a lc, and we don't necessarily want to go there now.
+            if(this != newCallNode) {
+                return Node.replaceInLexicalContext(lc, this, newCallNode);
             }
-
-            return visitor.leaveCallNode(this);
         }
 
         return this;
@@ -226,7 +225,7 @@
         if (hasCallSiteType()) {
             sb.append('{');
             final String desc = getType().getDescriptor();
-            sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor());
+            sb.append(desc.charAt(desc.length() - 1) == ';' ? 'O' : getType().getDescriptor());
             sb.append('}');
         }
 
@@ -261,8 +260,11 @@
      * Reset the arguments for the call
      * @param args new arguments list
      */
-    public void setArgs(final List<Node> args) {
-        this.args = args;
+    private CallNode setArgs(final List<Node> args) {
+        if (this.args == args) {
+            return this;
+        }
+        return new CallNode(this, function, args, flags, type, evalArgs);
     }
 
     /**
@@ -278,9 +280,13 @@
      * {@code eval}
      *
      * @param evalArgs eval args
+     * @return same node or new one on state change
      */
-    public void setEvalArgs(final EvalArgs evalArgs) {
-        this.evalArgs = evalArgs;
+    public CallNode setEvalArgs(final EvalArgs evalArgs) {
+        if (this.evalArgs == evalArgs) {
+            return this;
+        }
+        return new CallNode(this, function, args, flags, type, evalArgs);
     }
 
     /**
@@ -301,10 +307,14 @@
 
     /**
      * Reset the function expression that this call invokes
-     * @param node the function
+     * @param function the function
+     * @return same node or new one on state change
      */
-    public void setFunction(final Node node) {
-        function = node;
+    public CallNode setFunction(final Node function) {
+        if (this.function == function) {
+            return this;
+        }
+        return new CallNode(this, function, args, flags, type, evalArgs);
     }
 
     /**
@@ -312,14 +322,15 @@
      * @return true if this a new operation
      */
     public boolean isNew() {
-        return isNew;
+        return (flags & IS_NEW) == IS_NEW;
     }
 
     /**
      * Flag this call as a new operation
+     * @return same node or new one on state change
      */
-    public void setIsNew() {
-        this.isNew = true;
+    public CallNode setIsNew() {
+        return setFlags(IS_NEW);
     }
 
     /**
@@ -327,13 +338,13 @@
      * @return true if the call is inside a {@code with} block
      */
     public boolean inWithBlock() {
-        return inWithBlock;
+        return (flags & IN_WITH_BLOCK) == IN_WITH_BLOCK;
     }
 
-    /**
-     * Flag this call to be inside a {@code with} block
-     */
-    public void setInWithBlock() {
-        this.inWithBlock = true;
+    private CallNode setFlags(final int flags) {
+        if (this.flags == flags) {
+            return this;
+        }
+        return new CallNode(this, function, args, flags, type, evalArgs);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java
index 61b8921..237536c 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java
@@ -26,19 +26,21 @@
 package jdk.nashorn.internal.ir;
 
 import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of CASE clause.
- *
+ * Case nodes are not BreakableNodes, but the SwitchNode is
  */
-public class CaseNode extends BreakableNode {
+@Immutable
+public final class CaseNode extends Node {
     /** Test expression. */
-    private Node test;
+    private final Node test;
 
     /** Statements. */
-    private Block body;
+    private final Block body;
 
     /** Case entry label. */
     private final Label entry;
@@ -60,17 +62,17 @@
         this.entry = new Label("entry");
     }
 
-    private CaseNode(final CaseNode caseNode, final CopyState cs) {
+    CaseNode(final CaseNode caseNode, final Node test, final Block body) {
         super(caseNode);
 
-        this.test  = cs.existingOrCopy(caseNode.test);
-        this.body  = (Block)cs.existingOrCopy(caseNode.body);
+        this.test  = test;
+        this.body  = body;
         this.entry = new Label(caseNode.entry);
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new CaseNode(this, cs);
+    public boolean isTerminal() {
+        return body.isTerminal();
     }
 
     /**
@@ -79,15 +81,11 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterCaseNode(this) != null) {
-            if (test != null) {
-                test = test.accept(visitor);
-            }
-            if (body != null) {
-                body = (Block)body.accept(visitor);
-            }
+        if (visitor.enterCaseNode(this)) {
+            final Node  newTest = test == null ? null : test.accept(visitor);
+            final Block newBody = body == null ? null : (Block)body.accept(visitor);
 
-            return visitor.leaveCaseNode(this);
+            return visitor.leaveCaseNode(setTest(newTest).setBody(newBody));
         }
 
         return this;
@@ -131,8 +129,19 @@
     /**
      * Reset the test expression for this case node
      * @param test new test expression
+     * @return new or same CaseNode
      */
-    public void setTest(final Node test) {
-        this.test = test;
+    public CaseNode setTest(final Node test) {
+        if (this.test == test) {
+            return this;
+        }
+        return new CaseNode(this, test, body);
+    }
+
+    private CaseNode setBody(final Block body) {
+        if (this.body == body) {
+            return this;
+        }
+        return new CaseNode(this, test, body);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java b/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java
index 005ffa8..5e1b411 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java
@@ -25,26 +25,23 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of a catch clause.
- *
  */
-public class CatchNode extends Node {
+@Immutable
+public final class CatchNode extends Node {
     /** Exception identifier. */
-    private IdentNode exception;
+    private final IdentNode exception;
 
     /** Exception condition. */
-    private Node exceptionCondition;
+    private final Node exceptionCondition;
 
     /** Catch body. */
-    private Block body;
-
-    /** Is rethrow - e.g. synthetic catch block for e.g. finallies, the parser case where
-     * there has to be at least on catch for syntactic validity */
-    private boolean isSyntheticRethrow;
+    private final Block body;
 
     /**
      * Constructors
@@ -64,18 +61,12 @@
         this.body               = body;
     }
 
-    private CatchNode(final CatchNode catchNode, final CopyState cs) {
+    private CatchNode(final CatchNode catchNode, final IdentNode exception, final Node exceptionCondition, final Block body) {
         super(catchNode);
 
-        this.exception          = (IdentNode)cs.existingOrCopy(catchNode.exception);
-        this.exceptionCondition = cs.existingOrCopy(catchNode.exceptionCondition);
-        this.body               = (Block)cs.existingOrCopy(catchNode.body);
-        this.isSyntheticRethrow = catchNode.isSyntheticRethrow;
-     }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new CatchNode(this, cs);
+        this.exception          = exception;
+        this.exceptionCondition = exceptionCondition;
+        this.body               = body;
     }
 
     /**
@@ -84,21 +75,22 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterCatchNode(this) != null) {
-            exception = (IdentNode)exception.accept(visitor);
-
-            if (exceptionCondition != null) {
-                exceptionCondition = exceptionCondition.accept(visitor);
-            }
-
-            body = (Block)body.accept(visitor);
-            return visitor.leaveCatchNode(this);
+        if (visitor.enterCatchNode(this)) {
+            return visitor.leaveCatchNode(
+                setException((IdentNode)exception.accept(visitor)).
+                setExceptionCondition(exceptionCondition == null ? null : exceptionCondition.accept(visitor)).
+                setBody((Block)body.accept(visitor)));
         }
 
         return this;
     }
 
     @Override
+    public boolean isTerminal() {
+        return body.isTerminal();
+    }
+
+    @Override
     public void toString(final StringBuilder sb) {
         sb.append(" catch (");
         exception.toString(sb);
@@ -111,23 +103,6 @@
     }
 
     /**
-     * Check if this catch is a synthetic rethrow
-     * @return true if this is a synthetic rethrow
-     */
-    public boolean isSyntheticRethrow() {
-        return isSyntheticRethrow;
-    }
-
-    /**
-     * Flag this as deliberatly generated catch all that rethrows the
-     * caught exception. This is used for example for generating finally
-     * expressions
-     */
-    public void setIsSyntheticRethrow() {
-        this.isSyntheticRethrow = true;
-    }
-
-    /**
      * Get the identifier representing the exception thrown
      * @return the exception identifier
      */
@@ -146,9 +121,13 @@
     /**
      * Reset the exception condition for this catch block
      * @param exceptionCondition the new exception condition
+     * @return new or same CatchNode
      */
-    public void setExceptionCondition(final Node exceptionCondition) {
-        this.exceptionCondition = exceptionCondition;
+    public CatchNode setExceptionCondition(final Node exceptionCondition) {
+        if (this.exceptionCondition == exceptionCondition) {
+            return this;
+        }
+        return new CatchNode(this, exception, exceptionCondition, body);
     }
 
     /**
@@ -158,4 +137,18 @@
     public Block getBody() {
         return body;
     }
+
+    private CatchNode setException(final IdentNode exception) {
+        if (this.exception == exception) {
+            return this;
+        }
+        return new CatchNode(this, exception, exceptionCondition, body);
+    }
+
+    private CatchNode setBody(final Block body) {
+        if (this.body == body) {
+            return this;
+        }
+        return new CatchNode(this, exception, exceptionCondition, body);
+    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java b/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java
index cbc7bff..c8cc309 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java
@@ -25,43 +25,39 @@
 
 package jdk.nashorn.internal.ir;
 
-import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for CONTINUE statements.
- *
  */
-public class ContinueNode extends LabeledNode {
+@Immutable
+public class ContinueNode extends Node {
+
+    private IdentNode label;
 
     /**
      * Constructor
      *
-     * @param source     the source
-     * @param token      token
-     * @param finish     finish
-     * @param labelNode  the continue label
-     * @param targetNode node to continue to
-     * @param tryChain   surrounding try chain
+     * @param source source code
+     * @param token  token
+     * @param finish finish
+     * @param label  label for break or null if none
      */
-    public ContinueNode(final Source source, final long token, final int finish, final LabelNode labelNode, final Node targetNode, final TryNode tryChain) {
-        super(source, token, finish, labelNode, targetNode, tryChain);
-        setHasGoto();
-    }
-
-    private ContinueNode(final ContinueNode continueNode, final CopyState cs) {
-        super(continueNode, cs);
+    public ContinueNode(final Source source, final long token, final int finish, final IdentNode label) {
+        super(source, token, finish);
+        this.label = label;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new ContinueNode(this, cs);
+    public boolean hasGoto() {
+        return true;
     }
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterContinueNode(this) != null) {
+        if (visitor.enterContinueNode(this)) {
             return visitor.leaveContinueNode(this);
         }
 
@@ -69,21 +65,20 @@
     }
 
     /**
-     * Return the target label of this continue node.
-     * @return the target label.
+     * Get the label for this break node
+     * @return label, or null if none
      */
-    public Label getTargetLabel() {
-        assert targetNode instanceof WhileNode : "continue target must be a while node";
-        return ((WhileNode)targetNode).getContinueLabel();
+    public IdentNode getLabel() {
+        return label;
     }
 
     @Override
     public void toString(final StringBuilder sb) {
         sb.append("continue");
 
-        if (labelNode != null) {
+        if (label != null) {
             sb.append(' ');
-            labelNode.getLabel().toString(sb);
+            label.toString(sb);
         }
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java b/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java
index 15330a3..6751612 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java
@@ -25,14 +25,15 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for an empty statement.
- *
  */
-public class EmptyNode extends Node {
+@Immutable
+public final class EmptyNode extends Node {
 
     /**
      * Constructor
@@ -57,7 +58,7 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterEmptyNode(this) != null) {
+        if (visitor.enterEmptyNode(this)) {
             return visitor.leaveEmptyNode(this);
         }
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java b/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java
index 8ae7d55..f6dd7d1 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java
@@ -25,6 +25,7 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
@@ -33,9 +34,10 @@
  * node means "this code will be executed" and evaluating it results in
  * statements being added to the IR
  */
-public class ExecuteNode extends Node {
+@Immutable
+public final class ExecuteNode extends Node {
     /** Expression to execute. */
-    private Node expression;
+    private final Node expression;
 
     /**
      * Constructor
@@ -50,6 +52,11 @@
         this.expression = expression;
     }
 
+    private ExecuteNode(final ExecuteNode executeNode, final Node expression) {
+        super(executeNode);
+        this.expression = expression;
+    }
+
     /**
      * Constructor
      *
@@ -60,34 +67,15 @@
         this.expression = expression;
     }
 
-    private ExecuteNode(final ExecuteNode executeNode, final CopyState cs) {
-        super(executeNode);
-        this.expression = cs.existingOrCopy(executeNode.expression);
-    }
-
     @Override
-    protected Node copy(final CopyState cs) {
-        return new ExecuteNode(this, cs);
-    }
-
-    @Override
-    public boolean equals(final Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-        return expression.equals(((ExecuteNode)other).getExpression());
-    }
-
-    @Override
-    public int hashCode() {
-        return super.hashCode() ^ expression.hashCode();
+    public boolean isTerminal() {
+        return expression.isTerminal();
     }
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterExecuteNode(this) != null) {
-            setExpression(expression.accept(visitor));
-            return visitor.leaveExecuteNode(this);
+        if (visitor.enterExecuteNode(this)) {
+            return visitor.leaveExecuteNode(setExpression(expression.accept(visitor)));
         }
 
         return this;
@@ -109,8 +97,12 @@
     /**
      * Reset the expression to be executed
      * @param expression the expression
+     * @return new or same execute node
      */
-    public void setExpression(final Node expression) {
-        this.expression = expression;
+    public ExecuteNode setExpression(final Node expression) {
+        if (this.expression == expression) {
+            return this;
+        }
+        return new ExecuteNode(this, expression);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Flags.java b/nashorn/src/jdk/nashorn/internal/ir/Flags.java
new file mode 100644
index 0000000..94a2109
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/ir/Flags.java
@@ -0,0 +1,69 @@
+/*
+ * 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.ir;
+
+/**
+ * Interface implemented by all nodes that have flags in
+ * a lexical context. This is needed as we sometimes have to save
+ * the setting of flags in the lexical context until a block
+ * is completely finished and its final version (after multiple
+ * copy on writes) is placed in the lexical context
+ *
+ * @param <T> lexical context node that can have flags set during code generation
+ */
+public interface Flags<T extends LexicalContextNode> {
+
+    /**
+     * Check if a flag is set in a lexical context node
+     * @param flag flag to check
+     * @return flags
+     */
+    public boolean getFlag(int flag);
+
+    /**
+     * Clear a flag of a LexicalContextNode
+     * @param lc lexical context
+     * @param flag flag to clear
+     * @return the new LexicalContext node if flags were changed, same otherwise
+     */
+    public T clearFlag(final LexicalContext lc, int flag);
+
+    /**
+     * Set a flag of a LexicalContextNode
+     * @param lc lexical context
+     * @param flag flag to set
+     * @return the new LexicalContext node if flags were changed, same otherwise
+     */
+    public T setFlag(final LexicalContext lc, int flag);
+
+    /**
+     * Set all flags of a LexicalContextNode, overwriting previous flags
+     * @param lc lexical context
+     * @param flags new flags value
+     * @return the new LexicalContext node if flags were changed, same otherwise
+     */
+    public T setFlags(final LexicalContext lc, int flags);
+}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ForNode.java b/nashorn/src/jdk/nashorn/internal/ir/ForNode.java
index e55054d..057b846 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ForNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ForNode.java
@@ -25,73 +25,75 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representing a FOR statement.
- *
  */
-public class ForNode extends WhileNode {
+@Immutable
+public final class ForNode extends LoopNode {
     /** Initialize expression. */
-    private Node init;
+    private final Node init;
 
     /** Test expression. */
-    private Node modify;
+    private final Node modify;
 
     /** Iterator symbol. */
     private Symbol iterator;
 
-    /** is for in */
-    private boolean isForIn;
+    /** Is this a normal for loop? */
+    public static final int IS_FOR      = 1 << 0;
 
-    /** is for each */
-    private boolean isForEach;
+    /** Is this a normal for in loop? */
+    public static final int IS_FOR_IN   = 1 << 1;
+
+    /** Is this a normal for each in loop? */
+    public static final int IS_FOR_EACH = 1 << 2;
+
+    private final int flags;
 
     /**
      * Constructor
      *
-     * @param source     the source
-     * @param token      token
-     * @param finish     finish
+     * @param source the source
+     * @param token  token
+     * @param finish finish
+     * @param init   init
+     * @param test   test
+     * @param body   body
+     * @param modify modify
+     * @param flags  flags
      */
-    public ForNode(final Source source, final long token, final int finish) {
-        super(source, token, finish);
+    public ForNode(final Source source, final long token, final int finish, final Node init, final Node test, final Block body, final Node modify, final int flags) {
+        super(source, token, finish, test, body, false);
+        this.init   = init;
+        this.modify = modify;
+        this.flags  = flags;
     }
 
-    private ForNode(final ForNode forNode, final CopyState cs) {
-        super(forNode, cs);
-
-        this.init      = cs.existingOrCopy(forNode.init);
-        this.modify    = cs.existingOrCopy(forNode.modify);
-        this.iterator  = forNode.iterator;
-        this.isForIn   = forNode.isForIn;
-        this.isForEach = forNode.isForEach;
+    private ForNode(final ForNode forNode, final Node init, final Node test, final Block body, final Node modify, final int flags, final boolean controlFlowEscapes) {
+        super(forNode, test, body, controlFlowEscapes);
+        this.init   = init;
+        this.modify = modify;
+        this.flags  = flags;
+        this.iterator = forNode.iterator; //TODO is this acceptable? symbols are never cloned, just copied as references
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new ForNode(this, cs);
+    public Node ensureUniqueLabels(LexicalContext lc) {
+        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
     }
 
     @Override
-    public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterForNode(this) != null) {
-            if (init != null) {
-                init = init.accept(visitor);
-            }
-
-            if (test != null) {
-                test = test.accept(visitor);
-            }
-
-            if (modify != null) {
-                modify = modify.accept(visitor);
-            }
-
-            body = (Block)body.accept(visitor);
-
-            return visitor.leaveForNode(this);
+    protected Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterForNode(this)) {
+            return visitor.leaveForNode(
+                setInit(lc, init == null ? null : init.accept(visitor)).
+                setTest(lc, test == null ? null : test.accept(visitor)).
+                setModify(lc, modify == null ? null : modify.accept(visitor)).
+                setBody(lc, (Block)body.accept(visitor)));
         }
 
         return this;
@@ -122,6 +124,19 @@
         sb.append(')');
     }
 
+    @Override
+    public boolean hasGoto() {
+        return !isForIn() && test == null;
+    }
+
+    @Override
+    public boolean mustEnter() {
+        if (isForIn()) {
+            return false; //may be an empty set to iterate over, then we skip the loop
+        }
+        return test == null;
+    }
+
     /**
      * Get the initialization expression for this for loop
      * @return the initialization expression
@@ -132,10 +147,15 @@
 
     /**
      * Reset the initialization expression for this for loop
+     * @param lc lexical context
      * @param init new initialization expression
+     * @return new for node if changed or existing if not
      */
-    public void setInit(final Node init) {
-        this.init = init;
+    public ForNode setInit(final LexicalContext lc, final Node init) {
+        if (this.init == init) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
     }
 
     /**
@@ -143,14 +163,16 @@
      * @return true if this is a for in constructor
      */
     public boolean isForIn() {
-        return isForIn;
+        return (flags & IS_FOR_IN) != 0;
     }
 
     /**
      * Flag this to be a for in construct
+     * @param lc lexical context
+     * @return new for node if changed or existing if not
      */
-    public void setIsForIn() {
-        this.isForIn = true;
+    public ForNode setIsForIn(final LexicalContext lc) {
+        return setFlags(lc, flags | IS_FOR_IN);
     }
 
     /**
@@ -159,14 +181,16 @@
      * @return true if this is a for each construct
      */
     public boolean isForEach() {
-        return isForEach;
+        return (flags & IS_FOR_EACH) != 0;
     }
 
     /**
      * Flag this to be a for each construct
+     * @param lc lexical context
+     * @return new for node if changed or existing if not
      */
-    public void setIsForEach() {
-        this.isForEach = true;
+    public ForNode setIsForEach(final LexicalContext lc) {
+        return setFlags(lc, flags | IS_FOR_EACH);
     }
 
     /**
@@ -195,10 +219,15 @@
 
     /**
      * Reset the modification expression for this ForNode
+     * @param lc lexical context
      * @param modify new modification expression
+     * @return new for node if changed or existing if not
      */
-    public void setModify(final Node modify) {
-        this.modify = modify;
+    public ForNode setModify(final LexicalContext lc, final Node modify) {
+        if (this.modify == modify) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
     }
 
     @Override
@@ -207,7 +236,39 @@
     }
 
     @Override
-    public void setTest(final Node test) {
-        this.test = test;
+    public ForNode setTest(final LexicalContext lc, final Node test) {
+        if (this.test == test) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
     }
+
+    @Override
+    public Block getBody() {
+        return body;
+    }
+
+    @Override
+    public ForNode setBody(final LexicalContext lc, final Block body) {
+        if (this.body == body) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
+    }
+
+    @Override
+    public ForNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes) {
+        if (this.controlFlowEscapes == controlFlowEscapes) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
+    }
+
+    private ForNode setFlags(final LexicalContext lc, final int flags) {
+        if (this.flags == flags) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new ForNode(this, init, test, body, modify, flags, controlFlowEscapes));
+    }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
index 2b0e109..b0edc4a 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
@@ -30,23 +30,19 @@
 import static jdk.nashorn.internal.ir.Symbol.IS_CONSTANT;
 import static jdk.nashorn.internal.ir.Symbol.IS_TEMP;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Iterator;
+import java.util.HashSet;
 import java.util.List;
-import java.util.Map;
-import java.util.Stack;
+import java.util.Set;
 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.CompilerConstants;
 import jdk.nashorn.internal.codegen.Namespace;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.annotations.Ignore;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
-import jdk.nashorn.internal.parser.Parser;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.Source;
 import jdk.nashorn.internal.runtime.UserAccessorProperty;
@@ -55,9 +51,11 @@
 /**
  * IR representation for function (or script.)
  */
-public class FunctionNode extends Block {
+@Immutable
+public final class FunctionNode extends LexicalContextNode implements Flags<FunctionNode> {
 
-    private static final Type FUNCTION_TYPE = Type.typeFor(ScriptFunction.class);
+    /** Type used for all FunctionNodes */
+    public static final Type FUNCTION_TYPE = Type.typeFor(ScriptFunction.class);
 
     /** Function kinds */
     public enum Kind {
@@ -90,131 +88,105 @@
         /** method has had its types finalized */
         FINALIZED,
         /** method has been emitted to bytecode */
-        EMITTED,
-        /** code installed in a class loader */
-        INSTALLED
+        EMITTED
     }
 
     /** External function identifier. */
     @Ignore
-    private IdentNode ident;
+    private final IdentNode ident;
+
+    /** The body of the function node */
+    private final Block body;
 
     /** Internal function name. */
-    private String name;
+    private final String name;
 
     /** Compilation unit. */
-    private CompileUnit compileUnit;
-
-    /** Method emitter for current method. */
-    private MethodEmitter method;
+    private final CompileUnit compileUnit;
 
     /** Function kind. */
-    private Kind kind;
+    private final Kind kind;
 
     /** List of parameters. */
-    private List<IdentNode> parameters;
+    private final List<IdentNode> parameters;
 
     /** First token of function. **/
-    private long firstToken;
+    private final long firstToken;
 
     /** Last token of function. **/
-    private long lastToken;
+    private final long lastToken;
 
-    /** Variable frames. */
-    private Frame frames;
+    /** Declared symbols in this function node */
+    @Ignore
+    private final Set<Symbol> declaredSymbols;
 
     /** Method's namespace. */
     private final Namespace namespace;
 
-    /** Node representing current this. */
-    @Ignore
-    private IdentNode thisNode;
-
-    /** Node representing current scope. */
-    @Ignore
-    private IdentNode scopeNode;
-
-    /** Node representing return value. */
-    @Ignore
-    private IdentNode resultNode;
-
-    /** Node representing current arguments. */
-    @Ignore
-    private IdentNode argumentsNode;
-
-    /** Node representing callee */
-    @Ignore
-    private IdentNode calleeNode;
-
-    /** Node representing varargs */
-    @Ignore
-    private IdentNode varArgsNode;
-
-    /** Pending label list. */
-    private final Stack<LabelNode> labelStack;
-
-    /** Pending control list. */
-    private final Stack<Node> controlStack;
-
-    /** VarNode for this function statement */
-    @Ignore //this is explicit code anyway and should not be traversed after lower
-    private VarNode funcVarNode;
-
-    /** Line number for function declaration */
-    @Ignore
-    private LineNumberNode funcVarLineNumberNode;
-
-    /** Initializer var func = __callee__, where applicable */
-    @Ignore
-    private Node selfSymbolInit;
-
     /** Current compilation state */
     @Ignore
     private final EnumSet<CompilationState> compilationState;
 
-    /** Type hints, e.g based on parameters at call site */
-    private final Map<IdentNode, Type> specializedTypes;
-
     /** Function flags. */
-    private int flags;
+    private final int flags;
 
     /** Is anonymous function flag. */
-    private static final int IS_ANONYMOUS                = 1 << 0;
+    public static final int IS_ANONYMOUS                = 1 << 0;
+
     /** Is the function created in a function declaration (as opposed to a function expression) */
-    private static final int IS_DECLARED                 = 1 << 1;
+    public static final int IS_DECLARED                 = 1 << 1;
+
     /** is this a strict mode function? */
-    private static final int IS_STRICT_MODE              = 1 << 2;
+    public static final int IS_STRICT                   = 1 << 2;
+
     /** Does the function use the "arguments" identifier ? */
-    private static final int USES_ARGUMENTS              = 1 << 3;
-    /** Are we lowered ? */
-    private static final int IS_LOWERED                  = 1 << 4;
+    public static final int USES_ARGUMENTS              = 1 << 3;
+
     /** Has this node been split because it was too large? */
-    private static final int IS_SPLIT                    = 1 << 5;
+    public static final int IS_SPLIT                    = 1 << 4;
+
     /** Does the function call eval? */
-    private static final int HAS_EVAL                    = 1 << 6;
+    public static final int HAS_EVAL                    = 1 << 5;
+
     /** Does the function contain a with block ? */
-    private static final int HAS_WITH                    = 1 << 7;
+    public static final int HAS_WITH                    = 1 << 6;
+
     /** Does a descendant function contain a with or eval? */
-    private static final int HAS_DESCENDANT_WITH_OR_EVAL = 1 << 8;
-    /** Does the function define "arguments" identifier as a parameter of nested function name? */
-    private static final int DEFINES_ARGUMENTS           = 1 << 9;
-    /** Does the function need a self symbol? */
-    private static final int NEEDS_SELF_SYMBOL           = 1 << 10;
+    public static final int HAS_DESCENDANT_WITH_OR_EVAL = 1 << 7;
+
+    /**
+     * Flag this function as one that defines the identifier "arguments" as a function parameter or nested function
+     * name. This precludes it from needing to have an Arguments object defined as "arguments" local variable. Note that
+     * defining a local variable named "arguments" still requires construction of the Arguments object (see
+     * ECMAScript 5.1 Chapter 10.5).
+     * @see #needsArguments()
+     */
+    public static final int DEFINES_ARGUMENTS           = 1 << 8;
+
     /** Does this function or any of its descendants use variables from an ancestor function's scope (incl. globals)? */
-    private static final int USES_ANCESTOR_SCOPE         = 1 << 11;
+    public static final int USES_ANCESTOR_SCOPE         = 1 << 9;
+
     /** Is this function lazily compiled? */
-    private static final int IS_LAZY                     = 1 << 12;
+    public static final int IS_LAZY                     = 1 << 10;
+
     /** Does this function have lazy, yet uncompiled children */
-    private static final int HAS_LAZY_CHILDREN           = 1 << 13;
+    public static final int HAS_LAZY_CHILDREN           = 1 << 11;
+
     /** Does this function have lazy, yet uncompiled children */
-    private static final int IS_PROGRAM                   = 1 << 14;
+    public static final int IS_PROGRAM                  = 1 << 12;
+
+    /** Does this function have nested declarations? */
+    public static final int HAS_FUNCTION_DECLARATIONS   = 1 << 13;
 
     /** Does this function or any nested functions contain a with or an eval? */
     private static final int HAS_DEEP_WITH_OR_EVAL = HAS_EVAL | HAS_WITH | HAS_DESCENDANT_WITH_OR_EVAL;
+
     /** Does this function need to store all its variables in scope? */
     private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_WITH_OR_EVAL | IS_SPLIT | HAS_LAZY_CHILDREN;
+
     /** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */
     private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL;
+
     /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep with or eval.
      *  We also pessimistically need a parent scope if we have lazy children that have not yet been compiled */
     private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_WITH_OR_EVAL | HAS_LAZY_CHILDREN;
@@ -225,107 +197,77 @@
     /**
      * Constructor
      *
-     * @param source    the source
-     * @param token     token
-     * @param finish    finish
-     * @param namespace the namespace
-     * @param ident     the identifier
-     * @param name      the name of the function
+     * @param source     the source
+     * @param token      token
+     * @param finish     finish
+     * @param firstToken first token of the funtion node (including the function declaration)
+     * @param namespace  the namespace
+     * @param ident      the identifier
+     * @param name       the name of the function
+     * @param parameters parameter list
+     * @param kind       kind of function as in {@link FunctionNode.Kind}
+     * @param flags      initial flags
      */
-    public FunctionNode(final Source source, final long token, final int finish, final Namespace namespace, final IdentNode ident, final String name) {
+    public FunctionNode(
+        final Source source,
+        final long token,
+        final int finish,
+        final long firstToken,
+        final Namespace namespace,
+        final IdentNode ident,
+        final String name,
+        final List<IdentNode> parameters,
+        final FunctionNode.Kind kind,
+        final int flags) {
         super(source, token, finish);
 
         this.ident             = ident;
         this.name              = name;
-        this.kind              = Kind.NORMAL;
-        this.parameters        = new ArrayList<>();
-        this.firstToken        = token;
+        this.kind              = kind;
+        this.parameters        = parameters;
+        this.firstToken        = firstToken;
         this.lastToken         = token;
         this.namespace         = namespace;
-        this.labelStack        = new Stack<>();
-        this.controlStack      = new Stack<>();
         this.compilationState  = EnumSet.of(CompilationState.INITIALIZED);
-        this.specializedTypes  = new HashMap<>();
+        this.declaredSymbols   = new HashSet<>();
+        this.flags             = flags;
+        this.compileUnit       = null;
+        this.body              = null;
     }
 
-    private FunctionNode(final FunctionNode functionNode, final CopyState cs) {
-        super(functionNode, cs);
+    private FunctionNode(final FunctionNode functionNode, final long lastToken, final int flags, final Type returnType, final CompileUnit compileUnit, final EnumSet<CompilationState> compilationState, final Block body) {
+        super(functionNode);
+        this.flags = flags;
+        this.returnType = returnType;
+        this.compileUnit = compileUnit;
+        this.lastToken = lastToken;
+        this.compilationState = compilationState;
+        this.body  = body;
 
-        this.ident = (IdentNode)cs.existingOrCopy(functionNode.ident);
-        this.name  = functionNode.name;
+        // the fields below never change - they are final and assigned in constructor
+        this.name = functionNode.name;
+        this.ident = functionNode.ident;
+        this.namespace = functionNode.namespace;
+        this.declaredSymbols = functionNode.declaredSymbols;
         this.kind  = functionNode.kind;
-
-        this.parameters = new ArrayList<>();
-        for (final IdentNode param : functionNode.getParameters()) {
-            this.parameters.add((IdentNode)cs.existingOrCopy(param));
-        }
-
-        this.firstToken        = functionNode.firstToken;
-        this.lastToken         = functionNode.lastToken;
-        this.namespace         = functionNode.getNamespace();
-        this.thisNode          = (IdentNode)cs.existingOrCopy(functionNode.thisNode);
-        this.scopeNode         = (IdentNode)cs.existingOrCopy(functionNode.scopeNode);
-        this.resultNode        = (IdentNode)cs.existingOrCopy(functionNode.resultNode);
-        this.argumentsNode     = (IdentNode)cs.existingOrCopy(functionNode.argumentsNode);
-        this.varArgsNode       = (IdentNode)cs.existingOrCopy(functionNode.varArgsNode);
-        this.calleeNode        = (IdentNode)cs.existingOrCopy(functionNode.calleeNode);
-        this.labelStack        = new Stack<>();
-        this.controlStack      = new Stack<>();
-
-        this.flags = functionNode.flags;
-
-        this.funcVarNode = (VarNode)cs.existingOrCopy(functionNode.funcVarNode);
-        /** VarNode for this function statement */
-
-        this.compilationState = EnumSet.copyOf(functionNode.compilationState);
-        this.specializedTypes = new HashMap<>();
+        this.parameters = functionNode.parameters;
+        this.firstToken = functionNode.firstToken;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        // deep clone all parent blocks
-        return new FunctionNode(this, cs);
-    }
-
-    @Override
-    public Node accept(final NodeVisitor visitor) {
-        final FunctionNode  saveFunctionNode  = visitor.getCurrentFunctionNode();
-        final Block         saveBlock         = visitor.getCurrentBlock();
-        final MethodEmitter saveMethodEmitter = visitor.getCurrentMethodEmitter();
-        final CompileUnit   saveCompileUnit   = visitor.getCurrentCompileUnit();
-
-        visitor.setCurrentFunctionNode(this);
-        visitor.setCurrentBlock(this);
-
-        try {
-            if (visitor.enterFunctionNode(this) != null) {
-                if (ident != null) {
-                    ident = (IdentNode)ident.accept(visitor);
-                }
-
-                for (int i = 0, count = parameters.size(); i < count; i++) {
-                    parameters.set(i, (IdentNode)parameters.get(i).accept(visitor));
-                }
-
-                for (int i = 0, count = statements.size(); i < count; i++) {
-                    statements.set(i, statements.get(i).accept(visitor));
-                }
-
-                return visitor.leaveFunctionNode(this);
-            }
-        } finally {
-            visitor.setCurrentBlock(saveBlock);
-            visitor.setCurrentFunctionNode(saveFunctionNode);
-            visitor.setCurrentCompileUnit(saveCompileUnit);
-            visitor.setCurrentMethodEmitter(saveMethodEmitter);
+    public Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterFunctionNode(this)) {
+            return visitor.leaveFunctionNode(setBody(lc, (Block)body.accept(visitor)));
         }
-
         return this;
     }
 
-    @Override
-    public boolean needsScope() {
-        return super.needsScope() || isProgram();
+    /**
+     * Get the compilation state of this function
+     * @return the compilation state
+     */
+    public EnumSet<CompilationState> getState() {
+        return compilationState;
     }
 
     /**
@@ -357,62 +299,17 @@
      * FunctionNode has been lowered, the compiler will add
      * {@code CompilationState#LOWERED} to the state vector
      *
+     * @param lc lexical context
      * @param state {@link CompilationState} to add
+     * @return function node or a new one if state was changed
      */
-    public void setState(final CompilationState state) {
-        compilationState.add(state);
-    }
-
-    /*
-     * Frame management.
-     */
-
-    /**
-     * Push a new block frame.
-     *
-     * @return the new frame
-     */
-    public final Frame pushFrame() {
-        frames = new Frame(frames);
-        return frames;
-    }
-
-    /**
-     * Pop a block frame.
-     */
-    public final void popFrame() {
-        frames = frames.getPrevious();
-    }
-
-    /**
-     * Create a temporary variable to the current frame.
-     *
-     * @param currentFrame Frame to add to - defaults to current function frame
-     * @param type  Strong type of symbol.
-     * @param node  Primary node to use symbol.
-     *
-     * @return Symbol used.
-     */
-    public Symbol newTemporary(final Frame currentFrame, final Type type, final Node node) {
-        assert currentFrame != null;
-        Symbol symbol = node.getSymbol();
-
-        // If no symbol already present.
-        if (symbol == null) {
-            final String uname = uniqueName(TEMP_PREFIX.tag());
-            symbol = new Symbol(uname, IS_TEMP, type);
-            symbol.setNode(node);
+    public FunctionNode setState(final LexicalContext lc, final CompilationState state) {
+        if (this.compilationState.equals(state)) {
+            return this;
         }
-
-        // Assign a slot if it doesn't have one.
-        if (!symbol.hasSlot()) {
-            currentFrame.addSymbol(symbol);
-        }
-
-        // Set symbol to node.
-        node.setSymbol(symbol);
-
-        return symbol;
+        final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
+        newState.add(state);
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, newState, body));
     }
 
     /**
@@ -425,18 +322,6 @@
     }
 
     /**
-     * Add a new temporary variable to the current frame
-     *
-     * @param type Strong type of symbol
-     * @param node Primary node to use symbol
-     *
-     * @return symbol used
-     */
-    public Symbol newTemporary(final Type type, final Node node) {
-        return newTemporary(frames, type, node);
-    }
-
-    /**
      * Create a virtual symbol for a literal.
      *
      * @param literalNode Primary node to use symbol.
@@ -444,9 +329,8 @@
      * @return Symbol used.
      */
     public Symbol newLiteral(final LiteralNode<?> literalNode) {
-        final String uname = uniqueName(LITERAL_PREFIX.tag());
+        final String uname = uniqueName(LITERAL_PREFIX.symbolName());
         final Symbol symbol = new Symbol(uname, IS_CONSTANT, literalNode.getType());
-        symbol.setNode(literalNode);
         literalNode.setSymbol(symbol);
 
         return symbol;
@@ -482,29 +366,35 @@
         sb.append(')');
     }
 
+    @Override
+    public boolean getFlag(final int flag) {
+        return (flags & flag) != 0;
+    }
+
+    @Override
+    public FunctionNode setFlags(final LexicalContext lc, int flags) {
+        if (this.flags == flags) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body));
+    }
+
+    @Override
+    public FunctionNode clearFlag(final LexicalContext lc, final int flag) {
+        return setFlags(lc, flags & ~flag);
+    }
+
+    @Override
+    public FunctionNode setFlag(final LexicalContext lc, final int flag) {
+        return setFlags(lc, flags | flag);
+    }
+
     /**
      * Returns true if the function is the top-level program.
      * @return True if this function node represents the top-level program.
      */
     public boolean isProgram() {
-        return (flags & IS_PROGRAM) != 0;
-    }
-
-    /**
-     * Marks the function as representing the top-level program.
-     */
-    public void setProgram() {
-        flags |= IS_PROGRAM;
-    }
-
-    /**
-     * Get the control stack. Used when parsing to establish nesting depths of
-     * different control structures
-     *
-     * @return the control stack
-     */
-    public Stack<Node> getControlStack() {
-        return controlStack;
+        return getFlag(IS_PROGRAM);
     }
 
     /**
@@ -512,15 +402,7 @@
      * @return true if lazy
      */
     public boolean isLazy() {
-        return (flags & IS_LAZY) != 0;
-    }
-
-    /**
-     * Set if this function should be lazily generated
-     * @param isLazy is lazy
-     */
-    public void setIsLazy(final boolean isLazy) {
-        this.flags = isLazy ? flags | IS_LAZY : flags & ~IS_LAZY;
+        return getFlag(IS_LAZY);
     }
 
     /**
@@ -529,37 +411,7 @@
      * @return true if {@code with} is used
      */
     public boolean hasWith() {
-        return (flags & HAS_WITH) != 0;
-    }
-
-    /**
-     * Flag this function as using the {@code with} keyword
-     * @param ancestors the iterator over functions in this functions's containing lexical context
-     */
-    public void setHasWith(final Iterator<FunctionNode> ancestors) {
-        if(!hasWith()) {
-            this.flags |= HAS_WITH;
-            // with requires scope in parents.
-            // TODO: refine this. with should not force all variables in parents to be in scope, only those that are
-            // actually referenced as identifiers by name
-            markParentForWithOrEval(ancestors);
-        }
-    }
-
-    private void markParentForWithOrEval(final Iterator<FunctionNode> ancestors) {
-        // If this is invoked, then either us or a descendant uses with or eval, meaning we must have our own scope.
-        setNeedsScope();
-
-        if(ancestors.hasNext()) {
-            ancestors.next().setDescendantHasWithOrEval(ancestors);
-        }
-    }
-
-    private void setDescendantHasWithOrEval(final Iterator<FunctionNode> ancestors) {
-        if((flags & HAS_DESCENDANT_WITH_OR_EVAL) == 0) {
-            flags |= HAS_DESCENDANT_WITH_OR_EVAL;
-            markParentForWithOrEval(ancestors);
-        }
+        return getFlag(HAS_WITH);
     }
 
     /**
@@ -568,18 +420,7 @@
      * @return true if {@code eval} is used
      */
     public boolean hasEval() {
-        return (flags & HAS_EVAL) != 0;
-    }
-
-    /**
-     * Flag this function as calling the {@code eval} function
-     * @param ancestors the iterator over functions in this functions's containing lexical context
-     */
-    public void setHasEval(final Iterator<FunctionNode> ancestors) {
-        if(!hasEval()) {
-            this.flags |= HAS_EVAL;
-            markParentForWithOrEval(ancestors);
-        }
+        return getFlag(HAS_EVAL);
     }
 
     /**
@@ -591,7 +432,7 @@
      * @return true if this or a nested function contains with or eval
      */
     public boolean hasDeepWithOrEval() {
-        return (flags & HAS_DEEP_WITH_OR_EVAL) != 0;
+        return getFlag(HAS_DEEP_WITH_OR_EVAL);
     }
 
     /**
@@ -603,90 +444,11 @@
     }
 
     /**
-     * Set the first token for this function
-     * @param firstToken the first token
+     * Check whether this function has nested function declarations
+     * @return true if nested function declarations exist
      */
-    public void setFirstToken(final long firstToken) {
-        this.firstToken = firstToken;
-    }
-
-    /**
-     * Returns a list of functions declared by this function. Only includes declared functions, and does not include any
-     * function expressions that might occur in its body.
-     * @return a list of functions declared by this function.
-     */
-    public List<FunctionNode> getDeclaredFunctions() {
-        // Note that the function does not have a dedicated list of declared functions, but rather relies on the
-        // invariant that all function declarations are at the beginning of the statement list as VarNode with a
-        // FunctionNode marked as statement with its variable initializer. Every VarNode is also preceded by a
-        // LineNumberNode. This invariant is established by the parser and has to be preserved in visitors.
-        final List<FunctionNode> fns = new ArrayList<>();
-        for (final Node stmt : statements) {
-            if(stmt instanceof LineNumberNode) {
-                continue;
-            } else if(stmt instanceof VarNode) {
-                final Node init = ((VarNode)stmt).getInit();
-                if(init instanceof FunctionNode) {
-                    final FunctionNode fn = (FunctionNode)init;
-                    if(fn.isDeclared()) {
-                        fns.add(fn);
-                        continue;
-                    }
-                }
-            }
-            // Node is neither a LineNumberNode, nor a function declaration VarNode. Since all function declarations are
-            // at the start of the function, we've reached the end of function declarations.
-            break;
-        }
-        return fns;
-    }
-
-    /**
-     * Get the label stack. This is used by the parser to establish
-     * label nesting depth
-     *
-     * @return the label stack
-     */
-    public Stack<LabelNode> getLabelStack() {
-        return labelStack;
-    }
-
-    /**
-     * If this function needs to use var args, return the identifier to the node used
-     * for the var args structure
-     *
-     * @return IdentNode representing the var args structure
-     */
-    public IdentNode getVarArgsNode() {
-        return varArgsNode;
-    }
-
-    /**
-     * Set the identifier to the node used for the var args structure
-     *
-     * @param varArgsNode IdentNode representing the var args
-     */
-    public void setVarArgsNode(final IdentNode varArgsNode) {
-        this.varArgsNode = varArgsNode;
-    }
-
-    /**
-     * If this function uses the {@code callee} variable, return the node used
-     * as this variable
-     *
-     * @return an IdentNode representing the {@code callee} variable
-     */
-    public IdentNode getCalleeNode() {
-        return calleeNode;
-    }
-
-    /**
-     * If this function uses the {@code callee} variable, set the node representing the
-     * callee
-     * @param calleeNode an IdentNode representing the callee
-     */
-    public void setCalleeNode(final IdentNode calleeNode) {
-        this.calleeNode = calleeNode;
+    public boolean hasDeclaredFunctions() {
+        return getFlag(HAS_FUNCTION_DECLARATIONS);
     }
 
     /**
@@ -697,26 +459,7 @@
      * @return true if the function's generated Java method needs a {@code callee} parameter.
      */
     public boolean needsCallee() {
-        return needsParentScope() || needsSelfSymbol() || (needsArguments() && !isStrictMode());
-    }
-
-    /**
-     * If this is a function where {@code arguments} is used, return the node used as the {@code arguments}
-     * variable
-     * @return an IdentNode representing {@code arguments}
-     */
-    public IdentNode getArgumentsNode() {
-        return argumentsNode;
-    }
-
-    /**
-     * If this is a Function where {@code arguments} is used, an identifier to the node representing
-     * the {@code arguments} value has to be supplied by the compiler
-     *
-     * @param argumentsNode IdentNode that represents {@code arguments}
-     */
-    public void setArgumentsNode(final IdentNode argumentsNode) {
-        this.argumentsNode = argumentsNode;
+        return needsParentScope() || needsSelfSymbol() || (needsArguments() && !isStrict());
     }
 
     /**
@@ -728,11 +471,42 @@
     }
 
     /**
-     * Reset the identifier for this function
-     * @param ident IdentNode for new identifier
+     * Return a set of symbols declared in this function node. This
+     * is only relevant after Attr, otherwise it will be an empty
+     * set as no symbols have been introduced
+     * @return set of declared symbols in function
      */
-    public void setIdent(final IdentNode ident) {
-        this.ident = ident;
+    public Set<Symbol> getDeclaredSymbols() {
+        return Collections.unmodifiableSet(declaredSymbols);
+    }
+
+    /**
+     * Add a declared symbol to this function node
+     * @param symbol symbol that is declared
+     */
+    public void addDeclaredSymbol(final Symbol symbol) {
+        declaredSymbols.add(symbol);
+    }
+
+    /**
+     * Get the function body
+     * @return the function body
+     */
+    public Block getBody() {
+        return body;
+    }
+
+    /**
+     * Reset the function body
+     * @param lc lexical context
+     * @param body new body
+     * @return new function node if body changed, same if not
+     */
+    public FunctionNode setBody(final LexicalContext lc, final Block body) {
+        if(this.body == body) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body));
     }
 
     /**
@@ -748,17 +522,6 @@
     }
 
     /**
-     * Flag this function as one that defines the identifier "arguments" as a function parameter or nested function
-     * name. This precludes it from needing to have an Arguments object defined as "arguments" local variable. Note that
-     * defining a local variable named "arguments" still requires construction of the Arguments object (see
-     * ECMAScript 5.1 Chapter 10.5).
-     * @see #needsArguments()
-     */
-    public void setDefinesArguments() {
-        this.flags |= DEFINES_ARGUMENTS;
-    }
-
-    /**
      * Returns true if this function needs to have an Arguments object defined as a local variable named "arguments".
      * Functions that use "arguments" as identifier and don't define it as a name of a parameter or a nested function
      * (see ECMAScript 5.1 Chapter 10.5), as well as any function that uses eval or with, or has a nested function that
@@ -770,15 +533,7 @@
     public boolean needsArguments() {
         // uses "arguments" or calls eval, but it does not redefine "arguments", and finally, it's not a script, since
         // for top-level script, "arguments" is picked up from Context by Global.init() instead.
-        return (flags & MAYBE_NEEDS_ARGUMENTS) != 0 && (flags & DEFINES_ARGUMENTS) == 0 && !isProgram();
-    }
-
-    /**
-     * Flags this function as one that uses the "arguments" identifier.
-     * @see #needsArguments()
-     */
-    public void setUsesArguments() {
-        flags |= USES_ARGUMENTS;
+        return getFlag(MAYBE_NEEDS_ARGUMENTS) && !getFlag(DEFINES_ARGUMENTS) && !isProgram();
     }
 
     /**
@@ -789,7 +544,7 @@
      * @return true if the function needs parent scope.
      */
     public boolean needsParentScope() {
-        return (flags & NEEDS_PARENT_SCOPE) != 0 || isProgram();
+        return getFlag(NEEDS_PARENT_SCOPE) || isProgram();
     }
 
     /**
@@ -802,15 +557,6 @@
     }
 
     /**
-     * Set the kind of this function
-     * @see FunctionNode.Kind
-     * @param kind the kind
-     */
-    public void setKind(final Kind kind) {
-        this.kind = kind;
-    }
-
-    /**
      * Return the last token for this function's code
      * @return last token
      */
@@ -820,10 +566,15 @@
 
     /**
      * Set the last token for this function's code
+     * @param lc lexical context
      * @param lastToken the last token
+     * @return function node or a new one if state was changed
      */
-    public void setLastToken(final long lastToken) {
-        this.lastToken = lastToken;
+    public FunctionNode setLastToken(final LexicalContext lc, final long lastToken) {
+        if (this.lastToken == lastToken) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body));
     }
 
     /**
@@ -835,21 +586,13 @@
     }
 
     /**
-     * Set the name of this function
-     * @param name the name
-     */
-    public void setName(final String name) {
-        this.name = name;
-    }
-
-    /**
      * Check if this function should have all its variables in its own scope. Scripts, split sub-functions, and
      * functions having with and/or eval blocks are such.
      *
      * @return true if all variables should be in scope
      */
     public boolean allVarsInScope() {
-        return isProgram() || (flags & HAS_ALL_VARS_IN_SCOPE) != 0;
+        return isProgram() || getFlag(HAS_ALL_VARS_IN_SCOPE);
     }
 
     /**
@@ -858,15 +601,7 @@
      * @return true if this function is split from a larger one
      */
     public boolean isSplit() {
-        return (flags & IS_SPLIT) != 0;
-    }
-
-    /**
-     * Flag this function node as being a sub-function generated by the splitter
-     */
-    public void setIsSplit() {
-        this.flags |= IS_SPLIT;
-        setNeedsScope();
+        return getFlag(IS_SPLIT);
     }
 
     /**
@@ -875,15 +610,7 @@
      * @return true if there are lazy child functions
      */
     public boolean hasLazyChildren() {
-        return (flags & HAS_LAZY_CHILDREN) != 0;
-    }
-
-    /**
-     * Flag this function node as having yet-to-be-generated child functions
-     */
-    public void setHasLazyChildren() {
-        this.flags |= HAS_LAZY_CHILDREN;
-        setNeedsScope();
+        return getFlag(HAS_LAZY_CHILDREN);
     }
 
     /**
@@ -895,66 +622,13 @@
     }
 
     /**
-     * Set the paremeters to this function
-     * @param parameters a list of IdentNodes representing parameters in left to right order
-     */
-    public void setParameters(final List<IdentNode> parameters) {
-        this.parameters = parameters;
-    }
-
-    /**
      * Get a specialized type for an identity, if one exists
      * @param node node to check specialized type for
      * @return null if no specialization exists, otherwise type
      */
+    @SuppressWarnings("static-method")
     public Type getSpecializedType(final IdentNode node) {
-        return specializedTypes.get(node);
-    }
-
-    /**
-     * Set parameter type hints for specialization.
-     * @param types types array of length equal to parameter list size
-     */
-    public void setParameterTypes(final Class<?>[] types) {
-        assert types.length == parameters.size() : "Type vector length doesn't correspond to parameter types";
-        //diff - skip the callee and this etc, they are not explicit params in the parse tree
-        for (int i = 0; i < types.length ; i++) {
-            specializedTypes.put(parameters.get(i), Type.typeFor(types[i]));
-        }
-    }
-
-    /**
-     * Get the identifier for the variable in which the function return value
-     * should be stored
-     * @return an IdentNode representing the return value
-     */
-    public IdentNode getResultNode() {
-        return resultNode;
-    }
-
-    /**
-     * Set the identifier representing the variable in which the function return
-     * value should be stored
-     * @param resultNode an IdentNode representing the return value
-     */
-    public void setResultNode(final IdentNode resultNode) {
-        this.resultNode = resultNode;
-    }
-
-    /**
-     * Get the identifier representing this function's scope
-     * @return an IdentNode representing this function's scope
-     */
-    public IdentNode getScopeNode() {
-        return scopeNode;
-    }
-
-    /**
-     * Set the identifier representing this function's scope
-     * @param scopeNode an IdentNode representing this function's scope
-     */
-    public void setScopeNode(final IdentNode scopeNode) {
-        this.scopeNode = scopeNode;
+        return null; //TODO implement specialized types later
     }
 
     /**
@@ -962,15 +636,7 @@
      * @return true if function is declared.
      */
     public boolean isDeclared() {
-        return (flags & IS_DECLARED) != 0;
-    }
-
-    /**
-     * Flag this function as being created as a function declaration (as opposed to a function expression).
-     * @see Parser
-     */
-    public void setIsDeclared() {
-        this.flags |= IS_DECLARED;
+        return getFlag(IS_DECLARED);
     }
 
     /**
@@ -978,15 +644,7 @@
      * @return true if function is anonymous
      */
     public boolean isAnonymous() {
-        return (flags & IS_ANONYMOUS) != 0;
-    }
-
-    /**
-     * Flag this function as an anonymous function.
-     * @see Parser
-     */
-    public void setIsAnonymous() {
-        this.flags |= IS_ANONYMOUS;
+        return getFlag(IS_ANONYMOUS);
     }
 
     /**
@@ -995,109 +653,7 @@
      * @return true if function needs a symbol for self
      */
     public boolean needsSelfSymbol() {
-        return (flags & NEEDS_SELF_SYMBOL) != 0;
-    }
-
-    /**
-     * Get the initializer statement for the __callee__ variable, where applicable
-     * for self references
-     * @return initialization
-     */
-    public Node getSelfSymbolInit() {
-        return this.selfSymbolInit;
-    }
-
-    /**
-     * Flag the function as needing a self symbol. This is needed only for
-     * self referring functions
-     * @param selfSymbolInit initialization expression for self symbol
-     */
-    public void setNeedsSelfSymbol(final Node selfSymbolInit) {
-        this.flags |= NEEDS_SELF_SYMBOL;
-        this.selfSymbolInit = selfSymbolInit;
-    }
-
-    /**
-     * Marks this function as using any of its ancestors' scopes.
-     */
-    public void setUsesAncestorScope() {
-        this.flags |= USES_ANCESTOR_SCOPE;
-    }
-
-    @Override
-    void setUsesParentScopeSymbol(Symbol symbol, Iterator<Block> ancestors) {
-        setUsesAncestorScope();
-        super.setUsesParentScopeSymbol(symbol, ancestors);
-    }
-
-    /**
-     * Return the node representing {@code this} in this function
-     * @return IdentNode representing {@code this}
-     */
-    public IdentNode getThisNode() {
-        return thisNode;
-    }
-
-    /**
-     * Set the node representing {@code this} in this function
-     * @param thisNode identifier representing {@code this}
-     */
-    public void setThisNode(final IdentNode thisNode) {
-        this.thisNode = thisNode;
-    }
-
-    /**
-     * Every function declared as {@code function x()} is internally hoisted
-     * and represented as {@code var x = function()  ... }. This getter returns
-     * the VarNode representing this virtual assignment
-     *
-     * @return the var node emitted for setting this function symbol
-     */
-    public VarNode getFunctionVarNode() {
-        return funcVarNode;
-    }
-
-    /**
-     * Set the virtual VarNode assignment for this function.
-     * @see FunctionNode#getFunctionVarNode()
-     *
-     * @param varNode the virtual var node assignment
-     */
-    public void setFunctionVarNode(final VarNode varNode) {
-        funcVarNode = varNode;
-    }
-
-    /**
-     * The line number information where the function was declared must be propagated
-     * to the virtual {@code var x = function() ... } assignment described in
-     * {@link FunctionNode#getFunctionVarNode()}
-     * This maintains the line number of the declaration
-     *
-     * @return a line number node representing the line this function was declared
-     */
-    public LineNumberNode getFunctionVarLineNumberNode() {
-        return funcVarLineNumberNode;
-    }
-
-    /**
-     * Set the virtual VarNode assignment for this function, along with
-     * a line number node for tracking the original start line of the function
-     * declaration
-     *
-     * @param varNode    the virtual var node assignment
-     * @param lineNumber the line number node for the function declaration
-     */
-    public void setFunctionVarNode(final VarNode varNode, final LineNumberNode lineNumber) {
-        funcVarNode           = varNode;
-        funcVarLineNumberNode = lineNumber;
-    }
-
-    /**
-     * Get the namespace this function uses for its symbols
-     * @return the namespace
-     */
-    public Namespace getNamespace() {
-        return namespace;
+        return body.getFlag(Block.NEEDS_SELF_SYMBOL);
     }
 
     @Override
@@ -1118,47 +674,38 @@
 
     /**
      * Set the function return type
-     *
+     * @param lc lexical context
      * @param returnType new return type
+     * @return function node or a new one if state was changed
      */
-    public void setReturnType(final Type returnType) {
+    public FunctionNode setReturnType(final LexicalContext lc, final Type returnType) {
         //we never bother with object types narrower than objects, that will lead to byte code verification errors
         //as for instance even if we know we are returning a string from a method, the code generator will always
         //treat it as an object, at least for now
-        this.returnType = Type.widest(this.returnType,  returnType.isObject() ? Type.OBJECT : returnType);
-    }
-
-    /**
-     * Set strict mode on or off for this function
-     *
-     * @param isStrictMode true if strict mode should be enabled
-     */
-    public void setStrictMode(final boolean isStrictMode) {
-        flags = isStrictMode ? flags | IS_STRICT_MODE : flags & ~IS_STRICT_MODE;
+        if (this.returnType == returnType) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(
+            lc,
+            this,
+            new FunctionNode(
+                this,
+                lastToken,
+                flags,
+                Type.widest(this.returnType, returnType.isObject() ?
+                    Type.OBJECT :
+                    returnType),
+                compileUnit,
+                compilationState,
+                body));
     }
 
     /**
      * Check if the function is generated in strict mode
      * @return true if strict mode enabled for function
      */
-    public boolean isStrictMode() {
-        return (flags & IS_STRICT_MODE) != 0;
-    }
-
-    /**
-     * Set the lowered state
-     */
-    public void setIsLowered() {
-        flags |= IS_LOWERED;
-    }
-
-    /**
-     * Get the lowered state
-     *
-     * @return true if function is lowered
-     */
-    public boolean isLowered() {
-        return (flags & IS_LOWERED) != 0;
+    public boolean isStrict() {
+        return getFlag(IS_STRICT);
     }
 
     /**
@@ -1173,25 +720,47 @@
     /**
      * Reset the compile unit used to compile this function
      * @see Compiler
+     * @param lc lexical context
      * @param compileUnit the compile unit
+     * @return function node or a new one if state was changed
      */
-    public void setCompileUnit(final CompileUnit compileUnit) {
-        this.compileUnit = compileUnit;
+    public FunctionNode setCompileUnit(final LexicalContext lc, final CompileUnit compileUnit) {
+        if (this.compileUnit == compileUnit) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, returnType, compileUnit, compilationState, body));
     }
 
     /**
-     * Return the method emitter used to write bytecode for this function
-     * @return the method emitter
+     * Create a temporary variable to the current frame.
+     *
+     * @param block that needs the temporary
+     * @param type  Strong type of symbol.
+     * @param node  Primary node to use symbol.
+     *
+     * @return Symbol used.
      */
-    public MethodEmitter getMethodEmitter() {
-        return method;
+    public Symbol ensureSymbol(final Block block, final Type type, final Node node) {
+        Symbol symbol = node.getSymbol();
+
+        // If no symbol already present.
+        if (symbol == null) {
+            final String uname = uniqueName(TEMP_PREFIX.symbolName());
+            symbol = new Symbol(uname, IS_TEMP, type);
+            block.putSymbol(uname, symbol);
+            node.setSymbol(symbol);
+        }
+
+        return symbol;
     }
 
     /**
-     * Set the method emitter that is to be used to write bytecode for this function
-     * @param method a method emitter
+     * Get the symbol for a compiler constant, or null if not available (yet)
+     * @param cc compiler constant
+     * @return symbol for compiler constant, or null if not defined yet (for example in Lower)
      */
-    public void setMethodEmitter(final MethodEmitter method) {
-        this.method = method;
+    public Symbol compilerConstant(final CompilerConstants cc) {
+        return body.getExistingSymbol(cc.symbolName());
     }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java
index 889a870..daf79ee 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java
@@ -32,13 +32,15 @@
 
 import jdk.nashorn.internal.codegen.ObjectClassGenerator;
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for an identifier.
  */
-public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNode>, FunctionCall {
+@Immutable
+public final class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNode>, FunctionCall {
     private static final int PROPERTY_NAME    = 1 << 0;
     private static final int INITIALIZED_HERE = 1 << 1;
     private static final int FUNCTION         = 1 << 2;
@@ -47,9 +49,9 @@
     private final String name;
 
     /** Type for a callsite, e.g. X in a get()X or a set(X)V */
-    private Type callSiteType;
+    private final Type callSiteType;
 
-    private byte flags;
+    private final int flags;
 
     /**
      * Constructor
@@ -62,6 +64,15 @@
     public IdentNode(final Source source, final long token, final int finish, final String name) {
         super(source, token, finish);
         this.name = name;
+        this.callSiteType = null;
+        this.flags = 0;
+    }
+
+    private IdentNode(final IdentNode identNode, final String name, final Type callSiteType, final int flags) {
+        super(identNode);
+        this.name = name;
+        this.callSiteType = callSiteType;
+        this.flags = flags;
     }
 
     /**
@@ -71,8 +82,9 @@
      */
     public IdentNode(final IdentNode identNode) {
         super(identNode);
-        this.name  = identNode.getName();
-        this.flags = identNode.flags;
+        this.name         = identNode.getName();
+        this.callSiteType = null;
+        this.flags        = identNode.flags;
     }
 
     @Override
@@ -92,40 +104,15 @@
 
     @Override
     public IdentNode setType(final Type type) {
-        if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
-            ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType());
-        }
         // do NOT, repeat NOT touch the symbol here. it might be a local variable or whatever. This is the override if it isn't
-        if(this.callSiteType == type) {
+        if (this.callSiteType == type) {
             return this;
         }
-        final IdentNode n = (IdentNode)clone();
-        n.callSiteType = type;
-        return n;
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new IdentNode(this);
-    }
-
-    /**
-     * Test to see if two IdentNode are the same.
-     *
-     * @param other Other ident.
-     * @return true if the idents are the same.
-     */
-    @Override
-    public boolean equals(final Object other) {
-        if (other instanceof IdentNode) {
-            return name.equals(((IdentNode)other).name);
+        if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
+            ObjectClassGenerator.LOG.info(getClass().getName(), " ", this, " => ", type, " instead of ", getType());
         }
-        return false;
-    }
 
-    @Override
-    public int hashCode() {
-        return name.hashCode();
+        return new IdentNode(this, name, type, flags);
     }
 
     /**
@@ -135,7 +122,7 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterIdentNode(this) != null) {
+        if (visitor.enterIdentNode(this)) {
             return visitor.leaveIdentNode(this);
         }
 
@@ -147,7 +134,7 @@
         if (hasCallSiteType()) {
             sb.append('{');
             final String desc = getType().getDescriptor();
-            sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor());
+            sb.append(desc.charAt(desc.length() - 1) == ';' ? 'O' : getType().getDescriptor());
             sb.append('}');
         }
 
@@ -191,10 +178,10 @@
      * @return a node equivalent to this one except for the requested change.
      */
     public IdentNode setIsPropertyName() {
-        if(isPropertyName()) return this;
-        final IdentNode n = (IdentNode)clone();
-        n.flags |= PROPERTY_NAME;
-        return n;
+        if (isPropertyName()) {
+            return this;
+        }
+        return new IdentNode(this, name, callSiteType, flags | PROPERTY_NAME);
     }
 
     /**
@@ -210,10 +197,10 @@
      * @return a node equivalent to this one except for the requested change.
      */
     public IdentNode setIsInitializedHere() {
-        if(isInitializedHere()) return this;
-        final IdentNode n = (IdentNode)clone();
-        n.flags |= INITIALIZED_HERE;
-        return n;
+        if (isInitializedHere()) {
+            return this;
+        }
+        return new IdentNode(this, name, callSiteType, flags | INITIALIZED_HERE);
     }
 
     /**
@@ -223,7 +210,7 @@
      * @return true if this IdentNode is special
      */
     public boolean isSpecialIdentity() {
-        return name.equals(__DIR__.tag()) || name.equals(__FILE__.tag()) || name.equals(__LINE__.tag());
+        return name.equals(__DIR__.symbolName()) || name.equals(__FILE__.symbolName()) || name.equals(__LINE__.symbolName());
     }
 
     @Override
@@ -236,9 +223,9 @@
      * @return an ident node identical to this one in all aspects except with its function flag set.
      */
     public IdentNode setIsFunction() {
-        if(isFunction()) return this;
-        final IdentNode n = (IdentNode)clone();
-        n.flags |= FUNCTION;
-        return n;
+        if (isFunction()) {
+            return this;
+        }
+        return new IdentNode(this, name, callSiteType, flags | FUNCTION);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/IfNode.java b/nashorn/src/jdk/nashorn/internal/ir/IfNode.java
index 3ddcf1d..027e1a8 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/IfNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/IfNode.java
@@ -25,22 +25,23 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for an IF statement.
- *
  */
-public class IfNode extends Node {
+@Immutable
+public final class IfNode extends Node {
     /** Test expression. */
-    private Node test;
+    private final Node test;
 
     /** Pass statements. */
-    private Block pass;
+    private final Block pass;
 
     /** Fail statements. */
-    private Block fail;
+    private final Block fail;
 
     /**
      * Constructor
@@ -54,37 +55,30 @@
      */
     public IfNode(final Source source, final long token, final int finish, final Node test, final Block pass, final Block fail) {
         super(source, token, finish);
-
         this.test = test;
         this.pass = pass;
         this.fail = fail;
     }
 
-    private IfNode(final IfNode ifNode, final CopyState cs) {
+    private IfNode(final IfNode ifNode, final Node test, final Block pass, final Block fail) {
         super(ifNode);
-
-        this.test = cs.existingOrCopy(ifNode.test);
-        this.pass = (Block)cs.existingOrCopy(ifNode.pass);
-        this.fail = (Block)cs.existingOrCopy(ifNode.fail);
+        this.test = test;
+        this.pass = pass;
+        this.fail = fail;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new IfNode(this, cs);
+    public boolean isTerminal() {
+        return pass.isTerminal() && fail != null && fail.isTerminal();
     }
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterIfNode(this) != null) {
-            test = test.accept(visitor);
-
-            pass = (Block)pass.accept(visitor);
-
-            if (fail != null) {
-                fail = (Block)fail.accept(visitor);
-            }
-
-            return visitor.leaveIfNode(this);
+        if (visitor.enterIfNode(this)) {
+            return visitor.leaveIfNode(
+                setTest(test.accept(visitor)).
+                setPass((Block)pass.accept(visitor)).
+                setFail(fail == null ? null : (Block)fail.accept(visitor)));
         }
 
         return this;
@@ -105,6 +99,13 @@
         return fail;
     }
 
+    private IfNode setFail(final Block fail) {
+        if (this.fail == fail) {
+            return this;
+        }
+        return new IfNode(this, test, pass, fail);
+    }
+
     /**
      * Get the then block for this IfNode
      * @return the then block
@@ -113,6 +114,13 @@
         return pass;
     }
 
+    private IfNode setPass(final Block pass) {
+        if (this.pass == pass) {
+            return this;
+        }
+        return new IfNode(this, test, pass, fail);
+    }
+
     /**
      * Get the test expression for this IfNode
      * @return the test expression
@@ -124,8 +132,12 @@
     /**
      * Reset the test expression for this IfNode
      * @param test a new test expression
+     * @return new or same IfNode
      */
-    public void setTest(final Node test) {
-        this.test = test;
+    public IfNode setTest(final Node test) {
+        if (this.test == test) {
+            return this;
+        }
+        return new IfNode(this, test, pass, fail);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java b/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java
index 4745bf6..764ee38 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java
@@ -25,22 +25,18 @@
 
 package jdk.nashorn.internal.ir;
 
-import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS;
-
-import jdk.nashorn.internal.codegen.ObjectClassGenerator;
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of an indexed access (brackets operator.)
- *
  */
-public class IndexNode extends BaseNode implements TypeOverride<IndexNode> {
-    /** Property ident. */
-    private Node index;
-
-    private boolean hasCallSiteType;
+@Immutable
+public final class IndexNode extends BaseNode {
+    /** Property index. */
+    private final Node index;
 
     /**
      * Constructors
@@ -52,50 +48,27 @@
      * @param index   index for access
      */
     public IndexNode(final Source source, final long token, final int finish, final Node base, final Node index) {
-        super(source, token, finish, base);
-
+        super(source, token, finish, base, false, false);
         this.index = index;
     }
 
-    /**
-     * Copy constructor
-     *
-     * @param indexNode source node
-     */
-    public IndexNode(final IndexNode indexNode) {
-        this(indexNode, new CopyState());
-    }
-
-    private IndexNode(final IndexNode indexNode, final CopyState cs) {
-        super(indexNode, cs);
-
-        index = cs.existingOrCopy(indexNode.index);
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new IndexNode(this, cs);
-    }
-
-    @Override
-    public boolean equals(final Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-        return index.equals(((IndexNode)other).getIndex());
-    }
-
-    @Override
-    public int hashCode() {
-        return super.hashCode() ^ getIndex().hashCode();
+    private IndexNode(final IndexNode indexNode, final Node base, final Node index, final boolean isFunction, final boolean hasCallSiteType) {
+        super(indexNode, base, isFunction, hasCallSiteType);
+        this.index = index;
     }
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterIndexNode(this) != null) {
-            base = base.accept(visitor);
-            index = index.accept(visitor);
-            return visitor.leaveIndexNode(this);
+        if (visitor.enterIndexNode(this)) {
+            final Node      newBase  = base.accept(visitor);
+            final Node      newIndex = index.accept(visitor);
+            final IndexNode newNode;
+            if (newBase != base || newIndex != index) {
+                newNode = new IndexNode(this, newBase, newIndex, isFunction(), hasCallSiteType());
+            } else {
+                newNode = this;
+            }
+            return visitor.leaveIndexNode(newNode);
         }
 
         return this;
@@ -105,7 +78,7 @@
     public void toString(final StringBuilder sb) {
         final boolean needsParen = tokenType().needsParens(base.tokenType(), true);
 
-        if (hasCallSiteType) {
+        if (hasCallSiteType()) {
             sb.append('{');
             final String desc = getType().getDescriptor();
             sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : getType().getDescriptor());
@@ -135,27 +108,19 @@
         return index;
     }
 
-    /**
-     * Reset the index expression for this IndexNode
-     * @param index a new index expression
-     */
-    public void setIndex(final Node index) {
-        this.index = index;
+    @Override
+    public BaseNode setIsFunction() {
+        if (isFunction()) {
+            return this;
+        }
+        return new IndexNode(this, base, index, true, hasCallSiteType());
     }
 
     @Override
     public IndexNode setType(final Type type) {
-        if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
-            ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType());
-        }
-        hasCallSiteType = true;
-        getSymbol().setTypeOverride(type);
-        return this;
-    }
-
-    @Override
-    public boolean canHaveCallSiteType() {
-        return true; //carried by the symbol and always the same nodetype==symboltype
+        logTypeChange(type);
+        getSymbol().setTypeOverride(type); //always a temp so this is fine.
+        return new IndexNode(this, base, index, isFunction(), true);
     }
 
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java b/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java
index 756ea2d..bf932db 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java
@@ -25,29 +25,20 @@
 
 package jdk.nashorn.internal.ir;
 
-import jdk.nashorn.internal.ir.annotations.Ignore;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for a labeled statement.
- *
  */
-
-public class LabelNode extends Node {
+@Immutable
+public final class LabelNode extends LexicalContextNode {
     /** Label ident. */
-    private IdentNode label;
+    private final IdentNode label;
 
     /** Statements. */
-    private Block body;
-
-    /** Node to break from. */
-    @Ignore
-    private Node breakNode;
-
-    /** Node to continue. */
-    @Ignore
-    private Node continueNode;
+    private final Block body;
 
     /**
      * Constructor
@@ -65,26 +56,23 @@
         this.body  = body;
     }
 
-    private LabelNode(final LabelNode labelNode, final CopyState cs) {
+    private LabelNode(final LabelNode labelNode, final IdentNode label, final Block body) {
         super(labelNode);
-
-        this.label        = (IdentNode)cs.existingOrCopy(labelNode.label);
-        this.body         = (Block)cs.existingOrCopy(labelNode.body);
-        this.breakNode    = cs.existingOrSame(labelNode.breakNode);
-        this.continueNode = cs.existingOrSame(labelNode.continueNode);
+        this.label = label;
+        this.body  = body;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new LabelNode(this, cs);
+    public boolean isTerminal() {
+        return body.isTerminal();
     }
 
     @Override
-    public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterLabelNode(this) != null) {
-            label = (IdentNode)label.accept(visitor);
-            body  = (Block)body.accept(visitor);
-            return visitor.leaveLabelNode(this);
+    public Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterLabelNode(this)) {
+            return visitor.leaveLabelNode(
+                setLabel(visitor.getLexicalContext(), (IdentNode)label.accept(visitor)).
+                setBody(visitor.getLexicalContext(), (Block)body.accept(visitor)));
         }
 
         return this;
@@ -106,44 +94,15 @@
 
     /**
      * Reset the body of the node
+     * @param lc lexical context
      * @param body new body
+     * @return new for node if changed or existing if not
      */
-    public void setBody(final Block body) {
-        this.body = body;
-    }
-
-    /**
-     * Get the break node for this node
-     * @return the break node
-     */
-    public Node getBreakNode() {
-        return breakNode;
-    }
-
-    /**
-     * Reset the break node for this node
-     * @param breakNode the break node
-     */
-    public void setBreakNode(final Node breakNode) {
-        assert breakNode instanceof BreakableNode || breakNode instanceof Block : "Invalid break node: " + breakNode;
-        this.breakNode = breakNode;
-    }
-
-    /**
-     * Get the continue node for this node
-     * @return the continue node
-     */
-    public Node getContinueNode() {
-        return continueNode;
-    }
-
-    /**
-     * Reset the continue node for this node
-     * @param continueNode the continue node
-     */
-    public void setContinueNode(final Node continueNode) {
-        assert continueNode instanceof WhileNode : "invalid continue node: " + continueNode;
-        this.continueNode = continueNode;
+    public LabelNode setBody(final LexicalContext lc, final Block body) {
+        if (this.body == body) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new LabelNode(this, label, body));
     }
 
     /**
@@ -154,4 +113,11 @@
         return label;
     }
 
+    private LabelNode setLabel(final LexicalContext lc, final IdentNode label) {
+        if (this.label == label) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new LabelNode(this, label, body));
+    }
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LabeledNode.java b/nashorn/src/jdk/nashorn/internal/ir/LabeledNode.java
deleted file mode 100644
index a3783e9..0000000
--- a/nashorn/src/jdk/nashorn/internal/ir/LabeledNode.java
+++ /dev/null
@@ -1,123 +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.ir;
-
-import jdk.nashorn.internal.ir.annotations.Ignore;
-import jdk.nashorn.internal.runtime.Source;
-
-/**
- * IR base class for break and continue.
- *
- */
-public abstract class LabeledNode extends Node {
-    /** Optional label. */
-    @Ignore
-    protected final LabelNode labelNode;
-
-    /** Target control node. */
-    @Ignore
-    protected final Node targetNode;
-
-    /** Try chain. */
-    @Ignore
-    protected final TryNode tryChain;
-
-    /** scope nesting level */
-    protected int scopeNestingLevel;
-
-    /**
-     * Constructor
-     *
-     * @param source     the source
-     * @param token      token
-     * @param finish     finish
-     * @param labelNode  the label node
-     * @param targetNode the place to break to
-     * @param tryChain   the try chain
-     */
-    public LabeledNode(final Source source, final long token, final int finish, final LabelNode labelNode, final Node targetNode, final TryNode tryChain) {
-        super(source, token, finish);
-
-        this.labelNode  = labelNode;
-        this.targetNode = targetNode;
-        this.tryChain   = tryChain;
-    }
-
-    /**
-     * Copy constructor
-     *
-     * @param labeledNode source node
-     * @param cs          copy state
-     */
-    protected LabeledNode(final LabeledNode labeledNode, final CopyState cs) {
-        super(labeledNode);
-
-        this.labelNode         = (LabelNode)cs.existingOrCopy(labeledNode.labelNode);
-        this.targetNode        = cs.existingOrSame(labeledNode.targetNode);
-        this.tryChain          = (TryNode)cs.existingOrSame(labeledNode.tryChain);
-        this.scopeNestingLevel = labeledNode.scopeNestingLevel;
-    }
-
-    /**
-     * Get the label
-     * @return the label
-     */
-    public LabelNode getLabel() {
-        return labelNode;
-    }
-
-    /**
-     * Get the target node
-     * @return the target node
-     */
-    public Node getTargetNode() {
-        return targetNode;
-    }
-
-    /**
-     * Get the surrounding try chain
-     * @return the try chain
-     */
-    public TryNode getTryChain() {
-        return tryChain;
-    }
-
-    /**
-     * Get the scope nesting level
-     * @return nesting level
-     */
-    public int getScopeNestingLevel() {
-        return scopeNestingLevel;
-    }
-
-    /**
-     * Set scope nesting level
-     * @param level the new level
-     */
-    public void setScopeNestingLevel(final int level) {
-        scopeNestingLevel = level;
-    }
-}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java b/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java
index 2db1f79..2e53adb 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java
@@ -1,40 +1,224 @@
+/*
+ * 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.ir;
 
-import java.util.ArrayDeque;
-import java.util.Deque;
+import java.io.File;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
+import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.runtime.Debug;
+import jdk.nashorn.internal.runtime.Source;
+
 /**
  * A class that tracks the current lexical context of node visitation as a stack of {@link Block} nodes. Has special
  * methods to retrieve useful subsets of the context.
+ *
+ * This is implemented with a primitive array and a stack pointer, because it really makes a difference
+ * performance wise. None of the collection classes were optimal
  */
-public class LexicalContext implements Cloneable {
-    private final Deque<Block> lexicalContext;
+public class LexicalContext {
+    private LexicalContextNode[] stack;
+
+    private int[] flags;
+    private int sp;
 
     /**
      * Creates a new empty lexical context.
      */
     public LexicalContext() {
-        lexicalContext = new ArrayDeque<>();
+        stack = new LexicalContextNode[16];
+        flags = new int[16];
     }
 
     /**
+     * Set the flags for a lexical context node on the stack. Does not
+     * replace the flags, but rather adds to them
+     *
+     * @param node  node
+     * @param flag  new flag to set
+     */
+    public void setFlag(final LexicalContextNode node, final int flag) {
+        if (flag != 0) {
+            for (int i = sp - 1; i >= 0; i--) {
+                if (stack[i] == node) {
+                    flags[i] |= flag;
+                    //System.err.println("Setting flag " + node + " " + flag);
+                    return;
+                }
+            }
+        }
+        assert false;
+    }
+
+    /**
+     * Get the flags for a lexical context node on the stack
+     * @param node node
+     * @return the flags for the node
+     */
+    public int getFlags(final LexicalContextNode node) {
+        for (int i = sp - 1; i >= 0; i--) {
+            if (stack[i] == node) {
+                return flags[i];
+            }
+        }
+        throw new AssertionError("flag node not on context stack");
+    }
+
+    /**
+     * Get the function body of a function node on the lexical context
+     * stack. This will trigger an assertion if node isn't present
+     * @param functionNode function node
+     * @return body of function node
+     */
+    public Block getFunctionBody(final FunctionNode functionNode) {
+        for (int i = sp - 1; i >= 0 ; i--) {
+            if (stack[i] == functionNode) {
+                return (Block)stack[i + 1];
+            }
+        }
+        throw new AssertionError(functionNode.getName() + " not on context stack");
+    }
+
+    /**
+     * Return all nodes in the LexicalContext
+     * @return all nodes
+     */
+    public Iterator<LexicalContextNode> getAllNodes() {
+        return new NodeIterator<>(LexicalContextNode.class);
+    }
+
+    /**
+     * Returns the outermost function in this context. It is either the program, or a lazily compiled function.
+     * @return the outermost function in this context.
+     */
+    public FunctionNode getOutermostFunction() {
+        return (FunctionNode)stack[0];
+    }
+
+
+
+    /**
      * Pushes a new block on top of the context, making it the innermost open block.
-     * @param block the new block
+     * @param node the new node
+     * @return the node that was pushed
      */
-    public void push(Block block) {
-        //new Exception(block.toString()).printStackTrace();
-        lexicalContext.push(block);
+    public <T extends LexicalContextNode> T push(final T node) {
+        if (sp == stack.length) {
+            final LexicalContextNode[] newStack = new LexicalContextNode[sp * 2];
+            System.arraycopy(stack, 0, newStack, 0, sp);
+            stack = newStack;
+
+            final int[] newFlags = new int[sp * 2];
+            System.arraycopy(flags, 0, newFlags, 0, sp);
+            flags = newFlags;
+
+        }
+        stack[sp] = node;
+        flags[sp] = 0;
+
+        sp++;
+
+        return node;
     }
 
     /**
-     * Pops the innermost block off the context.
-     * @param the block expected to be popped, used to detect unbalanced pushes/pops
+     * Is the context empty?
+     * @return true if empty
      */
-    public void pop(Block block) {
-        final Block popped = lexicalContext.pop();
-        assert popped == block;
+    public boolean isEmpty() {
+        return sp == 0;
+    }
+
+    /**
+     * The depth of the lexical context
+     * @return depth
+     */
+    public int size() {
+        return sp;
+    }
+
+    /**
+     * Pops the innermost block off the context and all nodes that has been contributed
+     * since it was put there
+     *
+     * @param node the node expected to be popped, used to detect unbalanced pushes/pops
+     * @return the node that was popped
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends LexicalContextNode> T pop(final T node) {
+        --sp;
+        final LexicalContextNode popped = stack[sp];
+        if (popped instanceof Flags) {
+            return (T)((Flags<?>)popped).setFlag(this, flags[sp]);
+        }
+
+        return (T)popped;
+    }
+
+
+    /**
+     * Return the top element in the context
+     * @return the node that was pushed last
+     */
+    public LexicalContextNode peek() {
+        return stack[sp - 1];
+    }
+
+    /**
+     * Check if a node is in the lexical context
+     * @param node node to check for
+     * @return true if in the context
+     */
+    public boolean contains(final LexicalContextNode node) {
+        for (int i = 0; i < sp; i++) {
+            if (stack[i] == node) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Replace a node on the lexical context with a new one. Normally
+     * you should try to engineer IR traversals so this isn't needed
+     *
+     * @param oldNode old node
+     * @param newNode new node
+     * @return the new node
+     */
+    public LexicalContextNode replace(final LexicalContextNode oldNode, final LexicalContextNode newNode) {
+       //System.err.println("REPLACE old=" + Debug.id(oldNode) + " new=" + Debug.id(newNode));
+        for (int i = sp - 1; i >= 0; i--) {
+            if (stack[i] == oldNode) {
+                assert i == (sp - 1) : "violation of contract - we always expect to find the replacement node on top of the lexical context stack: " + newNode + " has " + stack[i + 1].getClass() + " above it";
+                stack[i] = newNode;
+                break;
+            }
+         }
+        return newNode;
     }
 
     /**
@@ -42,7 +226,7 @@
      * @return an iterator over all blocks in the context.
      */
     public Iterator<Block> getBlocks() {
-        return lexicalContext.iterator();
+        return new NodeIterator<>(Block.class);
     }
 
     /**
@@ -50,47 +234,17 @@
      * @return an iterator over all functions in the context.
      */
     public Iterator<FunctionNode> getFunctions() {
-        return new FunctionIterator(getBlocks());
+        return new NodeIterator<>(FunctionNode.class);
     }
 
-    private static final class FunctionIterator implements Iterator<FunctionNode> {
-        private final Iterator<Block> it;
-        private FunctionNode next;
-
-        FunctionIterator(Iterator<Block> it) {
-            this.it = it;
-            next = findNext();
-        }
-
-        @Override
-        public boolean hasNext() {
-            return next != null;
-        }
-
-        @Override
-        public FunctionNode next() {
-            if(next == null) {
-                throw new NoSuchElementException();
-            }
-            FunctionNode lnext = next;
-            next = findNext();
-            return lnext;
-        }
-
-        private FunctionNode findNext() {
-            while(it.hasNext()) {
-                final Block block = it.next();
-                if(block instanceof FunctionNode) {
-                    return ((FunctionNode)block);
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public void remove() {
-            throw new UnsupportedOperationException();
-        }
+    /**
+     * Get the parent block for the current lexical context block
+     * @return parent block
+     */
+    public Block getParentBlock() {
+        final Iterator<Block> iter = new NodeIterator<>(Block.class, getCurrentFunction());
+        iter.next();
+        return iter.hasNext() ? iter.next() : null;
     }
 
     /**
@@ -98,12 +252,12 @@
      * @param block the block whose ancestors are returned
      * @return an iterator over all ancestors block of the given block.
      */
-    public Iterator<Block> getAncestorBlocks(Block block) {
-        final Iterator<Block> it = getBlocks();
-        while(it.hasNext()) {
-            final Block b = it.next();
-            if(block == b) {
-                return it;
+    public Iterator<Block> getAncestorBlocks(final Block block) {
+        final Iterator<Block> iter = getBlocks();
+        while (iter.hasNext()) {
+            final Block b = iter.next();
+            if (block == b) {
+                return iter;
             }
         }
         throw new AssertionError("Block is not on the current lexical context stack");
@@ -115,17 +269,17 @@
      * @return an iterator over a block and all its ancestors.
      */
     public Iterator<Block> getBlocks(final Block block) {
-        final Iterator<Block> it = getAncestorBlocks(block);
+        final Iterator<Block> iter = getAncestorBlocks(block);
         return new Iterator<Block>() {
             boolean blockReturned = false;
             @Override
             public boolean hasNext() {
-                return it.hasNext() || !blockReturned;
+                return iter.hasNext() || !blockReturned;
             }
             @Override
             public Block next() {
-                if(blockReturned) {
-                    return it.next();
+                if (blockReturned) {
+                    return iter.next();
                 }
                 blockReturned = true;
                 return block;
@@ -138,45 +292,25 @@
     }
 
     /**
-     * Returns the closest function node to the block. If the block is itself a function, it is returned.
-     * @param block the block
-     * @return the function closest to the block.
-     * @see #getParentFunction(Block)
+     * Get the function for this block. If the block is itself a function
+     * this returns identity
+     * @param block block for which to get function
+     * @return function for block
      */
-    public FunctionNode getFunction(Block block) {
-        if(block instanceof FunctionNode) {
-            return (FunctionNode)block;
-        }
-        return getParentFunction(block);
-    }
-
-    /**
-     * Returns the closest function node to the block and all its ancestor functions. If the block is itself a function,
-     * it is returned too.
-     * @param block the block
-     * @return the closest function node to the block and all its ancestor functions.
-     */
-    public Iterator<FunctionNode> getFunctions(final Block block) {
-        return new FunctionIterator(getBlocks(block));
-    }
-
-    /**
-     * Returns the containing function of the block. If the block is itself a function, its parent function is returned.
-     * @param block the block
-     * @return the containing function of the block.
-     * @see #getFunction(Block)
-     */
-    public FunctionNode getParentFunction(Block block) {
-        return getFirstFunction(getAncestorBlocks(block));
-    }
-
-    private static FunctionNode getFirstFunction(Iterator<Block> it) {
-        while(it.hasNext()) {
-            final Block ancestor = it.next();
-            if(ancestor instanceof FunctionNode) {
-                return (FunctionNode)ancestor;
+    public FunctionNode getFunction(final Block block) {
+        final Iterator<LexicalContextNode> iter = new NodeIterator<>(LexicalContextNode.class);
+        while (iter.hasNext()) {
+            final LexicalContextNode next = iter.next();
+            if (next == block) {
+                while (iter.hasNext()) {
+                    final LexicalContextNode next2 = iter.next();
+                    if (next2 instanceof FunctionNode) {
+                        return (FunctionNode)next2;
+                    }
+                }
             }
         }
+        assert false;
         return null;
     }
 
@@ -185,7 +319,7 @@
      * @return the innermost block in the context.
      */
     public Block getCurrentBlock() {
-        return lexicalContext.element();
+        return getBlocks().next();
     }
 
     /**
@@ -193,6 +327,292 @@
      * @return the innermost function in the context.
      */
     public FunctionNode getCurrentFunction() {
-        return getFirstFunction(getBlocks());
+        if (isEmpty()) {
+            return null;
+        }
+        return new NodeIterator<>(FunctionNode.class).next();
+    }
+
+    /**
+     * Get the block in which a symbol is defined
+     * @param symbol symbol
+     * @return block in which the symbol is defined, assert if no such block in context
+     */
+    public Block getDefiningBlock(final Symbol symbol) {
+        if (symbol.isTemp()) {
+            return null;
+        }
+        final String name = symbol.getName();
+        for (final Iterator<Block> it = getBlocks(); it.hasNext();) {
+            final Block next = it.next();
+            if (next.getExistingSymbol(name) == symbol) {
+                return next;
+            }
+        }
+        throw new AssertionError("Couldn't find symbol " + name + " in the context");
+    }
+
+    /**
+     * Get the function in which a symbol is defined
+     * @param symbol symbol
+     * @return function node in which this symbol is defined, assert if no such symbol exists in context
+     */
+    public FunctionNode getDefiningFunction(Symbol symbol) {
+        if (symbol.isTemp()) {
+            return null;
+        }
+        final String name = symbol.getName();
+        for (final Iterator<LexicalContextNode> iter = new NodeIterator<>(LexicalContextNode.class); iter.hasNext();) {
+            final LexicalContextNode next = iter.next();
+            if (next instanceof Block && ((Block)next).getExistingSymbol(name) == symbol) {
+                while (iter.hasNext()) {
+                    final LexicalContextNode next2 = iter.next();
+                    if (next2 instanceof FunctionNode) {
+                        return ((FunctionNode)next2);
+                    }
+                }
+                throw new AssertionError("Defining block for symbol " + name + " has no function in the context");
+            }
+        }
+        throw new AssertionError("Couldn't find symbol " + name + " in the context");
+    }
+
+    /**
+     * Is the topmost lexical context element a function body?
+     * @return true if function body
+     */
+    public boolean isFunctionBody() {
+        return getParentBlock() == null;
+    }
+
+    /**
+     * Returns true if the expression defining the function is a callee of a CallNode that should be the second
+     * element on the stack, e.g. <code>(function(){})()</code>. That is, if the stack ends with
+     * {@code [..., CallNode, FunctionNode]} then {@code callNode.getFunction()} should be equal to
+     * {@code functionNode}, and the top of the stack should itself be a variant of {@code functionNode}.
+     * @param functionNode the function node being tested
+     * @return true if the expression defining the current function is a callee of a call expression.
+     */
+    public boolean isFunctionDefinedInCurrentCall(FunctionNode functionNode) {
+        final LexicalContextNode parent = stack[sp - 2];
+        if(parent instanceof CallNode && ((CallNode)parent).getFunction() == functionNode) {
+            assert functionNode.getSource() == peek().getSource();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Get the parent function for a function in the lexical context
+     * @param functionNode function for which to get parent
+     * @return parent function of functionNode or null if none (e.g. if functionNode is the program)
+     */
+    public FunctionNode getParentFunction(final FunctionNode functionNode) {
+        final Iterator<FunctionNode> iter = new NodeIterator<>(FunctionNode.class);
+        while (iter.hasNext()) {
+            final FunctionNode next = iter.next();
+            if (next == functionNode) {
+                return iter.hasNext() ? iter.next() : null;
+            }
+        }
+        assert false;
+        return null;
+    }
+
+    /**
+     * Check if lexical context is currently inside a with block
+     * @return true if in a with block
+     */
+    public boolean inWith() {
+        return getScopeNestingLevelTo(null) > 0;
+    }
+
+    /**
+     * Count the number of with scopes until a given node
+     * @param until node to stop counting at, or null if all nodes should be counted
+     * @return number of with scopes encountered in the context
+     */
+    public int getScopeNestingLevelTo(final LexicalContextNode until) {
+        //count the number of with nodes until "until" is hit
+        int n = 0;
+        for (final Iterator<WithNode> iter = new NodeIterator<>(WithNode.class, until); iter.hasNext(); iter.next()) {
+            n++;
+        }
+        return n;
+    }
+
+    private BreakableNode getBreakable() {
+        for (final NodeIterator<BreakableNode> iter = new NodeIterator<>(BreakableNode.class, getCurrentFunction()); iter.hasNext(); ) {
+            final BreakableNode next = iter.next();
+            if (next.isBreakableWithoutLabel()) {
+                return next;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Find the breakable node corresponding to this label.
+     * @param label label to search for, if null the closest breakable node will be returned unconditionally, e.g. a while loop with no label
+     * @return closest breakable node
+     */
+    public BreakableNode getBreakable(final IdentNode label) {
+        if (label != null) {
+            final LabelNode foundLabel = findLabel(label.getName());
+            if (foundLabel != null) {
+                // iterate to the nearest breakable to the foundLabel
+                BreakableNode breakable = null;
+                for (final NodeIterator<BreakableNode> iter = new NodeIterator<>(BreakableNode.class, foundLabel); iter.hasNext(); ) {
+                    breakable = iter.next();
+                }
+                return breakable;
+            }
+            return null;
+        }
+        return getBreakable();
+    }
+
+    private LoopNode getContinueTo() {
+        final Iterator<LoopNode> iter = new NodeIterator<>(LoopNode.class, getCurrentFunction());
+        return iter.hasNext() ? iter.next() : null;
+    }
+
+    /**
+     * Find the continue target node corresponding to this label.
+     * @param label label to search for, if null the closest loop node will be returned unconditionally, e.g. a while loop with no label
+     * @return closest continue target node
+     */
+    public LoopNode getContinueTo(final IdentNode label) {
+        if (label != null) {
+            final LabelNode foundLabel = findLabel(label.getName());
+            if (foundLabel != null) {
+                // iterate to the nearest loop to the foundLabel
+                LoopNode loop = null;
+                for (final NodeIterator<LoopNode> iter = new NodeIterator<>(LoopNode.class, foundLabel); iter.hasNext(); ) {
+                    loop = iter.next();
+                }
+                return loop;
+            }
+            return null;
+        }
+        return getContinueTo();
+    }
+
+    /**
+     * Check the lexical context for a given label node by name
+     * @param name name of the label
+     * @return LabelNode if found, null otherwise
+     */
+    public LabelNode findLabel(final String name) {
+        for (final Iterator<LabelNode> iter = new NodeIterator<>(LabelNode.class, getCurrentFunction()); iter.hasNext(); ) {
+            final LabelNode next = iter.next();
+            if (next.getLabel().getName().equals(name)) {
+                return next;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Checks whether a given label is a jump destination that lies outside a given
+     * split node
+     * @param splitNode the split node
+     * @param label     the label
+     * @return true if label resides outside the split node
+     */
+    public boolean isExternalTarget(final SplitNode splitNode, final Label label) {
+        boolean targetFound = false;
+        for (int i = sp - 1; i >= 0; i--) {
+            final LexicalContextNode next = stack[i];
+            if (next == splitNode) {
+                return !targetFound;
+            }
+
+            if (next instanceof BreakableNode) {
+                for (final Label l : ((BreakableNode)next).getLabels()) {
+                    if (l == label) {
+                        targetFound = true;
+                        break;
+                    }
+                }
+            }
+        }
+        assert false : label + " was expected in lexical context " + LexicalContext.this + " but wasn't";
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuffer sb = new StringBuffer();
+        sb.append("[ ");
+        for (int i = 0; i < sp; i++) {
+            final Node node = stack[i];
+            sb.append(node.getClass().getSimpleName());
+            sb.append('@');
+            sb.append(Debug.id(node));
+            sb.append(':');
+            final Source source = node.getSource();
+            String src = source.toString();
+            if (src.indexOf(File.pathSeparator) != -1) {
+                src = src.substring(src.lastIndexOf(File.pathSeparator));
+            }
+            src += ' ';
+            src += source.getLine(node.getStart());
+            sb.append(' ');
+        }
+        sb.append(" ==> ]");
+        return sb.toString();
+    }
+
+    private class NodeIterator <T extends LexicalContextNode> implements Iterator<T> {
+        private int index;
+        private T next;
+        private final Class<T> clazz;
+        private LexicalContextNode until;
+
+        NodeIterator(final Class<T> clazz) {
+            this(clazz, null);
+        }
+
+        NodeIterator(final Class<T> clazz, final LexicalContextNode until) {
+            this.index = sp - 1;
+            this.clazz = clazz;
+            this.until = until;
+            this.next  = findNext();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return next != null;
+        }
+
+        @Override
+        public T next() {
+            if (next == null) {
+                throw new NoSuchElementException();
+            }
+            T lnext = next;
+            next = findNext();
+            return lnext;
+        }
+
+        private T findNext() {
+            for (int i = index; i >= 0; i--) {
+                final Node node = stack[i];
+                if (node == until) {
+                    return null;
+                }
+                if (clazz.isAssignableFrom(node.getClass())) {
+                    index = i - 1;
+                    return clazz.cast(node);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/DoWhileNode.java b/nashorn/src/jdk/nashorn/internal/ir/LexicalContextNode.java
similarity index 60%
rename from nashorn/src/jdk/nashorn/internal/ir/DoWhileNode.java
rename to nashorn/src/jdk/nashorn/internal/ir/LexicalContextNode.java
index 3939795..e48c6e0 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/DoWhileNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LexicalContextNode.java
@@ -22,61 +22,52 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-
 package jdk.nashorn.internal.ir;
 
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
- * Loop representing do while loops. This is mostly split from WhileNode
- * because of the different order of the Phi Traversals
- *
+ * Superclass for nodes that can be part of the lexical context
+ * @see LexicalContext
  */
-public class DoWhileNode extends WhileNode {
-
+public abstract class LexicalContextNode extends Node {
     /**
      * Constructor
      *
-     * @param source     the source
-     * @param token      token
-     * @param finish     finish
+     * @param source source
+     * @param token  token
+     * @param finish finish
      */
-    public DoWhileNode(final Source source, final long token, final int finish) {
+    protected LexicalContextNode(final Source source, final long token, final int finish) {
         super(source, token, finish);
     }
 
     /**
      * Copy constructor
      *
-     * @param doWhileNode source node
-     * @param cs          copy state
+     * @param node source node
      */
-    protected DoWhileNode(final DoWhileNode doWhileNode, final CopyState cs) {
-        super(doWhileNode, cs);
+    protected LexicalContextNode(final LexicalContextNode node) {
+        super(node);
     }
 
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new DoWhileNode(this, cs);
-    }
+    /**
+     * Accept function for the node given a lexical context. It must be prepared
+     * to replace itself if present in the lexical context
+     *
+     * @param lc      lexical context
+     * @param visitor node visitor
+     *
+     * @return new node or same node depending on state change
+     */
+    protected abstract Node accept(final LexicalContext lc, final NodeVisitor visitor);
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterDoWhileNode(this) != null) {
-            body = (Block)body.accept(visitor);
-            test = test.accept(visitor);
-
-            return visitor.leaveDoWhileNode(this);
-        }
-
-        return this;
-    }
-
-    @Override
-    public void toString(final StringBuilder sb) {
-        sb.append("while (");
-        test.toString(sb);
-        sb.append(')');
+        final LexicalContext lc = visitor.getLexicalContext();
+        lc.push(this);
+        final LexicalContextNode newNode = (LexicalContextNode)accept(lc, visitor);
+        return lc.pop(newNode);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java b/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java
index c7912ff..63f0459 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java
@@ -25,6 +25,7 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.Token;
 import jdk.nashorn.internal.runtime.Source;
@@ -32,8 +33,8 @@
 /**
  * IR Node representing a line number
  */
-
-public class LineNumberNode extends Node {
+@Immutable
+public final class LineNumberNode extends Node {
     /** Line number */
     private final int lineNumber;
 
@@ -46,24 +47,17 @@
      */
     public LineNumberNode(final Source source, final long token, final int lineNumber) {
         super(source, token, Token.descPosition(token));
-
         this.lineNumber = lineNumber;
     }
 
-   private LineNumberNode(final LineNumberNode lineNumberNode) {
+    private LineNumberNode(final LineNumberNode lineNumberNode) {
         super(lineNumberNode);
-
         this.lineNumber = lineNumberNode.getLineNumber();
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new LineNumberNode(this);
-    }
-
-    @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterLineNumberNode(this) != null) {
+        if (visitor.enterLineNumberNode(this)) {
             return visitor.leaveLineNumberNode(this);
         }
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java
index cc424b7a..ae80214 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java
@@ -30,6 +30,7 @@
 import java.util.List;
 import jdk.nashorn.internal.codegen.CompileUnit;
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.Lexer.LexerToken;
 import jdk.nashorn.internal.parser.Token;
@@ -44,6 +45,7 @@
  *
  * @param <T> the literal type
  */
+@Immutable
 public abstract class LiteralNode<T> extends Node implements PropertyKey {
     /** Literal value */
     protected final T value;
@@ -93,23 +95,6 @@
         return value == null;
     }
 
-    @Override
-    public int hashCode() {
-        return value == null ? 0 : value.hashCode();
-    }
-
-    @Override
-    public boolean equals(final Object other) {
-        if (!(other instanceof LiteralNode<?>)) {
-            return false;
-        }
-        final LiteralNode<?> otherNode = (LiteralNode<?>)other;
-        if (otherNode.isNull()) {
-            return isNull();
-        }
-        return ((LiteralNode<?>)other).getValue().equals(value);
-    }
-
     /**
      * Check if the literal value is boolean true
      * @return true if literal value is boolean true
@@ -226,7 +211,7 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterLiteralNode(this) != null) {
+        if (visitor.enterLiteralNode(this)) {
             return visitor.leaveLiteralNode(this);
         }
 
@@ -274,7 +259,8 @@
         return new NodeLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish());
     }
 
-    private static class BooleanLiteralNode extends LiteralNode<Boolean> {
+    @Immutable
+    private static final class BooleanLiteralNode extends LiteralNode<Boolean> {
 
         private BooleanLiteralNode(final Source source, final long token, final int finish, final boolean value) {
             super(source, Token.recast(token, value ? TokenType.TRUE : TokenType.FALSE), finish, value);
@@ -285,11 +271,6 @@
         }
 
         @Override
-        protected Node copy(final CopyState cs) {
-            return new BooleanLiteralNode(this);
-        }
-
-        @Override
         public boolean isTrue() {
             return value;
         }
@@ -331,7 +312,8 @@
         return new BooleanLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish(), value);
     }
 
-    private static class NumberLiteralNode extends LiteralNode<Number> {
+    @Immutable
+    private static final class NumberLiteralNode extends LiteralNode<Number> {
 
         private final Type type = numberGetType(value);
 
@@ -358,11 +340,6 @@
         }
 
         @Override
-        protected Node copy(final CopyState cs) {
-            return new NumberLiteralNode(this);
-        }
-
-        @Override
         public Type getType() {
             return type;
         }
@@ -407,11 +384,6 @@
         private UndefinedLiteralNode(final UndefinedLiteralNode literalNode) {
             super(literalNode);
         }
-
-        @Override
-        protected Node copy(final CopyState cs) {
-            return new UndefinedLiteralNode(this);
-        }
     }
 
     /**
@@ -440,6 +412,7 @@
         return new UndefinedLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish());
     }
 
+    @Immutable
     private static class StringLiteralNode extends LiteralNode<String> {
         private StringLiteralNode(final Source source, final long token, final int finish, final String value) {
             super(source, Token.recast(token, TokenType.STRING), finish, value);
@@ -450,11 +423,6 @@
         }
 
         @Override
-        protected Node copy(final CopyState cs) {
-            return new StringLiteralNode(this);
-        }
-
-        @Override
         public void toString(final StringBuilder sb) {
             sb.append('\"');
             sb.append(value);
@@ -488,6 +456,7 @@
         return new StringLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish(), value);
     }
 
+    @Immutable
     private static class LexerTokenLiteralNode extends LiteralNode<LexerToken> {
         private LexerTokenLiteralNode(final Source source, final long token, final int finish, final LexerToken value) {
             super(source, Token.recast(token, TokenType.STRING), finish, value); //TODO is string the correct token type here?
@@ -498,11 +467,6 @@
         }
 
         @Override
-        protected Node copy(final CopyState cs) {
-            return new LexerTokenLiteralNode(this);
-        }
-
-        @Override
         public Type getType() {
             return Type.OBJECT;
         }
@@ -539,7 +503,7 @@
         return new LexerTokenLiteralNode(parent.getSource(), parent.getToken(), parent.getFinish(), value);
     }
 
-    private static class NodeLiteralNode extends LiteralNode<Node> {
+    private static final class NodeLiteralNode extends LiteralNode<Node> {
 
         private NodeLiteralNode(final Source source, final long token, final int finish) {
             this(source, token, finish, null);
@@ -558,13 +522,8 @@
         }
 
         @Override
-        protected Node copy(final CopyState cs) {
-            return new NodeLiteralNode(this);
-        }
-
-        @Override
         public Node accept(final NodeVisitor visitor) {
-            if (visitor.enterLiteralNode(this) != null) {
+            if (visitor.enterLiteralNode(this)) {
                 if (value != null) {
                     final Node newValue = value.accept(visitor);
                     if(value != newValue) {
@@ -617,7 +576,7 @@
     /**
      * Array literal node class.
      */
-    public static class ArrayLiteralNode extends LiteralNode<Node[]> {
+    public static final class ArrayLiteralNode extends LiteralNode<Node[]> {
         private static class PostsetMarker {
             //empty
         }
@@ -705,11 +664,6 @@
             this.elementType = node.elementType;
         }
 
-        @Override
-        protected Node copy(final CopyState cs) {
-            return new ArrayLiteralNode(this);
-        }
-
         /**
          * Compute things like widest element type needed. Internal use from compiler only
          */
@@ -894,7 +848,7 @@
 
         @Override
         public Node accept(final NodeVisitor visitor) {
-            if (visitor.enterLiteralNode(this) != null) {
+            if (visitor.enterLiteralNode(this)) {
                 for (int i = 0; i < value.length; i++) {
                     final Node element = value[i];
                     if (element != null) {
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Location.java b/nashorn/src/jdk/nashorn/internal/ir/Location.java
index c8e01dd..cd9edb5 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Location.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Location.java
@@ -25,16 +25,13 @@
 
 package jdk.nashorn.internal.ir;
 
-import java.util.Objects;
 import jdk.nashorn.internal.parser.Token;
 import jdk.nashorn.internal.parser.TokenType;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * Used to locate an entity back to it's source file.
- *
  */
-
 public class Location implements Cloneable {
     /** Source of entity. */
     private final Source source;
@@ -73,22 +70,13 @@
     }
 
     @Override
-    public boolean equals(final Object other) {
-        if (other == null) {
-            return false;
-        }
-
-        if (other.getClass() != this.getClass()) {
-            return false;
-        }
-
-        final Location loc = (Location)other;
-        return token == loc.token && Objects.equals(source, loc.source);
+    public final boolean equals(final Object other) {
+        return super.equals(other);
     }
 
     @Override
-    public int hashCode() {
-        return Token.hashCode(token) ^ Objects.hashCode(source);
+    public final int hashCode() {
+        return super.hashCode();
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LoopNode.java b/nashorn/src/jdk/nashorn/internal/ir/LoopNode.java
new file mode 100644
index 0000000..b3909dc
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/ir/LoopNode.java
@@ -0,0 +1,176 @@
+/*
+ * 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.ir;
+
+import java.util.Arrays;
+import java.util.List;
+
+import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.runtime.Source;
+
+/**
+ * A loop node, for example a while node, do while node or for node
+ */
+public abstract class LoopNode extends BreakableNode {
+    /** loop continue label. */
+    protected final Label continueLabel;
+
+    /** Loop test node, null if infinite */
+    protected final Node test;
+
+    /** Loop body */
+    protected final Block body;
+
+    /** Can control flow escape from loop, e.g. through breaks or continues to outer loops? */
+    protected final boolean controlFlowEscapes;
+
+    /**
+     * Constructor
+     *
+     * @param source  source
+     * @param token   token
+     * @param finish  finish
+     * @param test    test, or null if infinite loop
+     * @param body    loop body
+     * @param controlFlowEscapes controlFlowEscapes
+     */
+    protected LoopNode(final Source source, final long token, final int finish, final Node test, final Block body, final boolean controlFlowEscapes) {
+        super(source, token, finish, new Label("while_break"));
+        this.continueLabel = new Label("while_continue");
+        this.test = test;
+        this.body = body;
+        this.controlFlowEscapes = controlFlowEscapes;
+    }
+
+    /**
+     * Constructor
+     *
+     * @param loopNode loop node
+     * @param test     new test
+     * @param body     new body
+     * @param controlFlowEscapes controlFlowEscapes
+     */
+    protected LoopNode(final LoopNode loopNode, final Node test, final Block body, final boolean controlFlowEscapes) {
+        super(loopNode);
+        this.continueLabel = new Label(loopNode.continueLabel);
+        this.test = test;
+        this.body = body;
+        this.controlFlowEscapes = controlFlowEscapes;
+    }
+
+    @Override
+    public abstract Node ensureUniqueLabels(final LexicalContext lc);
+
+    /**
+     * Does the control flow escape from this loop, i.e. through breaks or
+     * continues to outer loops?
+     * @return true if control flow escapes
+     */
+    public boolean controlFlowEscapes() {
+        return controlFlowEscapes;
+    }
+
+
+    @Override
+    public boolean isTerminal() {
+        if (!mustEnter()) {
+            return false;
+        }
+        //must enter but control flow may escape - then not terminal
+        if (controlFlowEscapes) {
+            return false;
+        }
+        //must enter, but body ends with return - then terminal
+        if (body.isTerminal()) {
+            return true;
+        }
+        //no breaks or returns, it is still terminal if we can never exit
+        return test == null;
+    }
+
+    /**
+     * Conservative check: does this loop have to be entered?
+     * @return true if body will execute at least once
+     */
+    public abstract boolean mustEnter();
+
+    /**
+     * Get the continue label for this while node, i.e. location to go to on continue
+     * @return continue label
+     */
+    public Label getContinueLabel() {
+        return continueLabel;
+    }
+
+    @Override
+    public List<Label> getLabels() {
+        return Arrays.asList(breakLabel, continueLabel);
+    }
+
+    @Override
+    public boolean isLoop() {
+        return true;
+    }
+
+    /**
+     * Get the body for this for node
+     * @return the body
+     */
+    public abstract Block getBody();
+
+    /**
+     * @param lc   lexical context
+     * @param body new body
+     * @return new for node if changed or existing if not
+     */
+    public abstract LoopNode setBody(final LexicalContext lc, final Block body);
+
+    /**
+     * Get the test for this for node
+     * @return the test
+     */
+    public abstract Node getTest();
+
+    /**
+     * Set the test for this for node
+     *
+     * @param lc lexical context
+     * @param test new test
+     * @return same or new node depending on if test was changed
+     */
+    public abstract LoopNode setTest(final LexicalContext lc, final Node test);
+
+    /**
+     * Set the control flow escapes flag for this node.
+     * TODO  - integrate this with Lowering in a better way
+     *
+     * @param lc lexical context
+     * @param controlFlowEscapes control flow escapes value
+     * @return new loop node if changed otherwise the same
+     */
+    public abstract LoopNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes);
+
+}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Node.java b/nashorn/src/jdk/nashorn/internal/ir/Node.java
index c5f0133..dfed903 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Node.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Node.java
@@ -25,7 +25,9 @@
 
 package jdk.nashorn.internal.ir;
 
-import java.util.IdentityHashMap;
+import java.util.ArrayList;
+import java.util.List;
+
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.Token;
@@ -33,30 +35,17 @@
 
 /**
  * Nodes are used to compose Abstract Syntax Trees.
- *
  */
 public abstract class Node extends Location {
     /** Node symbol. */
     private Symbol symbol;
 
     /** Start of source range. */
-    protected int start;
+    protected final int start;
 
     /** End of source range. */
     protected int finish;
 
-    /** Has this node been resolved - i.e. emitted code already */
-    private boolean isResolved;
-
-    /** Is this node terminal */
-    private boolean isTerminal;
-
-    /** Is this a goto node */
-    private boolean hasGoto;
-
-    /** Is this a discard */
-    private boolean shouldDiscard;
-
     /**
      * Constructor
      *
@@ -72,6 +61,21 @@
     }
 
     /**
+     * Constructor
+     *
+     * @param source  source
+     * @param token   token
+     * @param start   start
+     * @param finish  finish
+     */
+    protected Node(final Source source, final long token, final int start, final int finish) {
+        super(source, token);
+
+        this.start = start;
+        this.finish = finish;
+    }
+
+    /**
      * Copy constructor
      *
      * @param node source node
@@ -79,13 +83,9 @@
     protected Node(final Node node) {
         super(node);
 
-        this.symbol        = node.symbol;
-        this.isResolved    = node.isResolved;
-        this.isTerminal    = node.isTerminal;
-        this.hasGoto       = node.hasGoto;
-        this.shouldDiscard = node.shouldDiscard;
-        this.start         = node.start;
-        this.finish        = node.finish;
+        this.symbol = node.symbol;
+        this.start  = node.start;
+        this.finish = node.finish;
     }
 
     /**
@@ -156,28 +156,6 @@
     }
 
     /**
-     * Test to see if code been generated for this node. Set isResolved if not.
-     *
-     * @return True if node has already been resolved.
-     */
-    public boolean testResolved() {
-        if (isResolved()) {
-            return true;
-        }
-
-        setIsResolved(true);
-
-        return false;
-    }
-
-    /**
-     * Reset the resolved flag.
-     */
-    public void resetResolved() {
-        setIsResolved(false);
-    }
-
-    /**
      * Is this a debug info node like LineNumberNode etc?
      *
      * @return true if this is a debug node
@@ -187,72 +165,13 @@
     }
 
     /**
-     * Helper class used for node cloning
+     * For reference copies - ensure that labels in the copy node are unique
+     * using an appropriate copy constructor
+     * @param lc lexical context
+     * @return new node or same of no labels
      */
-    public static final class CopyState {
-        private final IdentityHashMap<Node, Node> cloneMap = new IdentityHashMap<>();
-
-        /**
-         * Find existing or create new copy of the node.
-         *
-         * @param node Node to copy.
-         *
-         * @return New object.
-         */
-        public Node existingOrCopy(final Node node) {
-            if (node != null) {
-                Node copy = cloneMap.get(node);
-
-                if (copy == null) {
-                    copy = node.copy(this);
-                    cloneMap.put(node, copy);
-                }
-
-                return copy;
-            }
-
-            return node;
-        }
-
-        /**
-         * Find existing or use old copy of the node.
-         *
-         * @param node Node to copy.
-         *
-         * @return new object.
-         */
-        public Node existingOrSame(final Node node) {
-            if (node != null) {
-                Node copy = cloneMap.get(node);
-
-                if (copy == null) {
-                    copy = node;
-                }
-
-                return copy;
-            }
-
-            return node;
-        }
-    }
-
-    /**
-     * Deep copy the node.
-     *
-     * @return Deep copy of the  Node.
-     */
-    public final Node copy() {
-        return copy(new CopyState());
-    }
-
-    /**
-     * Deep copy the node.
-     *
-     * @param cs CopyState passed around to re-use certain nodes.
-     * @return Deep copy of the  Node.
-     */
-    protected Node copy(final CopyState cs) {
-        return cs.existingOrCopy(this);
+    public Node ensureUniqueLabels(final LexicalContext lc) {
+        return this;
     }
 
     /**
@@ -283,35 +202,7 @@
      * @return true if terminal
      */
     public boolean hasTerminalFlags() {
-        return isTerminal || hasGoto;
-    }
-
-    /**
-     * Copy the terminal flags state of a node to another node
-     *
-     * @param other source node
-     */
-    public void copyTerminalFlags(final Node other) {
-        isTerminal = other.isTerminal;
-        hasGoto    = other.hasGoto;
-    }
-
-    /**
-     * Check if the return value of this expression should be discarded
-     * @return true if return value is discarded
-     */
-    public boolean shouldDiscard() {
-        return shouldDiscard;
-    }
-
-    /**
-     * Setter that determines whether this node's return value should be discarded
-     * or not
-     *
-     * @param shouldDiscard true if return value is discarded, false otherwise
-     */
-    public void setDiscard(final boolean shouldDiscard) {
-        this.shouldDiscard = shouldDiscard;
+        return isTerminal() || hasGoto();
     }
 
     /**
@@ -336,29 +227,7 @@
      * @return true if node has goto semantics
      */
     public boolean hasGoto() {
-        return hasGoto;
-    }
-
-    /**
-     * Flag this node as having goto semantics as described in {@link Node#hasGoto()}
-     */
-    public void setHasGoto() {
-        this.hasGoto = true;
-    }
-
-    /**
-     * Check whether this node is resolved, i.e. code has been generated for it
-     * @return true if node is resolved
-     */
-    public boolean isResolved() {
-        return isResolved;
-    }
-
-    /**
-     * Flag this node as resolved or not, i.e. code has been generated for it
-     */
-    private void setIsResolved(boolean isResolved) {
-        this.isResolved = isResolved;
+        return false;
     }
 
     /**
@@ -370,14 +239,6 @@
     }
 
     /**
-     * Set start position for node
-     * @param start start position
-     */
-    public void setStart(final int start) {
-        this.start = start;
-    }
-
-    /**
      * Return the Symbol the compiler has assigned to this Node. The symbol
      * is the place where it's expression value is stored after evaluation
      *
@@ -404,17 +265,29 @@
      * @return true if this node is terminal
      */
     public boolean isTerminal() {
-        return isTerminal;
+        return false;
     }
 
-    /**
-     * Set this to be a terminal node, i.e. it terminates control flow as described
-     * in {@link Node#isTerminal()}
-     *
-     * @param isTerminal true if this is a terminal node, false otherwise
-     */
-    public void setIsTerminal(final boolean isTerminal) {
-        this.isTerminal = isTerminal;
+    //on change, we have to replace the entire list, that's we can't simple do ListIterator.set
+    static <T extends Node> List<T> accept(final NodeVisitor visitor, final Class<T> clazz, final List<T> list) {
+        boolean changed = false;
+        final List<T> newList = new ArrayList<>();
+
+        for (final Node node : list) {
+            final T newNode = clazz.cast(node.accept(visitor));
+            if (newNode != node) {
+                changed = true;
+            }
+            newList.add(newNode);
+        }
+
+        return changed ? newList : list;
     }
 
+    static <T extends LexicalContextNode> T replaceInLexicalContext(final LexicalContext lc, final T oldNode, final T newNode) {
+        if (lc != null) {
+            lc.replace(oldNode, newNode);
+        }
+        return newNode;
+    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java b/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java
index f6724a6..744a44d 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java
@@ -25,16 +25,18 @@
 
 package jdk.nashorn.internal.ir;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of an object literal.
  */
-public class ObjectNode extends Node {
+@Immutable
+public final class ObjectNode extends Node {
 
     /** Literal elements. */
     private final List<Node> elements;
@@ -49,35 +51,18 @@
      */
     public ObjectNode(final Source source, final long token, final int finish, final List<Node> elements) {
         super(source, token, finish);
-
         this.elements = elements;
     }
 
-    private ObjectNode(final ObjectNode objectNode, final CopyState cs) {
+    private ObjectNode(final ObjectNode objectNode, final List<Node> elements) {
         super(objectNode);
-
-        final List<Node> newElements = new ArrayList<>();
-
-        for (final Node element : objectNode.elements) {
-            newElements.add(cs.existingOrCopy(element));
-        }
-
-        this.elements = newElements;
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new ObjectNode(this, cs);
+        this.elements = elements;
     }
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterObjectNode(this) != null) {
-            for (int i = 0, count = elements.size(); i < count; i++) {
-                elements.set(i, elements.get(i).accept(visitor));
-            }
-
-            return visitor.leaveObjectNode(this);
+        if (visitor.enterObjectNode(this)) {
+            return visitor.leaveObjectNode(setElements(Node.accept(visitor, Node.class, elements)));
         }
 
         return this;
@@ -112,4 +97,11 @@
     public List<Node> getElements() {
         return Collections.unmodifiableList(elements);
     }
+
+    private ObjectNode setElements(final List<Node> elements) {
+        if (this.elements == elements) {
+            return this;
+        }
+        return new ObjectNode(this, elements);
+    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java b/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java
index a6bc49d..635e1aa 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java
@@ -25,28 +25,27 @@
 
 package jdk.nashorn.internal.ir;
 
-import jdk.nashorn.internal.ir.annotations.Reference;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of an object literal property.
  */
-public class PropertyNode extends Node {
+@Immutable
+public final class PropertyNode extends Node {
 
     /** Property key. */
-    private PropertyKey key;
+    private final PropertyKey key;
 
     /** Property value. */
-    private Node value;
+    private final Node value;
 
     /** Property getter. */
-    @Reference
-    private Node getter;
+    private final FunctionNode getter;
 
     /** Property getter. */
-    @Reference
-    private Node setter;
+    private final FunctionNode setter;
 
     /**
      * Constructor
@@ -56,26 +55,23 @@
      * @param finish  finish
      * @param key     the key of this property
      * @param value   the value of this property
+     * @param getter  getter function body
+     * @param setter  setter function body
      */
-    public PropertyNode(final Source source, final long token, final int finish, final PropertyKey key, final Node value) {
+    public PropertyNode(final Source source, final long token, final int finish, final PropertyKey key, final Node value, final FunctionNode getter, final FunctionNode setter) {
         super(source, token, finish);
-
         this.key    = key;
         this.value  = value;
+        this.getter = getter;
+        this.setter = setter;
     }
 
-    private PropertyNode(final PropertyNode propertyNode, final CopyState cs) {
+    private PropertyNode(final PropertyNode propertyNode, final PropertyKey key, final Node value, final FunctionNode getter, final FunctionNode setter) {
         super(propertyNode);
-
-        this.key    = (PropertyKey)cs.existingOrCopy((Node)propertyNode.key);
-        this.value  = cs.existingOrCopy(propertyNode.value);
-        this.getter = cs.existingOrSame(propertyNode.getter);
-        this.setter = cs.existingOrSame(propertyNode.setter);
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new PropertyNode(this, cs);
+        this.key    = key;
+        this.value  = value;
+        this.getter = getter;
+        this.setter = setter;
     }
 
     /**
@@ -88,22 +84,12 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterPropertyNode(this) != null) {
-            key = (PropertyKey)((Node)key).accept(visitor);
-
-            if (value != null) {
-                value = value.accept(visitor);
-            }
-
-            if (getter != null) {
-                getter = getter.accept(visitor);
-            }
-
-            if (setter != null) {
-                setter = setter.accept(visitor);
-            }
-
-            return visitor.leavePropertyNode(this);
+        if (visitor.enterPropertyNode(this)) {
+            return visitor.leavePropertyNode(
+                setKey((PropertyKey)((Node)key).accept(visitor)).
+                setValue(value == null ? null : value.accept(visitor)).
+                setGetter(getter == null ? null : (FunctionNode)getter.accept(visitor)).
+                setSetter(setter == null ? null : (FunctionNode)setter.accept(visitor)));
         }
 
         return this;
@@ -136,16 +122,20 @@
      * Get the getter for this property
      * @return getter or null if none exists
      */
-    public Node getGetter() {
+    public FunctionNode getGetter() {
         return getter;
     }
 
     /**
      * Set the getter of this property, null if none
      * @param getter getter
+     * @return same node or new node if state changed
      */
-    public void setGetter(final Node getter) {
-        this.getter = getter;
+    public PropertyNode setGetter(final FunctionNode getter) {
+        if (this.getter == getter) {
+            return this;
+        }
+        return new PropertyNode(this, key, value, getter, setter);
     }
 
     /**
@@ -156,20 +146,31 @@
         return (Node)key;
     }
 
+    private PropertyNode setKey(final PropertyKey key) {
+        if (this.key == key) {
+            return this;
+        }
+        return new PropertyNode(this, key, value, getter, setter);
+    }
+
     /**
      * Get the setter for this property
      * @return setter or null if none exists
      */
-    public Node getSetter() {
+    public FunctionNode getSetter() {
         return setter;
     }
 
     /**
      * Set the setter for this property, null if none
      * @param setter setter
+     * @return same node or new node if state changed
      */
-    public void setSetter(final Node setter) {
-        this.setter = setter;
+    public PropertyNode setSetter(final FunctionNode setter) {
+        if (this.setter == setter) {
+            return this;
+        }
+        return new PropertyNode(this, key, value, getter, setter);
     }
 
     /**
@@ -183,8 +184,12 @@
     /**
      * Set the value of this property
      * @param value new value
+     * @return same node or new node if state changed
      */
-    public void setValue(final Node value) {
-        this.value = value;
-    }
+    public PropertyNode setValue(final Node value) {
+        if (this.value == value) {
+            return this;
+        }
+        return new PropertyNode(this, key, value, getter, setter);
+   }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java b/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java
index 1400f39..dafc956 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java
@@ -27,22 +27,17 @@
 
 import static jdk.nashorn.internal.parser.TokenType.RETURN;
 import static jdk.nashorn.internal.parser.TokenType.YIELD;
-
-import jdk.nashorn.internal.ir.annotations.Ignore;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for RETURN or YIELD statements.
- *
  */
+@Immutable
 public class ReturnNode extends Node {
     /** Optional expression. */
-    private Node expression;
-
-    /** Try chain. */
-    @Ignore
-    private final TryNode tryChain;
+    private final Node expression;
 
     /**
      * Constructor
@@ -51,27 +46,20 @@
      * @param token      token
      * @param finish     finish
      * @param expression expression to return
-     * @param tryChain   surrounding try chain.
      */
-    public ReturnNode(final Source source, final long token, final int finish, final Node expression, final TryNode tryChain) {
+    public ReturnNode(final Source source, final long token, final int finish, final Node expression) {
         super(source, token, finish);
-
         this.expression = expression;
-        this.tryChain   = tryChain;
-
-        setIsTerminal(true);
     }
 
-    private ReturnNode(final ReturnNode returnNode, final CopyState cs) {
+    private ReturnNode(final ReturnNode returnNode, final Node expression) {
         super(returnNode);
-
-        this.expression = cs.existingOrCopy(returnNode.expression);
-        this.tryChain   = (TryNode)cs.existingOrSame(returnNode.tryChain);
+        this.expression = expression;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new ReturnNode(this, cs);
+    public boolean isTerminal() {
+        return true;
     }
 
     /**
@@ -100,11 +88,10 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterReturnNode(this) != null) {
+        if (visitor.enterReturnNode(this)) {
             if (expression != null) {
-                expression = expression.accept(visitor);
+                return visitor.leaveReturnNode(setExpression(expression.accept(visitor)));
             }
-
             return visitor.leaveReturnNode(this);
         }
 
@@ -121,25 +108,6 @@
         }
     }
 
-    @Override
-    public boolean equals(final Object other) {
-        if (other instanceof ReturnNode) {
-            final ReturnNode otherReturn = (ReturnNode)other;
-            if (hasExpression() != otherReturn.hasExpression()) {
-                return false;
-            } else if (hasExpression()) {
-                return otherReturn.getExpression().equals(getExpression());
-            }
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return 0x4711_17 ^ (expression == null ? 0 : expression.hashCode());
-    }
-
     /**
      * Get the expression this node returns
      * @return return expression, or null if void return
@@ -151,16 +119,13 @@
     /**
      * Reset the expression this node returns
      * @param expression new expression, or null if void return
+     * @return new or same return node
      */
-    public void setExpression(final Node expression) {
-        this.expression = expression;
+    public ReturnNode setExpression(final Node expression) {
+        if (this.expression == expression) {
+            return this;
+        }
+        return new ReturnNode(this, expression);
     }
 
-    /**
-     * Get the surrounding try chain for this return node
-     * @return try chain
-     */
-    public TryNode getTryChain() {
-        return tryChain;
-    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java b/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java
index 461007c..7bdb6c0 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java
@@ -30,14 +30,15 @@
 import java.util.Collections;
 import java.util.List;
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.TokenType;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for a runtime call.
- *
  */
+@Immutable
 public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> {
 
     /**
@@ -271,10 +272,10 @@
     private final List<Node> args;
 
     /** Call site override - e.g. we know that a ScriptRuntime.ADD will return an int */
-    private Type callSiteType;
+    private final Type callSiteType;
 
     /** is final - i.e. may not be removed again, lower in the code pipeline */
-    private boolean isFinal;
+    private final boolean isFinal;
 
     /**
      * Constructor
@@ -290,6 +291,17 @@
 
         this.request      = request;
         this.args         = args;
+        this.callSiteType = null;
+        this.isFinal      = false;
+    }
+
+    private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final Type callSiteType, final boolean isFinal, final List<Node> args) {
+        super(runtimeNode);
+
+        this.request      = request;
+        this.args         = args;
+        this.callSiteType = callSiteType;
+        this.isFinal      = isFinal;
     }
 
     /**
@@ -326,8 +338,10 @@
     public RuntimeNode(final Node parent, final Request request, final List<Node> args) {
         super(parent);
 
-        this.request = request;
-        this.args    = args;
+        this.request      = request;
+        this.args         = args;
+        this.callSiteType = null;
+        this.isFinal      = false;
     }
 
     /**
@@ -350,20 +364,6 @@
         this(parent, request, parent.lhs(), parent.rhs());
     }
 
-    private RuntimeNode(final RuntimeNode runtimeNode, final CopyState cs) {
-        super(runtimeNode);
-
-        final List<Node> newArgs = new ArrayList<>();
-
-        for (final Node arg : runtimeNode.args) {
-            newArgs.add(cs.existingOrCopy(arg));
-        }
-
-        this.request      = runtimeNode.request;
-        this.args         = newArgs;
-        this.callSiteType = runtimeNode.callSiteType;
-    }
-
     /**
      * Is this node final - i.e. it can never be replaced with other nodes again
      * @return true if final
@@ -374,14 +374,14 @@
 
     /**
      * Flag this node as final - i.e it may never be replaced with other nodes again
+     * @param isFinal is the node final, i.e. can not be removed and replaced by a less generic one later in codegen
+     * @return same runtime node if already final, otherwise a new one
      */
-    public void setIsFinal() {
-        this.isFinal = true;
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new RuntimeNode(this, cs);
+    public RuntimeNode setIsFinal(final boolean isFinal) {
+        if (this.isFinal == isFinal) {
+            return this;
+        }
+        return new RuntimeNode(this, request, callSiteType, isFinal, args);
     }
 
     /**
@@ -394,8 +394,10 @@
 
     @Override
     public RuntimeNode setType(final Type type) {
-        this.callSiteType = type;
-        return this;
+        if (this.callSiteType == type) {
+            return this;
+        }
+        return new RuntimeNode(this, request, type, isFinal, args);
     }
 
     @Override
@@ -409,12 +411,12 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterRuntimeNode(this) != null) {
-            for (int i = 0, count = args.size(); i < count; i++) {
-                args.set(i, args.get(i).accept(visitor));
+        if (visitor.enterRuntimeNode(this)) {
+            final List<Node> newArgs = new ArrayList<>();
+            for (final Node arg : args) {
+                newArgs.add(arg.accept(visitor));
             }
-
-            return visitor.leaveRuntimeNode(this);
+            return visitor.leaveRuntimeNode(setArgs(newArgs));
         }
 
         return this;
@@ -449,6 +451,13 @@
         return Collections.unmodifiableList(args);
     }
 
+    private RuntimeNode setArgs(final List<Node> args) {
+        if (this.args == args) {
+            return this;
+        }
+        return new RuntimeNode(this, request, callSiteType, isFinal, args);
+    }
+
     /**
      * Get the request that this runtime node implements
      * @return the request
diff --git a/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java b/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java
index b751cdc..49c4092 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java
@@ -25,99 +25,65 @@
 
 package jdk.nashorn.internal.ir;
 
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import jdk.nashorn.internal.codegen.CompileUnit;
-import jdk.nashorn.internal.codegen.Label;
-import jdk.nashorn.internal.codegen.MethodEmitter;
-import jdk.nashorn.internal.ir.annotations.Ignore;
-import jdk.nashorn.internal.ir.annotations.Reference;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 
-
 /**
  * Node indicating code is split across classes.
  */
-public class SplitNode extends Node {
+@Immutable
+public class SplitNode extends LexicalContextNode {
     /** Split node method name. */
     private final String name;
 
     /** Compilation unit. */
-    private CompileUnit compileUnit;
-
-    /** Method emitter for current method. */
-    private MethodEmitter method;
-
-    /** Method emitter for caller method. */
-    private MethodEmitter caller;
-
-    /** Containing function. */
-    @Reference
-    private final FunctionNode functionNode;
-
-    /** A list of target labels in parent methods this split node may encounter. */
-    @Ignore
-    private final List<Label> externalTargets;
-
-    /** True if this split node or any of its children contain a return statement. */
-    private boolean hasReturn;
+    private final CompileUnit compileUnit;
 
     /** Body of split code. */
-    @Ignore
-    private Node body;
+    private final Node body;
 
     /**
      * Constructor
      *
-     * @param name          name of split node
-     * @param functionNode  function node to split in
-     * @param body          body of split code
+     * @param name        name of split node
+     * @param body        body of split code
+     * @param compileUnit compile unit to use for the body
      */
-    public SplitNode(final String name, final FunctionNode functionNode, final Node body) {
+    public SplitNode(final String name, final Node body, final CompileUnit compileUnit) {
         super(body.getSource(), body.getToken(), body.getFinish());
-
-        this.name         = name;
-        this.functionNode = functionNode;
-        this.body         = body;
-        this.externalTargets = new ArrayList<>();
+        this.name        = name;
+        this.body        = body;
+        this.compileUnit = compileUnit;
     }
 
-    private SplitNode(final SplitNode splitNode, final CopyState cs) {
+    private SplitNode(final SplitNode splitNode, final Node body) {
         super(splitNode);
-
-        this.name         = splitNode.name;
-        this.functionNode = (FunctionNode)cs.existingOrSame(splitNode.functionNode);
-        this.body         = cs.existingOrCopy(splitNode.body);
-        this.externalTargets = new ArrayList<>();
+        this.name        = splitNode.name;
+        this.body        = body;
+        this.compileUnit = splitNode.compileUnit;
     }
 
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new SplitNode(this, cs);
+    /**
+     * Get the body for this split node - i.e. the actual code it encloses
+     * @return body for split node
+     */
+    public Node getBody() {
+        return body;
     }
 
-    @Override
-    public Node accept(final NodeVisitor visitor) {
-        final CompileUnit   saveCompileUnit = visitor.getCurrentCompileUnit();
-        final MethodEmitter saveMethod      = visitor.getCurrentMethodEmitter();
-
-        setCaller(saveMethod);
-
-        visitor.setCurrentCompileUnit(getCompileUnit());
-        visitor.setCurrentMethodEmitter(getMethodEmitter());
-
-        try {
-            if (visitor.enterSplitNode(this) != null) {
-                body = body.accept(visitor);
-
-                return visitor.leaveSplitNode(this);
-            }
-        } finally {
-            visitor.setCurrentCompileUnit(saveCompileUnit);
-            visitor.setCurrentMethodEmitter(saveMethod);
+    private SplitNode setBody(final LexicalContext lc, final Node body) {
+        if (this.body == body) {
+            return this;
         }
+        return Node.replaceInLexicalContext(lc, this, new SplitNode(this, body));
+    }
 
+    @Override
+    public Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterSplitNode(this)) {
+            return visitor.leaveSplitNode(setBody(lc, body.accept(visitor)));
+        }
         return this;
     }
 
@@ -130,22 +96,6 @@
     }
 
     /**
-     * Get the method emitter of the caller for this split node
-     * @return caller method emitter
-     */
-    public MethodEmitter getCaller() {
-        return caller;
-    }
-
-    /**
-     * Set the caller method emitter for this split node
-     * @param caller method emitter
-     */
-    public void setCaller(final MethodEmitter caller) {
-        this.caller = caller;
-    }
-
-    /**
      * Get the name for this split node
      * @return name
      */
@@ -161,67 +111,4 @@
         return compileUnit;
     }
 
-    /**
-     * Set the compile unit for this split node
-     * @param compileUnit compile unit
-     */
-    public void setCompileUnit(final CompileUnit compileUnit) {
-        this.compileUnit = compileUnit;
-    }
-
-    /**
-     * Get the method emitter for this split node
-     * @return method emitter
-     */
-    public MethodEmitter getMethodEmitter() {
-        return method;
-    }
-
-    /**
-     * Set the method emitter for this split node
-     * @param method method emitter
-     */
-    public void setMethodEmitter(final MethodEmitter method) {
-        this.method = method;
-    }
-
-    /**
-     * Get the function node this SplitNode splits
-     * @return function node reference
-     */
-    public FunctionNode getFunctionNode() {
-        return functionNode;
-    }
-
-    /**
-     * Get the external targets for this SplitNode
-     * @return list of external targets
-     */
-    public List<Label> getExternalTargets() {
-        return Collections.unmodifiableList(externalTargets);
-    }
-
-    /**
-     * Add an external target for this SplitNode
-     * @param targetLabel target label
-     */
-    public void addExternalTarget(final Label targetLabel) {
-        externalTargets.add(targetLabel);
-    }
-
-    /**
-     * Check whether this SplitNode returns a value
-     * @return true if return
-     */
-    public boolean hasReturn() {
-        return hasReturn;
-    }
-
-    /**
-     * Set whether this SplitNode returns a value or not
-     * @param hasReturn true if return exists, false otherwise
-     */
-    public void setHasReturn(final boolean hasReturn) {
-        this.hasReturn = hasReturn;
-    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java b/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java
index 23d9c7e..7864a10 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java
@@ -28,73 +28,84 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+
 import jdk.nashorn.internal.codegen.Label;
-import jdk.nashorn.internal.ir.annotations.Ignore;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of a SWITCH statement.
  */
-public class SwitchNode extends BreakableNode {
+@Immutable
+public final class SwitchNode extends BreakableNode {
     /** Switch expression. */
-    private Node expression;
+    private final Node expression;
+
+    /** Switch cases. */
+    private final List<CaseNode> cases;
+
+    /** Switch default index. */
+    private final int defaultCaseIndex;
 
     /** Tag symbol. */
     private Symbol tag;
 
-    /** Switch cases. */
-    private List<CaseNode> cases;
-
-    /** Switch default. */
-    @Ignore //points to one of the members in the list above, don't traverse twice
-    private CaseNode defaultCase;
-
     /**
      * Constructor
      *
-     * @param source  the source
-     * @param token   token
-     * @param finish  finish
+     * @param source      the source
+     * @param token       token
+     * @param finish      finish
+     * @param expression  switch expression
+     * @param cases       cases
+     * @param defaultCase the default case node - null if none, otherwise has to be present in cases list
      */
-    public SwitchNode(final Source source, final long token, final int finish) {
-        super(source, token, finish);
-        this.breakLabel  = new Label("switch_break");
+    public SwitchNode(final Source source, final long token, final int finish, final Node expression, final List<CaseNode> cases, final CaseNode defaultCase) {
+        super(source, token, finish, new Label("switch_break"));
+        this.expression       = expression;
+        this.cases            = cases;
+        this.defaultCaseIndex = defaultCase == null ? -1 : cases.indexOf(defaultCase);
     }
 
-    private SwitchNode(final SwitchNode switchNode, final CopyState cs) {
+    private SwitchNode(final SwitchNode switchNode, final Node expression, final List<CaseNode> cases, final int defaultCase) {
         super(switchNode);
+        this.expression       = expression;
+        this.cases            = cases;
+        this.defaultCaseIndex = defaultCase;
+        this.tag              = switchNode.getTag(); //TODO are symbols inhereted as references?
+    }
 
+    @Override
+    public Node ensureUniqueLabels(final LexicalContext lc) {
         final List<CaseNode> newCases = new ArrayList<>();
-
-        for (final CaseNode caseNode : switchNode.getCases()) {
-           newCases.add((CaseNode)cs.existingOrCopy(caseNode));
+        for (final CaseNode caseNode : cases) {
+            newCases.add(new CaseNode(caseNode, caseNode.getTest(), caseNode.getBody()));
         }
-
-        this.expression  = cs.existingOrCopy(switchNode.getExpression());
-        this.tag         = switchNode.getTag();
-        this.cases       = newCases;
-        this.defaultCase = (CaseNode)cs.existingOrCopy(switchNode.getDefaultCase());
-        this.breakLabel  = new Label(switchNode.getBreakLabel());
+        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, newCases, defaultCaseIndex));
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new SwitchNode(this, cs);
-    }
-
-    @Override
-    public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterSwitchNode(this) != null) {
-            expression = expression.accept(visitor);
-
-            for (int i = 0, count = cases.size(); i < count; i++) {
-                cases.set(i, (CaseNode)cases.get(i).accept(visitor));
+    public boolean isTerminal() {
+        //there must be a default case, and that including all other cases must terminate
+        if (!cases.isEmpty() && defaultCaseIndex != -1) {
+            for (final CaseNode caseNode : cases) {
+                if (!caseNode.isTerminal()) {
+                    return false;
+                }
             }
+            return true;
+        }
+        return false;
 
-            //the default case is in the cases list and should not be explicitly traversed!
+    }
 
-            return visitor.leaveSwitchNode(this);
+    @Override
+    public Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterSwitchNode(this)) {
+            return visitor.leaveSwitchNode(
+                setExpression(visitor.getLexicalContext(), expression.accept(visitor)).
+                setCases(visitor.getLexicalContext(), Node.accept(visitor, CaseNode.class, cases), defaultCaseIndex));
         }
 
         return this;
@@ -108,6 +119,14 @@
     }
 
     /**
+     * Return the case node that is default case
+     * @return default case or null if none
+     */
+    public CaseNode getDefaultCase() {
+        return defaultCaseIndex == -1 ? null : cases.get(defaultCaseIndex);
+    }
+
+    /**
      * Get the cases in this switch
      * @return a list of case nodes
      */
@@ -116,27 +135,33 @@
     }
 
     /**
+     * Replace case nodes with new list. the cases have to be the same
+     * and the default case index the same. This is typically used
+     * by NodeVisitors who perform operations on every case node
+     * @param lc    lexical context
+     * @param cases list of cases
+     * @return new switcy node or same if no state was changed
+     */
+    public SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases) {
+        return setCases(lc, cases, defaultCaseIndex);
+    }
+
+    private SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases, final int defaultCaseIndex) {
+        if (this.cases == cases) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex));
+    }
+
+    /**
      * Set or reset the list of cases in this switch
+     * @param lc lexical context
      * @param cases a list of cases, case nodes
+     * @param defaultCase a case in the list that is the default - must be in the list or class will assert
+     * @return new switch node or same if no state was changed
      */
-    public void setCases(final List<CaseNode> cases) {
-        this.cases = cases;
-    }
-
-    /**
-     * Get the default case for this switch
-     * @return default case node
-     */
-    public CaseNode getDefaultCase() {
-        return defaultCase;
-    }
-
-    /**
-     * Set the default case for this switch
-     * @param defaultCase default case node
-     */
-    public void setDefaultCase(final CaseNode defaultCase) {
-        this.defaultCase = defaultCase;
+    public SwitchNode setCases(final LexicalContext lc, final List<CaseNode> cases, final CaseNode defaultCase) {
+        return setCases(lc, cases, defaultCase == null ? -1 : cases.indexOf(defaultCase));
     }
 
     /**
@@ -149,10 +174,15 @@
 
     /**
      * Set or reset the expression to switch on
+     * @param lc lexical context
      * @param expression switch expression
+     * @return new switch node or same if no state was changed
      */
-    public void setExpression(final Node expression) {
-        this.expression = expression;
+    public SwitchNode setExpression(final LexicalContext lc, final Node expression) {
+        if (this.expression == expression) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new SwitchNode(this, expression, cases, defaultCaseIndex));
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Symbol.java b/nashorn/src/jdk/nashorn/internal/ir/Symbol.java
index 603b8b0..da22f64 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Symbol.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Symbol.java
@@ -29,8 +29,10 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.StringTokenizer;
+
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.Debug;
 import jdk.nashorn.internal.runtime.options.Options;
 
 /**
@@ -63,6 +65,8 @@
     public static final int IS_LET           = 1 << 8;
     /** Is this an internal symbol, never represented explicitly in source code */
     public static final int IS_INTERNAL      = 1 << 9;
+    /** Is this a function self-reference symbol */
+    public static final int IS_FUNCTION_SELF = 1 << 10;
 
     /** Null or name identifying symbol. */
     private final String name;
@@ -70,12 +74,6 @@
     /** Symbol flags. */
     private int flags;
 
-    /** Defining node. */
-    private Node node;
-
-    /** Definition block. */
-    private final Block block;
-
     /** Type of symbol. */
     private Type type;
 
@@ -121,16 +119,12 @@
      *
      * @param name  name of symbol
      * @param flags symbol flags
-     * @param node  node this symbol is in
-     * @param block block this symbol is in
      * @param type  type of this symbol
      * @param slot  bytecode slot for this symbol
      */
-    protected Symbol(final String name, final int flags, final Node node, final Block block, final Type type, final int slot) {
+    protected Symbol(final String name, final int flags, final Type type, final int slot) {
         this.name       = name;
         this.flags      = flags;
-        this.node       = node;
-        this.block      = block;
         this.type       = type;
         this.slot       = slot;
         this.fieldIndex = -1;
@@ -142,11 +136,9 @@
      *
      * @param name  name of symbol
      * @param flags symbol flags
-     * @param node  node this symbol is in
-     * @param block block this symbol is in
      */
-    public Symbol(final String name, final int flags, final Node node, final Block block) {
-        this(name, flags, node, block, Type.UNKNOWN, -1);
+    public Symbol(final String name, final int flags) {
+        this(name, flags, Type.UNKNOWN, -1);
     }
 
     /**
@@ -157,7 +149,7 @@
      * @param type  type of this symbol
      */
     public Symbol(final String name, final int flags, final Type type) {
-        this(name, flags, null, null, type, -1);
+        this(name, flags, type, -1);
     }
 
     private static String align(final String string, final int max) {
@@ -269,20 +261,6 @@
         return type.isCategory2() ? 2 : 1;
     }
 
-    @Override
-    public boolean equals(final Object other) {
-        if (!(other instanceof Symbol)) {
-            return false;
-        }
-        final Symbol symbol = (Symbol) other;
-        return name.equals(symbol.name) && block.equals(symbol.block);
-    }
-
-    @Override
-    public int hashCode() {
-        return name.hashCode() ^ block.hashCode();
-    }
-
     private static String type(final String desc) {
         switch (desc.charAt(desc.length() - 1)) {
         case ';':
@@ -371,14 +349,14 @@
     /**
      * Flag this symbol as scope as described in {@link Symbol#isScope()}
      */
-    public void setIsScope() {
+    /**
+     * Flag this symbol as scope as described in {@link Symbol#isScope()}
+     */
+     public void setIsScope() {
         if (!isScope()) {
             trace("SET IS SCOPE");
         }
         flags |= IS_SCOPE;
-        if(!isGlobal()) {
-            getBlock().setNeedsScope();
-        }
     }
 
     /**
@@ -478,11 +456,11 @@
     }
 
     /**
-     * Get the block in which the symbol is defined
-     * @return a block
+     * Flag this symbol as a function's self-referencing symbol.
+     * @return true if this symbol as a function's self-referencing symbol.
      */
-    public Block getBlock() {
-        return block;
+    public boolean isFunctionSelf() {
+        return (flags & IS_FUNCTION_SELF) == IS_FUNCTION_SELF;
     }
 
     /**
@@ -492,7 +470,7 @@
      * @return field index
      */
     public int getFieldIndex() {
-        assert fieldIndex != -1 : "fieldIndex must be initialized";
+        assert fieldIndex != -1 : "fieldIndex must be initialized " + fieldIndex;
         return fieldIndex;
     }
 
@@ -503,7 +481,6 @@
      * @param fieldIndex field index - a positive integer
      */
     public void setFieldIndex(final int fieldIndex) {
-        assert this.fieldIndex == -1 : "fieldIndex must be initialized only once";
         this.fieldIndex = fieldIndex;
     }
 
@@ -524,22 +501,6 @@
     }
 
     /**
-     * Get the node this symbol stores the result for
-     * @return node
-     */
-    public Node getNode() {
-        return node;
-    }
-
-    /**
-     * Set the node this symbol stores the result for
-     * @param node node
-     */
-    public void setNode(final Node node) {
-        this.node = node;
-    }
-
-    /**
      * Get the name of this symbol
      * @return symbol name
      */
@@ -616,18 +577,25 @@
     }
 
     /**
-     * Check if this symbol is in the global scope, i.e. it is on the outermost level
-     * in the script
-     * @return true if this this is a global scope symbol
+     * From a lexical context, set this symbol as needing scope, which
+     * will set flags for the defining block that will be written when
+     * block is popped from the lexical context stack, used by codegen
+     * when flags need to be tagged, but block is in the
+     * middle of evaluation and cannot be modified.
+     *
+     * @param lc     lexical context
+     * @param symbol symbol
      */
-    public boolean isTopLevel() {
-        return block instanceof FunctionNode && ((FunctionNode) block).isProgram();
+    public static void setSymbolIsScope(final LexicalContext lc, final Symbol symbol) {
+        symbol.setIsScope();
+        if (!symbol.isGlobal()) {
+            lc.setFlag(lc.getDefiningBlock(symbol), Block.NEEDS_SCOPE);
+        }
     }
 
-
     private void trace(final String desc) {
         if (TRACE_SYMBOLS != null && (TRACE_SYMBOLS.isEmpty() || TRACE_SYMBOLS.contains(name))) {
-            Context.err("SYMBOL: '" + name + "' " + desc);
+            Context.err(Debug.id(this) + " SYMBOL: '" + name + "' " + desc);
             if (TRACE_SYMBOLS_STACKTRACE != null && (TRACE_SYMBOLS_STACKTRACE.isEmpty() || TRACE_SYMBOLS_STACKTRACE.contains(name))) {
                 new Throwable().printStackTrace(Context.getCurrentErr());
             }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java b/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java
index de33385..e2ccdb9 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java
@@ -25,15 +25,21 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * TernaryNode nodes represent three operand operations (?:).
  */
-public class TernaryNode extends BinaryNode {
+@Immutable
+public final class TernaryNode extends Node {
+    private final Node lhs;
+
+    private final Node rhs;
+
     /** Third argument. */
-    private Node third;
+    private final Node third;
 
     /**
      * Constructor
@@ -45,43 +51,26 @@
      * @param third  third node
      */
     public TernaryNode(final Source source, final long token, final Node lhs, final Node rhs, final Node third) {
-        super(source, token, lhs, rhs);
-
-        this.finish = third.getFinish();
+        super(source, token, third.getFinish());
+        this.lhs = lhs;
+        this.rhs = rhs;
         this.third = third;
     }
 
-    private TernaryNode(final TernaryNode ternaryNode, final CopyState cs) {
-        super(ternaryNode, cs);
-
-        this.third = cs.existingOrCopy(ternaryNode.third);
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new TernaryNode(this, cs);
-    }
-
-    @Override
-    public boolean equals(final Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-        return third.equals(((TernaryNode)other).third());
-    }
-
-    @Override
-    public int hashCode() {
-        return super.hashCode() ^ third().hashCode();
+    private TernaryNode(final TernaryNode ternaryNode, final Node lhs, final Node rhs, final Node third) {
+        super(ternaryNode);
+        this.lhs = lhs;
+        this.rhs = rhs;
+        this.third = third;
     }
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterTernaryNode(this) != null) {
+        if (visitor.enterTernaryNode(this)) {
             final Node newLhs = lhs().accept(visitor);
             final Node newRhs = rhs().accept(visitor);
             final Node newThird = third.accept(visitor);
-            return visitor.leaveTernaryNode((TernaryNode)setThird(newThird).setLHS(newLhs).setRHS(newRhs));
+            return visitor.leaveTernaryNode(setThird(newThird).setLHS(newLhs).setRHS(newRhs));
         }
 
         return this;
@@ -123,6 +112,22 @@
     }
 
     /**
+     * Get the lhs node for this ternary expression, i.e. "x" in x ? y : z
+     * @return a node
+     */
+    public Node lhs() {
+        return lhs;
+    }
+
+    /**
+     * Get the rhs node for this ternary expression, i.e. "y" in x ? y : z
+     * @return a node
+     */
+    public Node rhs() {
+        return rhs;
+    }
+
+    /**
      * Get the "third" node for this ternary expression, i.e. "z" in x ? y : z
      * @return a node
      */
@@ -131,14 +136,38 @@
     }
 
     /**
+     * Set the left hand side expression for this node
+     * @param lhs new left hand side expression
+     * @return a node equivalent to this one except for the requested change.
+     */
+    public TernaryNode setLHS(final Node lhs) {
+        if (this.lhs == lhs) {
+            return this;
+        }
+        return new TernaryNode(this, lhs, rhs, third);
+    }
+
+    /**
+     * Set the right hand side expression for this node
+     * @param rhs new left hand side expression
+     * @return a node equivalent to this one except for the requested change.
+     */
+    public TernaryNode setRHS(final Node rhs) {
+        if (this.rhs == rhs) {
+            return this;
+        }
+        return new TernaryNode(this, lhs, rhs, third);
+    }
+
+    /**
      * Reset the "third" node for this ternary expression, i.e. "z" in x ? y : z
      * @param third a node
      * @return a node equivalent to this one except for the requested change.
      */
     public TernaryNode setThird(final Node third) {
-        if(this.third == third) return this;
-        final TernaryNode n = (TernaryNode)clone();
-        n.third = third;
-        return n;
+        if (this.third == third) {
+            return this;
+        }
+        return new TernaryNode(this, lhs, rhs, third);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java b/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java
index ab6d59e..7a10a6a 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java
@@ -25,20 +25,17 @@
 
 package jdk.nashorn.internal.ir;
 
-import jdk.nashorn.internal.ir.annotations.Ignore;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for THROW statements.
  */
-public class ThrowNode extends Node {
+@Immutable
+public final class ThrowNode extends Node {
     /** Exception expression. */
-    private Node expression;
-
-    /** Try chain. */
-    @Ignore
-    private final TryNode tryChain;
+    private final Node expression;
 
     /**
      * Constructor
@@ -47,26 +44,21 @@
      * @param token      token
      * @param finish     finish
      * @param expression expression to throw
-     * @param tryChain   surrounding try chain
      */
-    public ThrowNode(final Source source, final long token, final int finish, final Node expression, final TryNode tryChain) {
+    public ThrowNode(final Source source, final long token, final int finish, final Node expression) {
         super(source, token, finish);
 
         this.expression = expression;
-        this.tryChain = tryChain;
-        setIsTerminal(true);
     }
 
-    private ThrowNode(final ThrowNode throwNode, final CopyState cs) {
-        super(throwNode);
-
-        this.expression = cs.existingOrCopy(throwNode.expression);
-        this.tryChain = (TryNode)cs.existingOrSame(throwNode.tryChain);
+    private ThrowNode(final Node node, final Node expression) {
+        super(node);
+        this.expression = expression;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new ThrowNode(this, cs);
+    public boolean isTerminal() {
+        return true;
     }
 
     /**
@@ -75,9 +67,8 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterThrowNode(this) != null) {
-            setExpression(expression.accept(visitor));
-            return visitor.leaveThrowNode(this);
+        if (visitor.enterThrowNode(this)) {
+            return visitor.leaveThrowNode(setExpression(expression.accept(visitor)));
         }
 
         return this;
@@ -103,16 +94,13 @@
     /**
      * Reset the expression being thrown by this node
      * @param expression new expression
+     * @return new or same thrownode
      */
-    public void setExpression(final Node expression) {
-        this.expression = expression;
+    public ThrowNode setExpression(final Node expression) {
+        if (this.expression == expression) {
+            return this;
+        }
+        return new ThrowNode(this, expression);
     }
 
-    /**
-     * Get surrounding tryChain for this node
-     * @return try chain
-     */
-    public TryNode getTryChain() {
-        return tryChain;
-    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/TryNode.java b/nashorn/src/jdk/nashorn/internal/ir/TryNode.java
index 7d3864b..5e3ff7a 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/TryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/TryNode.java
@@ -28,30 +28,28 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+
 import jdk.nashorn.internal.codegen.Label;
-import jdk.nashorn.internal.ir.annotations.Ignore;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation of a TRY statement.
  */
-public class TryNode extends Node {
-    /** Try chain. */
-    @Ignore //don't print, will be apparent from the AST
-    private TryNode next;
-
+@Immutable
+public final class TryNode extends Node {
     /** Try statements. */
-    private Block body;
+    private final Block body;
 
     /** List of catch clauses. */
-    private List<Block> catchBlocks;
+    private final List<Block> catchBlocks;
 
     /** Finally clause. */
-    private Block finallyBody;
+    private final Block finallyBody;
 
     /** Exit label. */
-    private Label exit;
+    private final Label exit;
 
     /** Exception symbol. */
     private Symbol exception;
@@ -62,37 +60,46 @@
     /**
      * Constructor
      *
-     * @param source  the source
-     * @param token   token
-     * @param finish  finish
-     * @param next    next try node in chain
+     * @param source      the source
+     * @param token       token
+     * @param finish      finish
+     * @param body        try node body
+     * @param catchBlocks list of catch blocks in order
+     * @param finallyBody body of finally block or null if none
      */
-    public TryNode(final Source source, final long token, final int finish, final TryNode next) {
+    public TryNode(final Source source, final long token, final int finish, final Block body, final List<Block> catchBlocks, final Block finallyBody) {
         super(source, token, finish);
-
-        this.next = next;
+        this.body = body;
+        this.catchBlocks = catchBlocks;
+        this.finallyBody = finallyBody;
         this.exit = new Label("exit");
     }
 
-    private TryNode(final TryNode tryNode, final CopyState cs) {
+    private TryNode(final TryNode tryNode, final Block body, final List<Block> catchBlocks, final Block finallyBody) {
         super(tryNode);
-
-        final List<Block> newCatchBlocks = new ArrayList<>();
-
-        for (final Block block : tryNode.catchBlocks) {
-            newCatchBlocks.add((Block)cs.existingOrCopy(block));
-        }
-
-        this.next        = (TryNode)cs.existingOrSame(tryNode.getNext());
-        this.body        = (Block)cs.existingOrCopy(tryNode.getBody());
-        this.catchBlocks = newCatchBlocks;
-        this.finallyBody = (Block)cs.existingOrCopy(tryNode.getFinallyBody());
-        this.exit        = new Label(tryNode.getExit());
+        this.body = body;
+        this.catchBlocks = catchBlocks;
+        this.finallyBody = finallyBody;
+        this.exit = new Label(tryNode.exit);
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new TryNode(this, cs);
+    public Node ensureUniqueLabels(final LexicalContext lc) {
+        //try nodes are never in lex context
+        return new TryNode(this, body, catchBlocks, finallyBody);
+    }
+
+    @Override
+    public boolean isTerminal() {
+        if (body.isTerminal()) {
+            for (final Block catchBlock : getCatchBlocks()) {
+                if (!catchBlock.isTerminal()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
     }
 
     /**
@@ -101,21 +108,16 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterTryNode(this) != null) {
-            // Need to do first for termination analysis.
-            if (finallyBody != null) {
-                finallyBody = (Block)finallyBody.accept(visitor);
-            }
-
-            body = (Block)body.accept(visitor);
-
-            final List<Block> newCatchBlocks = new ArrayList<>(catchBlocks.size());
-            for (final Block catchBlock : catchBlocks) {
-                newCatchBlocks.add((Block)catchBlock.accept(visitor));
-            }
-            this.catchBlocks = newCatchBlocks;
-
-            return visitor.leaveTryNode(this);
+        if (visitor.enterTryNode(this)) {
+            // Need to do finallybody first for termination analysis. TODO still necessary?
+            final Block newFinallyBody = finallyBody == null ? null : (Block)finallyBody.accept(visitor);
+            final Block newBody        = (Block)body.accept(visitor);
+            return visitor.leaveTryNode(
+                setBody(newBody).
+                setFinallyBody(newFinallyBody).
+                setCatchBlocks(Node.accept(visitor, Block.class, catchBlocks)).
+                setException(exception).
+                setFinallyCatchAll(finallyCatchAll));
         }
 
         return this;
@@ -123,7 +125,7 @@
 
     @Override
     public void toString(final StringBuilder sb) {
-        sb.append("try");
+        sb.append("try ");
     }
 
     /**
@@ -137,9 +139,13 @@
     /**
      * Reset the body of this try block
      * @param body new body
+     * @return new TryNode or same if unchanged
      */
-    public void setBody(final Block body) {
-        this.body = body;
+    public TryNode setBody(final Block body) {
+        if (this.body == body) {
+            return this;
+        }
+        return new TryNode(this,  body, catchBlocks, finallyBody);
     }
 
     /**
@@ -151,16 +157,7 @@
         for (final Block catchBlock : catchBlocks) {
             catches.add((CatchNode)catchBlock.getStatements().get(0));
         }
-        return catches;
-    }
-
-    /**
-     * Returns true if the specified block is the body of this try block, or any of its catch blocks.
-     * @param block the block
-     * @return true if the specified block is the body of this try block, or any of its catch blocks.
-     */
-    public boolean isChildBlock(Block block) {
-        return body == block || catchBlocks.contains(block);
+        return Collections.unmodifiableList(catches);
     }
 
     /**
@@ -174,9 +171,13 @@
     /**
      * Set the catch blocks of this try
      * @param catchBlocks list of catch blocks
+     * @return new TryNode or same if unchanged
      */
-    public void setCatchBlocks(final List<Block> catchBlocks) {
-        this.catchBlocks = catchBlocks;
+    public TryNode setCatchBlocks(final List<Block> catchBlocks) {
+        if (this.catchBlocks == catchBlocks) {
+            return this;
+        }
+        return new TryNode(this, body, catchBlocks, finallyBody);
     }
 
     /**
@@ -190,9 +191,11 @@
     /**
      * Set the exception symbol for this try block
      * @param exception a symbol for the compiler to store the exception in
+     * @return new TryNode or same if unchanged
      */
-    public void setException(final Symbol exception) {
+    public TryNode setException(final Symbol exception) {
         this.exception = exception;
+        return this;
     }
 
     /**
@@ -207,9 +210,13 @@
      * If a finally block exists, the synthetic catchall needs another symbol to
      * store its throwable
      * @param finallyCatchAll a symbol for the finally catch all exception
+     * @return new TryNode or same if unchanged
+     *
+     * TODO can this still be stateful?
      */
-    public void setFinallyCatchAll(final Symbol finallyCatchAll) {
+    public TryNode setFinallyCatchAll(final Symbol finallyCatchAll) {
         this.finallyCatchAll = finallyCatchAll;
+        return this;
     }
 
     /**
@@ -221,14 +228,6 @@
     }
 
     /**
-     * Set the exit label for this try block
-     * @param exit label
-     */
-    public void setExit(final Label exit) {
-        this.exit = exit;
-    }
-
-    /**
      * Get the body of the finally clause for this try
      * @return finally body, or null if no finally
      */
@@ -239,24 +238,12 @@
     /**
      * Set the finally body of this try
      * @param finallyBody new finally body
+     * @return new TryNode or same if unchanged
      */
-    public void setFinallyBody(final Block finallyBody) {
-        this.finallyBody = finallyBody;
-    }
-
-    /**
-     * Get next try node in try chain
-     * @return next try node
-     */
-    public TryNode getNext() {
-        return next;
-    }
-
-    /**
-     * Set next try node in try chain
-     * @param next next try node
-     */
-    public void setNext(final TryNode next) {
-        this.next = next;
+    public TryNode setFinallyBody(final Block finallyBody) {
+        if (this.finallyBody == finallyBody) {
+            return this;
+        }
+        return new TryNode(this, body, catchBlocks, finallyBody);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java b/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java
index d823c05..fed5e40 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java
@@ -31,6 +31,7 @@
 import static jdk.nashorn.internal.parser.TokenType.INCPOSTFIX;
 
 import jdk.nashorn.internal.codegen.types.Type;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.Token;
 import jdk.nashorn.internal.parser.TokenType;
@@ -39,9 +40,10 @@
 /**
  * UnaryNode nodes represent single operand operations.
  */
-public class UnaryNode extends Node implements Assignment<Node> {
+@Immutable
+public final class UnaryNode extends Node implements Assignment<Node> {
     /** Right hand side argument. */
-    private Node rhs;
+    private final Node rhs;
 
     /**
      * Constructor
@@ -51,23 +53,26 @@
      * @param rhs    expression
      */
     public UnaryNode(final Source source, final long token, final Node rhs) {
-        super(source, token, Token.descPosition(token));
-
-        this.start  = Math.min(rhs.getStart(), Token.descPosition(token));
-        this.finish = Math.max(Token.descPosition(token) + Token.descLength(token), rhs.getFinish());
-        this.rhs    = rhs;
+        this(source, token, Math.min(rhs.getStart(), Token.descPosition(token)), Math.max(Token.descPosition(token) + Token.descLength(token), rhs.getFinish()), rhs);
     }
 
     /**
-     * Copy constructor
-     *
-     * @param unaryNode source node
-     * @param cs        copy state
+     * Constructor
+     * @param source the source
+     * @param token  token
+     * @param start  start
+     * @param finish finish
+     * @param rhs    expression
      */
-    protected UnaryNode(final UnaryNode unaryNode, final CopyState cs) {
-        super(unaryNode);
+    public UnaryNode(final Source source, final long token, final int start, final int finish, final Node rhs) {
+        super(source, token, start, finish);
+        this.rhs = rhs;
+    }
 
-        this.rhs = cs.existingOrCopy(unaryNode.rhs);
+
+    private UnaryNode(final UnaryNode unaryNode, final Node rhs) {
+        super(unaryNode);
+        this.rhs = rhs;
     }
 
     /**
@@ -113,31 +118,13 @@
         return getAssignmentDest();
     }
 
-    @Override
-    public boolean equals(final Object other) {
-        if (!super.equals(other)) {
-            return false;
-        }
-        return rhs.equals(((UnaryNode)other).rhs());
-    }
-
-    @Override
-    public int hashCode() {
-        return super.hashCode() ^ rhs().hashCode();
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new UnaryNode(this, cs);
-    }
-
     /**
      * Assist in IR navigation.
      * @param visitor IR navigating visitor.
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterUnaryNode(this) != null) {
+        if (visitor.enterUnaryNode(this)) {
             return visitor.leaveUnaryNode(setRHS(rhs.accept(visitor)));
         }
 
@@ -219,9 +206,9 @@
      * @return a node equivalent to this one except for the requested change.
      */
     public UnaryNode setRHS(final Node rhs) {
-        if(this.rhs == rhs) return this;
-        final UnaryNode n = (UnaryNode)clone();
-        n.rhs = rhs;
-        return n;
+        if (this.rhs == rhs) {
+            return this;
+        }
+        return new UnaryNode(this, rhs);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/VarNode.java b/nashorn/src/jdk/nashorn/internal/ir/VarNode.java
index b719c99..fbc3eab 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/VarNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/VarNode.java
@@ -25,21 +25,31 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * Node represents a var/let declaration.
  */
-public class VarNode extends Node implements Assignment<IdentNode> {
+@Immutable
+public final class VarNode extends Node implements Assignment<IdentNode> {
     /** Var name. */
-    private IdentNode name;
+    private final IdentNode name;
 
     /** Initialization expression. */
-    private Node init;
+    private final Node init;
 
     /** Is this a var statement (as opposed to a "var" in a for loop statement) */
-    private final boolean isStatement;
+    private final int flags;
+
+    /** Flag that determines if this function node is a statement */
+    public static final int IS_STATEMENT = 1 << 0;
+
+    /** Flag that determines if this is the last function declaration in a function
+     *  This is used to micro optimize the placement of return value assignments for
+     *  a program node */
+    public static final int IS_LAST_FUNCTION_DECLARATION = 1 << 1;
 
     /**
      * Constructor
@@ -51,7 +61,14 @@
      * @param init   init node or null if just a declaration
      */
     public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init) {
-        this(source, token, finish, name, init, true);
+        this(source, token, finish, name, init, IS_STATEMENT);
+    }
+
+    private VarNode(final VarNode varNode, final IdentNode name, final Node init, final int flags) {
+        super(varNode);
+        this.name = init == null ? name : name.setIsInitializedHere();
+        this.init = init;
+        this.flags = flags;
     }
 
     /**
@@ -62,28 +79,14 @@
      * @param finish finish
      * @param name   name of variable
      * @param init   init node or null if just a declaration
-     * @param isStatement if this is a var statement (true), or a for-loop initializer (false)
+     * @param flags  flags
      */
-    public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init, boolean isStatement) {
+    public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init, final int flags) {
         super(source, token, finish);
 
         this.name  = init == null ? name : name.setIsInitializedHere();
         this.init  = init;
-        this.isStatement = isStatement;
-    }
-
-
-    private VarNode(final VarNode varNode, final CopyState cs) {
-        super(varNode);
-
-        this.name = (IdentNode)cs.existingOrCopy(varNode.name);
-        this.init = cs.existingOrCopy(varNode.init);
-        this.isStatement = varNode.isStatement;
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new VarNode(this, cs);
+        this.flags = flags;
     }
 
     @Override
@@ -115,45 +118,17 @@
     }
 
     /**
-     * Test to see if two VarNodes are the same.
-     * @param other Other VarNode.
-     * @return True if the VarNodes are the same.
-     */
-    @Override
-    public boolean equals(final Object other) {
-        if (other instanceof VarNode) {
-            final VarNode otherNode    = (VarNode)other;
-            final boolean nameMatches  = name.equals(otherNode.name);
-            if (hasInit() != otherNode.hasInit()) {
-                return false;
-            } else if (init == null) {
-                return nameMatches;
-            } else {
-                return nameMatches && init.equals(otherNode.init);
-            }
-        }
-        return false;
-    }
-
-    @Override
-    public int hashCode() {
-        return name.hashCode() ^ (init == null ? 0 : init.hashCode());
-    }
-
-    /**
      * Assist in IR navigation.
      * @param visitor IR navigating visitor.
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterVarNode(this) != null) {
+        if (visitor.enterVarNode(this)) {
             final IdentNode newName = (IdentNode)name.accept(visitor);
-            final Node newInit = init == null ? null : init.accept(visitor);
-            final VarNode newThis;
-            if(name != newName || init != newInit) {
-                newThis = (VarNode)clone();
-                newThis.init = newInit;
-                newThis.name = newInit == null ? newName : newName.setIsInitializedHere();
+            final Node      newInit = init == null ? null : init.accept(visitor);
+            final VarNode   newThis;
+            if (name != newName || init != newInit) {
+                newThis = new VarNode(this, newName, newInit, flags);
             } else {
                 newThis = this;
             }
@@ -187,10 +162,10 @@
      * @return a node equivalent to this one except for the requested change.
      */
     public VarNode setInit(final Node init) {
-        if(this.init == init) return this;
-        final VarNode n = (VarNode)clone();
-        n.init = init;
-        return n;
+        if (this.init == init) {
+            return this;
+        }
+        return new VarNode(this, name, init, flags);
     }
 
     /**
@@ -204,12 +179,38 @@
     /**
      * Reset the identifier for this VarNode
      * @param name new IdentNode representing the variable being set or declared
+     * @return a node equivalent to this one except for the requested change.
      */
-    private VarNode setName(final IdentNode name) {
-        if(this.name == name) return this;
-        final VarNode n = (VarNode)clone();
-        n.name = name;
-        return n;
+    public VarNode setName(final IdentNode name) {
+        if (this.name == name) {
+            return this;
+        }
+        return new VarNode(this, name, init, flags);
+    }
+
+    private VarNode setFlags(final int flags) {
+        if (this.flags == flags) {
+            return this;
+        }
+        return new VarNode(this, name, init, flags);
+    }
+
+    /**
+     * Check if a flag is set for this var node
+     * @param flag flag
+     * @return true if flag is set
+     */
+    public boolean getFlag(final int flag) {
+        return (flags & flag) == flag;
+    }
+
+    /**
+     * Set a flag for this var node
+     * @param flag flag
+     * @return new node if flags changed, same otherwise
+     */
+    public VarNode setFlag(final int flag) {
+        return setFlags(flags | flag);
     }
 
     /**
@@ -217,7 +218,7 @@
      * @return true if this is a var statement (as opposed to a var initializer in a for loop).
      */
     public boolean isStatement() {
-        return isStatement;
+        return (flags & IS_STATEMENT) != 0;
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java b/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java
index 8db31c0..438be01 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java
@@ -25,7 +25,7 @@
 
 package jdk.nashorn.internal.ir;
 
-import jdk.nashorn.internal.codegen.Label;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
@@ -33,130 +33,126 @@
  * IR representation for a WHILE statement. This is the superclass of all
  * loop nodes
  */
-public class WhileNode extends BreakableNode {
-    /** Test expression. */
-    protected Node test;
+@Immutable
+public final class WhileNode extends LoopNode {
 
-    /** For body. */
-    protected Block body;
-
-    /** loop continue label. */
-    protected Label continueLabel;
+    /** is this a do while node ? */
+    private final boolean isDoWhile;
 
     /**
      * Constructor
      *
-     * @param source  the source
-     * @param token   token
-     * @param finish  finish
+     * @param source    the source
+     * @param token     token
+     * @param finish    finish
+     * @param isDoWhile is this a do while loop?
      */
-    public WhileNode(final Source source, final long token, final int finish) {
-        super(source, token, finish);
-
-        this.breakLabel    = new Label("while_break");
-        this.continueLabel = new Label("while_continue");
+    public WhileNode(final Source source, final long token, final int finish, final boolean isDoWhile) {
+        super(source, token, finish, null, null, false);
+        this.isDoWhile = isDoWhile;
     }
 
     /**
-     * Copy constructor
+     * Internal copy constructor
      *
-     * @param whileNode source node
-     * @param cs        copy state
+     * @param whileNode while node
+     * @param test      test
+     * @param body      body
+     * @param controlFlowEscapes control flow escapes?
      */
-    protected WhileNode(final WhileNode whileNode, final CopyState cs) {
-        super(whileNode);
-
-        this.test          = cs.existingOrCopy(whileNode.test);
-        this.body          = (Block)cs.existingOrCopy(whileNode.body);
-        this.breakLabel    = new Label(whileNode.breakLabel);
-        this.continueLabel = new Label(whileNode.continueLabel);
+    protected WhileNode(final WhileNode whileNode, final Node test, final Block body, final boolean controlFlowEscapes) {
+        super(whileNode, test, body, controlFlowEscapes);
+        this.isDoWhile = whileNode.isDoWhile;
     }
 
     @Override
-    protected Node copy(final CopyState cs) {
-        return new WhileNode(this, cs);
+    public Node ensureUniqueLabels(final LexicalContext lc) {
+        return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
     }
 
     @Override
-    public boolean isLoop() {
-        return true;
+    public boolean hasGoto() {
+        return test == null;
     }
 
-    /**
-     * Assist in IR navigation.
-     * @param visitor IR navigating visitor.
-     */
     @Override
-    public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterWhileNode(this) != null) {
-            test = test.accept(visitor);
-            body = (Block)body.accept(visitor);
+    protected Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterWhileNode(this)) {
+            if (isDoWhile()) {
+                return visitor.leaveWhileNode(
+                        setTest(lc, test.accept(visitor)).
+                        setBody(lc, (Block)body.accept(visitor)));
+            }
+            return visitor.leaveWhileNode(
+                    setBody(lc, (Block)body.accept(visitor)).
+                    setTest(lc, test.accept(visitor)));
 
-            return visitor.leaveWhileNode(this);
         }
         return this;
     }
 
     @Override
-    public void toString(final StringBuilder sb) {
-        sb.append("while (");
-        test.toString(sb);
-        sb.append(')');
-    }
-
-    /**
-     * Get the loop body
-     * @return body
-     */
-    public Block getBody() {
-        return body;
-    }
-
-    /**
-     * Reset the loop body
-     * @param body new body
-     */
-    public void setBody(final Block body) {
-        this.body = body;
-    }
-
-    /**
-     * Set the break label (described in {@link WhileNode#getBreakLabel()} for this while node
-     * @param breakLabel break label
-     */
-    public void setBreakLabel(final Label breakLabel) {
-        this.breakLabel = breakLabel;
-    }
-
-    /**
-     * Get the continue label for this while node, i.e. location to go to on continue
-     * @return continue label
-     */
-    public Label getContinueLabel() {
-        return continueLabel;
-    }
-
-    /**
-     * Set the continue label (described in {@link WhileNode#getContinueLabel()} for this while node
-     * @param continueLabel continue label
-     */
-    public void setContinueLabel(final Label continueLabel) {
-        this.continueLabel = continueLabel;
-    }
-
-    /**
-     * Get the test expression for this loop, that upon evaluation to true does another iteration
-     * @return test expression
-     */
     public Node getTest() {
         return test;
     }
 
+    @Override
+    public WhileNode setTest(final LexicalContext lc, final Node test) {
+        if (this.test == test) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
+    }
+
+    @Override
+    public Block getBody() {
+        return body;
+    }
+
+    @Override
+    public WhileNode setBody(final LexicalContext lc, final Block body) {
+        if (this.body == body) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
+    }
+
+    @Override
+    public WhileNode setControlFlowEscapes(final LexicalContext lc, final boolean controlFlowEscapes) {
+        if (this.controlFlowEscapes == controlFlowEscapes) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new WhileNode(this, test, body, controlFlowEscapes));
+    }
+
     /**
-     * Set the test expression for this loop
-     * @param test test expression, null if infinite loop
+     * Check if this is a do while loop or a normal while loop
+     * @return true if do while
      */
-    public void setTest(final Node test) {
-        this.test = test;
+    public boolean isDoWhile() {
+        return isDoWhile;
+    }
+
+    @Override
+    public void toString(final StringBuilder sb) {
+        if (isDoWhile()) {
+            sb.append("do {");
+            body.toString(sb);
+            sb.append("} while (");
+            test.toString(sb);
+            sb.append(')');
+        } else {
+            sb.append("while (");
+            test.toString(sb);
+            sb.append(')');
+        }
+    }
+
+    @Override
+    public boolean mustEnter() {
+        if (isDoWhile()) {
+            return true;
+        }
+        return test == null;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/WithNode.java b/nashorn/src/jdk/nashorn/internal/ir/WithNode.java
index f5ad3b1..5ebbfd5 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/WithNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/WithNode.java
@@ -25,18 +25,20 @@
 
 package jdk.nashorn.internal.ir;
 
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for {@code with} statements.
  */
-public class WithNode extends Node {
+@Immutable
+public final class WithNode extends LexicalContextNode {
    /** This expression. */
-    private Node expression;
+    private final Node expression;
 
     /** Statements. */
-    private Block body;
+    private final Block body;
 
     /**
      * Constructor
@@ -44,45 +46,42 @@
      * @param source     the source
      * @param token      token
      * @param finish     finish
-     * @param expression expression in parenthesis
-     * @param body       with node body
      */
-    public WithNode(final Source source, final long token, final int finish, final Node expression, final Block body) {
+    public WithNode(final Source source, final long token, final int finish) {
         super(source, token, finish);
 
+        this.expression = null;
+        this.body       = null;
+    }
+
+    private WithNode(final WithNode node, final Node expression, final Block body) {
+        super(node);
+
         this.expression = expression;
         this.body       = body;
     }
 
-    private WithNode(final WithNode withNode, final CopyState cs) {
-        super(withNode);
-
-        this.expression = cs.existingOrCopy(withNode.expression);
-        this.body       = (Block)cs.existingOrCopy(withNode.body);
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new WithNode(this, cs);
-    }
-
     /**
      * Assist in IR navigation.
      *
      * @param visitor IR navigating visitor.
      */
     @Override
-    public Node accept(final NodeVisitor visitor) {
-        if (visitor.enterWithNode(this) != null) {
-            expression = expression.accept(visitor);
-            body = (Block)body.accept(visitor);
-            return visitor.leaveWithNode(this);
+    public Node accept(final LexicalContext lc, final NodeVisitor visitor) {
+        if (visitor.enterWithNode(this)) {
+             return visitor.leaveWithNode(
+                setExpression(lc, expression.accept(visitor)).
+                setBody(lc, (Block)body.accept(visitor)));
         }
-
         return this;
     }
 
     @Override
+    public boolean isTerminal() {
+        return body.isTerminal();
+    }
+
+    @Override
     public void toString(final StringBuilder sb) {
         sb.append("with (");
         expression.toString(sb);
@@ -99,10 +98,15 @@
 
     /**
      * Reset the body of this with node
+     * @param lc lexical context
      * @param body new body
+     * @return new or same withnode
      */
-    public void setBody(final Block body) {
-        this.body = body;
+    public WithNode setBody(final LexicalContext lc, final Block body) {
+        if (this.body == body) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new WithNode(this, expression, body));
     }
 
     /**
@@ -115,10 +119,15 @@
 
     /**
      * Reset the expression of this with node
+     * @param lc lexical context
      * @param expression new expression
+     * @return new or same withnode
      */
-    public void setExpression(final Node expression) {
-        this.expression = expression;
+    public WithNode setExpression(final LexicalContext lc, final Node expression) {
+        if (this.expression == expression) {
+            return this;
+        }
+        return Node.replaceInLexicalContext(lc, this, new WithNode(this, expression, body));
     }
 }
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/annotations/Immutable.java b/nashorn/src/jdk/nashorn/internal/ir/annotations/Immutable.java
new file mode 100644
index 0000000..663abf7
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/ir/annotations/Immutable.java
@@ -0,0 +1,34 @@
+/*
+ * 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.ir.annotations;
+
+/**
+ * Tag for nodes that are immutable. To be immutable all fields must be
+ * final and copy on write semantics must be in place
+ */
+public @interface Immutable {
+    //empty
+}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java
index 7bbe383..5d9b5df 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java
@@ -33,7 +33,9 @@
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
+
 import jdk.nashorn.internal.ir.BinaryNode;
+import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.TernaryNode;
 import jdk.nashorn.internal.ir.annotations.Ignore;
@@ -113,6 +115,10 @@
             type += "#" + node.getSymbol();
         }
 
+        if (node instanceof Block && ((Block)node).needsScope()) {
+            type += " <scope>";
+        }
+
         final List<Field> children = new LinkedList<>();
 
         if (!isReference) {
@@ -121,10 +127,6 @@
 
         String status = "";
 
-        if (node.shouldDiscard()) {
-            status += " Discard";
-        }
-
         if (node.isTerminal()) {
             status += " Terminal";
         }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
index a8c3c4a..988b756 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
@@ -36,7 +36,6 @@
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.EmptyNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
@@ -88,7 +87,7 @@
         final Parser       parser     = new Parser(env, new Source(name, code), new Context.ThrowErrorManager(), env._strict);
         final JSONWriter   jsonWriter = new JSONWriter(includeLoc);
         try {
-            final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.tag());
+            final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.symbolName());
             functionNode.accept(jsonWriter);
             return jsonWriter.getString();
         } catch (final ParserException e) {
@@ -98,11 +97,16 @@
     }
 
     @Override
-    protected Node enterDefault(final Node node) {
+    protected boolean enterDefault(final Node node) {
         objectStart();
         location(node);
 
-        return node;
+        return true;
+    }
+
+    private boolean leave() {
+        objectEnd();
+        return false;
     }
 
     @Override
@@ -112,7 +116,7 @@
     }
 
     @Override
-    public Node enterAccessNode(final AccessNode accessNode) {
+    public boolean enterAccessNode(final AccessNode accessNode) {
         enterDefault(accessNode);
 
         type("MemberExpression");
@@ -128,11 +132,11 @@
 
         property("computed", false);
 
-        return leaveDefault(accessNode);
+        return leave();
     }
 
     @Override
-    public Node enterBlock(final Block block) {
+    public boolean enterBlock(final Block block) {
         enterDefault(block);
 
         type("BlockStatement");
@@ -140,21 +144,21 @@
 
         array("body", block.getStatements());
 
-        return leaveDefault(block);
+        return leave();
     }
 
     private static boolean isLogical(final TokenType tt) {
         switch (tt) {
-            case AND:
-            case OR:
-                return true;
-            default:
-                return false;
+        case AND:
+        case OR:
+            return true;
+        default:
+            return false;
         }
     }
 
     @Override
-    public Node enterBinaryNode(final BinaryNode binaryNode) {
+    public boolean enterBinaryNode(final BinaryNode binaryNode) {
         enterDefault(binaryNode);
 
         final String name;
@@ -179,29 +183,29 @@
         property("right");
         binaryNode.rhs().accept(this);
 
-        return leaveDefault(binaryNode);
+        return leave();
     }
 
     @Override
-    public Node enterBreakNode(final BreakNode breakNode) {
+    public boolean enterBreakNode(final BreakNode breakNode) {
         enterDefault(breakNode);
 
         type("BreakStatement");
         comma();
 
-        final LabelNode label = breakNode.getLabel();
+        final IdentNode label = breakNode.getLabel();
         if (label != null) {
-            property("label", label.getLabel().getName());
+            property("label", label.getName());
         } else {
             property("label");
             nullValue();
         }
 
-        return leaveDefault(breakNode);
+        return leave();
     }
 
     @Override
-    public Node enterCallNode(final CallNode callNode) {
+    public boolean enterCallNode(final CallNode callNode) {
         enterDefault(callNode);
 
         type("CallExpression");
@@ -213,11 +217,11 @@
 
         array("arguments", callNode.getArgs());
 
-        return leaveDefault(callNode);
+        return leave();
     }
 
     @Override
-    public Node enterCaseNode(final CaseNode caseNode) {
+    public boolean enterCaseNode(final CaseNode caseNode) {
         enterDefault(caseNode);
 
         type("SwitchCase");
@@ -234,11 +238,11 @@
 
         array("consequent", caseNode.getBody().getStatements());
 
-        return leaveDefault(caseNode);
+        return leave();
     }
 
     @Override
-    public Node enterCatchNode(final CatchNode catchNode) {
+    public boolean enterCatchNode(final CatchNode catchNode) {
         enterDefault(catchNode);
 
         type("CatchClause");
@@ -260,55 +264,38 @@
         property("body");
         catchNode.getBody().accept(this);
 
-        return leaveDefault(catchNode);
+        return leave();
     }
 
     @Override
-    public Node enterContinueNode(final ContinueNode continueNode) {
+    public boolean enterContinueNode(final ContinueNode continueNode) {
         enterDefault(continueNode);
 
         type("ContinueStatement");
         comma();
 
-        final LabelNode label = continueNode.getLabel();
+        final IdentNode label = continueNode.getLabel();
         if (label != null) {
-            property("label", label.getLabel().getName());
+            property("label", label.getName());
         } else {
             property("label");
             nullValue();
         }
 
-        return leaveDefault(continueNode);
+        return leave();
     }
 
     @Override
-    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
-        enterDefault(doWhileNode);
-
-        type("DoWhileStatement");
-        comma();
-
-        property("body");
-        doWhileNode.getBody().accept(this);
-        comma();
-
-        property("test");
-        doWhileNode.getTest().accept(this);
-
-        return leaveDefault(doWhileNode);
-    }
-
-    @Override
-    public Node enterEmptyNode(final EmptyNode emptyNode) {
+    public boolean enterEmptyNode(final EmptyNode emptyNode) {
         enterDefault(emptyNode);
 
         type("EmptyStatement");
 
-        return leaveDefault(emptyNode);
+        return leave();
     }
 
     @Override
-    public Node enterExecuteNode(final ExecuteNode executeNode) {
+    public boolean enterExecuteNode(final ExecuteNode executeNode) {
         enterDefault(executeNode);
 
         type("ExpressionStatement");
@@ -317,11 +304,11 @@
         property("expression");
         executeNode.getExpression().accept(this);
 
-        return leaveDefault(executeNode);
+        return leave();
     }
 
     @Override
-    public Node enterForNode(final ForNode forNode) {
+    public boolean enterForNode(final ForNode forNode) {
         enterDefault(forNode);
 
         if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
@@ -380,11 +367,11 @@
             forNode.getBody().accept(this);
         }
 
-        return leaveDefault(forNode);
+        return leave();
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
         enterDefault(functionNode);
 
         final boolean program = functionNode.isProgram();
@@ -419,7 +406,7 @@
         }
 
         // body consists of nested functions and statements
-        final List<Node> stats = functionNode.getStatements();
+        final List<Node> stats = functionNode.getBody().getStatements();
         final int size = stats.size();
         int idx = 0;
         arrayStart("body");
@@ -435,11 +422,11 @@
         }
         arrayEnd();
 
-        return leaveDefault(functionNode);
+        return leave();
     }
 
     @Override
-    public Node enterIdentNode(final IdentNode identNode) {
+    public boolean enterIdentNode(final IdentNode identNode) {
         enterDefault(identNode);
 
         final String name = identNode.getName();
@@ -451,11 +438,11 @@
             property("name", identNode.getName());
         }
 
-        return leaveDefault(identNode);
+        return leave();
     }
 
     @Override
-    public Node enterIfNode(final IfNode ifNode) {
+    public boolean enterIfNode(final IfNode ifNode) {
         enterDefault(ifNode);
 
         type("IfStatement");
@@ -477,11 +464,11 @@
             nullValue();
         }
 
-        return leaveDefault(ifNode);
+        return leave();
     }
 
     @Override
-    public Node enterIndexNode(final IndexNode indexNode) {
+    public boolean enterIndexNode(final IndexNode indexNode) {
         enterDefault(indexNode);
 
         type("MemberExpression");
@@ -497,11 +484,11 @@
 
         property("computed", true);
 
-        return leaveDefault(indexNode);
+        return leave();
     }
 
     @Override
-    public Node enterLabelNode(final LabelNode labelNode) {
+    public boolean enterLabelNode(final LabelNode labelNode) {
         enterDefault(labelNode);
 
         type("LabeledStatement");
@@ -514,17 +501,17 @@
         property("body");
         labelNode.getBody().accept(this);
 
-        return leaveDefault(labelNode);
+        return leave();
     }
 
     @Override
-    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
-        return null;
+    public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) {
+        return false;
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enterLiteralNode(final LiteralNode literalNode) {
+    public boolean enterLiteralNode(final LiteralNode literalNode) {
         enterDefault(literalNode);
 
         if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
@@ -556,11 +543,11 @@
             }
         }
 
-        return leaveDefault(literalNode);
+        return leave();
     }
 
     @Override
-    public Node enterObjectNode(final ObjectNode objectNode) {
+    public boolean enterObjectNode(final ObjectNode objectNode) {
         enterDefault(objectNode);
 
         type("ObjectExpression");
@@ -568,11 +555,11 @@
 
         array("properties", objectNode.getElements());
 
-        return leaveDefault(objectNode);
+        return leave();
     }
 
     @Override
-    public Node enterPropertyNode(final PropertyNode propertyNode) {
+    public boolean enterPropertyNode(final PropertyNode propertyNode) {
         final Node key = propertyNode.getKey();
 
         final Node value = propertyNode.getValue();
@@ -634,11 +621,11 @@
             }
         }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterReturnNode(final ReturnNode returnNode) {
+    public boolean enterReturnNode(final ReturnNode returnNode) {
         enterDefault(returnNode);
 
         type("ReturnStatement");
@@ -652,31 +639,29 @@
             nullValue();
         }
 
-        return leaveDefault(returnNode);
+        return leave();
     }
 
     @Override
-    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
+    public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
         final RuntimeNode.Request req = runtimeNode.getRequest();
 
         if (req == RuntimeNode.Request.DEBUGGER) {
             enterDefault(runtimeNode);
-
             type("DebuggerStatement");
-
-            return leaveDefault(runtimeNode);
+            return leave();
         }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterSplitNode(final SplitNode splitNode) {
-        return null;
+    public boolean enterSplitNode(final SplitNode splitNode) {
+        return false;
     }
 
     @Override
-    public Node enterSwitchNode(final SwitchNode switchNode) {
+    public boolean enterSwitchNode(final SwitchNode switchNode) {
         enterDefault(switchNode);
 
         type("SwitchStatement");
@@ -688,11 +673,11 @@
 
         array("cases", switchNode.getCases());
 
-        return leaveDefault(switchNode);
+        return leave();
     }
 
     @Override
-    public Node enterTernaryNode(final TernaryNode ternaryNode) {
+    public boolean enterTernaryNode(final TernaryNode ternaryNode) {
         enterDefault(ternaryNode);
 
         type("ConditionalExpression");
@@ -709,11 +694,11 @@
         property("alternate");
         ternaryNode.third().accept(this);
 
-        return leaveDefault(ternaryNode);
+        return leave();
     }
 
     @Override
-    public Node enterThrowNode(final ThrowNode throwNode) {
+    public boolean enterThrowNode(final ThrowNode throwNode) {
         enterDefault(throwNode);
 
         type("ThrowStatement");
@@ -722,11 +707,11 @@
         property("argument");
         throwNode.getExpression().accept(this);
 
-        return leaveDefault(throwNode);
+        return leave();
     }
 
     @Override
-    public Node enterTryNode(final TryNode tryNode) {
+    public boolean enterTryNode(final TryNode tryNode) {
         enterDefault(tryNode);
 
         type("TryStatement");
@@ -747,11 +732,11 @@
             nullValue();
         }
 
-        return leaveDefault(tryNode);
+        return leave();
     }
 
     @Override
-    public Node enterUnaryNode(final UnaryNode unaryNode) {
+    public boolean enterUnaryNode(final UnaryNode unaryNode) {
         enterDefault(unaryNode);
 
         final TokenType tokenType = unaryNode.tokenType();
@@ -769,25 +754,25 @@
             final boolean prefix;
             final String operator;
             switch (tokenType) {
-                case INCPOSTFIX:
-                    prefix = false;
-                    operator = "++";
-                    break;
-                case DECPOSTFIX:
-                    prefix = false;
-                    operator = "--";
-                    break;
-                case INCPREFIX:
-                    operator = "++";
-                    prefix = true;
-                    break;
-                case DECPREFIX:
-                    operator = "--";
-                    prefix = true;
-                    break;
-                default:
-                    prefix = false;
-                    operator = tokenType.getName();
+            case INCPOSTFIX:
+                prefix = false;
+                operator = "++";
+                break;
+            case DECPOSTFIX:
+                prefix = false;
+                operator = "--";
+                break;
+            case INCPREFIX:
+                operator = "++";
+                prefix = true;
+                break;
+            case DECPREFIX:
+                operator = "--";
+                prefix = true;
+                break;
+            default:
+                prefix = false;
+                operator = tokenType.getName();
             }
 
             type(unaryNode.isAssignment()? "UpdateExpression" : "UnaryExpression");
@@ -803,11 +788,11 @@
             unaryNode.rhs().accept(this);
         }
 
-        return leaveDefault(unaryNode);
+        return leave();
     }
 
     @Override
-    public Node enterVarNode(final VarNode varNode) {
+    public boolean enterVarNode(final VarNode varNode) {
         enterDefault(varNode);
 
         type("VariableDeclaration");
@@ -839,28 +824,37 @@
         // declarations
         arrayEnd();
 
-        return leaveDefault(varNode);
+        return leave();
     }
 
     @Override
-    public Node enterWhileNode(final WhileNode whileNode) {
+    public boolean enterWhileNode(final WhileNode whileNode) {
         enterDefault(whileNode);
 
-        type("WhileStatement");
+        type(whileNode.isDoWhile() ? "DoWhileStatement" : "WhileStatement");
         comma();
 
-        property("test");
-        whileNode.getTest().accept(this);
-        comma();
+        if (whileNode.isDoWhile()) {
+            property("body");
+            whileNode.getBody().accept(this);
+            comma();
 
-        property("block");
-        whileNode.getBody().accept(this);
+            property("test");
+            whileNode.getTest().accept(this);
+        } else {
+            property("test");
+            whileNode.getTest().accept(this);
+            comma();
 
-        return leaveDefault(whileNode);
+            property("block");
+            whileNode.getBody().accept(this);
+        }
+
+        return leave();
     }
 
     @Override
-    public Node enterWithNode(final WithNode withNode) {
+    public boolean enterWithNode(final WithNode withNode) {
         enterDefault(withNode);
 
         type("WithStatement");
@@ -873,8 +867,8 @@
         property("body");
         withNode.getBody().accept(this);
 
-        return leaveDefault(withNode);
-    }
+        return leave();
+   }
 
     // Internals below
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java
index d2f40d1..8637b66 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java
@@ -26,30 +26,22 @@
 package jdk.nashorn.internal.ir.debug;
 
 import java.util.List;
-import jdk.nashorn.internal.ir.AccessNode;
+
+import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
-import jdk.nashorn.internal.ir.BreakNode;
-import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
-import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
 import jdk.nashorn.internal.ir.IfNode;
-import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LineNumberNode;
 import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.ReturnNode;
-import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.SplitNode;
 import jdk.nashorn.internal.ir.SwitchNode;
 import jdk.nashorn.internal.ir.Symbol;
-import jdk.nashorn.internal.ir.ThrowNode;
 import jdk.nashorn.internal.ir.TryNode;
-import jdk.nashorn.internal.ir.UnaryNode;
 import jdk.nashorn.internal.ir.VarNode;
 import jdk.nashorn.internal.ir.WhileNode;
 import jdk.nashorn.internal.ir.WithNode;
@@ -136,21 +128,20 @@
     /*
      * Visits.
      */
+
     @Override
-    public Node enterAccessNode(final AccessNode accessNode) {
-        accessNode.toString(sb);
-        return null;
+    public boolean enterDefault(final Node node) {
+        node.toString(sb);
+        return false;
     }
 
     @Override
-    public Node enterBlock(final Block block) {
+    public boolean enterBlock(final Block block) {
         sb.append(' ');
         sb.append('{');
 
         indent += TABWIDTH;
 
-        final boolean isFunction = block instanceof FunctionNode;
-
         final List<Node> statements = block.getStatements();
 
         boolean lastLineNumber = false;
@@ -161,14 +152,14 @@
                 indent();
             }
 
-            if (statement instanceof UnaryNode) {
-                statement.toString(sb);
-            } else {
-                statement.accept(this);
-            }
+            statement.accept(this);
 
             lastLineNumber = statement instanceof LineNumberNode;
 
+            if (statement instanceof FunctionNode) {
+                continue;
+            }
+
             final Symbol symbol = statement.getSymbol();
 
             if (symbol != null) {
@@ -200,72 +191,42 @@
         indent();
         sb.append("}");
 
-        if (isFunction) {
-            sb.append(EOLN);
-        }
-
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterBreakNode(final BreakNode breakNode) {
-        breakNode.toString(sb);
-        return null;
-    }
-
-    @Override
-    public Node enterCallNode(final CallNode callNode) {
-        callNode.toString(sb);
-        return null;
-    }
-
-    @Override
-    public Node enterContinueNode(final ContinueNode continueNode) {
-        continueNode.toString(sb);
-        return null;
-    }
-
-    @Override
-    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
-        sb.append("do");
-        doWhileNode.getBody().accept(this);
+    public boolean enterBinaryNode(final BinaryNode binaryNode) {
+        binaryNode.lhs().accept(this);
         sb.append(' ');
-        doWhileNode.toString(sb);
-
-        return null;
+        sb.append(binaryNode.tokenType());
+        sb.append(' ');
+        binaryNode.rhs().accept(this);
+        return false;
     }
 
     @Override
-    public Node enterExecuteNode(final ExecuteNode executeNode) {
-        final Node expression = executeNode.getExpression();
-
-        if (expression instanceof UnaryNode) {
-            expression.toString(sb);
-        } else {
-            expression.accept(this);
-        }
-
-        return null;
+    public boolean enterExecuteNode(final ExecuteNode executeNode) {
+        executeNode.getExpression().accept(this);
+        return false;
     }
 
     @Override
-    public Node enterForNode(final ForNode forNode) {
+    public boolean enterForNode(final ForNode forNode) {
         forNode.toString(sb);
         forNode.getBody().accept(this);
-
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterFunctionNode(final FunctionNode functionNode) {
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
         functionNode.toString(sb);
-        enterBlock(functionNode);
-
-        return null;
+        enterBlock(functionNode.getBody());
+        sb.append(EOLN);
+        return false;
     }
 
     @Override
-    public Node enterIfNode(final IfNode ifNode) {
+    public boolean enterIfNode(final IfNode ifNode) {
         ifNode.toString(sb);
         ifNode.getPass().accept(this);
 
@@ -276,55 +237,36 @@
             fail.accept(this);
         }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterIndexNode(final IndexNode indexNode) {
-        indexNode.toString(sb);
-        return null;
-    }
-
-    @Override
-    public Node enterLabelNode(final LabelNode labeledNode) {
+    public boolean enterLabelNode(final LabelNode labeledNode) {
         indent -= TABWIDTH;
         indent();
         indent += TABWIDTH;
         labeledNode.toString(sb);
         labeledNode.getBody().accept(this);
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
+    public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) {
         if (printLineNumbers) {
             lineNumberNode.toString(sb);
         }
 
-        return null;
-    }
-
-
-    @Override
-    public Node enterReturnNode(final ReturnNode returnNode) {
-        returnNode.toString(sb);
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
-        runtimeNode.toString(sb);
-        return null;
-    }
-
-    @Override
-    public Node enterSplitNode(final SplitNode splitNode) {
+    public boolean enterSplitNode(final SplitNode splitNode) {
         splitNode.toString(sb);
         sb.append(EOLN);
         indent += TABWIDTH;
         indent();
-        return splitNode;
+        return true;
     }
 
     @Override
@@ -337,7 +279,7 @@
     }
 
     @Override
-    public Node enterSwitchNode(final SwitchNode switchNode) {
+    public boolean enterSwitchNode(final SwitchNode switchNode) {
         switchNode.toString(sb);
         sb.append(" {");
 
@@ -357,24 +299,18 @@
         indent();
         sb.append("}");
 
-        return null;
-   }
-
-    @Override
-    public Node enterThrowNode(final ThrowNode throwNode) {
-        throwNode.toString(sb);
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterTryNode(final TryNode tryNode) {
+    public boolean enterTryNode(final TryNode tryNode) {
         tryNode.toString(sb);
         tryNode.getBody().accept(this);
 
         final List<Block> catchBlocks = tryNode.getCatchBlocks();
 
         for (final Block catchBlock : catchBlocks) {
-            final CatchNode catchNode = (CatchNode) catchBlock.getStatements().get(0);
+            final CatchNode catchNode = (CatchNode)catchBlock.getStatements().get(0);
             catchNode.toString(sb);
             catchNode.getBody().accept(this);
         }
@@ -386,35 +322,42 @@
             finallyBody.accept(this);
         }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterVarNode(final VarNode varNode) {
+    public boolean enterVarNode(final VarNode varNode) {
         sb.append("var ");
         varNode.getName().toString(sb);
         final Node init = varNode.getInit();
-        if(init != null) {
+        if (init != null) {
             sb.append(" = ");
             init.accept(this);
         }
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterWhileNode(final WhileNode whileNode) {
-        whileNode.toString(sb);
-        whileNode.getBody().accept(this);
+    public boolean enterWhileNode(final WhileNode whileNode) {
+        if (whileNode.isDoWhile()) {
+            sb.append("do");
+            whileNode.getBody().accept(this);
+            sb.append(' ');
+            whileNode.toString(sb);
+        } else {
+            whileNode.toString(sb);
+            whileNode.getBody().accept(this);
+        }
 
-        return null;
+        return false;
     }
 
     @Override
-    public Node enterWithNode(final WithNode withNode) {
+    public boolean enterWithNode(final WithNode withNode) {
         withNode.toString(sb);
         withNode.getBody().accept(this);
 
-        return null;
+        return false;
     }
 
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java
index 0021b7d..4f128412 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java
@@ -25,9 +25,8 @@
 
 package jdk.nashorn.internal.ir.visitor;
 
-import jdk.nashorn.internal.codegen.CompileUnit;
-import jdk.nashorn.internal.codegen.MethodEmitter;
 import jdk.nashorn.internal.ir.BinaryNode;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.UnaryNode;
 
@@ -45,15 +44,14 @@
     /**
      * Constructor
      *
-     * @param compileUnit compile unit
-     * @param method      method emitter
+     * @param lc a custom lexical context
      */
-    public NodeOperatorVisitor(final CompileUnit compileUnit, final MethodEmitter method) {
-        super(compileUnit, method);
+    public NodeOperatorVisitor(final LexicalContext lc) {
+        super(lc);
     }
 
     @Override
-    public final Node enterUnaryNode(final UnaryNode unaryNode) {
+    public final boolean enterUnaryNode(final UnaryNode unaryNode) {
         switch (unaryNode.tokenType()) {
         case ADD:
             return enterADD(unaryNode);
@@ -119,7 +117,7 @@
     }
 
     @Override
-    public final Node enterBinaryNode(final BinaryNode binaryNode) {
+    public final boolean enterBinaryNode(final BinaryNode binaryNode) {
         switch (binaryNode.tokenType()) {
         case ADD:
             return enterADD(binaryNode);
@@ -287,17 +285,6 @@
     }
 
     /*
-    @Override
-    public Node enter(final TernaryNode ternaryNode) {
-        return enterDefault(ternaryNode);
-    }
-
-    @Override
-    public Node leave(final TernaryNode ternaryNode) {
-        return leaveDefault(ternaryNode);
-    }*/
-
-    /*
      * Unary entries and exists.
      */
 
@@ -305,9 +292,9 @@
      * Unary enter - callback for entering a unary +
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterADD(final UnaryNode unaryNode) {
+    public boolean enterADD(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -325,9 +312,9 @@
      * Unary enter - callback for entering a ~ operator
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterBIT_NOT(final UnaryNode unaryNode) {
+    public boolean enterBIT_NOT(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -345,9 +332,9 @@
      * Unary enter - callback for entering a conversion
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterCONVERT(final UnaryNode unaryNode) {
+    public boolean enterCONVERT(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -365,9 +352,9 @@
      * Unary enter - callback for entering a ++ or -- operator
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterDECINC(final UnaryNode unaryNode) {
+    public boolean enterDECINC(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -387,7 +374,7 @@
      * @param  unaryNode the node
      * @return processed node
      */
-    public Node enterDELETE(final UnaryNode unaryNode) {
+    public boolean enterDELETE(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -405,9 +392,9 @@
      * Unary enter - callback for entering a discard operator
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterDISCARD(final UnaryNode unaryNode) {
+    public boolean enterDISCARD(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -425,9 +412,9 @@
      * Unary enter - callback for entering a new operator
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterNEW(final UnaryNode unaryNode) {
+    public boolean enterNEW(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -445,9 +432,9 @@
      * Unary enter - callback for entering a ! operator
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterNOT(final UnaryNode unaryNode) {
+    public boolean enterNOT(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -465,9 +452,9 @@
      * Unary enter - callback for entering a unary -
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterSUB(final UnaryNode unaryNode) {
+    public boolean enterSUB(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -485,9 +472,9 @@
      * Unary enter - callback for entering a typeof
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterTYPEOF(final UnaryNode unaryNode) {
+    public boolean enterTYPEOF(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -505,9 +492,9 @@
      * Unary enter - callback for entering a void
      *
      * @param  unaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterVOID(final UnaryNode unaryNode) {
+    public boolean enterVOID(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -525,9 +512,9 @@
      * Binary enter - callback for entering + operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterADD(final BinaryNode binaryNode) {
+    public boolean enterADD(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -545,9 +532,9 @@
      * Binary enter - callback for entering {@literal &&} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterAND(final BinaryNode binaryNode) {
+    public boolean enterAND(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -565,9 +552,9 @@
      * Binary enter - callback for entering an assignment
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN(final BinaryNode binaryNode) {
+    public boolean enterASSIGN(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -585,9 +572,9 @@
      * Binary enter - callback for entering += operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_ADD(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_ADD(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -605,9 +592,9 @@
      * Binary enter - callback for entering {@literal &=} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_AND(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -625,9 +612,9 @@
      * Binary enter - callback for entering |= operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_OR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -645,9 +632,9 @@
      * Binary enter - callback for entering ^= operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_BIT_XOR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -665,9 +652,9 @@
      * Binary enter - callback for entering /= operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_DIV(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_DIV(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -685,9 +672,9 @@
      * Binary enter - callback for entering %= operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_MOD(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_MOD(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -705,9 +692,9 @@
      * Binary enter - callback for entering *= operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_MUL(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_MUL(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -725,9 +712,9 @@
      * Binary enter - callback for entering {@literal >>=} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_SAR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SAR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -745,9 +732,9 @@
      * Binary enter - callback for entering a {@literal <<=} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_SHL(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SHL(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -765,9 +752,9 @@
      * Binary enter - callback for entering {@literal >>>=} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_SHR(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SHR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -785,9 +772,9 @@
      * Binary enter - callback for entering -= operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterASSIGN_SUB(final BinaryNode binaryNode) {
+    public boolean enterASSIGN_SUB(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -805,9 +792,9 @@
      * Binary enter - callback for entering a bind operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterBIND(final BinaryNode binaryNode) {
+    public boolean enterBIND(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -825,9 +812,9 @@
      * Binary enter - callback for entering {@literal &} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterBIT_AND(final BinaryNode binaryNode) {
+    public boolean enterBIT_AND(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -845,9 +832,9 @@
      * Binary enter - callback for entering | operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterBIT_OR(final BinaryNode binaryNode) {
+    public boolean enterBIT_OR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -865,9 +852,9 @@
      * Binary enter - callback for entering ^ operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterBIT_XOR(final BinaryNode binaryNode) {
+    public boolean enterBIT_XOR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -886,9 +873,9 @@
      * (a, b) where the result is a
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterCOMMALEFT(final BinaryNode binaryNode) {
+    public boolean enterCOMMALEFT(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -908,9 +895,9 @@
      * (a, b) where the result is b
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterCOMMARIGHT(final BinaryNode binaryNode) {
+    public boolean enterCOMMARIGHT(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -929,9 +916,9 @@
      * Binary enter - callback for entering a division
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterDIV(final BinaryNode binaryNode) {
+    public boolean enterDIV(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -949,9 +936,9 @@
      * Binary enter - callback for entering == operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterEQ(final BinaryNode binaryNode) {
+    public boolean enterEQ(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -969,9 +956,9 @@
      * Binary enter - callback for entering === operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterEQ_STRICT(final BinaryNode binaryNode) {
+    public boolean enterEQ_STRICT(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -989,9 +976,9 @@
      * Binary enter - callback for entering {@literal >=} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterGE(final BinaryNode binaryNode) {
+    public boolean enterGE(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1009,9 +996,9 @@
      * Binary enter - callback for entering {@literal >} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterGT(final BinaryNode binaryNode) {
+    public boolean enterGT(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1029,9 +1016,9 @@
      * Binary enter - callback for entering in operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterIN(final BinaryNode binaryNode) {
+    public boolean enterIN(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1049,9 +1036,9 @@
      * Binary enter - callback for entering instanceof operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterINSTANCEOF(final BinaryNode binaryNode) {
+    public boolean enterINSTANCEOF(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1069,9 +1056,9 @@
      * Binary enter - callback for entering {@literal <=} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterLE(final BinaryNode binaryNode) {
+    public boolean enterLE(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1089,9 +1076,9 @@
      * Binary enter - callback for entering {@literal <} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterLT(final BinaryNode binaryNode) {
+    public boolean enterLT(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1108,9 +1095,9 @@
      * Binary enter - callback for entering % operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterMOD(final BinaryNode binaryNode) {
+    public boolean enterMOD(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1128,9 +1115,9 @@
      * Binary enter - callback for entering * operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterMUL(final BinaryNode binaryNode) {
+    public boolean enterMUL(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1148,9 +1135,9 @@
      * Binary enter - callback for entering != operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterNE(final BinaryNode binaryNode) {
+    public boolean enterNE(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1168,9 +1155,9 @@
      * Binary enter - callback for entering a !== operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterNE_STRICT(final BinaryNode binaryNode) {
+    public boolean enterNE_STRICT(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1188,9 +1175,9 @@
      * Binary enter - callback for entering || operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterOR(final BinaryNode binaryNode) {
+    public boolean enterOR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1208,9 +1195,9 @@
      * Binary enter - callback for entering {@literal >>} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterSAR(final BinaryNode binaryNode) {
+    public boolean enterSAR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1228,9 +1215,9 @@
      * Binary enter - callback for entering {@literal <<} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterSHL(final BinaryNode binaryNode) {
+    public boolean enterSHL(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1247,9 +1234,9 @@
      * Binary enter - callback for entering {@literal >>>} operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterSHR(final BinaryNode binaryNode) {
+    public boolean enterSHR(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -1267,9 +1254,9 @@
      * Binary enter - callback for entering - operator
      *
      * @param  binaryNode the node
-     * @return processed node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterSUB(final BinaryNode binaryNode) {
+    public boolean enterSUB(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
index f10d8c0..e3c0d34 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
@@ -25,8 +25,6 @@
 
 package jdk.nashorn.internal.ir.visitor;
 
-import jdk.nashorn.internal.codegen.CompileUnit;
-import jdk.nashorn.internal.codegen.MethodEmitter;
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
@@ -35,7 +33,6 @@
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.EmptyNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
@@ -44,6 +41,7 @@
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.LabelNode;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LineNumberNode;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.Node;
@@ -65,41 +63,30 @@
  * Visitor used to navigate the IR.
  */
 public abstract class NodeVisitor {
-    /** Current functionNode. */
-    private FunctionNode currentFunctionNode;
-
-    /** Current compile unit used for class generation. */
-    private CompileUnit compileUnit;
+    private final LexicalContext lc;
 
     /**
-     * Current method visitor used for method generation.
-     * <p>
-     * TODO: protected is just for convenience and readability, so that
-     * subclasses can directly use 'method' - might want to change that
-     */
-    protected MethodEmitter method;
-
-    /** Current block. */
-    private Block currentBlock;
-
-    /**
-     * Constructor.
+     * Constructor
      */
     public NodeVisitor() {
-        this(null, null);
+        this(new LexicalContext());
     }
 
     /**
      * Constructor
      *
-     * @param compileUnit compile unit for this node visitor
-     * @param method method emitter for this node visitor
+     * @param lc a custom lexical context
      */
-    public NodeVisitor(final CompileUnit compileUnit, final MethodEmitter method) {
-        super();
+    public NodeVisitor(final LexicalContext lc) {
+        this.lc = lc;
+    }
 
-        this.compileUnit = compileUnit;
-        this.method      = method;
+    /**
+     * Get the lexical context of this node visitor
+     * @return lexical context
+     */
+    public LexicalContext getLexicalContext() {
+        return lc;
     }
 
     /**
@@ -118,10 +105,10 @@
      *
      * @see NodeVisitor#leaveDefault(Node)
      * @param node the node to visit
-     * @return the node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    protected Node enterDefault(final Node node) {
-        return node;
+    protected boolean enterDefault(final Node node) {
+        return true;
     }
 
     /**
@@ -150,9 +137,9 @@
      * Callback for entering an AccessNode
      *
      * @param  accessNode the node
-     * @return processed node, null if traversal should end, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterAccessNode(final AccessNode accessNode) {
+    public boolean enterAccessNode(final AccessNode accessNode) {
         return enterDefault(accessNode);
     }
 
@@ -170,9 +157,9 @@
      * Callback for entering a Block
      *
      * @param  block     the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterBlock(final Block block) {
+    public boolean enterBlock(final Block block) {
         return enterDefault(block);
     }
 
@@ -192,7 +179,7 @@
      * @param  binaryNode  the node
      * @return processed   node
      */
-    public Node enterBinaryNode(final BinaryNode binaryNode) {
+    public boolean enterBinaryNode(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -210,9 +197,9 @@
      * Callback for entering a BreakNode
      *
      * @param  breakNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterBreakNode(final BreakNode breakNode) {
+    public boolean enterBreakNode(final BreakNode breakNode) {
         return enterDefault(breakNode);
     }
 
@@ -230,9 +217,9 @@
      * Callback for entering a CallNode
      *
      * @param  callNode  the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterCallNode(final CallNode callNode) {
+    public boolean enterCallNode(final CallNode callNode) {
         return enterDefault(callNode);
     }
 
@@ -250,9 +237,9 @@
      * Callback for entering a CaseNode
      *
      * @param  caseNode  the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterCaseNode(final CaseNode caseNode) {
+    public boolean enterCaseNode(final CaseNode caseNode) {
         return enterDefault(caseNode);
     }
 
@@ -270,9 +257,9 @@
      * Callback for entering a CatchNode
      *
      * @param  catchNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterCatchNode(final CatchNode catchNode) {
+    public boolean enterCatchNode(final CatchNode catchNode) {
         return enterDefault(catchNode);
     }
 
@@ -290,9 +277,9 @@
      * Callback for entering a ContinueNode
      *
      * @param  continueNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterContinueNode(final ContinueNode continueNode) {
+    public boolean enterContinueNode(final ContinueNode continueNode) {
         return enterDefault(continueNode);
     }
 
@@ -307,32 +294,12 @@
     }
 
     /**
-     * Callback for entering a DoWhileNode
-     *
-     * @param  doWhileNode the node
-     * @return processed   node
-     */
-    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
-        return enterDefault(doWhileNode);
-    }
-
-    /**
-     * Callback for leaving a DoWhileNode
-     *
-     * @param  doWhileNode the node
-     * @return processed node, which will replace the original one, or the original node
-     */
-    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
-        return leaveDefault(doWhileNode);
-    }
-
-    /**
      * Callback for entering an EmptyNode
      *
      * @param  emptyNode   the node
-     * @return processed   node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterEmptyNode(final EmptyNode emptyNode) {
+    public boolean enterEmptyNode(final EmptyNode emptyNode) {
         return enterDefault(emptyNode);
     }
 
@@ -350,9 +317,9 @@
      * Callback for entering an ExecuteNode
      *
      * @param  executeNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterExecuteNode(final ExecuteNode executeNode) {
+    public boolean enterExecuteNode(final ExecuteNode executeNode) {
         return enterDefault(executeNode);
     }
 
@@ -370,9 +337,9 @@
      * Callback for entering a ForNode
      *
      * @param  forNode   the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterForNode(final ForNode forNode) {
+    public boolean enterForNode(final ForNode forNode) {
         return enterDefault(forNode);
     }
 
@@ -390,9 +357,9 @@
      * Callback for entering a FunctionNode
      *
      * @param  functionNode the node
-     * @return processed    node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterFunctionNode(final FunctionNode functionNode) {
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
         return enterDefault(functionNode);
     }
 
@@ -410,9 +377,9 @@
      * Callback for entering an IdentNode
      *
      * @param  identNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterIdentNode(final IdentNode identNode) {
+    public boolean enterIdentNode(final IdentNode identNode) {
         return enterDefault(identNode);
     }
 
@@ -429,10 +396,10 @@
     /**
      * Callback for entering an IfNode
      *
-     * @param  ifNode    the node
-     * @return processed node, null if traversal should end
+     * @param  ifNode the node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterIfNode(final IfNode ifNode) {
+    public boolean enterIfNode(final IfNode ifNode) {
         return enterDefault(ifNode);
     }
 
@@ -450,9 +417,9 @@
      * Callback for entering an IndexNode
      *
      * @param  indexNode  the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterIndexNode(final IndexNode indexNode) {
+    public boolean enterIndexNode(final IndexNode indexNode) {
         return enterDefault(indexNode);
     }
 
@@ -470,9 +437,9 @@
      * Callback for entering a LabelNode
      *
      * @param  labelNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterLabelNode(final LabelNode labelNode) {
+    public boolean enterLabelNode(final LabelNode labelNode) {
         return enterDefault(labelNode);
     }
 
@@ -490,9 +457,9 @@
      * Callback for entering a LineNumberNode
      *
      * @param  lineNumberNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
+    public boolean enterLineNumberNode(final LineNumberNode lineNumberNode) {
         return enterDefault(lineNumberNode);
     }
 
@@ -510,9 +477,9 @@
      * Callback for entering a LiteralNode
      *
      * @param  literalNode the node
-     * @return processed   node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterLiteralNode(final LiteralNode<?> literalNode) {
+    public boolean enterLiteralNode(final LiteralNode<?> literalNode) {
         return enterDefault(literalNode);
     }
 
@@ -530,9 +497,9 @@
      * Callback for entering an ObjectNode
      *
      * @param  objectNode the node
-     * @return processed  node
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterObjectNode(final ObjectNode objectNode) {
+    public boolean enterObjectNode(final ObjectNode objectNode) {
         return enterDefault(objectNode);
     }
 
@@ -550,9 +517,9 @@
      * Callback for entering a PropertyNode
      *
      * @param  propertyNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterPropertyNode(final PropertyNode propertyNode) {
+    public boolean enterPropertyNode(final PropertyNode propertyNode) {
         return enterDefault(propertyNode);
     }
 
@@ -570,9 +537,9 @@
      * Callback for entering a ReturnNode
      *
      * @param  returnNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterReturnNode(final ReturnNode returnNode) {
+    public boolean enterReturnNode(final ReturnNode returnNode) {
         return enterDefault(returnNode);
     }
 
@@ -590,9 +557,9 @@
      * Callback for entering a RuntimeNode
      *
      * @param  runtimeNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
+    public boolean enterRuntimeNode(final RuntimeNode runtimeNode) {
         return enterDefault(runtimeNode);
     }
 
@@ -610,9 +577,9 @@
      * Callback for entering a SplitNode
      *
      * @param  splitNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterSplitNode(final SplitNode splitNode) {
+    public boolean enterSplitNode(final SplitNode splitNode) {
         return enterDefault(splitNode);
     }
 
@@ -630,9 +597,9 @@
      * Callback for entering a SwitchNode
      *
      * @param  switchNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterSwitchNode(final SwitchNode switchNode) {
+    public boolean enterSwitchNode(final SwitchNode switchNode) {
         return enterDefault(switchNode);
     }
 
@@ -650,9 +617,9 @@
      * Callback for entering a TernaryNode
      *
      * @param  ternaryNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterTernaryNode(final TernaryNode ternaryNode) {
+    public boolean enterTernaryNode(final TernaryNode ternaryNode) {
         return enterDefault(ternaryNode);
     }
 
@@ -670,9 +637,9 @@
      * Callback for entering a ThrowNode
      *
      * @param  throwNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterThrowNode(final ThrowNode throwNode) {
+    public boolean enterThrowNode(final ThrowNode throwNode) {
         return enterDefault(throwNode);
     }
 
@@ -690,9 +657,9 @@
      * Callback for entering a TryNode
      *
      * @param  tryNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterTryNode(final TryNode tryNode) {
+    public boolean enterTryNode(final TryNode tryNode) {
         return enterDefault(tryNode);
     }
 
@@ -710,9 +677,9 @@
      * Callback for entering a UnaryNode
      *
      * @param  unaryNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterUnaryNode(final UnaryNode unaryNode) {
+    public boolean enterUnaryNode(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -730,9 +697,9 @@
      * Callback for entering a VarNode
      *
      * @param  varNode   the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterVarNode(final VarNode varNode) {
+    public boolean enterVarNode(final VarNode varNode) {
         return enterDefault(varNode);
     }
 
@@ -750,9 +717,9 @@
      * Callback for entering a WhileNode
      *
      * @param  whileNode the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterWhileNode(final WhileNode whileNode) {
+    public boolean enterWhileNode(final WhileNode whileNode) {
         return enterDefault(whileNode);
     }
 
@@ -770,9 +737,9 @@
      * Callback for entering a WithNode
      *
      * @param  withNode  the node
-     * @return processed node, null if traversal should end
+     * @return true if traversal should continue and node children be traversed, false otherwise
      */
-    public Node enterWithNode(final WithNode withNode) {
+    public boolean enterWithNode(final WithNode withNode) {
         return enterDefault(withNode);
     }
 
@@ -786,74 +753,5 @@
         return leaveDefault(withNode);
     }
 
-    /**
-     * Get the current function node for this NodeVisitor
-     * @see FunctionNode
-     * @return the function node being visited
-     */
-    public FunctionNode getCurrentFunctionNode() {
-        return currentFunctionNode;
-    }
-
-    /**
-     * Reset the current function node being visited for this NodeVisitor
-     * @see FunctionNode
-     * @param currentFunctionNode a new function node to traverse
-     */
-    public void setCurrentFunctionNode(final FunctionNode currentFunctionNode) {
-        this.currentFunctionNode = currentFunctionNode;
-    }
-
-    /**
-     * Get the current compile unit for this NodeVisitor
-     * @see CompileUnit
-     * @return a compile unit, or null if not a compiling NodeVisitor
-     */
-    public CompileUnit getCurrentCompileUnit() {
-        return compileUnit;
-    }
-
-    /**
-     * Set the current compile unit for this NodeVisitor
-     * @see CompileUnit
-     * @param compileUnit a new compile unit
-     */
-    public void setCurrentCompileUnit(final CompileUnit compileUnit) {
-        this.compileUnit = compileUnit;
-    }
-
-    /**
-     * Get the current method emitter for this NodeVisitor
-     * @see MethodEmitter
-     * @return the method emitter
-     */
-    public MethodEmitter getCurrentMethodEmitter() {
-        return method;
-    }
-
-    /**
-     * Reset the current method emitter for this NodeVisitor
-     * @see MethodEmitter
-     * @param method a new method emitter
-     */
-    public void setCurrentMethodEmitter(final MethodEmitter method) {
-        this.method = method;
-    }
-
-    /**
-     * Get the current Block being traversed for this NodeVisitor
-     * @return the current block
-     */
-    public Block getCurrentBlock() {
-        return currentBlock;
-    }
-
-    /**
-     * Reset the Block to be traversed for this NodeVisitor
-     * @param currentBlock the new current block
-     */
-    public void setCurrentBlock(final Block currentBlock) {
-        this.currentBlock = currentBlock;
-    }
 
 }
diff --git a/nashorn/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java b/nashorn/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java
index 2161c78..4d8a297 100644
--- a/nashorn/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java
+++ b/nashorn/src/jdk/nashorn/internal/lookup/MethodHandleFactory.java
@@ -137,7 +137,7 @@
      */
     static Object traceReturn(final DebugLogger logger, final Object value) {
         final String str = "\treturn: " + stripName(value) + " [type=" + (value == null ? "null" : stripName(value.getClass()) + ']');
-        logger.log(str, TRACE_LEVEL);
+        logger.log(TRACE_LEVEL, str);
         return value;
     }
 
@@ -173,7 +173,7 @@
         }
 
         assert logger != null;
-        logger.log(sb.toString(), TRACE_LEVEL);
+        logger.log(TRACE_LEVEL, sb);
         stacktrace(logger);
     }
 
@@ -184,7 +184,7 @@
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
         final PrintStream ps = new PrintStream(baos);
         new Throwable().printStackTrace(ps);
-        logger.log(baos.toString(), TRACE_LEVEL);
+        logger.log(TRACE_LEVEL, baos.toString());
     }
 
     private static String argString(final Object arg) {
@@ -614,7 +614,7 @@
         @Override
         public SwitchPoint createSwitchPoint() {
             final SwitchPoint sp = super.createSwitchPoint();
-            LOG.log("createSwitchPoint " + sp, TRACE_LEVEL);
+            LOG.log(TRACE_LEVEL, "createSwitchPoint ", sp);
             return sp;
         }
 
@@ -627,7 +627,7 @@
         @Override
         public MethodType type(final Class<?> returnType, final Class<?>... paramTypes) {
             final MethodType mt = super.type(returnType, paramTypes);
-            LOG.log("methodType " + returnType + ' ' + Arrays.toString(paramTypes) + ' ' + mt, TRACE_LEVEL);
+            LOG.log(TRACE_LEVEL, "methodType ", returnType, " ", Arrays.toString(paramTypes), " ", mt);
             return mt;
         }
     }
@@ -638,7 +638,7 @@
     private static class TraceCreateMethodHandleFunctionality extends TraceMethodHandleFunctionality {
         @Override
         public MethodHandle debug(final MethodHandle master, final String str, final Object... args) {
-            LOG.log(str + ' ' + describe(args), TRACE_LEVEL);
+            LOG.log(TRACE_LEVEL, str, " ", describe(args));
             stacktrace(LOG);
             return master;
         }
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java b/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java
index 4724cda..1be4cf7 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeDate.java
@@ -851,11 +851,11 @@
         }
         final ScriptObject sobj  = (ScriptObject)selfObj;
         final Object       value = sobj.getDefaultValue(Number.class);
-
-        final double num = (value instanceof Number) ? ((Number)value).doubleValue() : NaN;
-
-        if (isInfinite(num) || isNaN(num)) {
-            return null;
+        if (value instanceof Number) {
+            final double num = ((Number)value).doubleValue();
+            if (isInfinite(num) || isNaN(num)) {
+                return null;
+            }
         }
 
         try {
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeFunction.java b/nashorn/src/jdk/nashorn/internal/objects/NativeFunction.java
index f5c0c29..13c1bc1 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeFunction.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeFunction.java
@@ -81,23 +81,13 @@
 
         Object[] args = null;
 
-        if (ScriptObject.isArray(array)) {
-            args = ((NativeArray)array).asObjectArray();
-        } else if (array instanceof ScriptObject) {
+        if (array instanceof ScriptObject) {
             // look for array-like object
             final ScriptObject sobj = (ScriptObject)array;
             final Object       len  = sobj.getLength();
-
-            if (len == UNDEFINED || len == null) {
-                throw typeError("function.apply.expects.array");
-            }
-
             final int n = (int)JSType.toUint32(len);
-            if (n != JSType.toNumber(len)) {
-                throw typeError("function.apply.expects.array");
-            }
 
-            args = new Object[(int)JSType.toUint32(len)];
+            args = new Object[n];
             for (int i = 0; i < args.length; i++) {
                 args[i] = sobj.get(i);
             }
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeJSAdapter.java b/nashorn/src/jdk/nashorn/internal/objects/NativeJSAdapter.java
index 409b9fa..8a3f42d 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeJSAdapter.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeJSAdapter.java
@@ -148,11 +148,7 @@
         if (overrides instanceof ScriptObject) {
             this.overrides = true;
             final ScriptObject sobj = (ScriptObject)overrides;
-            final Iterator<String> iter = sobj.propertyIterator();
-            while (iter.hasNext()) {
-                final String prop = iter.next();
-                super.set(prop, sobj.get(prop), false);
-            }
+            this.addBoundProperties(sobj);
         } else {
             this.overrides = false;
         }
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeString.java b/nashorn/src/jdk/nashorn/internal/objects/NativeString.java
index 838b8c4..bffe553 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeString.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeString.java
@@ -179,7 +179,6 @@
         return ((ScriptObject) Global.toObject(self)).get(key);
     }
 
-    @SuppressWarnings("unused")
     private static Object get(final Object self, final int key) {
         final CharSequence cs = JSType.toCharSequence(self);
         if (key >= 0 && key < cs.length()) {
diff --git a/nashorn/src/jdk/nashorn/internal/parser/AbstractParser.java b/nashorn/src/jdk/nashorn/internal/parser/AbstractParser.java
index a3263b8..676ca7f 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/AbstractParser.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/AbstractParser.java
@@ -197,9 +197,10 @@
      *
      * @param message    Error message.
      * @param errorToken Offending token.
+     * @return ParserException upon failure. Caller should throw and not ignore
      */
-    protected final void error(final String message, final long errorToken) {
-        error(JSErrorType.SYNTAX_ERROR, message, errorToken);
+    protected final ParserException error(final String message, final long errorToken) {
+        return error(JSErrorType.SYNTAX_ERROR, message, errorToken);
     }
 
     /**
@@ -208,22 +209,24 @@
      * @param errorType  The error type
      * @param message    Error message.
      * @param errorToken Offending token.
+     * @return ParserException upon failure. Caller should throw and not ignore
      */
-    protected final void error(final JSErrorType errorType, final String message, final long errorToken) {
+    protected final ParserException error(final JSErrorType errorType, final String message, final long errorToken) {
         final int position  = Token.descPosition(errorToken);
         final int lineNum   = source.getLine(position);
         final int columnNum = source.getColumn(position);
         final String formatted = ErrorManager.format(message, source, lineNum, columnNum, errorToken);
-        throw new ParserException(errorType, formatted, source, lineNum, columnNum, errorToken);
+        return new ParserException(errorType, formatted, source, lineNum, columnNum, errorToken);
     }
 
     /**
      * Report an error.
      *
      * @param message Error message.
+     * @return ParserException upon failure. Caller should throw and not ignore
      */
-    protected final void error(final String message) {
-        error(JSErrorType.SYNTAX_ERROR, message);
+    protected final ParserException error(final String message) {
+        return error(JSErrorType.SYNTAX_ERROR, message);
     }
 
     /**
@@ -231,13 +234,14 @@
      *
      * @param errorType  The error type
      * @param message    Error message.
+     * @return ParserException upon failure. Caller should throw and not ignore
      */
-    protected final void error(final JSErrorType errorType, final String message) {
+    protected final ParserException error(final JSErrorType errorType, final String message) {
         // TODO - column needs to account for tabs.
         final int position = Token.descPosition(token);
         final int column = position - linePosition;
         final String formatted = ErrorManager.format(message, source, line, column, token);
-        throw new ParserException(errorType, formatted, source, line, column, token);
+        return new ParserException(errorType, formatted, source, line, column, token);
     }
 
     /**
@@ -270,7 +274,7 @@
      */
     protected final void expect(final TokenType expected) throws ParserException {
         if (type != expected) {
-            error(expectMessage(expected));
+            throw error(expectMessage(expected));
         }
 
         next();
@@ -285,7 +289,7 @@
      */
     protected final Object expectValue(final TokenType expected) throws ParserException {
         if (type != expected) {
-            error(expectMessage(expected));
+            throw error(expectMessage(expected));
         }
 
         final Object value = getValue();
@@ -429,7 +433,7 @@
                 try {
                     RegExpFactory.validate(regex.getExpression(), regex.getOptions());
                 } catch (final ParserException e) {
-                    error(e.getMessage());
+                    throw error(e.getMessage());
                 }
             }
             node = LiteralNode.newInstance(source, literalToken, finish, (LexerToken)value);
diff --git a/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java b/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java
index 5468ca3..e074cf0 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java
@@ -170,8 +170,7 @@
                 }
             case '"':
             case '\\':
-                error(AbstractParser.message("unexpected.token", str));
-                break;
+                throw error(AbstractParser.message("unexpected.token", str));
             }
         }
 
@@ -222,14 +221,12 @@
                 return new UnaryNode(source, literalToken, LiteralNode.newInstance(source, realToken, finish, (Number)value));
             }
 
-            error(AbstractParser.message("expected", "number", type.getNameOrType()));
-            break;
+            throw error(AbstractParser.message("expected", "number", type.getNameOrType()));
         default:
             break;
         }
 
-        error(AbstractParser.message("expected", "json literal", type.getNameOrType()));
-        return null;
+        throw error(AbstractParser.message("expected", "json literal", type.getNameOrType()));
     }
 
     /**
@@ -265,7 +262,7 @@
                 elements.add(jsonLiteral());
                 // Comma between array elements is mandatory in JSON.
                 if (type != COMMARIGHT && type != RBRACKET) {
-                    error(AbstractParser.message("expected", ", or ]", type.getNameOrType()));
+                   throw error(AbstractParser.message("expected", ", or ]", type.getNameOrType()));
                 }
                 break;
             }
@@ -306,7 +303,7 @@
 
                 // Comma between property assigments is mandatory in JSON.
                 if (type != RBRACE && type != COMMARIGHT) {
-                    error(AbstractParser.message("expected", ", or }", type.getNameOrType()));
+                    throw error(AbstractParser.message("expected", ", or }", type.getNameOrType()));
                 }
                 break;
             }
@@ -334,13 +331,11 @@
         if (name != null) {
             expect(COLON);
             final Node value = jsonLiteral();
-            return new PropertyNode(source, propertyToken, value.getFinish(), name, value);
+            return new PropertyNode(source, propertyToken, value.getFinish(), name, value, null, null);
         }
 
         // Raise an error.
-        error(AbstractParser.message("expected", "string", type.getNameOrType()));
-
-        return null;
+        throw error(AbstractParser.message("expected", "string", type.getNameOrType()));
     }
 
 }
diff --git a/nashorn/src/jdk/nashorn/internal/parser/Parser.java b/nashorn/src/jdk/nashorn/internal/parser/Parser.java
index b810043..5873ede 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/Parser.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/Parser.java
@@ -54,24 +54,23 @@
 import static jdk.nashorn.internal.parser.TokenType.WHILE;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
-import java.util.Stack;
 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;
+import jdk.nashorn.internal.ir.BlockLexicalContext;
 import jdk.nashorn.internal.ir.BreakNode;
 import jdk.nashorn.internal.ir.BreakableNode;
 import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.CaseNode;
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ContinueNode;
-import jdk.nashorn.internal.ir.DoWhileNode;
 import jdk.nashorn.internal.ir.EmptyNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
@@ -84,6 +83,7 @@
 import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LineNumberNode;
 import jdk.nashorn.internal.ir.LiteralNode;
+import jdk.nashorn.internal.ir.LoopNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ObjectNode;
 import jdk.nashorn.internal.ir.PropertyKey;
@@ -117,9 +117,10 @@
     /** Is scripting mode. */
     private final boolean scripting;
 
-    private final LexicalContext lexicalContext = new LexicalContext();
     private List<Node> functionDeclarations;
 
+    private final BlockLexicalContext lc = new BlockLexicalContext();
+
     /** Namespace for function names where not explicitly given */
     private final Namespace namespace;
 
@@ -146,7 +147,7 @@
      */
     public Parser(final ScriptEnvironment env, final Source source, final ErrorManager errors, final boolean strict) {
         super(source, errors, strict);
-        this.env   = env;
+        this.env       = env;
         this.namespace = new Namespace(env.getNamespace());
         this.scripting = env._scripting;
     }
@@ -162,7 +163,7 @@
      * @return function node resulting from successful parse
      */
     public FunctionNode parse() {
-        return parse(RUN_SCRIPT.tag());
+        return parse(RUN_SCRIPT.symbolName());
     }
 
     /**
@@ -176,7 +177,7 @@
      */
     public FunctionNode parse(final String scriptName) {
         final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L;
-        LOG.info(this + " begin for '" + scriptName + "'");
+        LOG.info(this, " begin for '", scriptName, "'");
 
         try {
             stream = new TokenStream();
@@ -214,7 +215,7 @@
              final String end = this + " end '" + scriptName + "'";
              if (Timing.isEnabled()) {
                  Timing.accumulateTime(toString(), System.currentTimeMillis() - t0);
-                 LOG.info(end + "' in " + (System.currentTimeMillis() - t0) + " ms");
+                 LOG.info(end, "' in ", (System.currentTimeMillis() - t0), " ms");
              } else {
                  LOG.info(end);
              }
@@ -275,8 +276,7 @@
      */
     private Block newBlock() {
         final Block block = new Block(source, token, Token.descPosition(token));
-        lexicalContext.push(block);
-        return block;
+        return lc.push(block);
     }
 
     /**
@@ -285,36 +285,60 @@
      * @param ident Name of function.
      * @return New block.
      */
-    private FunctionNode newFunctionBlock(final IdentNode ident) {
+    private FunctionNode newFunctionNode(final long startToken, final IdentNode ident, final List<IdentNode> parameters, final FunctionNode.Kind kind) {
         // Build function name.
         final StringBuilder sb = new StringBuilder();
 
-        final FunctionNode parentFunction = getFunction();
-        if(parentFunction != null && !parentFunction.isProgram()) {
+        final FunctionNode parentFunction = lc.getCurrentFunction();
+        if (parentFunction != null && !parentFunction.isProgram()) {
             sb.append(parentFunction.getName()).append('$');
         }
 
-        sb.append(ident != null ? ident.getName() : FUNCTION_PREFIX.tag());
+        sb.append(ident != null ? ident.getName() : FUNCTION_PREFIX.symbolName());
         final String name = namespace.uniqueName(sb.toString());
-        assert parentFunction != null || name.equals(RUN_SCRIPT.tag())  : "name = " + name;// must not rename runScript().
+        assert parentFunction != null || name.equals(RUN_SCRIPT.symbolName())  : "name = " + name;// must not rename runScript().
+
+        int flags = 0;
+        if (parentFunction == null) {
+            flags |= FunctionNode.IS_PROGRAM;
+        }
+        if (isStrictMode) {
+            flags |= FunctionNode.IS_STRICT;
+        }
 
         // Start new block.
-        final FunctionNode functionBlock = new FunctionNode(source, token, Token.descPosition(token), namespace, ident, name);
-        if(parentFunction == null) {
-            functionBlock.setProgram();
-        }
-        functionBlock.setStrictMode(isStrictMode);
-        functionBlock.setState(errors.hasErrors() ? CompilationState.PARSE_ERROR : CompilationState.PARSED);
-        lexicalContext.push(functionBlock);
+        FunctionNode functionNode =
+            new FunctionNode(
+                source,
+                token,
+                Token.descPosition(token),
+                startToken,
+                namespace,
+                ident,
+                name,
+                parameters,
+                kind,
+                flags);
 
-        return functionBlock;
+        functionNode = functionNode.setState(lc, errors.hasErrors() ? CompilationState.PARSE_ERROR : CompilationState.PARSED);
+        lc.push(functionNode);
+        // Create new block, and just put it on the context stack, restoreFunctionNode() will associate it with the
+        // FunctionNode.
+        newBlock();
+        return functionNode;
     }
 
     /**
      * Restore the current block.
      */
-    private void restoreBlock(Block block) {
-        lexicalContext.pop(block);
+    private Block restoreBlock(final Block block) {
+        return lc.pop(block);//.setFlag(lc, flags);
+    }
+
+    private FunctionNode restoreFunctionNode(final FunctionNode functionNode, final long lastToken) {
+        final Block newBody = restoreBlock(lc.getFunctionBody(functionNode));
+
+        return lc.pop(functionNode).setBody(lc, newBody).setLastToken(lc, lastToken);
     }
 
     /**
@@ -323,23 +347,17 @@
      */
     private Block getBlock(final boolean needsBraces) {
         // Set up new block. Captures LBRACE.
-        final Block newBlock = newBlock();
+        Block newBlock = newBlock();
         try {
-            pushControlNode(newBlock);
-
             // Block opening brace.
             if (needsBraces) {
                 expect(LBRACE);
             }
+            // Accumulate block statements.
+            statementList();
 
-            try {
-                // Accumulate block statements.
-                statementList();
-            } finally {
-                popControlNode();
-            }
         } finally {
-            restoreBlock(newBlock);
+            newBlock = restoreBlock(newBlock);
         }
 
         final int possibleEnd = Token.descPosition(token) + Token.descLength(token);
@@ -363,15 +381,12 @@
             return getBlock(true);
         }
         // Set up new block. Captures first token.
-        final Block newBlock = newBlock();
-
+        Block newBlock = newBlock();
         try {
-            // Accumulate statements.
             statement();
         } finally {
-            restoreBlock(newBlock);
+            newBlock = restoreBlock(newBlock);
         }
-
         return newBlock;
     }
 
@@ -382,11 +397,8 @@
     private void detectSpecialFunction(final IdentNode ident) {
         final String name = ident.getName();
 
-        if (EVAL.tag().equals(name)) {
-            final Iterator<FunctionNode> it = lexicalContext.getFunctions();
-            if(it.hasNext()) {
-                it.next().setHasEval(it);
-            }
+        if (EVAL.symbolName().equals(name)) {
+            markWithOrEval(lc, FunctionNode.HAS_EVAL);
         }
     }
 
@@ -397,8 +409,8 @@
     private void detectSpecialProperty(final IdentNode ident) {
         final String name = ident.getName();
 
-        if (ARGUMENTS.tag().equals(name)) {
-            getFunction().setUsesArguments();
+        if (ARGUMENTS.symbolName().equals(name)) {
+            lc.setFlag(lc.getCurrentFunction(), FunctionNode.USES_ARGUMENTS);
         }
     }
 
@@ -439,7 +451,7 @@
                   lhs instanceof IndexNode ||
                   lhs instanceof IdentNode)) {
                 if (env._early_lvalue_error) {
-                    error(JSErrorType.REFERENCE_ERROR, AbstractParser.message("invalid.lvalue"), lhs.getToken());
+                    throw error(JSErrorType.REFERENCE_ERROR, AbstractParser.message("invalid.lvalue"), lhs.getToken());
                 }
                 return referenceError(lhs, rhs);
             }
@@ -469,144 +481,14 @@
      * @return           Reduced expression.
      */
     private Node incDecExpression(final long firstToken, final TokenType tokenType, final Node expression, final boolean isPostfix) {
-        long incDecToken = firstToken;
         if (isPostfix) {
-            incDecToken = Token.recast(incDecToken, tokenType == DECPREFIX ? DECPOSTFIX : INCPOSTFIX);
+            return new UnaryNode(source, Token.recast(firstToken, tokenType == DECPREFIX ? DECPOSTFIX : INCPOSTFIX), expression.getStart(), Token.descPosition(firstToken) + Token.descLength(firstToken), expression);
         }
 
-        final UnaryNode node = new UnaryNode(source, incDecToken, expression);
-        if (isPostfix) {
-            node.setStart(expression.getStart());
-            node.setFinish(Token.descPosition(incDecToken) + Token.descLength(incDecToken));
-        }
-
-        return node;
+        return new UnaryNode(source, firstToken, expression);
     }
 
     /**
-     * Find a label node in the label stack.
-     * @param ident Ident to find.
-     * @return null or the found label node.
-     */
-    private LabelNode findLabel(final IdentNode ident) {
-        for (final LabelNode labelNode : getFunction().getLabelStack()) {
-            if (labelNode.getLabel().equals(ident)) {
-                return labelNode;
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Add a label to the label stack.
-     * @param labelNode Label to add.
-     */
-    private void pushLabel(final LabelNode labelNode) {
-        getFunction().getLabelStack().push(labelNode);
-    }
-
-    /**
-      * Remove a label from the label stack.
-      */
-    private void popLabel() {
-        getFunction().getLabelStack().pop();
-    }
-
-    /**
-     * Track the current nesting of controls for break and continue.
-     * @param node For, while, do or switch node.
-     */
-    private void pushControlNode(final Node node) {
-        final boolean isLoop = node instanceof WhileNode;
-        final boolean isBreakable = node instanceof BreakableNode || node instanceof Block;
-        final FunctionNode function = getFunction();
-        function.getControlStack().push(node);
-
-        for (final LabelNode labelNode : function.getLabelStack()) {
-            if (isBreakable && labelNode.getBreakNode() == null) {
-                labelNode.setBreakNode(node);
-            }
-
-            if (isLoop && labelNode.getContinueNode() == null) {
-                labelNode.setContinueNode(node);
-            }
-        }
-    }
-
-    /**
-     * Finish with control.
-     */
-    private void popControlNode() {
-        // Get control stack.
-        final Stack<Node> controlStack = getFunction().getControlStack();
-
-        // Can be empty if missing brace.
-        if (!controlStack.isEmpty()) {
-            controlStack.pop();
-        }
-    }
-
-    private void popControlNode(final Node node) {
-        // Get control stack.
-        final Stack<Node> controlStack = getFunction().getControlStack();
-
-        // Can be empty if missing brace.
-        if (!controlStack.isEmpty() && controlStack.peek() == node) {
-            controlStack.pop();
-        }
-    }
-
-    private boolean isInWithBlock() {
-        final Stack<Node> controlStack = getFunction().getControlStack();
-        for (int i = controlStack.size() - 1; i >= 0; i--) {
-            final Node node = controlStack.get(i);
-
-            if (node instanceof WithNode) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-
-    private <T extends Node> T findControl(final Class<T> ctype) {
-        final Stack<Node> controlStack = getFunction().getControlStack();
-        for (int i = controlStack.size() - 1; i >= 0; i--) {
-            final Node node = controlStack.get(i);
-
-            if (ctype.isAssignableFrom(node.getClass())) {
-                return ctype.cast(node);
-            }
-        }
-
-        return null;
-    }
-
-    private <T extends Node> List<T> findControls(final Class<T> ctype, final Node to) {
-        final List<T> nodes = new ArrayList<>();
-        final Stack<Node> controlStack = getFunction().getControlStack();
-        for (int i = controlStack.size() - 1; i >= 0; i--) {
-            final Node node = controlStack.get(i);
-
-            if (to == node) {
-                break; //stop looking
-            }
-
-            if (ctype.isAssignableFrom(node.getClass())) {
-                nodes.add(ctype.cast(node));
-            }
-        }
-
-        return nodes;
-    }
-
-    private <T extends Node> int countControls(final Class<T> ctype, final Node to) {
-        return findControls(ctype, to).size();
-    }
-
-
-    /**
      * -----------------------------------------------------------------------
      *
      * Grammar based on
@@ -630,18 +512,23 @@
         final long functionToken = Token.toDesc(FUNCTION, 0, source.getLength());
         // Set up the script to append elements.
 
-        final FunctionNode script = newFunctionBlock(new IdentNode(source, functionToken, Token.descPosition(functionToken), scriptName));
+        FunctionNode script = newFunctionNode(
+            functionToken,
+            new IdentNode(source, functionToken, Token.descPosition(functionToken), scriptName),
+            new ArrayList<IdentNode>(),
+            FunctionNode.Kind.SCRIPT);
 
-        script.setKind(FunctionNode.Kind.SCRIPT);
-        script.setFirstToken(functionToken);
         functionDeclarations = new ArrayList<>();
         sourceElements();
-        script.prependStatements(functionDeclarations);
+        addFunctionDeclarations(script);
         functionDeclarations = null;
+
         expect(EOF);
-        script.setLastToken(token);
+
         script.setFinish(source.getLength() - 1);
 
+        script = restoreFunctionNode(script, token); //commit code
+        script = script.setBody(lc, script.getBody().setNeedsScope(lc));
         return script;
     }
 
@@ -670,24 +557,6 @@
     }
 
     /**
-     * Return last node in a statement list.
-     *
-     * @param statements Statement list.
-     *
-     * @return Last (non-debug) statement or null if empty block.
-     */
-    private static Node lastStatement(final List<Node> statements) {
-        for (int lastIndex = statements.size() - 1; lastIndex >= 0; lastIndex--) {
-            final Node node = statements.get(lastIndex);
-            if (!node.isDebug()) {
-                return node;
-            }
-        }
-
-        return null;
-    }
-
-    /**
      * SourceElements :
      *      SourceElement
      *      SourceElements SourceElement
@@ -716,7 +585,7 @@
                     // check for directive prologues
                     if (checkDirective) {
                         // skip any debug statement like line number to get actual first line
-                        final Node lastStatement = lastStatement(getBlock().getStatements());
+                        final Node lastStatement = lc.getLastStatement();
 
                         // get directive prologue, if any
                         final String directive = getDirective(lastStatement);
@@ -736,8 +605,8 @@
                             // handle use strict directive
                             if ("use strict".equals(directive)) {
                                 isStrictMode = true;
-                                final FunctionNode function = getFunction();
-                                function.setStrictMode(true);
+                                final FunctionNode function = lc.getCurrentFunction();
+                                lc.setFlag(lc.getCurrentFunction(), FunctionNode.IS_STRICT);
 
                                 // We don't need to check these, if lexical environment is already strict
                                 if (!oldStrictMode && directiveStmts != null) {
@@ -759,7 +628,7 @@
                         }
                     }
                 } catch (final Exception e) {
-                    // Recover parsing.
+                    //recover parsing
                     recover(e);
                 }
 
@@ -807,13 +676,15 @@
             // As per spec (ECMA section 12), function declarations as arbitrary statement
             // is not "portable". Implementation can issue a warning or disallow the same.
             if (isStrictMode && !topLevel) {
-                error(AbstractParser.message("strict.no.func.here"), token);
+                throw error(AbstractParser.message("strict.no.func.here"), token);
             }
             functionExpression(true, topLevel);
             return;
         }
 
-        getBlock().addStatement(lineNumberNode);
+        if (lineNumberNode != null) {
+            appendStatement(lineNumberNode);
+        }
 
         switch (type) {
         case LBRACE:
@@ -893,13 +764,9 @@
      * Parse a statement block.
      */
     private void block() {
-        // Get statements in block.
         final Block newBlock = getBlock(true);
-
         // Force block execution.
-        final ExecuteNode executeNode = new ExecuteNode(source, newBlock.getToken(), finish, newBlock);
-
-        getBlock().addStatement(executeNode);
+        appendStatement(new ExecuteNode(source, newBlock.getToken(), finish, newBlock));
     }
 
     /**
@@ -942,7 +809,7 @@
             switch (ident.getName()) {
             case "eval":
             case "arguments":
-                error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken());
+                throw error(AbstractParser.message("strict.name", ident.getName(), contextString), ident.getToken());
             default:
                 break;
             }
@@ -995,8 +862,7 @@
             // Allocate var node.
             final VarNode var = new VarNode(source, varToken, finish, name, init);
             vars.add(var);
-            // Add to current block.
-            getBlock().addStatement(var);
+            appendStatement(var);
 
             if (type != COMMARIGHT) {
                 break;
@@ -1009,7 +875,7 @@
             boolean semicolon = type == SEMICOLON;
             endOfLine();
             if (semicolon) {
-                getBlock().setFinish(finish);
+                lc.getCurrentBlock().setFinish(finish);
             }
         }
 
@@ -1026,8 +892,7 @@
      */
     private void emptyStatement() {
         if (env._empty_statements) {
-            getBlock().addStatement(new EmptyNode(source, token,
-                    Token.descPosition(token) + Token.descLength(token)));
+            appendStatement(new EmptyNode(source, token, Token.descPosition(token) + Token.descLength(token)));
         }
 
         // SEMICOLON checked in caller.
@@ -1052,7 +917,7 @@
         ExecuteNode executeNode = null;
         if (expression != null) {
             executeNode = new ExecuteNode(source, expressionToken, finish, expression);
-            getBlock().addStatement(executeNode);
+            appendStatement(executeNode);
         } else {
             expect(null);
         }
@@ -1061,7 +926,7 @@
 
         if (executeNode != null) {
             executeNode.setFinish(finish);
-            getBlock().setFinish(finish);
+            lc.getCurrentBlock().setFinish(finish);
         }
     }
 
@@ -1081,29 +946,17 @@
         next();
 
         expect(LPAREN);
-
-        // Get the test expression.
         final Node test = expression();
-
         expect(RPAREN);
-
-        // Get the pass statement.
         final Block pass = getStatement();
 
-        // Assume no else.
         Block fail = null;
-
         if (type == ELSE) {
             next();
-
-            // Get the else block.
             fail = getStatement();
         }
 
-        // Construct and add new if node.
-        final IfNode ifNode = new IfNode(source, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail);
-
-        getBlock().addStatement(ifNode);
+        appendStatement(new IfNode(source, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail));
     }
 
     /**
@@ -1120,12 +973,12 @@
      */
     private void forStatement() {
         // Create FOR node, capturing FOR token.
-        final ForNode forNode = new ForNode(source, token, Token.descPosition(token));
+        ForNode forNode = new ForNode(source, token, Token.descPosition(token), null, null, null, null, ForNode.IS_FOR);
 
-        pushControlNode(forNode);
 
         // Set up new block for scope of vars. Captures first token.
-        final Block outer = newBlock();
+        Block outer = newBlock();
+        lc.push(forNode);
 
         try {
             // FOR tested in caller.
@@ -1134,31 +987,97 @@
             // Nashorn extension: for each expression.
             // iterate property values rather than property names.
             if (!env._no_syntax_extensions && type == IDENT && "each".equals(getValue())) {
-                forNode.setIsForEach();
+                forNode = forNode.setIsForEach(lc);
                 next();
             }
 
             expect(LPAREN);
 
-            /// Capture control information.
-            forControl(forNode);
+            List<VarNode> vars = null;
+
+            switch (type) {
+            case VAR:
+                // Var statements captured in for outer block.
+                vars = variableStatement(false);
+                break;
+            case SEMICOLON:
+                break;
+            default:
+                final Node expression = expression(unaryExpression(), COMMARIGHT.getPrecedence(), true);
+                forNode = forNode.setInit(lc, expression);
+                break;
+            }
+
+            switch (type) {
+            case SEMICOLON:
+                // for (init; test; modify)
+                expect(SEMICOLON);
+                if (type != SEMICOLON) {
+                    forNode = forNode.setTest(lc, expression());
+                }
+                expect(SEMICOLON);
+                if (type != RPAREN) {
+                    forNode = forNode.setModify(lc, expression());
+                }
+                break;
+
+            case IN:
+                forNode = forNode.setIsForIn(lc);
+                if (vars != null) {
+                    // for (var i in obj)
+                    if (vars.size() == 1) {
+                        forNode = forNode.setInit(lc, new IdentNode(vars.get(0).getName()));
+                    } else {
+                        // for (var i, j in obj) is invalid
+                        throw error(AbstractParser.message("many.vars.in.for.in.loop"), vars.get(1).getToken());
+                    }
+
+                } else {
+                    // for (expr in obj)
+                    final Node init = forNode.getInit();
+                    assert init != null : "for..in init expression can not be null here";
+
+                    // check if initial expression is a valid L-value
+                    if (!(init instanceof AccessNode ||
+                          init instanceof IndexNode ||
+                          init instanceof IdentNode)) {
+                        throw error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken());
+                    }
+
+                    if (init instanceof IdentNode) {
+                        if (!checkIdentLValue((IdentNode)init)) {
+                            throw error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken());
+                        }
+                        verifyStrictIdent((IdentNode)init, "for-in iterator");
+                    }
+                }
+
+                next();
+
+                // Get the collection expression.
+                forNode = forNode.setModify(lc, expression());
+                break;
+
+            default:
+                expect(SEMICOLON);
+                break;
+            }
 
             expect(RPAREN);
 
             // Set the for body.
             final Block body = getStatement();
-            forNode.setBody(body);
+            forNode = forNode.setBody(lc, body);
             forNode.setFinish(body.getFinish());
             outer.setFinish(body.getFinish());
 
-            // Add for to current block.
-            getBlock().addStatement(forNode);
+            appendStatement(forNode);
         } finally {
-            restoreBlock(outer);
-            popControlNode();
+            lc.pop(forNode);
+            outer = restoreBlock(outer);
         }
 
-        getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
+        appendStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
      }
 
     /**
@@ -1175,87 +1094,7 @@
      * comprehensions.
      * @param forNode Owning FOR.
      */
-    private void forControl(final ForNode forNode) {
-        List<VarNode> vars = null;
 
-        switch (type) {
-        case VAR:
-            // Var statements captured in for outer block.
-            vars = variableStatement(false);
-            break;
-
-        case SEMICOLON:
-            break;
-
-        default:
-            final Node expression = expression(unaryExpression(), COMMARIGHT.getPrecedence(), true);
-            forNode.setInit(expression);
-        }
-
-        switch (type) {
-        case SEMICOLON:
-            // for (init; test; modify)
-            expect(SEMICOLON);
-
-            // Get the test expression.
-            if (type != SEMICOLON) {
-                forNode.setTest(expression());
-            }
-
-            expect(SEMICOLON);
-
-            // Get the modify expression.
-            if (type != RPAREN) {
-                final Node expression = expression();
-                forNode.setModify(expression);
-            }
-
-            break;
-
-        case IN:
-            forNode.setIsForIn();
-
-            if (vars != null) {
-                // for (var i in obj)
-                if (vars.size() == 1) {
-                    forNode.setInit(new IdentNode(vars.get(0).getName()));
-                } else {
-                    // for (var i, j in obj) is invalid
-                    error(AbstractParser.message("many.vars.in.for.in.loop"), vars.get(1).getToken());
-                }
-
-            } else {
-                // for (expr in obj)
-                final Node init = forNode.getInit();
-                assert init != null : "for..in init expression can not be null here";
-
-                // check if initial expression is a valid L-value
-                if (!(init instanceof AccessNode ||
-                      init instanceof IndexNode ||
-                      init instanceof IdentNode)) {
-                    error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken());
-                }
-
-                if (init instanceof IdentNode) {
-                    if (!checkIdentLValue((IdentNode)init)) {
-                        error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken());
-                    }
-                    verifyStrictIdent((IdentNode)init, "for-in iterator");
-                }
-            }
-
-            next();
-
-            // Get the collection expression.
-            forNode.setModify(expression());
-            break;
-
-        default:
-            expect(SEMICOLON);
-            break;
-        }
-
-    }
 
     /**
      * ...IterationStatement :
@@ -1274,27 +1113,17 @@
         next();
 
         // Construct WHILE node.
-        final WhileNode whileNode = new WhileNode(source, whileToken, Token.descPosition(whileToken));
-        pushControlNode(whileNode);
+        WhileNode whileNode = new WhileNode(source, whileToken, Token.descPosition(whileToken), false);
+        lc.push(whileNode);
 
         try {
             expect(LPAREN);
-
-            // Get the test expression.
-            final Node test = expression();
-            whileNode.setTest(test);
-
+            whileNode = whileNode.setTest(lc, expression());
             expect(RPAREN);
-
-            // Get WHILE body.
-            final Block statements = getStatement();
-            whileNode.setBody(statements);
-            whileNode.setFinish(statements.getFinish());
-
-            // Add WHILE node.
-            getBlock().addStatement(whileNode);
+            whileNode = whileNode.setBody(lc, getStatement());
+            appendStatement(whileNode);
         } finally {
-            popControlNode();
+            lc.pop(whileNode);
         }
     }
 
@@ -1314,34 +1143,25 @@
         // DO tested in the caller.
         next();
 
-        final WhileNode doWhileNode = new DoWhileNode(source, doToken, Token.descPosition(doToken));
-        pushControlNode(doWhileNode);
+        WhileNode doWhileNode = new WhileNode(source, doToken, Token.descPosition(doToken), true);
+        lc.push(doWhileNode);
 
         try {
            // Get DO body.
-            final Block statements = getStatement();
-            doWhileNode.setBody(statements);
+            doWhileNode = doWhileNode.setBody(lc, getStatement());
 
             expect(WHILE);
-
             expect(LPAREN);
-
-            // Get the test expression.
-            final Node test = expression();
-            doWhileNode.setTest(test);
-
+            doWhileNode = doWhileNode.setTest(lc, expression());
             expect(RPAREN);
 
             if (type == SEMICOLON) {
                 endOfLine();
             }
-
             doWhileNode.setFinish(finish);
-
-            // Add DO node.
-            getBlock().addStatement(doWhileNode);
+            appendStatement(doWhileNode);
         } finally {
-            popControlNode();
+            lc.pop(doWhileNode);
         }
     }
 
@@ -1370,28 +1190,26 @@
 
         default:
             final IdentNode ident = getIdent();
-            labelNode = findLabel(ident);
+            labelNode = lc.findLabel(ident.getName());
 
             if (labelNode == null) {
-                error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken());
+                throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken());
             }
 
             break;
         }
 
-        final Node targetNode = labelNode != null ? labelNode.getContinueNode() : findControl(WhileNode.class);
+        final IdentNode label = labelNode == null ? null : labelNode.getLabel();
+        final LoopNode targetNode = lc.getContinueTo(label);
 
         if (targetNode == null) {
-            error(AbstractParser.message("illegal.continue.stmt"), continueToken);
+            throw error(AbstractParser.message("illegal.continue.stmt"), continueToken);
         }
 
         endOfLine();
 
         // Construct and add CONTINUE node.
-        final ContinueNode continueNode = new ContinueNode(source, continueToken, finish, labelNode, targetNode, findControl(TryNode.class));
-        continueNode.setScopeNestingLevel(countControls(WithNode.class, targetNode));
-
-        getBlock().addStatement(continueNode);
+        appendStatement(new ContinueNode(source, continueToken, finish, label == null ? null : new IdentNode(label)));
     }
 
     /**
@@ -1418,28 +1236,27 @@
 
         default:
             final IdentNode ident = getIdent();
-            labelNode = findLabel(ident);
+            labelNode = lc.findLabel(ident.getName());
 
             if (labelNode == null) {
-                error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken());
+                throw error(AbstractParser.message("undefined.label", ident.getName()), ident.getToken());
             }
 
             break;
         }
 
-        final Node targetNode = labelNode != null ? labelNode.getBreakNode() : findControl(BreakableNode.class);
-
+        //either an explicit label - then get its node or just a "break" - get first breakable
+        //targetNode is what we are breaking out from.
+        final IdentNode label = labelNode == null ? null : labelNode.getLabel();
+        final BreakableNode targetNode = lc.getBreakable(label);
         if (targetNode == null) {
-            error(AbstractParser.message("illegal.break.stmt"), breakToken);
+            throw error(AbstractParser.message("illegal.break.stmt"), breakToken);
         }
 
         endOfLine();
 
         // Construct and add BREAK node.
-        final BreakNode breakNode = new BreakNode(source, breakToken, finish, labelNode, targetNode, findControl(TryNode.class));
-        breakNode.setScopeNestingLevel(countControls(WithNode.class, targetNode));
-
-        getBlock().addStatement(breakNode);
+        appendStatement(new BreakNode(source, breakToken, finish, label == null ? null : new IdentNode(label)));
     }
 
     /**
@@ -1452,8 +1269,8 @@
      */
     private void returnStatement() {
         // check for return outside function
-        if (getFunction().getKind() == FunctionNode.Kind.SCRIPT) {
-            error(AbstractParser.message("invalid.return"));
+        if (lc.getCurrentFunction().getKind() == FunctionNode.Kind.SCRIPT) {
+            throw error(AbstractParser.message("invalid.return"));
         }
 
         // Capture RETURN token.
@@ -1478,8 +1295,7 @@
         endOfLine();
 
         // Construct and add RETURN node.
-        final ReturnNode returnNode = new ReturnNode(source, returnToken, finish, expression, findControl(TryNode.class));
-        getBlock().addStatement(returnNode);
+        appendStatement(new ReturnNode(source, returnToken, finish, expression));
     }
 
     /**
@@ -1513,8 +1329,7 @@
         endOfLine();
 
         // Construct and add YIELD node.
-        final ReturnNode yieldNode = new ReturnNode(source, yieldToken, finish, expression, findControl(TryNode.class));
-        getBlock().addStatement(yieldNode);
+        appendStatement(new ReturnNode(source, yieldToken, finish, expression));
     }
 
     /**
@@ -1533,35 +1348,24 @@
 
         // ECMA 12.10.1 strict mode restrictions
         if (isStrictMode) {
-            error(AbstractParser.message("strict.no.with"), withToken);
+            throw error(AbstractParser.message("strict.no.with"), withToken);
         }
 
         // Get WITH expression.
-        final WithNode withNode = new WithNode(source, withToken, finish, null, null);
-        final Iterator<FunctionNode> it = lexicalContext.getFunctions();
-        if(it.hasNext()) {
-            it.next().setHasWith(it);
-        }
+        WithNode withNode = new WithNode(source, withToken, finish);
+        markWithOrEval(lc, FunctionNode.HAS_WITH);
 
         try {
-            pushControlNode(withNode);
-
+            lc.push(withNode);
             expect(LPAREN);
-
-            final Node expression = expression();
-            withNode.setExpression(expression);
-
+            withNode = withNode.setExpression(lc, expression());
             expect(RPAREN);
-
-            // Get WITH body.
-            final Block statements = getStatement();
-            withNode.setBody(statements);
-            withNode.setFinish(finish);
+            withNode = withNode.setBody(lc, getStatement());
         } finally {
-            popControlNode(withNode);
+            lc.pop(withNode);
         }
 
-        getBlock().addStatement(withNode);
+        appendStatement(withNode);
     }
 
     /**
@@ -1587,22 +1391,17 @@
      * Parse SWITCH statement.
      */
     private void switchStatement() {
-        // Capture SWITCH token.
         final long switchToken = token;
         // SWITCH tested in caller.
         next();
 
         // Create and add switch statement.
-        final SwitchNode switchNode = new SwitchNode(source, switchToken, Token.descPosition(switchToken));
-        pushControlNode(switchNode);
+        SwitchNode switchNode = new SwitchNode(source, switchToken, Token.descPosition(switchToken), null, new ArrayList<CaseNode>(), null);
+        lc.push(switchNode);
 
         try {
             expect(LPAREN);
-
-            // Get switch expression.
-            final Node switchExpression = expression();
-            switchNode.setExpression(switchExpression);
-
+            switchNode = switchNode.setExpression(lc, expression());
             expect(RPAREN);
 
             expect(LBRACE);
@@ -1619,19 +1418,14 @@
                 switch (type) {
                 case CASE:
                     next();
-
-                    // Get case expression.
                     caseExpression = expression();
-
                     break;
 
                 case DEFAULT:
                     if (defaultCase != null) {
-                        error(AbstractParser.message("duplicate.default.in.switch"));
+                        throw error(AbstractParser.message("duplicate.default.in.switch"));
                     }
-
                     next();
-
                     break;
 
                 default:
@@ -1654,16 +1448,13 @@
                 cases.add(caseNode);
             }
 
-            switchNode.setCases(cases);
-            switchNode.setDefaultCase(defaultCase);
-
+            switchNode = switchNode.setCases(lc, cases, defaultCase);
             next();
-
             switchNode.setFinish(finish);
 
-            getBlock().addStatement(switchNode);
+            appendStatement(switchNode);
         } finally {
-            popControlNode();
+            lc.pop(switchNode);
         }
     }
 
@@ -1683,23 +1474,19 @@
 
         expect(COLON);
 
-        if (findLabel(ident) != null) {
-            error(AbstractParser.message("duplicate.label", ident.getName()), labelToken);
+        if (lc.findLabel(ident.getName()) != null) {
+            throw error(AbstractParser.message("duplicate.label", ident.getName()), labelToken);
         }
 
+        LabelNode labelNode = new LabelNode(source, labelToken, finish, ident, null);
         try {
-            // Create and add label.
-            final LabelNode labelNode = new LabelNode(source, labelToken, finish, ident, null);
-            pushLabel(labelNode);
-            // Get and save body.
-            final Block statements = getStatement();
-            labelNode.setBody(statements);
+            lc.push(labelNode);
+            labelNode = labelNode.setBody(lc, getStatement());
             labelNode.setFinish(finish);
-
-            getBlock().addStatement(labelNode);
+            appendStatement(labelNode);
         } finally {
-            // Remove label.
-            popLabel();
+            assert lc.peek() instanceof LabelNode;
+            lc.pop(labelNode);
         }
     }
 
@@ -1732,14 +1519,12 @@
         }
 
         if (expression == null) {
-            error(AbstractParser.message("expected.operand", type.getNameOrType()));
+            throw error(AbstractParser.message("expected.operand", type.getNameOrType()));
         }
 
         endOfLine();
 
-        // Construct and add THROW node.
-        final ThrowNode throwNode = new ThrowNode(source, throwToken, finish, expression, findControl(TryNode.class));
-        getBlock().addStatement(throwNode);
+        appendStatement(new ThrowNode(source, throwToken, finish, expression));
     }
 
     /**
@@ -1766,28 +1551,18 @@
         next();
 
         // Container block needed to act as target for labeled break statements
-        final Block outer = newBlock();
-        pushControlNode(outer);
+        Block outer = newBlock();
 
         // Create try.
-        final TryNode tryNode = new TryNode(source, tryToken, Token.descPosition(tryToken), findControl(TryNode.class));
-        pushControlNode(tryNode);
 
         try {
-            // Get TRY body.
-            final Block tryBody = getBlock(true);
-
-            // Prepare to accumulate catches.
+            final Block       tryBody     = getBlock(true);
             final List<Block> catchBlocks = new ArrayList<>();
 
             while (type == CATCH) {
-                // Capture CATCH token.
                 final long catchToken = token;
                 next();
-
                 expect(LPAREN);
-
-                // Get exception ident.
                 final IdentNode exception = getIdent();
 
                 // ECMA 12.4.1 strict mode restrictions
@@ -1795,28 +1570,23 @@
 
                 // Check for conditional catch.
                 Node ifExpression = null;
-
                 if (type == IF) {
                     next();
-
                     // Get the exception condition.
                     ifExpression = expression();
                 }
 
                 expect(RPAREN);
 
-                final Block catchBlock = newBlock();
+                Block catchBlock = newBlock();
                 try {
-
                     // Get CATCH body.
                     final Block catchBody = getBlock(true);
-
-                    // Create and add catch.
                     final CatchNode catchNode = new CatchNode(source, catchToken, finish, exception, ifExpression, catchBody);
-                    getBlock().addStatement(catchNode);
-                    catchBlocks.add(catchBlock);
+                    appendStatement(catchNode);
                 } finally {
-                    restoreBlock(catchBlock);
+                    catchBlock = restoreBlock(catchBlock);
+                    catchBlocks.add(catchBlock);
                 }
 
                 // If unconditional catch then should to be the end.
@@ -1825,38 +1595,32 @@
                 }
             }
 
-            popControlNode();
-
             // Prepare to capture finally statement.
             Block finallyStatements = null;
 
             if (type == FINALLY) {
                 next();
-
-                // Get FINALLY body.
                 finallyStatements = getBlock(true);
             }
 
             // Need at least one catch or a finally.
             if (catchBlocks.isEmpty() && finallyStatements == null) {
-                error(AbstractParser.message("missing.catch.or.finally"), tryToken);
+                throw error(AbstractParser.message("missing.catch.or.finally"), tryToken);
             }
 
-            tryNode.setBody(tryBody);
-            tryNode.setCatchBlocks(catchBlocks);
-            tryNode.setFinallyBody(finallyStatements);
+            final TryNode tryNode = new TryNode(source, tryToken, Token.descPosition(tryToken), tryBody, catchBlocks, finallyStatements);
+            // Add try.
+            assert lc.peek() == outer;
+            appendStatement(tryNode);
+
             tryNode.setFinish(finish);
             outer.setFinish(finish);
 
-            // Add try.
-            outer.addStatement(tryNode);
         } finally {
-            popControlNode(tryNode);
-            restoreBlock(outer);
-            popControlNode(outer);
+            outer = restoreBlock(outer);
         }
 
-        getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
+        appendStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
     }
 
     /**
@@ -1872,11 +1636,8 @@
         final long debuggerToken = token;
         // DEBUGGER tested in caller.
         next();
-
         endOfLine();
-
-        final RuntimeNode runtimeNode = new RuntimeNode(source, debuggerToken, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Node>());
-        getBlock().addStatement(runtimeNode);
+        appendStatement(new RuntimeNode(source, debuggerToken, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Node>()));
     }
 
     /**
@@ -1912,7 +1673,7 @@
             return ident;
         case OCTAL:
             if (isStrictMode) {
-               error(AbstractParser.message("strict.no.octal"), token);
+               throw error(AbstractParser.message("strict.no.octal"), token);
             }
         case STRING:
         case ESCSTRING:
@@ -1981,7 +1742,7 @@
         // Skip ending of edit string expression.
         expect(RBRACE);
 
-        return new CallNode(source, primaryToken, finish, execIdent, arguments);
+        return new CallNode(source, primaryToken, finish, execIdent, arguments, 0);
     }
 
     /**
@@ -2036,7 +1797,7 @@
 
             default:
                 if (!elision) {
-                    error(AbstractParser.message("expected.comma", type.getNameOrType()));
+                    throw error(AbstractParser.message("expected.comma", type.getNameOrType()));
                 }
                 // Add expression element.
                 final Node expression = assignmentExpression(false);
@@ -2077,8 +1838,8 @@
 
         // Object context.
         // Prepare to accumulate elements.
-        final List<Node> elements = new ArrayList<>();
-        final Map<Object, PropertyNode> map = new HashMap<>();
+       // final List<Node> elements = new ArrayList<>();
+        final Map<String, PropertyNode> map = new LinkedHashMap<>();
 
         // Create a block for the object literal.
             boolean commaSeen = true;
@@ -2096,25 +1857,30 @@
 
                 default:
                     if (!commaSeen) {
-                        error(AbstractParser.message("expected.comma", type.getNameOrType()));
-                }
+                        throw error(AbstractParser.message("expected.comma", type.getNameOrType()));
+                    }
 
-                commaSeen = false;
-                // Get and add the next property.
-                final PropertyNode property = propertyAssignment();
-                final Object key = property.getKeyName();
-                final PropertyNode existingProperty = map.get(key);
+                    commaSeen = false;
+                    // Get and add the next property.
+                    final PropertyNode property = propertyAssignment();
+                    final String key = property.getKeyName();
+                    final PropertyNode existingProperty = map.get(key);
 
-                if (existingProperty != null) {
+                    if (existingProperty == null) {
+                        map.put(key, property);
+                       // elements.add(property);
+                        break;
+                    }
+
                     // ECMA section 11.1.5 Object Initialiser
                     // point # 4 on property assignment production
-                    final Node value  = property.getValue();
-                    final Node getter = property.getGetter();
-                    final Node setter = property.getSetter();
+                    final Node         value  = property.getValue();
+                    final FunctionNode getter = property.getGetter();
+                    final FunctionNode setter = property.getSetter();
 
-                    final Node prevValue  = existingProperty.getValue();
-                    final Node prevGetter = existingProperty.getGetter();
-                    final Node prevSetter = existingProperty.getSetter();
+                    final Node         prevValue  = existingProperty.getValue();
+                    final FunctionNode prevGetter = existingProperty.getGetter();
+                    final FunctionNode prevSetter = existingProperty.getSetter();
 
                     boolean redefinitionOk = true;
                     // ECMA 11.1.5 strict mode restrictions
@@ -2125,7 +1891,7 @@
                     }
 
                     final boolean isPrevAccessor = prevGetter != null || prevSetter != null;
-                    final boolean isAccessor = getter != null || setter != null;
+                    final boolean isAccessor     = getter != null     || setter != null;
 
                     // data property redefined as accessor property
                     if (prevValue != null && isAccessor) {
@@ -2145,40 +1911,33 @@
                     }
 
                     if (!redefinitionOk) {
-                        error(AbstractParser.message("property.redefinition", key.toString()), property.getToken());
+                        throw error(AbstractParser.message("property.redefinition", key.toString()), property.getToken());
                     }
 
+                    PropertyNode newProperty = existingProperty;
                     if (value != null) {
-                        final Node existingValue = existingProperty.getValue();
-
-                        if (existingValue == null) {
-                            existingProperty.setValue(value);
+                        if (prevValue == null) {
+                            map.put(key, newProperty = newProperty.setValue(value));
                         } else {
-                            final long propertyToken = Token.recast(existingProperty.getToken(), COMMARIGHT);
-                            existingProperty.setValue(new BinaryNode(source, propertyToken, existingValue, value));
+                            final long propertyToken = Token.recast(newProperty.getToken(), COMMARIGHT);
+                            map.put(key, newProperty = newProperty.setValue(new BinaryNode(source, propertyToken, prevValue, value)));
                         }
 
-                        existingProperty.setGetter(null);
-                        existingProperty.setSetter(null);
+                        map.put(key, newProperty = newProperty.setGetter(null).setSetter(null));
                     }
 
                     if (getter != null) {
-                        existingProperty.setGetter(getter);
+                        map.put(key, newProperty = newProperty.setGetter(getter));
                     }
 
                     if (setter != null) {
-                        existingProperty.setSetter(setter);
+                        map.put(key, newProperty = newProperty.setSetter(setter));
                     }
-                } else {
-                    map.put(key, property);
-                    elements.add(property);
-                }
-
-                break;
+                    break;
             }
         }
 
-        return new ObjectNode(source, objectToken, finish, elements);
+        return new ObjectNode(source, objectToken, finish, new ArrayList<Node>(map.values()));
     }
 
     /**
@@ -2198,7 +1957,7 @@
             return getIdent();
         case OCTAL:
             if (isStrictMode) {
-                error(AbstractParser.message("strict.no.octal"), token);
+                throw error(AbstractParser.message("strict.no.octal"), token);
             }
         case STRING:
         case ESCSTRING:
@@ -2235,8 +1994,6 @@
         final long propertyToken = token;
 
         FunctionNode functionNode;
-        List<IdentNode> parameters;
-        PropertyNode propertyNode;
         PropertyKey propertyName;
 
         if (type == IDENT) {
@@ -2253,11 +2010,8 @@
                     final IdentNode getNameNode = new IdentNode(source, ((Node)getIdent).getToken(), finish, "get " + getterName);
                     expect(LPAREN);
                     expect(RPAREN);
-                    parameters = new ArrayList<>();
-                    functionNode = functionBody(getSetToken, getNameNode, parameters, FunctionNode.Kind.GETTER);
-                    propertyNode = new PropertyNode(source, propertyToken, finish, getIdent, null);
-                    propertyNode.setGetter(functionNode);
-                    return propertyNode;
+                    functionNode = functionBody(getSetToken, getNameNode, new ArrayList<IdentNode>(), FunctionNode.Kind.GETTER);
+                    return new PropertyNode(source, propertyToken, finish, getIdent, null, functionNode, null);
 
                 case "set":
                     final PropertyKey setIdent = propertyName();
@@ -2267,12 +2021,10 @@
                     final IdentNode argIdent = getIdent();
                     verifyStrictIdent(argIdent, "setter argument");
                     expect(RPAREN);
-                    parameters = new ArrayList<>();
+                    List<IdentNode> parameters = new ArrayList<>();
                     parameters.add(argIdent);
                     functionNode = functionBody(getSetToken, setNameNode, parameters, FunctionNode.Kind.SETTER);
-                    propertyNode = new PropertyNode(source, propertyToken, finish, setIdent, null);
-                    propertyNode.setSetter(functionNode);
-                    return propertyNode;
+                    return new PropertyNode(source, propertyToken, finish, setIdent, null, null, functionNode);
 
                 default:
                     break;
@@ -2286,9 +2038,11 @@
 
         expect(COLON);
 
-        final Node value = assignmentExpression(false);
-        propertyNode =  new PropertyNode(source, propertyToken, finish, propertyName, value);
-        return propertyNode;
+        return new PropertyNode(source, propertyToken, finish, propertyName, assignmentExpression(false), null, null);
+    }
+
+    private int callNodeFlags() {
+        return lc.inWith() ? CallNode.IN_WITH_BLOCK : 0;
     }
 
     /**
@@ -2320,10 +2074,7 @@
                 detectSpecialFunction((IdentNode)lhs);
             }
 
-            lhs = new CallNode(source, callToken, finish, lhs, arguments);
-            if (isInWithBlock()) {
-                ((CallNode)lhs).setInWithBlock();
-            }
+            lhs = new CallNode(source, callToken, finish, lhs, arguments, callNodeFlags());
         }
 
 loop:
@@ -2337,10 +2088,7 @@
                 final List<Node> arguments = argumentList();
 
                 // Create call node.
-                lhs = new CallNode(source, callToken, finish, lhs, arguments);
-                if (isInWithBlock()) {
-                    ((CallNode)lhs).setInWithBlock();
-                }
+                lhs = new CallNode(source, callToken, finish, lhs, arguments, callNodeFlags());
 
                 break;
 
@@ -2419,10 +2167,7 @@
             arguments.add(objectLiteral());
         }
 
-        final CallNode callNode = new CallNode(source, constructor.getToken(), finish, constructor, arguments);
-        if (isInWithBlock()) {
-            callNode.setInWithBlock();
-        }
+        final CallNode callNode = new CallNode(source, constructor.getToken(), finish, constructor, arguments, callNodeFlags());
 
         return new UnaryNode(source, newToken, callNode);
     }
@@ -2482,8 +2227,7 @@
 
             case PERIOD:
                 if (lhs == null) {
-                    error(AbstractParser.message("expected.operand", type.getNameOrType()));
-                    return null;
+                    throw error(AbstractParser.message("expected.operand", type.getNameOrType()));
                 }
 
                 next();
@@ -2585,29 +2329,27 @@
         }
 
         expect(LPAREN);
-
         final List<IdentNode> parameters = formalParameterList();
-
         expect(RPAREN);
 
-        final FunctionNode functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL);
+        FunctionNode functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL);
 
         if (isStatement) {
-            if(topLevel) {
-                functionNode.setIsDeclared();
+            if (topLevel) {
+                functionNode = functionNode.setFlag(lc, FunctionNode.IS_DECLARED);
             }
-            if(ARGUMENTS.tag().equals(name.getName())) {
-                getFunction().setDefinesArguments();
+            if (ARGUMENTS.symbolName().equals(name.getName())) {
+                functionNode = functionNode.setFlag(lc, FunctionNode.DEFINES_ARGUMENTS);
             }
         }
 
         if (isAnonymous) {
-            functionNode.setIsAnonymous();
+            functionNode = functionNode.setFlag(lc, FunctionNode.IS_ANONYMOUS);
         }
 
         final int arity = parameters.size();
 
-        final boolean strict = functionNode.isStrictMode();
+        final boolean strict = functionNode.isStrict();
         if (arity > 1) {
             final HashSet<String> parametersSet = new HashSet<>(arity);
 
@@ -2615,39 +2357,37 @@
                 final IdentNode parameter = parameters.get(i);
                 String parameterName = parameter.getName();
 
-                if (ARGUMENTS.tag().equals(parameterName)) {
-                    functionNode.setDefinesArguments();
+                if (ARGUMENTS.symbolName().equals(parameterName)) {
+                    functionNode = functionNode.setFlag(lc, FunctionNode.DEFINES_ARGUMENTS);
                 }
 
                 if (parametersSet.contains(parameterName)) {
                     // redefinition of parameter name
                     if (strict) {
-                        error(AbstractParser.message("strict.param.redefinition", parameterName), parameter.getToken());
-                    } else {
-                        // rename in non-strict mode
-                        parameterName = functionNode.uniqueName(parameterName);
-                        final long parameterToken = parameter.getToken();
-                        parameters.set(i, new IdentNode(source, parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName)));
+                        throw error(AbstractParser.message("strict.param.redefinition", parameterName), parameter.getToken());
                     }
+                    // rename in non-strict mode
+                    parameterName = functionNode.uniqueName(parameterName);
+                    final long parameterToken = parameter.getToken();
+                    parameters.set(i, new IdentNode(source, parameterToken, Token.descPosition(parameterToken), functionNode.uniqueName(parameterName)));
                 }
 
                 parametersSet.add(parameterName);
             }
         } else if (arity == 1) {
-            if (ARGUMENTS.tag().equals(parameters.get(0).getName())) {
-                functionNode.setDefinesArguments();
+            if (ARGUMENTS.symbolName().equals(parameters.get(0).getName())) {
+                functionNode = functionNode.setFlag(lc, FunctionNode.DEFINES_ARGUMENTS);
             }
         }
 
         if (isStatement) {
-            final VarNode varNode = new VarNode(source, functionToken, finish, name, functionNode, true);
-            if(topLevel) {
+            final VarNode varNode = new VarNode(source, functionToken, finish, name, functionNode, VarNode.IS_STATEMENT);
+            if (topLevel) {
                 functionDeclarations.add(lineNumber);
                 functionDeclarations.add(varNode);
             } else {
-                final Block block = getBlock();
-                block.addStatement(lineNumber);
-                block.addStatement(varNode);
+                appendStatement(lineNumber);
+                appendStatement(varNode);
             }
         }
 
@@ -2701,13 +2441,11 @@
      */
     private FunctionNode functionBody(final long firstToken, final IdentNode ident, final List<IdentNode> parameters, final FunctionNode.Kind kind) {
         FunctionNode functionNode = null;
+        long lastToken = 0L;
 
         try {
             // Create a new function block.
-            functionNode = newFunctionBlock(ident);
-            functionNode.setParameters(parameters);
-            functionNode.setKind(kind);
-            functionNode.setFirstToken(firstToken);
+            functionNode = newFunctionNode(firstToken, ident, parameters, kind);
 
             // Nashorn extension: expression closures
             if (!env._no_syntax_extensions && type != LBRACE) {
@@ -2720,14 +2458,12 @@
 
                 // just expression as function body
                 final Node expr = expression();
-
+                assert lc.getCurrentBlock() == lc.getFunctionBody(functionNode);
                 // create a return statement - this creates code in itself and does not need to be
                 // wrapped into an ExecuteNode
-                final ReturnNode  returnNode = new ReturnNode(source, expr.getToken(), finish, expr, null);
-
-                // add the return statement
-                functionNode.addStatement(returnNode);
-                functionNode.setLastToken(token);
+                final ReturnNode returnNode = new ReturnNode(source, expr.getToken(), finish, expr);
+                appendStatement(returnNode);
+                lastToken = token;
                 functionNode.setFinish(Token.descPosition(token) + Token.descLength(token));
 
             } else {
@@ -2738,23 +2474,35 @@
                 functionDeclarations = new ArrayList<>();
                 try {
                     sourceElements();
-                    functionNode.prependStatements(functionDeclarations);
+                    addFunctionDeclarations(functionNode);
                 } finally {
                     functionDeclarations = prevFunctionDecls;
                 }
 
-                functionNode.setLastToken(token);
+                lastToken = token;
                 expect(RBRACE);
                 functionNode.setFinish(finish);
 
             }
         } finally {
-            restoreBlock(functionNode);
+            functionNode = restoreFunctionNode(functionNode, lastToken);
         }
-
         return functionNode;
     }
 
+    private void addFunctionDeclarations(final FunctionNode functionNode) {
+        assert lc.peek() == lc.getFunctionBody(functionNode);
+        VarNode lastDecl = null;
+        for (int i = functionDeclarations.size() - 1; i >= 0; i--) {
+            Node decl = functionDeclarations.get(i);
+            if (lastDecl == null && decl instanceof VarNode) {
+                decl = lastDecl = ((VarNode)decl).setFlag(VarNode.IS_LAST_FUNCTION_DECLARATION);
+                lc.setFlag(functionNode, FunctionNode.HAS_FUNCTION_DECLARATIONS);
+            }
+            prependStatement(decl);
+        }
+    }
+
     private RuntimeNode referenceError(final Node lhs, final Node rhs) {
         final ArrayList<Node> args = new ArrayList<>();
         args.add(lhs);
@@ -2764,9 +2512,7 @@
             args.add(rhs);
         }
         args.add(LiteralNode.newInstance(source, lhs.getToken(), lhs.getFinish(), lhs.toString()));
-        final RuntimeNode runtimeNode = new RuntimeNode(source, lhs.getToken(),
-                      lhs.getFinish(), RuntimeNode.Request.REFERENCE_ERROR, args);
-        return runtimeNode;
+        return new RuntimeNode(source, lhs.getToken(), lhs.getFinish(), RuntimeNode.Request.REFERENCE_ERROR, args);
     }
 
     /*
@@ -2815,19 +2561,7 @@
         case BIT_NOT:
         case NOT:
             next();
-
             final Node expr = unaryExpression();
-
-            /*
-             // Not sure if "delete <ident>" is a compile-time error or a
-             // runtime error in strict mode.
-
-             if (isStrictMode) {
-                 if (unaryTokenType == DELETE && expr instanceof IdentNode) {
-                     error(message("strict.cant.delete.ident", ((IdentNode)expr).getName()), expr.getToken());
-                 }
-             }
-             */
             return new UnaryNode(source, unaryToken, expr);
 
         case INCPREFIX:
@@ -2890,7 +2624,7 @@
         }
 
         if (expression == null) {
-            error(AbstractParser.message("expected.operand", type.getNameOrType()));
+            throw error(AbstractParser.message("expected.operand", type.getNameOrType()));
         }
 
         return expression;
@@ -2992,6 +2726,7 @@
         // Include commas in expression parsing.
         return expression(unaryExpression(), COMMARIGHT.getPrecedence(), false);
     }
+
     private Node expression(final Node exprLhs, final int minPrecedence, final boolean noIn) {
         // Get the precedence of the next operator.
         int precedence = type.getPrecedence();
@@ -3087,11 +2822,26 @@
         return "[JavaScript Parsing]";
     }
 
-    private Block getBlock() {
-        return lexicalContext.getCurrentBlock();
+    private static void markWithOrEval(final LexicalContext lc, int flag) {
+        final Iterator<FunctionNode> iter = lc.getFunctions();
+        boolean flaggedCurrentFn = false;
+        while (iter.hasNext()) {
+            final FunctionNode fn = iter.next();
+            if (!flaggedCurrentFn) {
+                lc.setFlag(fn, flag);
+                flaggedCurrentFn = true;
+            } else {
+                lc.setFlag(fn, FunctionNode.HAS_DESCENDANT_WITH_OR_EVAL);
+            }
+            lc.setFlag(lc.getFunctionBody(fn), Block.NEEDS_SCOPE);
+        }
     }
 
-    private FunctionNode getFunction() {
-        return lexicalContext.getCurrentFunction();
+    private void prependStatement(final Node statement) {
+        lc.prependStatement(statement);
+    }
+
+    private void appendStatement(final Node statement) {
+        lc.appendStatement(statement);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/parser/TokenType.java b/nashorn/src/jdk/nashorn/internal/parser/TokenType.java
index 9a2b7b2..657c8c4 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/TokenType.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/TokenType.java
@@ -280,6 +280,11 @@
        return values;
     }
 
+    @Override
+    public String toString() {
+        return name;
+    }
+
     static {
         // Avoid cloning of enumeration.
         values = TokenType.values();
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/Context.java b/nashorn/src/jdk/nashorn/internal/runtime/Context.java
index 6a72761..4ef0926 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java
@@ -56,8 +56,6 @@
 import jdk.nashorn.internal.parser.Parser;
 import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
 import jdk.nashorn.internal.runtime.options.Options;
-import sun.reflect.CallerSensitive;
-import sun.reflect.Reflection;
 
 /**
  * This class manages the global state of execution. Context is immutable.
@@ -114,24 +112,9 @@
      * Get the current global scope
      * @return the current global scope
      */
-    @CallerSensitive
     public static ScriptObject getGlobal() {
-        final SecurityManager sm = System.getSecurityManager();
-        if (sm != null) {
-            // skip getCallerClass and getGlobal and get to the real caller
-            Class<?> caller = Reflection.getCallerClass();
-            ClassLoader callerLoader = caller.getClassLoader();
-
-            // Allow this method only for nashorn's own classes, objects
-            // package classes and Java adapter classes. Rest should
-            // have the necessary security permission.
-            if (callerLoader != myLoader &&
-                !(callerLoader instanceof StructureLoader) &&
-                !(JavaAdapterFactory.isAdapterClass(caller))) {
-                sm.checkPermission(new RuntimePermission("nashorn.getGlobal"));
-            }
-        }
-
+        // This class in a package.access protected package.
+        // Trusted code only can call this method.
         return getGlobalTrusted();
     }
 
@@ -399,7 +382,7 @@
             // We need to get strict mode flag from compiled class. This is
             // because eval code may start with "use strict" directive.
             try {
-                strictFlag = clazz.getField(STRICT_MODE.tag()).getBoolean(null);
+                strictFlag = clazz.getField(STRICT_MODE.symbolName()).getBoolean(null);
             } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
                 //ignored
                 strictFlag = false;
@@ -713,7 +696,7 @@
                 MH.findStatic(
                     MethodHandles.lookup(),
                     script,
-                    RUN_SCRIPT.tag(),
+                    RUN_SCRIPT.symbolName(),
                     MH.type(
                         Object.class,
                         ScriptFunction.class,
@@ -722,13 +705,13 @@
         boolean strict;
 
         try {
-            strict = script.getField(STRICT_MODE.tag()).getBoolean(null);
+            strict = script.getField(STRICT_MODE.symbolName()).getBoolean(null);
         } catch (final NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
             strict = false;
         }
 
         // Package as a JavaScript function and pass function back to shell.
-        return ((GlobalObject)Context.getGlobalTrusted()).newScriptFunction(RUN_SCRIPT.tag(), runMethodHandle, scope, strict);
+        return ((GlobalObject)Context.getGlobalTrusted()).newScriptFunction(RUN_SCRIPT.symbolName(), runMethodHandle, scope, strict);
     }
 
     private ScriptFunction compileScript(final Source source, final ScriptObject scope, final ErrorManager errMan) {
@@ -746,13 +729,13 @@
             global = (GlobalObject)Context.getGlobalTrusted();
             script = global.findCachedClass(source);
             if (script != null) {
-                Compiler.LOG.fine("Code cache hit for " + source + " avoiding recompile.");
+                Compiler.LOG.fine("Code cache hit for ", source, " avoiding recompile.");
                 return script;
             }
         }
 
         final FunctionNode functionNode = new Parser(env, source, errMan, strict).parse();
-        if (errors.hasErrors() || env._parse_only) {
+        if (errors.hasErrors()) {
             return null;
         }
 
@@ -764,6 +747,10 @@
             getErr().println(new PrintVisitor(functionNode));
         }
 
+        if (env._parse_only) {
+            return null;
+        }
+
         final URL          url    = source.getURL();
         final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader;
         final CodeSource   cs     = url == null ? null : new CodeSource(url, (CodeSigner[])null);
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java b/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java
index aa7f05d..dda24bb 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/DebugLogger.java
@@ -135,7 +135,16 @@
      * @param str the string to log
      */
     public void finest(final String str) {
-        log(str, Level.FINEST);
+        log(Level.FINEST, str);
+    }
+
+    /**
+     * Shorthand for outputting a log string as log level
+     * {@link java.util.logging.Level#FINEST} on this logger
+     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
+     */
+    public void finest(final Object... objs) {
+        log(Level.FINEST, objs);
     }
 
     /**
@@ -144,7 +153,16 @@
      * @param str the string to log
      */
     public void finer(final String str) {
-        log(str, Level.FINER);
+        log(Level.FINER, str);
+    }
+
+    /**
+     * Shorthand for outputting a log string as log level
+     * {@link java.util.logging.Level#FINER} on this logger
+     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
+     */
+    public void finer(final Object... objs) {
+        log(Level.FINER, objs);
     }
 
     /**
@@ -153,7 +171,16 @@
      * @param str the string to log
      */
     public void fine(final String str) {
-        log(str, Level.FINE);
+        log(Level.FINE, str);
+    }
+
+    /**
+     * Shorthand for outputting a log string as log level
+     * {@link java.util.logging.Level#FINE} on this logger
+     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
+     */
+    public void fine(final Object... objs) {
+        log(Level.FINE, objs);
     }
 
     /**
@@ -162,7 +189,16 @@
      * @param str the string to log
      */
     public void config(final String str) {
-        log(str, Level.CONFIG);
+        log(Level.CONFIG, str);
+    }
+
+    /**
+     * Shorthand for outputting a log string as log level
+     * {@link java.util.logging.Level#CONFIG} on this logger
+     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
+     */
+    public void config(final Object... objs) {
+        log(Level.CONFIG, objs);
     }
 
     /**
@@ -171,7 +207,16 @@
      * @param str the string to log
      */
     public void info(final String str) {
-        log(str, Level.INFO);
+        log(Level.INFO, str);
+    }
+
+    /**
+     * Shorthand for outputting a log string as log level
+     * {@link java.util.logging.Level#FINE} on this logger
+     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
+     */
+    public void info(final Object... objs) {
+        log(Level.INFO, objs);
     }
 
     /**
@@ -180,7 +225,16 @@
      * @param str the string to log
      */
     public void warning(final String str) {
-        log(str, Level.WARNING);
+        log(Level.WARNING, str);
+    }
+
+    /**
+     * Shorthand for outputting a log string as log level
+     * {@link java.util.logging.Level#FINE} on this logger
+     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
+     */
+    public void warning(final Object... objs) {
+        log(Level.WARNING, objs);
     }
 
     /**
@@ -189,20 +243,28 @@
      * @param str the string to log
      */
     public void severe(final String str) {
-        log(str, Level.SEVERE);
+        log(Level.SEVERE, str);
+    }
+
+    /**
+     * Shorthand for outputting a log string as log level
+     * {@link java.util.logging.Level#FINE} on this logger
+     * @param objs object array to log - use this to perform lazy concatenation to avoid unconditional toString overhead
+     */
+    public void severe(final Object... objs) {
+        log(Level.SEVERE, objs);
     }
 
     /**
      * Output log line on this logger at a given level of verbosity
      * @see java.util.logging.Level
      *
-     * @param str   string to log
      * @param level minimum log level required for logging to take place
+     * @param str   string to log
      */
-    public void log(final String str, final Level level) {
+    public void log(final Level level, final String str) {
         if (isEnabled) {
             final StringBuilder sb = new StringBuilder();
-
             for (int i = 0 ; i < indent ; i++) {
                 sb.append(' ');
             }
@@ -210,4 +272,24 @@
             logger.log(level, sb.toString());
         }
     }
+
+    /**
+     * Output log line on this logger at a given level of verbosity
+     * @see java.util.logging.Level
+     *
+     * @param level minimum log level required for logging to take place
+     * @param objs  objects for which to invoke toString and concatenate to log
+     */
+    public void log(final Level level, final Object... objs) {
+        if (isEnabled) {
+            final StringBuilder sb = new StringBuilder();
+            for (int i = 0 ; i < indent ; i++) {
+                sb.append(' ');
+            }
+            for (final Object obj : objs) {
+                sb.append(obj);
+            }
+            logger.log(level, sb.toString());
+        }
+    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java b/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
index 35ffed9..903d1d5 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/FindProperty.java
@@ -105,6 +105,22 @@
     }
 
     /**
+     * Return the appropriate receiver for a getter.
+     * @return appropriate receiver
+     */
+    public ScriptObject getGetterReceiver() {
+        return property != null && property.hasGetterFunction() ? self : prototype;
+    }
+
+   /**
+     * Return the appropriate receiver for a setter.
+     * @return appropriate receiver
+     */
+    public ScriptObject getSetterReceiver() {
+        return property != null && property.hasSetterFunction() ? self : prototype;
+    }
+
+    /**
      * Return the property that was found
      * @return property
      */
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java b/nashorn/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java
index d75abd3..b10b72d 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/NativeJavaPackage.java
@@ -170,7 +170,7 @@
         Class<?> javaClass = null;
         try {
             javaClass = context.findClass(fullName);
-        } catch (final ClassNotFoundException e) {
+        } catch (final NoClassDefFoundError | ClassNotFoundException e) {
             //ignored
         }
 
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
index 03f0ab5..c225989 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
@@ -25,10 +25,11 @@
 
 package jdk.nashorn.internal.runtime;
 
+import static jdk.nashorn.internal.lookup.Lookup.MH;
+
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.codegen.CompilerConstants;
 import jdk.nashorn.internal.codegen.FunctionSignature;
@@ -37,8 +38,6 @@
 import jdk.nashorn.internal.parser.Token;
 import jdk.nashorn.internal.parser.TokenType;
 
-import static jdk.nashorn.internal.lookup.Lookup.MH;
-
 /**
  * This is a subclass that represents a script function that may be regenerated,
  * for example with specialization based on call site types, or lazily generated.
@@ -47,7 +46,7 @@
  */
 public final class RecompilableScriptFunctionData extends ScriptFunctionData {
 
-    private final FunctionNode functionNode;
+    private FunctionNode functionNode;
     private final PropertyMap  allocatorMap;
     private final CodeInstaller<ScriptEnvironment> installer;
     private final String allocatorClassName;
@@ -70,7 +69,7 @@
                 "" :
                 functionNode.getIdent().getName(),
               functionNode.getParameters().size(),
-              functionNode.isStrictMode(),
+              functionNode.isStrict(),
               false,
               true);
 
@@ -129,7 +128,7 @@
 
     private void ensureHasAllocator() throws ClassNotFoundException {
         if (allocator == null && allocatorClassName != null) {
-            this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.tag(), MH.type(ScriptObject.class, PropertyMap.class));
+            this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.symbolName(), MH.type(ScriptObject.class, PropertyMap.class));
         }
     }
 
@@ -148,8 +147,11 @@
          // therefore, currently method specialization is disabled. TODO
 
          if (functionNode.isLazy()) {
-             Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '" + functionNode.getName() + "'");
-             new Compiler(installer, functionNode).compile().install();
+             Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '", functionNode.getName(), "'");
+             final Compiler compiler = new Compiler(installer, functionNode);
+             functionNode = compiler.compile();
+             assert !functionNode.isLazy();
+             compiler.install();
 
              // we don't need to update any flags - varArgs and needsCallee are instrincic
              // in the function world we need to get a destination node from the compile instead
@@ -159,7 +161,7 @@
          // we can't get here unless we have bytecode, either from eager compilation or from
          // running a lazy compile on the lines above
 
-         assert functionNode.hasState(CompilationState.INSTALLED);
+         assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " " + functionNode.getState() + " " + Debug.id(functionNode);
 
          // code exists - look it up and add it into the automatically sorted invoker list
          code.add(
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java
index 1942ec2..5cc3419 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptObject.java
@@ -901,7 +901,7 @@
         final MethodHandle getter = find.getGetter(int.class);
         if (getter != null) {
             try {
-                return (int)getter.invokeExact((Object)find.getOwner());
+                return (int)getter.invokeExact((Object)find.getGetterReceiver());
             } catch (final Error|RuntimeException e) {
                 throw e;
             } catch (final Throwable e) {
@@ -916,7 +916,7 @@
         final MethodHandle getter = find.getGetter(long.class);
         if (getter != null) {
             try {
-                return (long)getter.invokeExact((Object)find.getOwner());
+                return (long)getter.invokeExact((Object)find.getGetterReceiver());
             } catch (final Error|RuntimeException e) {
                 throw e;
             } catch (final Throwable e) {
@@ -931,7 +931,7 @@
         final MethodHandle getter = find.getGetter(double.class);
         if (getter != null) {
             try {
-                return (double)getter.invokeExact((Object)find.getOwner());
+                return (double)getter.invokeExact((Object)find.getGetterReceiver());
             } catch (final Error|RuntimeException e) {
                 throw e;
             } catch (final Throwable e) {
@@ -953,7 +953,7 @@
         final MethodHandle getter = find.getGetter(Object.class);
         if (getter != null) {
             try {
-                return getter.invokeExact((Object)find.getOwner());
+                return getter.invokeExact((Object)find.getGetterReceiver());
             } catch (final Error|RuntimeException e) {
                 throw e;
             } catch (final Throwable e) {
@@ -1679,12 +1679,7 @@
      * @return GuardedInvocation to be invoked at call site.
      */
     protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) {
-        final String name = desc.getNameToken(2);
-
-        if (request.isCallSiteUnstable()) {
-            return findMegaMorphicGetMethod(desc, name);
-        }
-
+        final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
         final FindProperty find = findProperty(name, true);
 
         MethodHandle methodHandle;
@@ -1700,6 +1695,10 @@
             throw new AssertionError(); // never invoked with any other operation
         }
 
+        if (request.isCallSiteUnstable()) {
+            return findMegaMorphicGetMethod(desc, name);
+        }
+
         final Class<?> returnType = desc.getMethodType().returnType();
         final Property property = find.getProperty();
         methodHandle = find.getGetter(returnType);
@@ -1727,7 +1726,9 @@
     }
 
     private static GuardedInvocation findMegaMorphicGetMethod(final CallSiteDescriptor desc, final String name) {
-        final GuardedInvocation inv = findGetIndexMethod(desc.getMethodType().insertParameterTypes(1, Object.class));
+        final MethodType mhType = desc.getMethodType().insertParameterTypes(1, Object.class);
+        final GuardedInvocation inv = findGetIndexMethod(mhType);
+
         return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard());
     }
 
@@ -1890,8 +1891,8 @@
     }
 
     private static GuardedInvocation findMegaMorphicSetMethod(final CallSiteDescriptor desc, final String name) {
-        final GuardedInvocation inv = findSetIndexMethod(desc.getMethodType().insertParameterTypes(1, Object.class),
-                NashornCallSiteDescriptor.isStrict(desc));
+        final MethodType type = desc.getMethodType().insertParameterTypes(1, Object.class);
+        final GuardedInvocation inv = findSetIndexMethod(type, NashornCallSiteDescriptor.isStrict(desc));
         return inv.replaceMethods(MH.insertArguments(inv.getInvocation(), 1, name), inv.getGuard());
     }
 
@@ -1949,7 +1950,7 @@
      * @return GuardedInvocation to be invoked at call site.
      */
     public GuardedInvocation noSuchProperty(final CallSiteDescriptor desc, final LinkRequest request) {
-        final String name = desc.getNameToken(2);
+        final String name = desc.getNameToken(CallSiteDescriptor.NAME_OPERAND);
         final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true);
         final boolean scopeAccess = isScope() && NashornCallSiteDescriptor.isScope(desc);
 
@@ -1973,6 +1974,24 @@
 
         return createEmptyGetter(desc, name);
     }
+    /**
+     * Invoke fall back if a property is not found.
+     * @param name Name of property.
+     * @return Result from call.
+     */
+    private Object invokeNoSuchProperty(final String name) {
+        final FindProperty find = findProperty(NO_SUCH_PROPERTY_NAME, true);
+
+        if (find != null) {
+            final Object func = getObjectValue(find);
+
+            if (func instanceof ScriptFunction) {
+                return ScriptRuntime.apply((ScriptFunction)func, this, name);
+            }
+        }
+
+        return UNDEFINED;
+    }
 
     private GuardedInvocation createEmptyGetter(final CallSiteDescriptor desc, final String name) {
         return new GuardedInvocation(Lookup.emptyGetter(desc.getMethodType().returnType()), getMap().getProtoGetSwitchPoint(name), NashornGuards.getMapGuard(getMap()));
@@ -2239,310 +2258,158 @@
            setArray(getArray().shrink(newLength));
            getArray().setLength(newLength);
        }
-   }
+    }
+
+    private int getInt(final int index, final String key) {
+        for (ScriptObject object = this; object != null; object = object.getProto()) {
+            final ArrayData array = object.getArray();
+
+            if (array.has(index)) {
+                return array.getInt(index);
+            }
+
+            final FindProperty find = object.findProperty(key, false);
+
+            if (find != null) {
+                return getIntValue(new FindProperty(this, find.getOwner(), find.getProperty()));
+            }
+        }
+
+        return JSType.toInt32(invokeNoSuchProperty(key));
+    }
 
     @Override
     public int getInt(final Object key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getInt(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getIntValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getInt(key) : 0;
+        return getInt(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public int getInt(final double key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getInt(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getIntValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getInt(key) : 0;
+        return getInt(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public int getInt(final long key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getInt(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getIntValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getInt(key) : 0;
+        return getInt(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public int getInt(final int key) {
-        final int index = getArrayIndexNoThrow(key);
+        return getInt(getArrayIndexNoThrow(key), convertKey(key));
+    }
 
-        if (getArray().has(index)) {
-            return getArray().getInt(index);
+    private long getLong(final int index, final String key) {
+        for (ScriptObject object = this; object != null; object = object.getProto()) {
+            final ArrayData array = object.getArray();
+
+            if (array.has(index)) {
+                return array.getLong(index);
+            }
+
+            final FindProperty find = object.findProperty(key, false);
+
+            if (find != null) {
+                return getLongValue(new FindProperty(this, find.getOwner(), find.getProperty()));
+            }
         }
 
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getIntValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getInt(key) : 0;
+        return JSType.toLong(invokeNoSuchProperty(key));
     }
 
     @Override
     public long getLong(final Object key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getLong(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getLongValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getLong(key) : 0L;
+        return getLong(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public long getLong(final double key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getLong(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getLongValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getLong(key) : 0L;
+        return getLong(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public long getLong(final long key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getLong(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getLongValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getLong(key) : 0L;
+        return getLong(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public long getLong(final int key) {
-        final int index = getArrayIndexNoThrow(key);
+        return getLong(getArrayIndexNoThrow(key), convertKey(key));
+    }
 
-        if (getArray().has(index)) {
-            return getArray().getLong(index);
+    private double getDouble(final int index, final String key) {
+        for (ScriptObject object = this; object != null; object = object.getProto()) {
+            final ArrayData array = object.getArray();
+
+            if (array.has(index)) {
+                return array.getDouble(index);
+            }
+
+            final FindProperty find = object.findProperty(key, false);
+
+            if (find != null) {
+                return getDoubleValue(new FindProperty(this, find.getOwner(), find.getProperty()));
+            }
         }
 
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getLongValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getLong(key) : 0L;
+        return JSType.toNumber(invokeNoSuchProperty(key));
     }
 
     @Override
     public double getDouble(final Object key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getDouble(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getDoubleValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getDouble(key) : Double.NaN;
+        return getDouble(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public double getDouble(final double key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getDouble(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getDoubleValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getDouble(key) : Double.NaN;
+        return getDouble(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public double getDouble(final long key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getDouble(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getDoubleValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getDouble(key) : Double.NaN;
+        return getDouble(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public double getDouble(final int key) {
-        final int index = getArrayIndexNoThrow(key);
+        return getDouble(getArrayIndexNoThrow(key), convertKey(key));
+    }
 
-        if (getArray().has(index)) {
-            return getArray().getDouble(index);
+    private Object get(final int index, final String key) {
+        for (ScriptObject object = this; object != null; object = object.getProto()) {
+            final ArrayData array = object.getArray();
+
+            if (array.has(index)) {
+                return array.getObject(index);
+            }
+
+            final FindProperty find = object.findProperty(key, false);
+
+            if (find != null) {
+                return getObjectValue(new FindProperty(this, find.getOwner(), find.getProperty()));
+            }
         }
 
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getDoubleValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.getDouble(key) : Double.NaN;
+        return invokeNoSuchProperty(key);
     }
 
     @Override
     public Object get(final Object key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getObject(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getObjectValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.get(key) : UNDEFINED;
+        return get(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public Object get(final double key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getObject(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getObjectValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.get(key) : UNDEFINED;
+        return get(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public Object get(final long key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getObject(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getObjectValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.get(key) : UNDEFINED;
+        return get(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     @Override
     public Object get(final int key) {
-        final int index = getArrayIndexNoThrow(key);
-
-        if (getArray().has(index)) {
-            return getArray().getObject(index);
-        }
-
-        final FindProperty find = findProperty(convertKey(key), false);
-
-        if (find != null) {
-            return getObjectValue(find);
-        }
-
-        final ScriptObject proto = this.getProto();
-
-        return proto != null ? proto.get(key) : UNDEFINED;
+        return get(getArrayIndexNoThrow(key), convertKey(key));
     }
 
     /**
@@ -2613,8 +2480,6 @@
             f = null;
         }
 
-        MethodHandle setter;
-
         if (f != null) {
             if (!f.getProperty().isWritable()) {
                 if (strict) {
@@ -2624,9 +2489,9 @@
                 return;
             }
 
-            setter = f.getSetter(Object.class, strict); //TODO specfields
             try {
-                setter.invokeExact((Object)f.getOwner(), value);
+                final MethodHandle setter = f.getSetter(Object.class, strict); //TODO specfields
+                setter.invokeExact((Object)f.getSetterReceiver(), value);
             } catch (final Error|RuntimeException e) {
                 throw e;
             } catch (final Throwable e) {
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java b/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java
index 35786b0..db55fff 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java
@@ -47,7 +47,7 @@
  *
  */
 final class StructureLoader extends NashornLoader {
-    private static final String JS_OBJECT_PREFIX_EXTERNAL = binaryName(SCRIPTS_PACKAGE) + '.' + JS_OBJECT_PREFIX.tag();
+    private static final String JS_OBJECT_PREFIX_EXTERNAL = binaryName(SCRIPTS_PACKAGE) + '.' + JS_OBJECT_PREFIX.symbolName();
     private static final String OBJECTS_PACKAGE_EXTERNAL  = binaryName(OBJECTS_PACKAGE);
 
     /**
@@ -110,7 +110,7 @@
     @Override
     protected Class<?> findClass(final String name) throws ClassNotFoundException {
         if (name.startsWith(JS_OBJECT_PREFIX_EXTERNAL)) {
-            final int start = name.indexOf(JS_OBJECT_PREFIX.tag()) + JS_OBJECT_PREFIX.tag().length();
+            final int start = name.indexOf(JS_OBJECT_PREFIX.symbolName()) + JS_OBJECT_PREFIX.symbolName().length();
             return generateClass(name, name.substring(start, name.length()));
         }
         return super.findClass(name);
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java b/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java
index 814f7ec..ca3fcac 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/WithObject.java
@@ -232,11 +232,18 @@
         return (Scope) proto;
     }
 
+    private static GuardedInvocation fixReceiverType(final GuardedInvocation link, final MethodHandle filter) {
+        // The receiver may be an Object or a ScriptObject.
+        final MethodType invType = link.getInvocation().type();
+        final MethodType newInvType = invType.changeParameterType(0, filter.type().returnType());
+        return link.asType(newInvType);
+    }
+
     private static GuardedInvocation fixExpressionCallSite(final NashornCallSiteDescriptor desc, final GuardedInvocation link) {
         // If it's not a getMethod, just add an expression filter that converts WithObject in "this" position to its
         // expression.
         if(!"getMethod".equals(desc.getFirstOperator())) {
-            return link.filterArguments(0, WITHEXPRESSIONFILTER);
+            return fixReceiverType(link, WITHEXPRESSIONFILTER).filterArguments(0, WITHEXPRESSIONFILTER);
         }
 
         final MethodHandle linkInvocation = link.getInvocation();
@@ -252,7 +259,8 @@
     }
 
     private static GuardedInvocation fixScopeCallSite(final GuardedInvocation link) {
-        return link.replaceMethods(filter(link.getInvocation(), WITHSCOPEFILTER), filterGuard(link, WITHSCOPEFILTER));
+        final GuardedInvocation newLink = fixReceiverType(link, WITHSCOPEFILTER);
+        return link.replaceMethods(filter(newLink.getInvocation(), WITHSCOPEFILTER), filterGuard(newLink, WITHSCOPEFILTER));
     }
 
     private static MethodHandle filterGuard(final GuardedInvocation link, final MethodHandle filter) {
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java b/nashorn/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java
index be9bc23..fdf9d3a 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/arrays/MapIterator.java
@@ -66,8 +66,7 @@
             bumpIndex();
         }
 
-        // special case - balk at iterating to infinity or MAX_UINT
-        return (length != JSType.MAX_UINT) && indexInArray();
+        return indexInArray();
     }
 
     @Override
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java
index 6ed3f03..d12df47 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java
@@ -185,4 +185,4 @@
         }
         return classesAndLoaders.keySet();
     }
-}
\ No newline at end of file
+}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java
index 1b5a586..13d569b 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/Parser.java
@@ -287,7 +287,10 @@
 
                     if (syntax.allowDoubleRangeOpInCC()) {
                         env.ccEscWarn("-");
-                        parseCharClassSbChar(cc, arg); // goto sb_char /* [0-9-a] is allowed as [0-9\-a] */
+                        arg.inType = CCVALTYPE.SB;
+                        arg.v = '-';
+                        arg.vIsRaw = false;
+                        parseCharClassValEntry2(cc, arg); // goto val_entry2 /* [0-9-a] is allowed as [0-9\-a] */
                         break;
                     }
                     newSyntaxException(ERR_UNMATCHED_RANGE_SPECIFIER_IN_CHAR_CLASS);
diff --git a/nashorn/src/jdk/nashorn/tools/Shell.java b/nashorn/src/jdk/nashorn/tools/Shell.java
index 0c040a2..a1c10cc 100644
--- a/nashorn/src/jdk/nashorn/tools/Shell.java
+++ b/nashorn/src/jdk/nashorn/tools/Shell.java
@@ -42,6 +42,8 @@
 import jdk.nashorn.api.scripting.NashornException;
 import jdk.nashorn.internal.codegen.Compiler;
 import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.debug.ASTWriter;
+import jdk.nashorn.internal.ir.debug.PrintVisitor;
 import jdk.nashorn.internal.parser.Parser;
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.ErrorManager;
@@ -254,6 +256,14 @@
                     return COMPILATION_ERROR;
                 }
 
+                if (env._print_ast) {
+                    context.getErr().println(new ASTWriter(functionNode));
+                }
+
+                if (env._print_parse) {
+                    context.getErr().println(new PrintVisitor(functionNode));
+                }
+
                 //null - pass no code installer - this is compile only
                 new Compiler(env, functionNode).compile();
             }
diff --git a/nashorn/test/script/basic/JDK-8011578.js b/nashorn/test/script/basic/JDK-8011578.js
new file mode 100644
index 0000000..9a2f743
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8011578.js
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * JDK-8011578 : -Dnashorn.unstable.relink.threshold=1 causes tests to fail.
+ *
+ * @test
+ * @option -Dnashorn.unstable.relink.threshold=1
+ * @run
+ */
+
+load(__DIR__ + "NASHORN-296.js");
+load(__DIR__ + "NASHORN-691.js");
+load(__DIR__ + "calllink.js");
+load(__DIR__ + "nosuchproperty.js");
+
+__noSuchProperty__ = function(x) {
+    print(x);
+    return x;
+}
+
+print(this["find"]);
diff --git a/nashorn/test/script/basic/JDK-8011578.js.EXPECTED b/nashorn/test/script/basic/JDK-8011578.js.EXPECTED
new file mode 100644
index 0000000..aa7184a
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8011578.js.EXPECTED
@@ -0,0 +1,22 @@
+o.foo = 33
+o.foo = 44
+o.foo = 3
+o.foo = hello
+obj1.func called
+obj2.func called
+no such method: func
+obj4's prototype func called
+MyConstructor.prototype.func
+MyConstructor.prototype.func
+obj1.func called
+obj2.func called
+new obj3.func called
+new obj4.func called
+all new MyConstructor.prototype.func
+all new MyConstructor.prototype.func
+obj.__noSuchProperty__ for foo
+new obj.__noSuchProperty__ for foo
+proto.__noSuchProperty__ for foo
+new proto.__noSuchProperty__ for foo
+find
+find
diff --git a/nashorn/test/script/basic/JDK-8012240.js b/nashorn/test/script/basic/JDK-8012240.js
new file mode 100644
index 0000000..2ac6eaf
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8012240.js
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * JDK-8012240: Array.prototype.map.call({length: -1, get 0(){throw 0}}, function(){}).length does not throw error
+ *
+ * @test
+ * @run
+ */
+
+var in_getter_for_0 = false;
+
+try {
+    Array.prototype.map.call(
+        {
+            length: -1, 
+            get 0() {
+                in_getter_for_0 = true;
+                throw 0;
+            }
+        }, 
+    function(){}).length;
+} catch (e) {
+    if (e !== 0 || !in_getter_for_0) {
+       fail("should have thrown error from getter for '0'th element");
+    }
+} 
diff --git a/nashorn/test/script/basic/JDK-8012457.js b/nashorn/test/script/basic/JDK-8012457.js
new file mode 100644
index 0000000..2f71a9a
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8012457.js
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * JDK-8012457: Function.prototype.apply should accept any array-like argument for function arguments
+ *
+ * @test
+ * @run
+ */
+
+// no exception for these
+Function().apply(null, {length: null})
+Function().apply(null, {length: 0.1})
+
+// getter should be called
+var getter_0_called = false;
+
+Function().apply(null, 
+    Object.defineProperty([],"0",
+        {  get: function(){ getter_0_called = true; return 0 }
+    })
+);
+
+if (! getter_0_called) {
+    fail("getter for '0' of arguments array not called");
+}
diff --git a/nashorn/test/script/basic/JDK-8012460.js b/nashorn/test/script/basic/JDK-8012460.js
new file mode 100644
index 0000000..0b41298
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8012460.js
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ *
+ * 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.
+ */
+
+/**
+ * JDK-8012460: RegExp regression
+ *
+ * @test
+ * @run
+ */
+
+
+var semver = "\\s*[v=]*\\s*([0-9]+)" // major
+        + "\\.([0-9]+)" // minor
+        + "\\.([0-9]+)" // patch
+        + "(-[0-9]+-?)?" // build
+        + "([a-zA-Z-+][a-zA-Z0-9-\.:]*)?" // tag
+    , exprComparator = "^((<|>)?=?)\s*("+semver+")$|^$";
+var validComparator = new RegExp("^"+exprComparator+"$");
+
+
+print(exprComparator);
+print(">=0.6.0-".match(validComparator));
+print("=0.6.0-".match(validComparator));
+print("0.6.0-".match(validComparator));
+print("<=0.6.0-".match(validComparator));
+print(">=0.6.0-a:b-c.d".match(validComparator));
+print("=0.6.0-a:b-c.d".match(validComparator));
+print("0.6.0+a:b-c.d".match(validComparator));
+print("<=0.6.0+a:b-c.d".match(validComparator));
+
+print(/[a-zA-Z-+]/.exec("a"));
+print(/[a-zA-Z-+]/.exec("b"));
+print(/[a-zA-Z-+]/.exec("y"));
+print(/[a-zA-Z-+]/.exec("z"));
+print(/[a-zA-Z-+]/.exec("B"));
+print(/[a-zA-Z-+]/.exec("Y"));
+print(/[a-zA-Z-+]/.exec("Z"));
+print(/[a-zA-Z-+]/.exec("-"));
+print(/[a-zA-Z-+]/.exec("+"));
diff --git a/nashorn/test/script/basic/JDK-8012460.js.EXPECTED b/nashorn/test/script/basic/JDK-8012460.js.EXPECTED
new file mode 100644
index 0000000..917cac5
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8012460.js.EXPECTED
@@ -0,0 +1,18 @@
+^((<|>)?=?)s*(\s*[v=]*\s*([0-9]+)\.([0-9]+)\.([0-9]+)(-[0-9]+-?)?([a-zA-Z-+][a-zA-Z0-9-.:]*)?)$|^$
+>=0.6.0-,>=,>,0.6.0-,0,6,0,,-
+=0.6.0-,=,,0.6.0-,0,6,0,,-
+0.6.0-,,,0.6.0-,0,6,0,,-
+<=0.6.0-,<=,<,0.6.0-,0,6,0,,-
+>=0.6.0-a:b-c.d,>=,>,0.6.0-a:b-c.d,0,6,0,,-a:b-c.d
+=0.6.0-a:b-c.d,=,,0.6.0-a:b-c.d,0,6,0,,-a:b-c.d
+0.6.0+a:b-c.d,,,0.6.0+a:b-c.d,0,6,0,,+a:b-c.d
+<=0.6.0+a:b-c.d,<=,<,0.6.0+a:b-c.d,0,6,0,,+a:b-c.d
+a
+b
+y
+z
+B
+Y
+Z
+-
++
diff --git a/nashorn/test/script/basic/JDK-8012462.js b/nashorn/test/script/basic/JDK-8012462.js
new file mode 100644
index 0000000..4e015a9
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8012462.js
@@ -0,0 +1,47 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * JDK-8012462: Date.prototype.toJSON does not handle non-Date 'this' as per the spec.
+ *
+ * @test
+ * @run
+ */
+
+var toJSON = Date.prototype.toJSON;
+
+function checkJSON(value, expected) {
+    var res = toJSON.call({
+        valueOf: function() { return value; },
+        toISOString: function() { return value; }
+    });
+
+    if (res !== expected) {
+        fail("Date.prototype.toJSON does not work for non-Date 'this'");
+    }
+}
+
+checkJSON(NaN, null);
+checkJSON(-Infinity, null);
+checkJSON(Infinity, null);
+checkJSON("foo", "foo");
diff --git a/nashorn/test/script/basic/try2.js b/nashorn/test/script/basic/try2.js
new file mode 100644
index 0000000..8a59c89
--- /dev/null
+++ b/nashorn/test/script/basic/try2.js
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ * 
+ * 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.
+ */
+
+/**
+ * Try throw test - nest finally
+ *
+ * @test
+ * @run 
+ */
+
+function f() {
+    print("a");
+    try {
+	print("b");
+    } finally {
+	print("c");
+	try {
+	    print("d");
+	} finally {
+	    print("e");
+	}
+	print("f");
+    }
+    print("g");
+}
+
+f();
+
+print("done");
diff --git a/nashorn/test/script/basic/try2.js.EXPECTED b/nashorn/test/script/basic/try2.js.EXPECTED
new file mode 100644
index 0000000..603b8cd
--- /dev/null
+++ b/nashorn/test/script/basic/try2.js.EXPECTED
@@ -0,0 +1,8 @@
+a
+b
+c
+d
+e
+f
+g
+done