8006529: Methods always get callee - it should be conditional
authorattila
Thu, 31 Jan 2013 18:34:42 +0100
changeset 16206 83069fa0935b
parent 16205 93fda2507e35
child 16207 ed4aec2d599c
8006529: Methods always get callee - it should be conditional Summary: This commit streamlines the bytecode function signatures, prologue, local variable use, scope creation, and invocation. It started out quite innocently when we noticed that we always emit __callee__ parameters for all functions even when they are not needed, but it turned out to be quite a deep rabbit hole. In the end, I identified exact conditions when functions need to have a callee parameter, when they need to receive parent scope, when they need to create their own scope, when they need to have variable arity signature, and when they need to have an "arguments" object, and made sure that callee parameters in signatures only show up when they are needed, that parent function's scope is only passed to a child function when it is needed, that the function only creates its own scope when it is needed. In crypto.js, the number of scopes dropped from 446 to 244, and the number of callees dropped from 315 to 145. Reviewed-by: jlaskey, lagergren
nashorn/src/jdk/nashorn/internal/codegen/Attr.java
nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
nashorn/src/jdk/nashorn/internal/codegen/Lower.java
nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java
nashorn/src/jdk/nashorn/internal/codegen/objects/FieldObjectCreator.java
nashorn/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java
nashorn/src/jdk/nashorn/internal/codegen/types/Type.java
nashorn/src/jdk/nashorn/internal/ir/Block.java
nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
nashorn/src/jdk/nashorn/internal/ir/Symbol.java
nashorn/src/jdk/nashorn/internal/objects/Global.java
nashorn/src/jdk/nashorn/internal/objects/NativeArguments.java
nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java
nashorn/src/jdk/nashorn/internal/parser/Parser.java
nashorn/src/jdk/nashorn/internal/runtime/ArgumentSetter.java
nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java
nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java
nashorn/test/script/basic/JDK-8006529-b.js
nashorn/test/script/basic/JDK-8006529-b.js.EXPECTED
nashorn/test/script/basic/JDK-8006529.js
nashorn/test/src/jdk/nashorn/internal/codegen/CompilerAccess.java
--- a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java	Thu Jan 31 18:34:42 2013 +0100
@@ -45,7 +45,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
-
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.BinaryNode;
@@ -413,6 +412,13 @@
             // 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();
+        }
+
+        if(symbol.isGlobal()) {
+            getCurrentFunctionNode().setUsesGlobalSymbol();
+        } else if(symbol.isScope()) {
+            getCurrentFunctionNode().setUsesScopeSymbol(symbol);
         }
 
         if (symbol != oldSymbol && !identNode.isInitializedHere()) {
@@ -666,7 +672,9 @@
             }
             final Node literalNode = LiteralNode.newInstance(unaryNode, name).accept(this);
 
-            args.add(currentFunctionNode.getScopeNode());
+            if (!failDelete) {
+                args.add(currentFunctionNode.getScopeNode());
+            }
             args.add(literalNode);
             args.add(strictFlagNode);
 
@@ -1157,21 +1165,24 @@
         //return symbol is always object as it's the __return__ thing. What returnType is is another matter though
     }
 
-    private static void initVarArg(final FunctionNode functionNode) {
-        assert functionNode.getCalleeNode() != null;
+    private void initVarArg(final FunctionNode functionNode) {
+        if (functionNode.isVarArg()) {
+            final Symbol varArgsSymbol = functionNode.defineSymbol(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);
 
-        final Symbol varArgsSymbol = functionNode.defineSymbol(VARARGS.tag(), IS_PARAM | IS_INTERNAL, null);
-        newType(varArgsSymbol, Type.OBJECT_ARRAY);
-        varArgsSymbol.setNeedsSlot(true);
-        functionNode.getVarArgsNode().setSymbol(varArgsSymbol);
-        LOG.info("Initialized varargs symbol: " + varArgsSymbol);
-
-        final String    argumentsName   = functionNode.getArgumentsNode().getName();
-        final Symbol    argumentsSymbol = functionNode.defineSymbol(argumentsName, IS_PARAM | IS_INTERNAL, null);
-        newType(argumentsSymbol, Type.OBJECT);
-        argumentsSymbol.setNeedsSlot(true);
-        functionNode.getArgumentsNode().setSymbol(argumentsSymbol);
-        LOG.info("Initialized vararg varArgsSymbol=" + varArgsSymbol + " argumentsSymbol=" + argumentsSymbol);
+            if (functionNode.needsArguments()) {
+                final String    argumentsName   = functionNode.getArgumentsNode().getName();
+                final Symbol    argumentsSymbol = functionNode.defineSymbol(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);
+            }
+        }
     }
 
     private static void initCallee(final FunctionNode functionNode) {
@@ -1238,7 +1249,7 @@
             LOG.info("parameter specialization possible: " + functionNode.getName() + " " + paramSpecializations);
         }
 
-        // parameters should not be slots for a vararg function, make sure this is the case
+        // parameters should not be slots for a function that uses variable arity signature
         if (functionNode.isVarArg()) {
             for (final IdentNode param : functionNode.getParameters()) {
                 param.getSymbol().setNeedsSlot(false);
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java	Thu Jan 31 18:34:42 2013 +0100
@@ -201,6 +201,9 @@
         final Symbol symbol = identNode.getSymbol();
 
         if (!symbol.isScope()) {
+            if(symbol.isParam()) {
+                return method.loadParam(symbol);
+            }
             assert symbol.hasSlot() && symbol.getSlot() != 0 || symbol.isThis();
             return method.load(symbol);
         }
@@ -383,7 +386,7 @@
         for (final Symbol symbol : symbols) {
             /*
              * The following symbols are guaranteed to be defined and thus safe
-             * from having unsigned written to them: parameters internals this
+             * from having undefined written to them: parameters internals this
              *
              * Otherwise we must, unless we perform control/escape analysis,
              * assign them undefined.
@@ -843,6 +846,15 @@
         /* Fix the predefined slots so they have numbers >= 0, like varargs. */
         frame.realign();
 
+        if (isFunctionNode) {
+            if (function.needsParentScope()) {
+                initParentScope();
+            }
+            if (function.needsArguments()) {
+                initArguments(function);
+            }
+        }
+
         /*
          * Determine if block needs scope, if not, just do initSymbols for this block.
          */
@@ -851,7 +863,6 @@
              * Determine if function is varargs and consequently variables have to
              * be in the scope.
              */
-            final boolean isVarArg    = function.isVarArg();
             final boolean varsInScope = function.varsInScope();
 
             // TODO for LET we can do better: if *block* does not contain any eval/with, we don't need its vars in scope.
@@ -859,39 +870,34 @@
             final List<String> nameList = new ArrayList<>();
             final List<Symbol> locals   = new ArrayList<>();
 
-            // If there are variable arguments, we need to load them (functions only).
-            if (isFunctionNode && isVarArg) {
-                method.loadVarArgs();
-                method.loadCallee();
-                method.load(function.getParameters().size());
-                globalAllocateArguments();
-                method.storeArguments();
-            }
 
             // 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()) {
                     continue;
                 }
 
-                if (symbol.isVar() && (varsInScope || symbol.isScope())) {
+                if (symbol.isVar()) {
+                    if(varsInScope || symbol.isScope()) {
+                        nameList.add(symbol.getName());
+                        newSymbols.add(symbol);
+                        values.add(null);
+                        assert symbol.isScope()   : "scope for " + symbol + " should have been set in Lower already " + function.getName();
+                        assert !symbol.hasSlot()  : "slot for " + symbol + " should have been removed in Lower already" + function.getName();
+                    } else {
+                        assert symbol.hasSlot() : symbol + " should have a slot only, no scope";
+                        locals.add(symbol);
+                    }
+                } else if (symbol.isParam() && (varsInScope || hasArguments || symbol.isScope())) {
                     nameList.add(symbol.getName());
                     newSymbols.add(symbol);
-                    values.add(null);
-                    assert symbol.isScope()   : "scope for " + symbol + " should have been set in Lower already " + function.getName();
-                    assert !symbol.hasSlot()  : "slot for " + symbol + " should have been removed in Lower already" + function.getName();
-                } else if (symbol.isVar()) {
-                    assert symbol.hasSlot() : symbol + " should have a slot only, no scope";
-                    locals.add(symbol);
-                } else if (symbol.isParam() && (varsInScope || isVarArg || symbol.isScope())) {
-                    nameList.add(symbol.getName());
-                    newSymbols.add(symbol);
-                    values.add(isVarArg ? null : symbol);
-                    assert symbol.isScope()   : "scope for " + symbol + " should have been set in Lower already " + function.getName() + " varsInScope="+varsInScope+" isVarArg="+isVarArg+" symbol.isScope()=" + symbol.isScope();
-                    assert !(isVarArg && symbol.hasSlot())  : "slot for " + symbol + " should have been removed in Lower already " + function.getName();
+                    values.add(hasArguments ? null : symbol);
+                    assert symbol.isScope()   : "scope for " + symbol + " should have been set in Lower already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope();
+                    assert !(hasArguments && symbol.hasSlot())  : "slot for " + symbol + " should have been removed in Lower already " + function.getName();
                 }
             }
 
@@ -901,15 +907,11 @@
             // we may have locals that need to be initialized
             initSymbols(locals);
 
-            if (isFunctionNode) {
-                initScope();
-            }
-
             /*
              * Create a new object based on the symbols and values, generate
              * bootstrap code for object
              */
-            final FieldObjectCreator<Symbol> foc = new FieldObjectCreator<Symbol>(this, nameList, newSymbols, values, true, isVarArg) {
+            final FieldObjectCreator<Symbol> foc = new FieldObjectCreator<Symbol>(this, nameList, newSymbols, values, true, hasArguments) {
                 @Override
                 protected Type getValueType(final Symbol value) {
                     return value.getSymbolType();
@@ -919,6 +921,15 @@
                 protected void loadValue(final Symbol value) {
                     method.load(value);
                 }
+
+                @Override
+                protected void loadScope(MethodEmitter m) {
+                    if(function.needsParentScope()) {
+                        m.loadScope();
+                    } else {
+                        m.loadNull();
+                    }
+                }
             };
             foc.makeObject(method);
 
@@ -929,18 +940,37 @@
 
             method.storeScope();
         } 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()) {
+                for (final IdentNode param : function.getParameters()) {
+                    param.getSymbol().setFieldIndex(nextParam++);
+                }
+            }
             initSymbols(symbols);
-
-            if (isFunctionNode) {
-                initScope();
-            }
         }
 
         // Debugging: print symbols? @see --print-symbols flag
         printSymbols(block, (isFunctionNode ? "Function " : "Block in ") + (function.getIdent() == null ? "<anonymous>" : function.getIdent().getName()));
     }
 
-    private void initScope() {
+    private void initArguments(final FunctionNode function) {
+        method.loadVarArgs();
+        if(function.needsCallee()) {
+            method.loadCallee();
+        } else {
+            // If function is strict mode, "arguments.callee" is not populated, so we don't necessarily need the
+            // caller.
+            assert function.isStrictMode();
+            method.loadNull();
+        }
+        method.load(function.getParameters().size());
+        globalAllocateArguments();
+        method.storeArguments();
+    }
+
+    private void initParentScope() {
         method.loadCallee();
         method.invoke(ScriptFunction.GET_SCOPE);
         method.storeScope();
@@ -1622,7 +1652,8 @@
         final String name       = splitNode.getName();
 
         final Class<?>   rtype  = fn.getReturnType().getTypeClass();
-        final Class<?>[] ptypes = fn.isVarArg() ?
+        final boolean needsArguments = fn.needsArguments();
+        final Class<?>[] ptypes = needsArguments ?
                 new Class<?>[] {Object.class, ScriptFunction.class, ScriptObject.class, Object.class} :
                 new Class<?>[] {Object.class, ScriptFunction.class, ScriptObject.class};
 
@@ -1647,9 +1678,13 @@
 
         final MethodEmitter caller = splitNode.getCaller();
         caller.loadThis();
-        caller.loadCallee();
+        if(fn.needsCallee()) {
+            caller.loadCallee();
+        } else {
+            caller.loadNull();
+        }
         caller.loadScope();
-        if (fn.isVarArg()) {
+        if (needsArguments) {
             caller.loadArguments();
         }
         caller.invoke(splitCall);
@@ -3142,17 +3177,34 @@
 
             target.accept(new NodeVisitor(compileUnit, method) {
                 @Override
+                protected Node enterDefault(Node node) {
+                    throw new AssertionError("Unexpected node " + node + " in store epilogue");
+                };
+
+                @Override
+                public Node enter(final UnaryNode node) {
+                    if(node.tokenType() == TokenType.CONVERT && node.getSymbol() != null) {
+                        method.convert(node.rhs().getType());
+                    }
+                    return node;
+                }
+
+                @Override
                 public Node enter(final IdentNode node) {
-                    final Symbol symbol = target.getSymbol();
+                    final Symbol symbol = node.getSymbol();
                     if (symbol.isScope()) {
                         if (symbol.isFastScope(currentFunction)) {
-                            storeFastScopeVar(target.getType(), symbol, CALLSITE_SCOPE | getCallSiteFlags());
+                            storeFastScopeVar(node.getType(), symbol, CALLSITE_SCOPE | getCallSiteFlags());
                         } else {
-                            method.dynamicSet(target.getType(), node.getName(), CALLSITE_SCOPE | getCallSiteFlags());
+                            method.dynamicSet(node.getType(), node.getName(), CALLSITE_SCOPE | getCallSiteFlags());
                         }
                     } else {
-                        assert targetSymbol != null;
-                        method.store(targetSymbol);
+                        assert symbol != null;
+                        if(symbol.isParam()) {
+                            method.storeParam(symbol);
+                        } else {
+                            method.store(symbol);
+                        }
                     }
                     return null;
 
@@ -3203,7 +3255,7 @@
     }
 
     private MethodEmitter globalAllocateArguments() {
-        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "allocateArguments", methodDescriptor(Object.class, Object[].class, Object.class, int.class));
+        return method.invokeStatic(Compiler.GLOBAL_OBJECT, "allocateArguments", methodDescriptor(ScriptObject.class, Object[].class, Object.class, int.class));
     }
 
     private MethodEmitter globalNewRegExp() {
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java	Thu Jan 31 18:34:42 2013 +0100
@@ -111,8 +111,8 @@
     /** Name of the Global object, cannot be referred to as .class, @see CodeGenerator */
     public static final String GLOBAL_OBJECT = OBJECTS_PACKAGE + '/' + "Global";
 
-    /** Name of the ScriptObjectImpl, cannot be referred to as .class @see FunctionObjectCreator */
-    public static final String SCRIPTOBJECT_IMPL_OBJECT = OBJECTS_PACKAGE + '/' + "ScriptFunctionImpl";
+    /** Name of the ScriptFunctionImpl, cannot be referred to as .class @see FunctionObjectCreator */
+    public static final String SCRIPTFUNCTION_IMPL_OBJECT = OBJECTS_PACKAGE + '/' + "ScriptFunctionImpl";
 
     /** Name of the Trampoline, cannot be referred to as .class @see FunctionObjectCreator */
     public static final String TRAMPOLINE_OBJECT = OBJECTS_PACKAGE + '/' + "Trampoline";
@@ -374,6 +374,7 @@
                 LOG.info("Lowering '" + functionNode.getName() + "'");
                 functionNode.accept(new Lower(this));
                 state.add(State.LOWERED);
+                debugPrintAST();
 
                 LOG.info("Attributing types '" + functionNode.getName() + "'");
                 functionNode.accept(new Attr(this));
--- a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java	Thu Jan 31 18:34:42 2013 +0100
@@ -27,7 +27,6 @@
 
 import java.util.HashSet;
 import java.util.List;
-
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.Assignment;
@@ -425,6 +424,19 @@
 
     @Override
     public Node enter(final FunctionNode 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);
+        }
+        // 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);
+        }
+
         updateSymbols(functionNode);
         return functionNode;
     }
@@ -548,7 +560,6 @@
      * @param block block for which to to finalize type info.
      */
     private static void updateSymbols(final Block block) {
-
         if (!block.needsScope()) {
             return; // nothing to do
         }
--- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java	Thu Jan 31 18:34:42 2013 +0100
@@ -38,7 +38,6 @@
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.List;
-
 import jdk.nashorn.internal.ir.AccessNode;
 import jdk.nashorn.internal.ir.BaseNode;
 import jdk.nashorn.internal.ir.BinaryNode;
@@ -956,14 +955,16 @@
         final long token  = functionNode.getToken();
         final int  finish = functionNode.getFinish();
 
-        LOG.info("Init this, scope, result, callee, varargs and argument for " + functionNode.getName());
-
         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()));
-        functionNode.setVarArgsNode(new IdentNode(source, token, finish, VARARGS.tag()));
-        functionNode.setArgumentsNode(new IdentNode(source, token, finish, functionNode.hideArguments() ? compiler.uniqueName('$' + ARGUMENTS.tag()) : ARGUMENTS.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()));
+            }
+        }
     }
 
 }
--- a/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java	Thu Jan 31 18:34:42 2013 +0100
@@ -86,10 +86,12 @@
 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.Context;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.JSType;
 import jdk.nashorn.internal.runtime.Scope;
+import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.linker.Bootstrap;
 import jdk.nashorn.internal.runtime.options.Options;
 import org.dynalang.dynalink.support.NameCodec;
@@ -273,7 +275,7 @@
      */
     private ArrayType popArray() {
         final Type type = stack.pop();
-        assert type.isArray();
+        assert type.isArray() : type;
         return (ArrayType)type;
     }
 
@@ -311,6 +313,7 @@
      * @return the method emitter
      */
     public MethodEmitter _new(final String classDescriptor) {
+        debug("new", classDescriptor);
         method.visitTypeInsn(NEW, classDescriptor);
         pushType(Type.OBJECT);
         return this;
@@ -833,6 +836,42 @@
     }
 
     /**
+     * Push a non-scope function parameter to the stack. Function parameters always arrive into a function as either
+     * explicit parameters on the stack, or collected into a final variable-arity {@code Object[]} parameter. If they
+     * end up being scoped (i.e. referenced from a child function or eval), then they're loaded as scoped symbols and
+     * this function is not invoked for them. If they aren't scoped, then they will be loaded from one of three places.
+     * First, if the function has an Arguments object, they're loaded from it. Otherwise, if the parameters come in a
+     * {@code Object[]} array, they are loaded from the array. Finally, if neither is the case, they're simply loaded
+     * from their bytecode slot.
+     *
+     * @param symbol the symbol representing the parameter.
+     *
+     * @return the method emitter
+     */
+    public MethodEmitter loadParam(final Symbol symbol) {
+        assert symbol != null && symbol.isParam() && !symbol.isScope();
+        if(symbol.hasSlot()) {
+            // Check that we aren't vararg, except if we're loading "this"
+            assert symbol.isThis() || !functionNode.isVarArg() : "Symbol=" + symbol + " functionNode=" + functionNode.getName();
+            // Just load it from a local variable
+            return load(symbol);
+        }
+        assert functionNode.isVarArg();
+        if(functionNode.needsArguments()) {
+            // ScriptObject.getArgument(int) on arguments
+            loadArguments();
+            load(symbol.getFieldIndex());
+            ScriptObject.GET_ARGUMENT.invoke(this);
+        } else {
+            // array load from __varargs__
+            loadVarArgs();
+            load(symbol.getFieldIndex());
+            arrayload();
+        }
+        return this;
+    }
+
+    /**
      * Push a local variable to the stack, given an explicit bytecode slot
      * This is used e.g. for stub generation where we know where items like
      * "this" and "scope" reside.
@@ -917,7 +956,7 @@
      * @return the method emitter
      */
     public MethodEmitter loadArguments() {
-        debug("load arguments " + functionNode.getVarArgsNode().getSymbol());
+        debug("load arguments ", functionNode.getArgumentsNode().getSymbol());
         assert functionNode.getArgumentsNode().getSymbol().getSlot() != 0;
         return load(functionNode.getArgumentsNode().getSymbol());
     }
@@ -1000,6 +1039,40 @@
     }
 
     /**
+     * Pop a value from the stack and store it in a non-scope function parameter. Function parameters always arrive into
+     * a function as either explicit parameters on th stack, or collected into a final variable-arity {@code Object[]}
+     * parameter. If they end up being scoped (i.e. referenced from a child function or eval), then they're stored as
+     * scoped symbols are and this function is not invoked for them. If they aren't scoped, then they will be stored
+     * to one of three places. First, if the function has an Arguments object, they're stored to it. Otherwise, if the
+     * parameters come in a {@code Object[]} array, they are stored to the array. Finally, if neither is the case,
+     * they're simply stored to their bytecode slot.
+     *
+     * @param symbol the symbol representing the parameter.
+     *
+     */
+    public void storeParam(final Symbol symbol) {
+        assert symbol != null && symbol.isParam() && !symbol.isScope();
+        if(symbol.hasSlot()) {
+            assert !functionNode.isVarArg();
+            // Just store it to a local variable
+            store(symbol);
+            return;
+        }
+        assert functionNode.isVarArg();
+        if(functionNode.needsArguments()) {
+            loadArguments();
+            load(symbol.getFieldIndex());
+            ArgumentSetter.SET_ARGUMENT.invoke(this);
+        } else {
+            // varargs without arguments object - just do array store to __varargs__
+            loadVarArgs();
+            load(symbol.getFieldIndex());
+            ArgumentSetter.SET_ARRAY_ELEMENT.invoke(this);
+        }
+    }
+
+
+    /**
      * Pop a value from the stack and store it in a given local variable
      * slot.
      *
--- a/nashorn/src/jdk/nashorn/internal/codegen/objects/FieldObjectCreator.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/objects/FieldObjectCreator.java	Thu Jan 31 18:34:42 2013 +0100
@@ -70,20 +70,28 @@
     /**
      * Constructor
      *
-     * @param codegen  code generator
-     * @param keys     keys for fields in object
-     * @param symbols  symbols for fields in object
-     * @param values   values (or null where no value) to be written to the fields
-     * @param isScope  is this a scope object
-     * @param isVarArg is this a vararg object
+     * @param codegen      code generator
+     * @param keys         keys for fields in object
+     * @param symbols      symbols for fields in object
+     * @param values       values (or null where no value) to be written to the fields
+     * @param isScope      is this a scope object
+     * @param hasArguments does the created object have an "arguments" object
      */
-    public FieldObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final List<T> values, final boolean isScope, final boolean isVarArg) {
-        super(codegen, keys, symbols, isScope, isVarArg);
+    public FieldObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final List<T> values, final boolean isScope, final boolean hasArguments) {
+        super(codegen, keys, symbols, isScope, hasArguments);
         this.values        = values;
         this.callSiteFlags = codegen.getCallSiteFlags();
     }
 
     /**
+     * Loads the scope on the stack through the passed method emitter.
+     * @param method the method emitter to use
+     */
+    protected void loadScope(final MethodEmitter method) {
+        method.loadScope();
+    }
+
+    /**
      * Construct an object.
      *
      * @param method the method emitter
@@ -96,7 +104,7 @@
         loadMap(method); //load the map
 
         if (isScope()) {
-            method.loadScope();
+            loadScope(method);
 
             if (isVarArg()) {
                 method.loadArguments();
--- a/nashorn/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/objects/FunctionObjectCreator.java	Thu Jan 31 18:34:42 2013 +0100
@@ -26,7 +26,7 @@
 package jdk.nashorn.internal.codegen.objects;
 
 import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.HANDLE_STATIC;
-import static jdk.nashorn.internal.codegen.Compiler.SCRIPTOBJECT_IMPL_OBJECT;
+import static jdk.nashorn.internal.codegen.Compiler.SCRIPTFUNCTION_IMPL_OBJECT;
 import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
@@ -93,10 +93,14 @@
          * Instantiate the function object, must be referred to by name as
          * class is not available at compile time
          */
-        method._new(SCRIPTOBJECT_IMPL_OBJECT).dup();
+        method._new(SCRIPTFUNCTION_IMPL_OBJECT).dup();
         method.load(functionNode.isAnonymous() ? "" : identNode.getName());
         loadHandle(method, signature);
-        method.loadScope();
+        if(functionNode.needsParentScope()) {
+            method.loadScope();
+        } else {
+            method.loadNull();
+        }
         method.getStatic(compileUnit.getUnitClassName(), SOURCE.tag(), SOURCE.descriptor());
         method.load(token);
         method.loadHandle(getClassName(), ALLOCATE.tag(), methodDescriptor(ScriptObject.class, PropertyMap.class), EnumSet.of(HANDLE_STATIC));
@@ -111,7 +115,7 @@
          */
         method.load(functionNode.needsCallee());
         method.load(functionNode.isStrictMode());
-        method.invoke(constructorNoLookup(SCRIPTOBJECT_IMPL_OBJECT,
+        method.invoke(constructorNoLookup(SCRIPTFUNCTION_IMPL_OBJECT,
                     String.class,
                     MethodHandle.class,
                     ScriptObject.class,
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java	Thu Jan 31 18:34:42 2013 +0100
@@ -779,13 +779,13 @@
     };
 
     /** Singleton for method handle arrays used for properties etc. */
-    public static final ArrayType METHODHANDLE_ARRAY = new ArrayType(new MethodHandle[0].getClass());
+    public static final ArrayType METHODHANDLE_ARRAY = new ArrayType(MethodHandle[].class);
 
     /** This is the singleton for string arrays */
-    public static final ArrayType STRING_ARRAY = new ArrayType(new String[0].getClass());
+    public static final ArrayType STRING_ARRAY = new ArrayType(String[].class);
 
     /** This is the singleton for object arrays */
-    public static final ArrayType OBJECT_ARRAY = new ArrayType(new Object[0].getClass());
+    public static final ArrayType OBJECT_ARRAY = new ArrayType(Object[].class);
 
     /** This type, always an object type, just a toString override */
     public static final Type THIS = new ObjectType() {
--- a/nashorn/src/jdk/nashorn/internal/ir/Block.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/Block.java	Thu Jan 31 18:34:42 2013 +0100
@@ -584,7 +584,7 @@
      * Set the needs scope flag.
      */
     public void setNeedsScope() {
-        this.needsScope = true;
+        needsScope = true;
     }
 
 }
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java	Thu Jan 31 18:34:42 2013 +0100
@@ -47,6 +47,7 @@
 import jdk.nashorn.internal.parser.Parser;
 import jdk.nashorn.internal.runtime.Source;
 import jdk.nashorn.internal.runtime.UserAccessorProperty;
+import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
 
 /**
  * IR representation for function (or script.)
@@ -150,36 +151,40 @@
     private int flags;
 
     /** Is anonymous function flag. */
-    private static final int IS_ANONYMOUS            = 0b0000_0000_0000_0001;
+    private static final int IS_ANONYMOUS                = 0b0000_0000_0000_0001;
     /** Is statement flag */
-    private static final int IS_STATEMENT            = 0b0000_0000_0000_0010;
+    private static final int IS_STATEMENT                = 0b0000_0000_0000_0010;
     /** is this a strict mode function? */
-    private static final int IS_STRICT_MODE          = 0b0000_0000_0000_0100;
-    /** is this is a vararg function? */
-    private static final int IS_VAR_ARG              = 0b0000_0000_0000_1000;
+    private static final int IS_STRICT_MODE              = 0b0000_0000_0000_0100;
+    /** Does the function use the "arguments" identifier ? */
+    private static final int USES_ARGUMENTS              = 0b0000_0000_0000_1000;
     /** Are we lowered ? */
-    private static final int IS_LOWERED              = 0b0000_0000_0001_0000;
+    private static final int IS_LOWERED                  = 0b0000_0000_0001_0000;
     /** Has this node been split because it was too large? */
-    private static final int IS_SPLIT                = 0b0000_0000_0010_0000;
+    private static final int IS_SPLIT                    = 0b0000_0000_0010_0000;
     /** Is this function lazily compiled? */
-    private static final int IS_LAZY                 = 0b0000_0000_0100_0000;
-    /** Does the function contain eval? */
-    private static final int HAS_EVAL                = 0b0000_0000_1000_0000;
+    private static final int IS_LAZY                     = 0b0000_0000_0100_0000;
+    /** Does the function call eval? */
+    private static final int HAS_EVAL                    = 0b0000_0000_1000_0000;
     /** Does the function contain a with block ? */
-    private static final int HAS_WITH                = 0b0000_0001_0000_0000;
-    /** Does a child function contain a with or eval? */
-    private static final int HAS_CHILD_WITH_OR_EVAL  = 0b0000_0010_0000_0000;
-    /** Hide arguments? */
-    private static final int HIDE_ARGS               = 0b0000_0100_0000_0000;
+    private static final int HAS_WITH                    = 0b0000_0001_0000_0000;
+    /** Does a descendant function contain a with or eval? */
+    private static final int HAS_DESCENDANT_WITH_OR_EVAL = 0b0000_0010_0000_0000;
+    /** Does the function define "arguments" identifier as a parameter of nested function name? */
+    private static final int DEFINES_ARGUMENTS           = 0b0000_0100_0000_0000;
     /** Does the function need a self symbol? */
-    private static final int NEEDS_SELF_SYMBOL       = 0b0000_1000_0000_0000;
+    private static final int NEEDS_SELF_SYMBOL           = 0b0000_1000_0000_0000;
+    /** Does this function or any of its descendants use variables from an ancestor function's scope (incl. globals)? */
+    private static final int USES_ANCESTOR_SCOPE         = 0b0001_0000_0000_0000;
 
     /** Does this function or any nested functions contain a with or an eval? */
-    private static final int HAS_DEEP_WITH_OR_EVAL = HAS_EVAL | HAS_WITH | HAS_CHILD_WITH_OR_EVAL;
+    private static final int HAS_DEEP_WITH_OR_EVAL = HAS_EVAL | HAS_WITH | HAS_DESCENDANT_WITH_OR_EVAL;
     /** Does this function need to store all its variables in scope? */
     private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_WITH_OR_EVAL | IS_SPLIT;
-    /** Does this function need a scope object? */
-    private static final int NEEDS_SCOPE           = HAS_ALL_VARS_IN_SCOPE | IS_VAR_ARG;
+    /** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */
+    private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL;
+    /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep with or eval. */
+    private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_WITH_OR_EVAL;
 
     /** What is the return type of this function? */
     private Type returnType = Type.UNKNOWN;
@@ -334,6 +339,11 @@
         }
     }
 
+    @Override
+    public boolean needsScope() {
+        return super.needsScope() || isScript();
+    }
+
     /*
      * Frame management.
      */
@@ -503,12 +513,29 @@
      * Flag this function as using the {@code with} keyword
      */
     public void setHasWith() {
-        this.flags |= HAS_WITH;
-        // with requires scope in parents.
-        FunctionNode parentFunction = findParentFunction();
-        while (parentFunction != null) {
-            parentFunction.setHasNestedWithOrEval();
-            parentFunction = parentFunction.findParentFunction();
+        if(!hasWith()) {
+            this.flags |= HAS_WITH;
+            // with requires scope in parents.
+            // TODO: refine this. with should not force all variables in parents to be in scope, only those that are
+            // actually referenced as identifiers by name
+            markParentForWithOrEval();
+        }
+    }
+
+    private void markParentForWithOrEval() {
+        // If this is invoked, then either us or a descendant uses with or eval, meaning we must have our own scope.
+        setNeedsScope();
+
+        final FunctionNode parentFunction = findParentFunction();
+        if(parentFunction != null) {
+            parentFunction.setDescendantHasWithOrEval();
+        }
+    }
+
+    private void setDescendantHasWithOrEval() {
+        if((flags & HAS_DESCENDANT_WITH_OR_EVAL) == 0) {
+            flags |= HAS_DESCENDANT_WITH_OR_EVAL;
+            markParentForWithOrEval();
         }
     }
 
@@ -522,22 +549,15 @@
     }
 
     /**
-     * Flag this function as using the {@code eval} keyword
+     * Flag this function as calling the {@code eval} function
      */
     public void setHasEval() {
-        this.flags |= HAS_EVAL;
-        // eval requires scope in parents.
-        FunctionNode parentFunction = findParentFunction();
-        while (parentFunction != null) {
-            parentFunction.setHasNestedWithOrEval();
-            parentFunction = parentFunction.findParentFunction();
+        if(!hasEval()) {
+            this.flags |= HAS_EVAL;
+            markParentForWithOrEval();
         }
     }
 
-    private void setHasNestedWithOrEval() {
-        flags |= HAS_CHILD_WITH_OR_EVAL;
-    }
-
     /**
      * Test whether this function or any of its nested functions contains a <tt>with</tt> statement
      * or an <tt>eval</tt> call.
@@ -623,11 +643,13 @@
     }
 
     /**
-     * Check if this function needs the {@code callee} parameter
-     * @return true if the function uses {@code callee}
+     * Check if this function's generated Java method needs a {@code callee} parameter. Functions that need access to
+     * their parent scope, functions that reference themselves, and non-strict functions that need an Arguments object
+     * (since it exposes {@code arguments.callee} property) will need to have a callee parameter.
+     * @return true if the function's generated Java method needs a {@code callee} parameter.
      */
     public boolean needsCallee() {
-        return getCalleeNode() != null;
+        return needsParentScope() || needsSelfSymbol() || (needsArguments() && !isStrictMode());
     }
 
     /**
@@ -666,37 +688,60 @@
     }
 
     /**
-     * Check if this function needs to use var args, which is the case for
-     * e.g. {@code eval} and functions where {@code arguments} is used
-     *
-     * @return true if the function needs to use var args
+     * Does this function's method needs to be variable arity (gather all script-declared parameters in a final
+     * {@code Object[]} parameter. Functions that need to have the "arguments" object as well as functions that simply
+     * declare too many arguments for JVM to handle with fixed arity will need to be variable arity.
+     * @return true if the Java method in the generated code that implements this function needs to be variable arity.
+     * @see #needsArguments()
+     * @see LinkerCallSite#ARGLIMIT
      */
     public boolean isVarArg() {
-        return (flags & IS_VAR_ARG) != 0 && !isScript();
+        return needsArguments() || parameters.size() > LinkerCallSite.ARGLIMIT;
+    }
+
+    /**
+     * Flag this function as one that defines the identifier "arguments" as a function parameter or nested function
+     * name. This precludes it from needing to have an Arguments object defined as "arguments" local variable. Note that
+     * defining a local variable named "arguments" still requires construction of the Arguments object (see
+     * ECMAScript 5.1 Chapter 10.5).
+     * @see #needsArguments()
+     */
+    public void setDefinesArguments() {
+        this.flags |= DEFINES_ARGUMENTS;
     }
 
     /**
-     * Flag this function as needing to use var args vector
+     * Returns true if this function needs to have an Arguments object defined as a local variable named "arguments".
+     * Functions that use "arguments" as identifier and don't define it as a name of a parameter or a nested function
+     * (see ECMAScript 5.1 Chapter 10.5), as well as any function that uses eval or with, or has a nested function that
+     * does the same, will have an "arguments" object. Also, if this function is a script, it will not have an
+     * "arguments" object, because it does not have local variables; rather the Global object will have an explicit
+     * "arguments" property that provides command-line arguments for the script.
+     * @return true if this function needs an arguments object.
      */
-    public void setIsVarArg() {
-        this.flags |= IS_VAR_ARG;
+    public boolean needsArguments() {
+        // uses "arguments" or calls eval, but it does not redefine "arguments", and finally, it's not a script, since
+        // for top-level script, "arguments" is picked up from Context by Global.init() instead.
+        return (flags & MAYBE_NEEDS_ARGUMENTS) != 0 && (flags & DEFINES_ARGUMENTS) == 0 && !isScript();
     }
 
     /**
-     * Ugly special case:
-     * Tells the compiler if {@code arguments} variable should be hidden and given
-     * a unique name, for example if it is used as an explicit parameter name
-     * @return true of {@code arguments} should be hidden
+     * Flags this function as one that uses the "arguments" identifier.
+     * @see #needsArguments()
      */
-    public boolean hideArguments() {
-        return (flags & HIDE_ARGS) != 0;
+    public void setUsesArguments() {
+        flags |= USES_ARGUMENTS;
     }
 
     /**
-     * Flag this function as needing to hide the {@code arguments} vectir
+     * Returns true if this function needs access to its parent scope. Functions referencing variables outside their
+     * scope (including global variables), as well as functions that call eval or have a with block, or have nested
+     * functions that call eval or have a with block, will need a parent scope. Top-level script functions also need a
+     * parent scope since they might be used from within eval, and eval will need an externally passed scope.
+     * @return true if the function needs parent scope.
      */
-    public void setHideArguments() {
-        this.flags |= HIDE_ARGS;
+    public boolean needsParentScope() {
+        return (flags & NEEDS_PARENT_SCOPE) != 0 || isScript();
     }
 
     /**
@@ -749,14 +794,9 @@
         this.name = name;
     }
 
-    @Override
-    public boolean needsScope() {
-        return needsScope || isScript() || (flags & NEEDS_SCOPE) != 0;
-    }
-
     /**
-     * Check if this function should have all its variables in scope, regardless
-     * of other properties.
+     * Check if this function should have all its variables in its own scope. Scripts, split sub-functions, and
+     * functions having with and/or eval blocks are such.
      *
      * @return true if all variables should be in scope
      */
@@ -780,6 +820,7 @@
      */
     public void setIsSplit() {
         this.flags |= IS_SPLIT;
+        setNeedsScope();
     }
 
     /**
@@ -893,6 +934,38 @@
     }
 
     /**
+     * Marks this function as one using any global symbol. The function and all its parent functions will all be marked
+     * as needing parent scope.
+     * @see #needsParentScope()
+     */
+    public void setUsesGlobalSymbol() {
+        this.flags |= USES_ANCESTOR_SCOPE;
+        final FunctionNode parentFn = findParentFunction();
+        if(parentFn != null) {
+            parentFn.setUsesGlobalSymbol();
+        }
+    }
+
+    /**
+     * Marks this function as using a specified scoped symbol. The function and its parent functions up to but not
+     * including the function defining the symbol will be marked as needing parent scope. The function defining the
+     * symbol will be marked as one that needs to have its own scope.
+     * @param symbol the symbol being used.
+     * @see #needsParentScope()
+     */
+    public void setUsesScopeSymbol(final Symbol symbol) {
+        if(symbol.getBlock() == this) {
+            setNeedsScope();
+        } else {
+            this.flags |= USES_ANCESTOR_SCOPE;
+            final FunctionNode parentFn = findParentFunction();
+            if(parentFn != null) {
+                parentFn.setUsesScopeSymbol(symbol);
+            }
+        }
+    }
+
+    /**
      * Return the node representing {@code this} in this function
      * @return IdentNode representing {@code this}
      */
--- a/nashorn/src/jdk/nashorn/internal/ir/Symbol.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/ir/Symbol.java	Thu Jan 31 18:34:42 2013 +0100
@@ -83,7 +83,7 @@
     /** Local variable slot. -1 indicates external property. */
     private int slot;
 
-    /** Field number in scope or property. */
+    /** Field number in scope or property; array index in varargs when not using arguments object. */
     private int fieldIndex;
 
     /** Number of times this symbol is used in code */
@@ -330,7 +330,11 @@
         }
 
         if (isScope()) {
-            sb.append(" S");
+            if(isGlobal()) {
+                sb.append(" G");
+            } else {
+                sb.append(" S");
+            }
         }
 
         if (canBePrimitive()) {
@@ -382,7 +386,9 @@
             trace("SET IS SCOPE");
         }
         flags |= IS_SCOPE;
-        getBlock().setNeedsScope();
+        if(!isGlobal()) {
+            getBlock().setNeedsScope();
+        }
     }
 
     /**
--- a/nashorn/src/jdk/nashorn/internal/objects/Global.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java	Thu Jan 31 18:34:42 2013 +0100
@@ -1213,7 +1213,7 @@
      *
      * @return the new array
      */
-    public static Object allocateArguments(final Object[] arguments, final Object callee, final int numParams) {
+    public static ScriptObject allocateArguments(final Object[] arguments, final Object callee, final int numParams) {
         return NativeArguments.allocate(arguments, (ScriptFunction)callee, numParams);
     }
 
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeArguments.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeArguments.java	Thu Jan 31 18:34:42 2013 +0100
@@ -551,7 +551,8 @@
      * @return Arguments Object
      */
     public static ScriptObject allocate(final Object[] arguments, final ScriptFunction callee, final int numParams) {
-        final boolean isStrict = callee.isStrict();
+        // Strict functions won't always have a callee for arguments, and will pass null instead.
+        final boolean isStrict = callee == null || callee.isStrict();
         return isStrict ? new NativeStrictArguments(arguments, numParams) : new NativeArguments(arguments, callee, numParams);
     }
 
--- a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java	Thu Jan 31 18:34:42 2013 +0100
@@ -49,6 +49,7 @@
     // per-function object flags
     private static final int IS_STRICT  = 0b0000_0001;
     private static final int IS_BUILTIN = 0b0000_0010;
+    private static final int HAS_CALLEE = 0b0000_0100;
 
     // set this function to be a builtin function
     private void setIsBuiltin() {
@@ -221,6 +222,16 @@
     }
 
     @Override
+    public final boolean hasCalleeParameter() {
+        return (flags & HAS_CALLEE) != 0;
+    }
+
+    @Override
+    protected void setHasCalleeParameter() {
+        flags |= HAS_CALLEE;
+    }
+
+    @Override
     public final boolean isBuiltin() {
         return (flags & IS_BUILTIN) != 0;
     }
--- a/nashorn/src/jdk/nashorn/internal/parser/Parser.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/parser/Parser.java	Thu Jan 31 18:34:42 2013 +0100
@@ -98,7 +98,6 @@
 import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.JSErrorType;
 import jdk.nashorn.internal.runtime.ParserException;
-import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
 
 /**
  * Builds the IR.
@@ -334,7 +333,6 @@
 
         if (EVAL.tag().equals(name)) {
             function.setHasEval();
-            function.setIsVarArg();
         }
     }
 
@@ -346,7 +344,7 @@
         final String name = ident.getName();
 
         if (ARGUMENTS.tag().equals(name)) {
-            function.setIsVarArg();
+            function.setUsesArguments();
         }
     }
 
@@ -2517,6 +2515,9 @@
 
         if (isStatement && !isInWithBlock()) {
             functionNode.setIsStatement();
+            if(ARGUMENTS.tag().equals(name.getName())) {
+                functionNode.findParentFunction().setDefinesArguments();
+            }
         }
 
         if (isAnonymous) {
@@ -2536,7 +2537,7 @@
                 String parameterName = parameter.getName();
 
                 if (ARGUMENTS.tag().equals(parameterName)) {
-                    functionNode.setHideArguments();
+                    functionNode.setDefinesArguments();
                 }
 
                 if (parametersSet.contains(parameterName)) {
@@ -2555,14 +2556,10 @@
             }
         } else if (arity == 1) {
             if (ARGUMENTS.tag().equals(parameters.get(0).getName())) {
-                functionNode.setHideArguments();
+                functionNode.setDefinesArguments();
             }
         }
 
-        if (arity > LinkerCallSite.ARGLIMIT) {
-            functionNode.setIsVarArg();
-        }
-
         if (isStatement) {
             final VarNode var = new VarNode(source, functionToken, finish, name, referenceNode);
             if (isInWithBlock()) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ArgumentSetter.java	Thu Jan 31 18:34:42 2013 +0100
@@ -0,0 +1,67 @@
+/*
+ * 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.runtime;
+
+import static jdk.nashorn.internal.codegen.CompilerConstants.staticCall;
+
+import jdk.nashorn.internal.codegen.CompilerConstants.Call;
+
+/**
+ * A class with static helper methods invoked from generated bytecode for setting values of parameters of variable-arity
+ * functions.
+ */
+public class ArgumentSetter {
+
+    /** Method handle for setting a function argument at a given index in an arguments object. Used from generated bytecode */
+    public static final Call SET_ARGUMENT      = staticCall(ArgumentSetter.class, "setArgument", void.class, Object.class, ScriptObject.class, int.class);
+
+    /** Method handle for setting a function argument at a given index in an arguments array. Used from generated bytecode */
+    public static final Call SET_ARRAY_ELEMENT = staticCall(ArgumentSetter.class, "setArrayElement", void.class, Object.class, Object[].class, int.class);
+
+
+    /**
+     * Used from generated bytecode to invoke {@link ScriptObject#setArgument(int, Object)} without having to reorder
+     * the arguments on the stack. When we're generating a store into the argument, we first have the value on the
+     * stack, and only afterwards load the target object and the index.
+     * @param value the value to write at the given argument index.
+     * @param arguments the arguments object that we're writing the value to
+     * @param key the index of the argument
+     */
+    public static void setArgument(final Object value, final ScriptObject arguments, final int key) {
+        arguments.setArgument(key, value);
+    }
+
+    /**
+     * Used from generated bytecode to set a variable arity parameter - an array element - without having to reorder
+     * the arguments on the stack. When we're generating a store into the array, we first have the value on the
+     * stack, and only afterwards load the target array and the index.
+     * @param value the value to write at the given argument index.
+     * @param arguments the arguments array that we're writing the value to
+     * @param key the index of the argument
+     */
+    public static void setArrayElement(final Object value, final Object[] arguments, final int key) {
+        arguments[key] = value;
+    }
+}
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java	Thu Jan 31 18:34:42 2013 +0100
@@ -128,7 +128,28 @@
             final PropertyMap map,
             final ScriptObject scope,
             final MethodHandle[] specs) {
-        this(name, methodHandle, map, scope, null, 0, false, specs);
+        this(name, methodHandle, map, scope, null, 0, needsCallee(methodHandle), specs);
+    }
+
+    /**
+     * Heuristic to figure out if the method handle has a callee argument. If it's type is either
+     * {@code (boolean, Object, ScriptFunction, ...)} or {@code (Object, ScriptFunction, ...)}, then we'll assume it has
+     * a callee argument. We need this as the constructor above is not passed this information, and can't just blindly
+     * assume it's false (notably, it's being invoked for creation of new scripts, and scripts have scopes, therefore
+     * they also always receive a callee.
+     * @param methodHandle the examined method handle
+     * @return true if the method handle expects a callee, false otherwise
+     */
+    private static boolean needsCallee(MethodHandle methodHandle) {
+        final MethodType type = methodHandle.type();
+        final int len = type.parameterCount();
+        if(len == 0) {
+            return false;
+        }
+        if(type.parameterType(0) == boolean.class) {
+            return len > 2 && type.parameterType(2) == ScriptFunction.class;
+        }
+        return len > 1 && type.parameterType(1) == ScriptFunction.class;
     }
 
     /**
@@ -193,27 +214,27 @@
             constructorCount++;
         }
 
-        // needCallee => scope != null
-        assert !needsCallee || scope != null;
         this.name   = name;
         this.source = source;
         this.token  = token;
         this.scope  = scope;
+        if(needsCallee) {
+            setHasCalleeParameter();
+        }
 
         final MethodType type       = methodHandle.type();
         final int        paramCount = type.parameterCount();
         final boolean    isVarArg   = type.parameterType(paramCount - 1).isArray();
 
-        final MethodHandle mh = MH.asType(methodHandle, adaptType(type, scope != null, isVarArg));
+        final MethodHandle mh = MH.asType(methodHandle, adaptType(type, needsCallee, isVarArg));
 
         this.arity = isVarArg ? -1 : paramCount - 1; //drop the self param for arity
 
+        if (needsCallee && !isVarArg) {
+            this.arity--;
+        }
+
         if (scope != null) {
-            if (needsCallee) {
-                if (!isVarArg) {
-                    this.arity--;
-                }
-            }
             this.invokeHandle    = mh;
             this.constructHandle = mh;
         } else if (isConstructor(mh)) {
@@ -698,9 +719,8 @@
         return hasCalleeParameter() ? MH.bindTo(bound, this) : bound;
     }
 
-    private boolean hasCalleeParameter() {
-        return scope != null;
-    }
+    protected abstract boolean hasCalleeParameter();
+    protected abstract void setHasCalleeParameter();
 
     /**
      * Get the construct handle - the most generic (and if no specializations are in place, only) constructor
@@ -878,12 +898,9 @@
         MethodHandle handle = MH.foldArguments(MH.dropArguments(NEWFILTER, 2, ctorArgs), constructor);
 
         if (hasCalleeParameter()) {
-            final MethodHandle allocate = MH.bindTo(MethodHandles.exactInvoker(ALLOCATE.type()), ScriptFunction.ALLOCATE);
-            handle = MH.foldArguments(handle, allocate);
-            handle = MH.asType(handle, handle.type().changeParameterType(0, Object.class)); // ScriptFunction => Object
+            handle = MH.foldArguments(handle, ALLOCATE);
         } else {
-            final MethodHandle allocate = MH.dropArguments(MH.bindTo(ScriptFunction.ALLOCATE, this), 0, Object.class);
-            handle = MH.filterArguments(handle, 0, allocate);
+            handle = MH.filterArguments(handle, 0, ALLOCATE);
         }
 
         final MethodHandle filterIn = MH.asType(pairArguments(handle, type), type);
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Thu Jan 31 20:07:40 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptRuntime.java	Thu Jan 31 18:34:42 2013 +0100
@@ -566,13 +566,12 @@
      * This is 'delete' that always fails. We have to check strict mode and throw error.
      * That is why this is a runtime function. Or else we could have inlined 'false'.
      *
-     * @param obj       object with property to delete
      * @param property  property to delete
      * @param strict    are we in strict mode
      *
      * @return false always
      */
-    public static boolean FAIL_DELETE(final Object obj, final Object property, final Object strict) {
+    public static boolean FAIL_DELETE(final Object property, final Object strict) {
         if (Boolean.TRUE.equals(strict)) {
             syntaxError("strict.cant.delete", safeToString(property));
         }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8006529-b.js	Thu Jan 31 18:34:42 2013 +0100
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8006529 : Constructor functions that don't need callees must not get
+ * linked with a MethodHandle boud to a specific function instance.
+ * @test
+ * @run
+ */
+
+Object.defineProperty(Object.prototype, "extends", {
+  value: function (superConstructor) {
+    function ProtoBridge() { }
+    ProtoBridge.prototype = superConstructor.prototype;
+    this.prototype = new ProtoBridge();
+    this.superConstructor = superConstructor;
+  }
+});
+
+function A() {
+}
+A.prototype.f = function () {
+  this.g();
+}
+
+function B() {
+  B.superConstructor.call(this);
+  this.f();
+}
+B.extends(A);
+
+B.prototype.g = function () {
+  print("It worked!")
+}
+
+function C() {
+  C.superConstructor.call(this);
+}
+C.extends(B);
+
+var x = [B, C]
+for(var i in x) {
+  print("Doing " + i)
+  new x[i]()
+}
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8006529-b.js.EXPECTED	Thu Jan 31 18:34:42 2013 +0100
@@ -0,0 +1,4 @@
+Doing 0
+It worked!
+Doing 1
+It worked!
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/script/basic/JDK-8006529.js	Thu Jan 31 18:34:42 2013 +0100
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ * 
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ * 
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ * 
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ * 
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * JDK-8006529 : Methods should not always get callee parameter, and they
+ * should not be too eager in creation of scopes.
+ *
+ * @test
+ * @run
+ */
+
+// compile(script) -- compiles a script specified as a string with its 
+// source code, returns a jdk.nashorn.internal.ir.FunctionNode object 
+// representing it.
+var compile = (function() {
+    var Compiler       = Java.type("jdk.nashorn.internal.codegen.Compiler")
+    var Context        = Java.type("jdk.nashorn.internal.runtime.Context")
+    var Source         = Java.type("jdk.nashorn.internal.runtime.Source")
+    var CompilerAccess = Java.type("jdk.nashorn.internal.codegen.CompilerAccess")
+    return function(source) {
+	    var compiler = Compiler.compiler(new Source("<no name>", source), Context.getContext())
+        compiler.compile()
+        return CompilerAccess.getScriptNode(compiler)
+    }
+})();
+
+var allAssertions = (function() {
+    var allAssertionList = ['isVarArg', 'needsParentScope', 'needsCallee', 'needsScope', 'needsSelfSymbol', 'isSplit', 'hasEval', 'hasWith', 'hasDeepWithOrEval', 'varsInScope', 'isStrictMode']
+    var allAssertions = {}
+    for(var assertion in allAssertionList) {
+        allAssertions[allAssertionList[assertion]] = true
+    }
+    return allAssertions;
+})();
+
+// test(f[, assertions...]) tests whether all the specified assertions on the
+// passed function node are true.
+function test(f) {
+    var assertions = {}
+    for(var i = 1; i < arguments.length; ++i) {
+        var assertion = arguments[i]
+        if(!allAssertions[assertion]) {
+            throw "Unknown assertion " + assertion + " for " + f;
+        }
+        assertions[assertion] = true
+    }
+    for(var assertion in allAssertions) {
+        var expectedValue = !!assertions[assertion]
+        if(f[assertion] == null) {
+            throw "Can't find " + assertion + " on " + f;
+        }
+        if(f[assertion]() !== expectedValue) {
+            throw "Expected " + assertion + " === " + expectedValue + " for " + f;
+        }
+    }
+}
+
+// testFirstFn(script[, assertions...] tests whether all the specified
+// assertions are true in the first function in the given script; "script"
+// is a string with the source text of the script.
+function testFirstFn(script) {
+    arguments[0] = compile(script).functions[0]
+    test.apply(null, arguments)
+}
+
+// ---------------------------------- ACTUAL TESTS START HERE --------------
+
+// The simplest possible functions have no attributes set
+testFirstFn("function f() { }")
+testFirstFn("function f(x) { x }")
+
+// A function referencing a global needs parent scope, and it needs callee
+// (because parent scope is passed through callee)
+testFirstFn("function f() { x }", 'needsCallee', 'needsParentScope')
+
+// A function referencing "arguments" will have to be vararg. It also needs
+// the callee, as it needs to fill out "arguments.callee".
+testFirstFn("function f() { arguments }", 'needsCallee', 'isVarArg')
+
+// A function referencing "arguments" will have to be vararg. If it is
+// strict, it will not have to have a callee, though.
+testFirstFn("function f() {'use strict'; arguments }", 'isVarArg', 'isStrictMode')
+
+// A function defining "arguments" as a parameter will not be vararg.
+testFirstFn("function f(arguments) { arguments }")
+
+// A function defining "arguments" as a nested function will not be vararg.
+testFirstFn("function f() { function arguments() {}; arguments; }")
+
+// A function defining "arguments" as a local variable will be vararg.
+testFirstFn("function f() { var arguments; arguments; }", 'isVarArg', 'needsCallee')
+
+// A self-referencing function defined as a statement doesn't need a self 
+// symbol, as it'll rather obtain itself from the parent scope.
+testFirstFn("function f() { f() }", 'needsCallee', 'needsParentScope')
+
+// A self-referencing function defined as an expression needs a self symbol,
+// as it can't obtain itself from the parent scope.
+testFirstFn("(function f() { f() })", 'needsCallee', 'needsSelfSymbol')
+
+// A child function accessing parent's variable triggers the need for scope
+// in parent
+testFirstFn("(function f() { var x; function g() { x } })", 'needsScope')
+
+// A child function accessing parent's parameter triggers the need for scope
+// in parent
+testFirstFn("(function f(x) { function g() { x } })", 'needsScope')
+
+// A child function accessing a global variable triggers the need for parent
+// scope in parent
+testFirstFn("(function f() { function g() { x } })", 'needsParentScope', 'needsCallee')
+
+// A child function redefining a local variable from its parent should not 
+// affect the parent function in any way
+testFirstFn("(function f() { var x; function g() { var x; x } })")
+
+// Using "with" unleashes a lot of needs: parent scope, callee, own scope, 
+// and all variables in scope. Actually, we could make "with" less wasteful,
+// and only put those variables in scope that it actually references, similar
+// to what nested functions do with variables in their parents.
+testFirstFn("(function f() { var o; with(o) {} })", 'needsParentScope', 'needsCallee', 'needsScope', 'hasWith', 'hasDeepWithOrEval', 'varsInScope')
+
+// Using "eval" is as bad as using "with" with the added requirement of
+// being vararg, 'cause we don't know if eval will be using "arguments".
+testFirstFn("(function f() { eval() })", 'needsParentScope', 'needsCallee', 'needsScope', 'hasEval', 'isVarArg', 'hasDeepWithOrEval', 'varsInScope')
+
+// Nested function using "with" is pretty much the same as the parent
+// function needing with.
+testFirstFn("(function f() { function g() { var o; with(o) {} } })", 'needsParentScope', 'needsCallee', 'needsScope', 'hasDeepWithOrEval', 'varsInScope')
+// Nested function using "eval" is almost the same as parent function using
+// eval, but at least the parent doesn't have to be vararg.
+testFirstFn("(function f() { function g() { eval() } })", 'needsParentScope', 'needsCallee', 'needsScope', 'hasDeepWithOrEval', 'varsInScope')
+
+// Function with 250 named parameters is ordinary
+testFirstFn("function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, p64, p65, p66, p67, p68, p69, p70, p71, p72, p73, p74, p75, p76, p77, p78, p79, p80, p81, p82, p83, p84, p85, p86, p87, p88, p89, p90, p91, p92, p93, p94, p95, p96, p97, p98, p99, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111, p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127, p128, p129, p130, p131, p132, p133, p134, p135, p136, p137, p138, p139, p140, p141, p142, p143, p144, p145, p146, p147, p148, p149, p150, p151, p152, p153, p154, p155, p156, p157, p158, p159, p160, p161, p162, p163, p164, p165, p166, p167, p168, p169, p170, p171, p172, p173, p174, p175, p176, p177, p178, p179, p180, p181, p182, p183, p184, p185, p186, p187, p188, p189, p190, p191, p192, p193, p194, p195, p196, p197, p198, p199, p200, p201, p202, p203, p204, p205, p206, p207, p208, p209, p210, p211, p212, p213, p214, p215, p216, p217, p218, p219, p220, p221, p222, p223, p224, p225, p226, p227, p228, p229, p230, p231, p232, p233, p234, p235, p236, p237, p238, p239, p240, p241, p242, p243, p244, p245, p246, p247, p248, p249, p250) { p250 = p249 }")
+
+// Function with 251 named parameters is variable arguments
+testFirstFn("function f(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, p26, p27, p28, p29, p30, p31, p32, p33, p34, p35, p36, p37, p38, p39, p40, p41, p42, p43, p44, p45, p46, p47, p48, p49, p50, p51, p52, p53, p54, p55, p56, p57, p58, p59, p60, p61, p62, p63, p64, p65, p66, p67, p68, p69, p70, p71, p72, p73, p74, p75, p76, p77, p78, p79, p80, p81, p82, p83, p84, p85, p86, p87, p88, p89, p90, p91, p92, p93, p94, p95, p96, p97, p98, p99, p100, p101, p102, p103, p104, p105, p106, p107, p108, p109, p110, p111, p112, p113, p114, p115, p116, p117, p118, p119, p120, p121, p122, p123, p124, p125, p126, p127, p128, p129, p130, p131, p132, p133, p134, p135, p136, p137, p138, p139, p140, p141, p142, p143, p144, p145, p146, p147, p148, p149, p150, p151, p152, p153, p154, p155, p156, p157, p158, p159, p160, p161, p162, p163, p164, p165, p166, p167, p168, p169, p170, p171, p172, p173, p174, p175, p176, p177, p178, p179, p180, p181, p182, p183, p184, p185, p186, p187, p188, p189, p190, p191, p192, p193, p194, p195, p196, p197, p198, p199, p200, p201, p202, p203, p204, p205, p206, p207, p208, p209, p210, p211, p212, p213, p214, p215, p216, p217, p218, p219, p220, p221, p222, p223, p224, p225, p226, p227, p228, p229, p230, p231, p232, p233, p234, p235, p236, p237, p238, p239, p240, p241, p242, p243, p244, p245, p246, p247, p248, p249, p250, p251) { p250 = p251 }", 'isVarArg')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/src/jdk/nashorn/internal/codegen/CompilerAccess.java	Thu Jan 31 18:34:42 2013 +0100
@@ -0,0 +1,35 @@
+
+package jdk.nashorn.internal.codegen;
+
+import java.lang.reflect.Field;
+import jdk.nashorn.internal.ir.FunctionNode;
+
+/**
+ * Since Compiler class doesn't give us access to its private {@code functionNode} field, we use this reflection-based
+ * access-check disabling helper to get to it in compilation tests.
+ *
+ */
+public class CompilerAccess {
+    private static final Field FUNCTION_NODE_FIELD = getCompilerFunctionNodeField();
+    static {
+        FUNCTION_NODE_FIELD.setAccessible(true);
+    }
+
+    /**
+     * Given a compiler, return its {@code functionNode} field, representing the root function (i.e. the compiled script).
+     * @param compiler the compiler that already run its {@link Compiler#compile()} method.
+     * @return the root function node representing the compiled script.
+     * @throws IllegalAccessException
+     */
+    public static FunctionNode getScriptNode(Compiler compiler) throws IllegalAccessException {
+        return (FunctionNode)FUNCTION_NODE_FIELD.get(compiler);
+    }
+
+    private static Field getCompilerFunctionNodeField() {
+        try {
+            return Compiler.class.getDeclaredField("functionNode");
+        } catch (NoSuchFieldException e) {
+            throw new AssertionError("", e);
+        }
+    }
+}