changeset 202:3a209cbd1d8f

8010701: Immutable nodes - final iteration Reviewed-by: sundar, hannesw, jlaskey
author lagergren
date Fri, 19 Apr 2013 16:11:16 +0200
parents c8460f668d0c
children e599a1cad89a
files bin/verbose_octane.sh src/jdk/nashorn/api/scripting/NashornScriptEngine.java src/jdk/nashorn/internal/codegen/Attr.java src/jdk/nashorn/internal/codegen/ClassEmitter.java src/jdk/nashorn/internal/codegen/CodeGenerator.java src/jdk/nashorn/internal/codegen/CompilationPhase.java src/jdk/nashorn/internal/codegen/Compiler.java src/jdk/nashorn/internal/codegen/CompilerConstants.java src/jdk/nashorn/internal/codegen/FieldObjectCreator.java src/jdk/nashorn/internal/codegen/FinalizeTypes.java src/jdk/nashorn/internal/codegen/FoldConstants.java src/jdk/nashorn/internal/codegen/Frame.java src/jdk/nashorn/internal/codegen/Lower.java src/jdk/nashorn/internal/codegen/MethodEmitter.java src/jdk/nashorn/internal/codegen/Namespace.java src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java src/jdk/nashorn/internal/codegen/ObjectCreator.java src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java src/jdk/nashorn/internal/codegen/Splitter.java src/jdk/nashorn/internal/codegen/WeighNodes.java src/jdk/nashorn/internal/ir/AccessNode.java src/jdk/nashorn/internal/ir/BaseNode.java src/jdk/nashorn/internal/ir/BinaryNode.java src/jdk/nashorn/internal/ir/Block.java src/jdk/nashorn/internal/ir/BlockLexicalContext.java src/jdk/nashorn/internal/ir/BreakNode.java src/jdk/nashorn/internal/ir/BreakableNode.java src/jdk/nashorn/internal/ir/CallNode.java src/jdk/nashorn/internal/ir/CaseNode.java src/jdk/nashorn/internal/ir/CatchNode.java src/jdk/nashorn/internal/ir/ContinueNode.java src/jdk/nashorn/internal/ir/DoWhileNode.java src/jdk/nashorn/internal/ir/EmptyNode.java src/jdk/nashorn/internal/ir/ExecuteNode.java src/jdk/nashorn/internal/ir/Flags.java src/jdk/nashorn/internal/ir/ForNode.java src/jdk/nashorn/internal/ir/FunctionNode.java src/jdk/nashorn/internal/ir/IdentNode.java src/jdk/nashorn/internal/ir/IfNode.java src/jdk/nashorn/internal/ir/IndexNode.java src/jdk/nashorn/internal/ir/LabelNode.java src/jdk/nashorn/internal/ir/LabeledNode.java src/jdk/nashorn/internal/ir/LexicalContext.java src/jdk/nashorn/internal/ir/LexicalContextNode.java src/jdk/nashorn/internal/ir/LineNumberNode.java src/jdk/nashorn/internal/ir/LiteralNode.java src/jdk/nashorn/internal/ir/Location.java src/jdk/nashorn/internal/ir/LoopNode.java src/jdk/nashorn/internal/ir/Node.java src/jdk/nashorn/internal/ir/ObjectNode.java src/jdk/nashorn/internal/ir/PropertyNode.java src/jdk/nashorn/internal/ir/ReturnNode.java src/jdk/nashorn/internal/ir/RuntimeNode.java src/jdk/nashorn/internal/ir/SplitNode.java src/jdk/nashorn/internal/ir/SwitchNode.java src/jdk/nashorn/internal/ir/Symbol.java src/jdk/nashorn/internal/ir/TernaryNode.java src/jdk/nashorn/internal/ir/ThrowNode.java src/jdk/nashorn/internal/ir/TryNode.java src/jdk/nashorn/internal/ir/UnaryNode.java src/jdk/nashorn/internal/ir/VarNode.java src/jdk/nashorn/internal/ir/WhileNode.java src/jdk/nashorn/internal/ir/WithNode.java src/jdk/nashorn/internal/ir/annotations/Immutable.java src/jdk/nashorn/internal/ir/debug/ASTWriter.java src/jdk/nashorn/internal/ir/debug/JSONWriter.java src/jdk/nashorn/internal/ir/debug/PrintVisitor.java src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java src/jdk/nashorn/internal/lookup/MethodHandleFactory.java src/jdk/nashorn/internal/objects/NativeString.java src/jdk/nashorn/internal/parser/AbstractParser.java src/jdk/nashorn/internal/parser/JSONParser.java src/jdk/nashorn/internal/parser/Parser.java src/jdk/nashorn/internal/parser/TokenType.java src/jdk/nashorn/internal/runtime/Context.java src/jdk/nashorn/internal/runtime/DebugLogger.java src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java src/jdk/nashorn/internal/runtime/StructureLoader.java src/jdk/nashorn/internal/runtime/linker/ClassAndLoader.java src/jdk/nashorn/tools/Shell.java test/script/basic/try2.js test/script/basic/try2.js.EXPECTED
diffstat 83 files changed, 5661 insertions(+), 6692 deletions(-) [+]
line wrap: on
line diff
--- a/bin/verbose_octane.sh	Fri Apr 19 18:23:00 2013 +0530
+++ b/bin/verbose_octane.sh	Fri Apr 19 16:11:16 2013 +0200
@@ -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}"
 
--- a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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");
--- a/src/jdk/nashorn/internal/codegen/Attr.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/Attr.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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();
+        final FunctionNode functionNode = getLexicalContext().getCurrentFunction();
+        final Block body = getLexicalContext().getCurrentBlock();
+        initCallee(body);
+        initThis(body);
+        if (functionNode.isVarArg()) {
+            initVarArg(body, functionNode.needsArguments());
         }
 
-        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);
-        if (functionNode.isVarArg()) {
-            initVarArg(functionNode);
-        }
-
-        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;
--- a/src/jdk/nashorn/internal/codegen/ClassEmitter.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/ClassEmitter.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
+    }
+
 }
--- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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(breakNode.getTargetLabel());
-
-        return null;
+        method.splitAwareGoto(getLexicalContext(), breakFrom.getBreakLabel());
+
+        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();
-
-        function.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
+        final Block        currentBlock    = getLexicalContext().getCurrentBlock();
+
+        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(continueNode.getTargetLabel());
-
-        return null;
+        method.splitAwareGoto(getLexicalContext(), continueTo.getContinueLabel());
+
+        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;
-
-        /*
-         * 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();
-
-        /* Fix the predefined slots so they have numbers >= 0, like varargs. */
-        frame.realign();
-
-        if (isFunctionNode) {
+        final boolean isFunctionBody = getLexicalContext().isFunctionBody();
+
+        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);
+
+        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;
+    public boolean enterFunctionNode(final FunctionNode functionNode) {
+        if (functionNode.isLazy()) {
+            // Must do it now; can't postpone it until leaveFunctionNode()
+            newFunctionObject(functionNode, functionNode);
+            return false;
         }
 
-        if(!(isCallee || functionNode == compiler.getFunctionNode())) {
-            newFunctionObject(functionNode);
-        }
-
-        if (functionNode.isLazy()) {
-            return null;
-        }
-
-        LOG.info("=== BEGIN " + functionNode.getName());
-        lexicalContext.push(functionNode);
-
-        setCurrentCompileUnit(functionNode.getCompileUnit());
-        assert getCurrentCompileUnit() != null;
-
-        setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(functionNode));
-        functionNode.setMethodEmitter(method);
+        LOG.info("=== BEGIN ", functionNode.getName());
+
+        assert functionNode.getCompileUnit() != null : "no compile unit for " + functionNode.getName() + " " + Debug.id(functionNode);
+        push(functionNode.getCompileUnit());
+        assert !compileUnits.isEmpty();
+
+        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();
-
-            try {
-                for (final ArrayUnit unit : units) {
-                    setCurrentCompileUnit(unit.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);
-
-                    setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature));
-                    method.setFunctionNode(getCurrentFunctionNode());
-                    method.begin();
-
-                    fixScopeSlot();
-
-                    method.load(arrayType, SPLIT_ARRAY_ARG.slot());
-
-                    for (int i = unit.getLo(); i < unit.getHi(); i++) {
-                        storeElement(nodes, elementType, postsets[i]);
-                    }
-
-                    method._return();
-                    method.end();
-
-                    savedMethod.loadThis();
-                    savedMethod.swap();
-                    savedMethod.loadCallee();
-                    savedMethod.swap();
-                    savedMethod.loadScope();
-                    savedMethod.swap();
-                    savedMethod.invokestatic(className, name, signature);
+            final MethodEmitter savedMethod = method;
+
+            for (final ArrayUnit arrayUnit : units) {
+                push(arrayUnit.getCompileUnit());
+
+                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);
+
+                final MethodEmitter me = unit.getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature);
+                pushMethodEmitter(me);
+
+                method.setFunctionNode(getLexicalContext().getCurrentFunction());
+                method.begin();
+
+                fixScopeSlot();
+
+                method.load(arrayType, SPLIT_ARRAY_ARG.slot());
+
+                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;
-        }
-
-        // 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);
-        }
+    public boolean enterReturnNode(final ReturnNode returnNode) {
+        method.registerReturn();
+
+        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());
-
-        return null;
+        method._return(returnType);
+
+        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();
-
-        if (splitNode.hasReturn() || targetCount > 0) {
-
-            caller.loadScope();
-            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 MethodEmitter caller = method;
+        final int     targetCount = targets.size();
+
+        //no external jump targets or return in switch node
+        if (!hasReturn && targets.isEmpty()) {
+            return splitNode;
+        }
+
+        caller.loadCompilerConstant(SCOPE);
+        caller.checkcast(Scope.class);
+        caller.invoke(Scope.GET_SPLIT_STATE);
+
+        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);
-
-            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();
-
-                if (catchNode.isSyntheticRethrow()) {
-                    // Generate catch body (inlined finally) and rethrow exception
-                    catchBody.accept(this);
+            //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);
+
+            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();
+
+            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");
+
+                    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;
+            }
+
+            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();
-                    lexicalContext.pop(catchBlock);
-                    continue;
+                } else {
+                    method.label(next);
                 }
-
-                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;
-                }
-
-                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);
-            } finally {
-                setCurrentBlock(saveBlock);
             }
+
+            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 (discard.peek() == rhs) {
+            assert !rhs.isAssignment();
+            method.pop();
+            discard.pop();
         }
-
-        final Node rhs = unaryNode.rhs();
-
-        load(rhs);
-
-        if (rhs.shouldDiscard()) {
-            method.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();
                 }
--- a/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/CompilationPhase.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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)));
     }
 
 }
--- a/src/jdk/nashorn/internal/codegen/Compiler.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/Compiler.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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;
     }
 
--- a/src/jdk/nashorn/internal/codegen/CompilerConstants.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/CompilerConstants.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
     }
 
     /**
--- a/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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));
--- a/src/jdk/nashorn/internal/codegen/FinalizeTypes.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/FinalizeTypes.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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 boolean allInteger = switchNode.getTag().getSymbolType().isInteger();
+
+        if (allInteger) {
+            return switchNode;
+        }
+
         final Node           expression  = switchNode.getExpression();
         final List<CaseNode> cases       = switchNode.getCases();
-        final boolean        allInteger  = switchNode.getTag().getSymbolType().isInteger();
+        final List<CaseNode> newCases    = new ArrayList<>();
 
-        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));
-                }
-            }
+        for (final CaseNode caseNode : cases) {
+            final Node test = caseNode.getTest();
+            newCases.add(test != null ? caseNode.setTest(convert(test, Type.OBJECT)) : caseNode);
         }
 
-        return switchNode;
+        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);
         }
     }
 
--- a/src/jdk/nashorn/internal/codegen/FoldConstants.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/FoldConstants.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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
--- a/src/jdk/nashorn/internal/codegen/Frame.java	Fri Apr 19 18:23:00 2013 +0530
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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);
-    }
- }
--- a/src/jdk/nashorn/internal/codegen/Lower.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/Lower.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
-
-        } finally {
-            this.statements = savedStatements;
-            this.lastStatement = savedLastStatement;
-            lexicalContext.pop(block);
+    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 null;
+        return true;
     }
 
     @Override
-    public Node enterBreakNode(final BreakNode breakNode) {
-        return enterBreakOrContinue(breakNode);
+    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);
+        }
+
+        if (last != null && last.isTerminal()) {
+            return block.setIsTerminal(lc, true);
+        }
+
+        return block;
     }
 
     @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;
+    public boolean enterBreakNode(final BreakNode breakNode) {
+        addStatement(breakNode);
+        return false;
     }
 
     @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);
+        if (!forNode.isForIn() && conservativeAlwaysTrue(test)) {
+            newForNode = forNode.setTest(getLexicalContext(), null);
         }
 
-        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);
-        }
-
-        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;
     }
 
 }
--- a/src/jdk/nashorn/internal/codegen/MethodEmitter.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/MethodEmitter.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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;
     }
+
 }
--- a/src/jdk/nashorn/internal/codegen/Namespace.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/Namespace.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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) {
--- a/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/ObjectClassGenerator.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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();
--- a/src/jdk/nashorn/internal/codegen/ObjectCreator.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/ObjectCreator.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk/nashorn/internal/codegen/SplitMethodEmitter.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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;
+    }
+}
--- a/src/jdk/nashorn/internal/codegen/Splitter.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/Splitter.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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() {
+        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);
+                }
+
+                @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 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;
+            public boolean enterFunctionNode(final FunctionNode child) {
+                if (child == functionNode) {
+                    return true;
                 }
-                // 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;
+                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;
     }
 }
 
--- a/src/jdk/nashorn/internal/codegen/WeighNodes.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/codegen/WeighNodes.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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
--- a/src/jdk/nashorn/internal/ir/AccessNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/AccessNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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());
+    }
+
 }
--- a/src/jdk/nashorn/internal/ir/BaseNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/BaseNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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());
+        }
     }
 }
--- a/src/jdk/nashorn/internal/ir/BinaryNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/BinaryNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
-
-        start  = lhs.getStart();
-        finish = rhs.getFinish();
-
-        this.lhs = lhs;
+        super(source, token, lhs.getStart(), rhs.getFinish());
+        this.lhs   = lhs;
+        this.rhs   = rhs;
     }
 
-    /**
-     * 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);
+    private BinaryNode(final BinaryNode binaryNode, final Node lhs, final Node rhs) {
+        super(binaryNode);
+        this.lhs = lhs;
+        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);
+    }
+
 }
--- a/src/jdk/nashorn/internal/ir/Block.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/Block.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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 final int flags;
 
-    /** Does the block/function need a new scope? */
-    protected boolean needsScope;
+    /** 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;
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk/nashorn/internal/ir/BlockLexicalContext.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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;
+    }
+}
--- a/src/jdk/nashorn/internal/ir/BreakNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/BreakNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
         }
     }
 }
--- a/src/jdk/nashorn/internal/ir/BreakableNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/BreakableNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
+    }
+
 }
--- a/src/jdk/nashorn/internal/ir/CallNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/CallNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
     }
 }
--- a/src/jdk/nashorn/internal/ir/CaseNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/CaseNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
     }
 }
--- a/src/jdk/nashorn/internal/ir/CatchNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/CatchNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
+    }
 }
--- a/src/jdk/nashorn/internal/ir/ContinueNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/ContinueNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
         }
     }
 }
--- a/src/jdk/nashorn/internal/ir/DoWhileNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,82 +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.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
- *
- */
-public class DoWhileNode extends WhileNode {
-
-    /**
-     * Constructor
-     *
-     * @param source     the source
-     * @param token      token
-     * @param finish     finish
-     */
-    public DoWhileNode(final Source source, final long token, final int finish) {
-        super(source, token, finish);
-    }
-
-    /**
-     * Copy constructor
-     *
-     * @param doWhileNode source node
-     * @param cs          copy state
-     */
-    protected DoWhileNode(final DoWhileNode doWhileNode, final CopyState cs) {
-        super(doWhileNode, cs);
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new DoWhileNode(this, cs);
-    }
-
-    @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(')');
-    }
-}
--- a/src/jdk/nashorn/internal/ir/EmptyNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/EmptyNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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;
--- a/src/jdk/nashorn/internal/ir/ExecuteNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/ExecuteNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/jdk/nashorn/internal/ir/Flags.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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);
+}
--- a/src/jdk/nashorn/internal/ir/ForNode.java	Fri Apr 19 18:23:00 2013 +0530
+++ b/src/jdk/nashorn/internal/ir/ForNode.java	Fri Apr 19 16:11:16 2013 +0200
@@ -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 (t