Merge
diff --git a/nashorn/bin/jjs b/nashorn/bin/jjs
index ca531f4..fe6665c 100644
--- a/nashorn/bin/jjs
+++ b/nashorn/bin/jjs
@@ -26,4 +26,4 @@
 
 [ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1;
 
-$JAVA_HOME/bin/java -server -XX:-TieredCompilation -Xms2G -Xmx2G -esa -ea -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:`dirname $0`/../dist -XX:+HeapDumpOnOutOfMemoryError -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -Dnashorn.debug=true jdk.nashorn.tools.Shell $*
+$JAVA_HOME/bin/java -server -XX:-TieredCompilation -Xms2G -Xmx2G -esa -ea -Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -XX:+HeapDumpOnOutOfMemoryError -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -Dnashorn.debug=true jdk.nashorn.tools.Shell $*
diff --git a/nashorn/bin/jjssecure b/nashorn/bin/jjssecure
index 614ffd3..db6bdfc 100644
--- a/nashorn/bin/jjssecure
+++ b/nashorn/bin/jjssecure
@@ -26,4 +26,4 @@
 
 [ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1;
 
-$JAVA_HOME/bin/java -Xms2G -Xmx2G -XX:-TieredCompilation -server -esa -ea -Djava.security.properties=`dirname $0`/../make/java.security.override -Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:`dirname $0`/../dist -XX:+HeapDumpOnOutOfMemoryError -Dnashorn.debug=true -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=true -Dnashorn.home=`dirname $0`/.. -Djava.security.manager jdk.nashorn.tools.Shell $*
+$JAVA_HOME/bin/java -Xms2G -Xmx2G -XX:-TieredCompilation -server -esa -ea -Djava.security.properties=`dirname $0`/../make/java.security.override -Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -XX:+HeapDumpOnOutOfMemoryError -Dnashorn.debug=true -Djava.lang.invoke.MethodHandle.DEBUG_NAMES=true -Dnashorn.home=`dirname $0`/.. -Djava.security.manager jdk.nashorn.tools.Shell $*
diff --git a/nashorn/bin/nashorn b/nashorn/bin/nashorn
index 3fccdd0..da22be1 100644
--- a/nashorn/bin/nashorn
+++ b/nashorn/bin/nashorn
@@ -26,4 +26,4 @@
 
 [ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1;
 
-$JAVA_HOME/bin/jrunscript -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:`dirname $0`/../dist -J-XX:+HeapDumpOnOutOfMemoryError -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -J-Dnashorn.debug=true -l nashorn $*
+$JAVA_HOME/bin/jrunscript -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -J-XX:+HeapDumpOnOutOfMemoryError -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -J-Dnashorn.debug=true -l nashorn $*
diff --git a/nashorn/bin/nashornsecure b/nashorn/bin/nashornsecure
index 3d02c52..77c7c52 100644
--- a/nashorn/bin/nashornsecure
+++ b/nashorn/bin/nashornsecure
@@ -26,4 +26,4 @@
 
 [ -z "$JAVA_HOME" ] && echo "Please set JAVA_HOME" && exit 1;
 
-$JAVA_HOME/bin/jrunscript -J-Djava.security.properties=`dirname $0`/../make/java.security.override -J-Djava.security.manager -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=$JAVA_HOME/jre/lib/ext:`dirname $0`/../dist -J-XX:+HeapDumpOnOutOfMemoryError -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -J-Dnashorn.debug=true -l nashorn $*
+$JAVA_HOME/bin/jrunscript -J-Djava.security.properties=`dirname $0`/../make/java.security.override -J-Djava.security.manager -J-Xms2G -J-Xmx2G -J-XX:-TieredCompilation -J-server -J-esa -J-ea -J-Djava.ext.dirs=`dirname $0`/../dist:$JAVA_HOME/jre/lib/ext -J-XX:+HeapDumpOnOutOfMemoryError -J-Djava.lang.invoke.MethodHandle.DEBUG_NAMES=false -J-Dnashorn.debug=true -l nashorn $*
diff --git a/nashorn/docs/DEVELOPER_README b/nashorn/docs/DEVELOPER_README
index 3bd220b..d0587ce 100644
--- a/nashorn/docs/DEVELOPER_README
+++ b/nashorn/docs/DEVELOPER_README
@@ -13,6 +13,17 @@
 This documentation of the system property flags assume that the
 default value of the flag is false, unless otherwise specified.
 
+SYSTEM PROPERTY: -Dnashorn.args=<string>
+
+This property takes as its value a space separated list of Nashorn
+command line options that should be passed to Nashorn. This might be useful 
+in environments where it is hard to tell how a nashorn.jar is launched.
+
+Example:
+
+> java -Dnashorn.args="--lazy-complation --log=compiler" large-java-app-with-nashorn.jar 
+> ant -Dnashorn.args="--log=codegen" antjob
+
 SYSTEM PROPERTY: -Dnashorn.unstable.relink.threshold=x
 
 This property controls how many call site misses are allowed before a 
diff --git a/nashorn/docs/JavaScriptingProgrammersGuide.html b/nashorn/docs/JavaScriptingProgrammersGuide.html
index cf24814..dd243d3 100644
--- a/nashorn/docs/JavaScriptingProgrammersGuide.html
+++ b/nashorn/docs/JavaScriptingProgrammersGuide.html
@@ -533,9 +533,8 @@
 <hr>
 <a name="jsarrays" id="jsarrays"></a>
 <h3>Creating, Converting and Using Java Arrays</h3>
-<p>While creating a Java object is the same as in Java, to create
-Java arrays in JavaScript we can use Java reflection
-explicitly. But once created the element access or length access is
+<p>
+Array element access or length access is
 the same as in Java. Also, a script array can be used when a Java
 method expects a Java array (auto conversion). So in most cases we
 don't have to create Java arrays explicitly.</p>
@@ -543,7 +542,8 @@
 // <a href="source/javaarray.js">javaarray.js</a>
 
 // create Java String array of 5 elements
-var a = java.lang.reflect.Array.newInstance(java.lang.String.class, 5);
+var StringArray = Java.type("java.lang.String[]");
+var a = new StringArray(5);
 
 // Accessing elements and length access is by usual Java syntax
 a[0] = "scripting is great!";
diff --git a/nashorn/docs/source/javaarray.js b/nashorn/docs/source/javaarray.js
index b9d93f0..a02aa3c 100644
--- a/nashorn/docs/source/javaarray.js
+++ b/nashorn/docs/source/javaarray.js
@@ -30,7 +30,8 @@
  */
 
 // create Java String array of 5 elements
-var a = java.lang.reflect.Array.newInstance(java.lang.String.class, 5);
+var StringArray = Java.type("java.lang.String[]");
+var a = new StringArray(5);
 
 // Accessing elements and length access is by usual Java syntax
 a[0] = "scripting is great!";
diff --git a/nashorn/make/build.xml b/nashorn/make/build.xml
index 34f56e4..945ccaa 100644
--- a/nashorn/make/build.xml
+++ b/nashorn/make/build.xml
@@ -124,7 +124,7 @@
     <echo message="release=${nashorn.version}" file="${build.classes.dir}/jdk/nashorn/internal/runtime/resources/version.properties" append="true"/>
   </target>
 
-  <target name="jar" depends="compile, run-nasgen" description="Creates nashorn.jar">
+  <target name="jar" depends="compile, run-nasgen, generate-cc-template" description="Creates nashorn.jar">
     <jar jarfile="${dist.jar}" manifest="${meta.inf.dir}/MANIFEST.MF" index="true" filesetmanifest="merge">
       <fileset dir="${build.classes.dir}"/>
       <manifest>
@@ -191,12 +191,12 @@
 
     <!-- tests that check nashorn internals and internal API -->
     <jar jarfile="${nashorn.internal.tests.jar}">
-      <fileset dir="${build.test.classes.dir}" excludes="**/api/*"/>
+      <fileset dir="${build.test.classes.dir}" excludes="**/api/**"/>
     </jar>
 
     <!-- tests that check nashorn script engine (jsr-223) API -->
     <jar jarfile="${nashorn.api.tests.jar}">
-      <fileset dir="${build.test.classes.dir}" includes="**/api/*"/>
+      <fileset dir="${build.test.classes.dir}" includes="**/api/**"/>
     </jar>
 
   </target>
diff --git a/nashorn/make/code_coverage.xml b/nashorn/make/code_coverage.xml
index 41d85ff..33980bd 100644
--- a/nashorn/make/code_coverage.xml
+++ b/nashorn/make/code_coverage.xml
@@ -36,7 +36,12 @@
       <equals arg1="${jcov}" arg2="dynamic" trim="true"/>
     </condition>
 
+    <condition property="cc.generate.template" value="true">
+      <equals arg1="${cc.dynamic.genereate.template}" arg2="true" trim="true"/>
+    </condition>
+
     <mkdir dir="${cc.dir}"/>
+    <mkdir dir="${build.dir}/to_be_instrumented"/>
 
     <!-- info -->
     <echo message="jcov=${jcov}"/>
@@ -51,25 +56,66 @@
     <property name="run.test.cc.jvmargs" value=""/>
   </target>
 
+  <target name="prepare-to-be-instrumented" depends="compile" description="Prepares to_be_instrumented dir">
+    <copy todir="${build.dir}/to_be_instrumented">
+      <fileset dir="${build.classes.dir}">
+        <include name="**/*.class"/>
+        <include name="**/*.clazz"/>
+      </fileset>
+    </copy>
+
+    <move todir="${build.dir}/to_be_instrumented/jdk/nashorn/internal/objects">
+      <fileset dir="${build.dir}/to_be_instrumented/jdk/nashorn/internal/objects">
+        <include name="**/*.clazz"/>
+      </fileset>
+      <mapper type="glob" from="*.clazz" to="*.class"/>
+    </move>
+  </target>
+
+  <target name="generate-cc-template" depends="prepare-to-be-instrumented" description="Generates code coverage template for dynamic CC" if="cc.generate.template">
+    <property name="cc.instrumented.path" location="${build.dir}/to_be_instrumented"/>
+    <java classname="com.sun.tdk.jcov.TmplGen">
+      <arg value="-verbose"/>
+      <arg line="-include ${cc.include}"/>
+      <arg line="-type all"/>
+      <arg line="-template ${cc.template}"/>
+      <arg value="${cc.instrumented.path}"/>
+      <classpath>
+        <pathelement location="${jcov.jar}"/>
+      </classpath>
+    </java>
+
+    <java classname="com.sun.tdk.jcov.RepGen">
+      <arg value="-verbose"/>
+      <arg line="-output ${cc.dir}/CC_template_report"/>
+      <arg value="${cc.template}"/>
+      <classpath>
+        <pathelement location="${jcov.jar}"/>
+      </classpath>
+    </java>
+  </target>
+
   <target name="init-cc" depends="init-cc-disabled, init-cc-enabled">
     <property name="run.test.cc.jvmargs" value=""/>
   </target>
 
   <target name="init-cc-cleanup" if="${cc.enabled}">
     <delete dir="${cc.dir}" failonerror="false" />
+    <delete dir="${build.dir}/to_be_instrumented" failonerror="false" />
   </target>
 
   <target name="check-merging-files" depends="init">
-	<resourcecount property="cc.xmls">
-  		<filelist dir="${cc.dir}" files="*.xml" />
-	</resourcecount>	
+	<echo message="checking avalibility of ${cc.template}"/>
     <condition property="nothing-to-merge" value="true">
-      <equals arg1="${cc.xmls}" arg2="1" trim="true"/>
+		<not>
+	      <available file="${cc.template}"/>
+		</not>
     </condition>
+	<echo message="nothing-to-merge = ${nothing-to-merge}"/>
   </target>
 
   <target name="fix-merging-files" depends="check-merging-files" if="${nothing-to-merge}">
-	<echo message="making pre-merge workaround"/>
+	<echo message="making pre-merge workaround due to missed template"/>
 	<move todir="${cc.dir}" includeemptydirs="false">
 		<fileset dir="${cc.dir}">
 			<include name="*.xml"/>
@@ -81,12 +127,12 @@
   <target name="merge-code-coverage" depends="fix-merging-files" unless="${nothing-to-merge}">
 	<echo message="merging files"/>
     <fileset dir="${cc.dir}" id="cc.xmls">
-      <include name="**/*${jcov}*.xml"/>
+      <include name="**/*_${jcov}_*.xml"/>
       <include name="**/CC_template.xml"/>
     </fileset>
 
     <pathconvert pathsep=" " property="cc.all.xmls" refid="cc.xmls"/>
-
+	<echo message="merging files - ${cc.all.xmls}" />
     <java classname="com.sun.tdk.jcov.Merger">
       <arg value="-verbose"/>
       <arg value="-output"/>
diff --git a/nashorn/make/java.security.override b/nashorn/make/java.security.override
index 0021985..a7edf33 100644
--- a/nashorn/make/java.security.override
+++ b/nashorn/make/java.security.override
@@ -3,7 +3,7 @@
 # We ensure that by overriding "package.access" security property.
 
 # The following "package.access" value was copied from  default java.security 
-# of jre/lib/security and appended with nashorn IR, Codegen and Parser packages.
+# of jre/lib/security and appended with nashorn sensitive packages.
 
 #
 # List of comma-separated packages that start with or equal this string
@@ -11,4 +11,4 @@
 # passed to checkPackageAccess unless the
 # corresponding RuntimePermission ("accessClassInPackage."+package) has
 # been granted.
-package.access=sun.,com.sun.xml.internal.ws.,com.sun.xml.internal.bind.,com.sun.imageio.,com.sun.org.apache.xerces.internal.utils.,com.sun.org.apache.xalan.internal.utils.,com.sun.org.glassfish.external.,com.sun.org.glassfish.gmbal.,jdk.internal.,jdk.nashorn.internal.ir., jdk.nashorn.internal.codegen., jdk.nashorn.internal.lookup., jdk.nashorn.internal.parser.
+package.access=sun.,com.sun.xml.internal.ws.,com.sun.xml.internal.bind.,com.sun.imageio.,com.sun.org.apache.xerces.internal.utils.,com.sun.org.apache.xalan.internal.utils.,com.sun.org.glassfish.external.,com.sun.org.glassfish.gmbal.,jdk.internal.,jdk.nashorn.internal.,jdk.nashorn.tools.
diff --git a/nashorn/make/project.properties b/nashorn/make/project.properties
index c4d0b94..58da977 100644
--- a/nashorn/make/project.properties
+++ b/nashorn/make/project.properties
@@ -210,7 +210,7 @@
 # add '-Dtest.js.outofprocess' to run each test in a new sub-process
 run.test.jvmargs.main=-server -Xmx${run.test.xmx} -XX:-TieredCompilation -esa -ea -Dnashorn.debug=true -Dfile.encoding=UTF-8
 #-XX:+HeapDumpOnOutOfMemoryError -XX:-UseCompressedKlassPointers -XX:+PrintHeapAtGC -XX:ClassMetaspaceSize=300M  
-run.test.jvmargs.octane.main=-Xms${run.test.xms} ${run.test.jvmargs}
+run.test.jvmargs.octane.main=-Xms${run.test.xms} ${run.test.jvmargs.main}
 
 run.test.jvmsecurityargs=-Xverify:all -Djava.security.properties=${basedir}/make/java.security.override -Djava.security.manager -Djava.security.policy=${basedir}/build/nashorn.policy
 
@@ -235,10 +235,12 @@
 	#naming of CC results
 	#NB directory specified in the cc.dir will be cleaned up!!!
 cc.dir=${basedir}/../Codecoverage_Nashorn
-cc.result.file.name=cc_nashorn.xml
+cc.result.file.name=CC_${jcov}_nashorn.xml
 	#dynamic CC parameters; please redefine in the ${user.home}/.nashorn.project.local.properties
 jcov2.lib.dir=${basedir}/../jcov2/lib
 jcov.jar=${jcov2.lib.dir}/jcov.jar
 cc.include=jdk\.nashorn\.*
 cc.exclude=jdk\.nashorn\.internal\.scripts\.*
+cc.dynamic.genereate.template=true
+cc.template=${cc.dir}/CC_template.xml
 cc.dynamic.args=-javaagent:${jcov.jar}=include=${cc.include},exclude=${cc.exclude},type=all,verbose=0,file=${cc.dir}/${cc.result.file.name}
diff --git a/nashorn/src/jdk/nashorn/api/scripting/Formatter.java b/nashorn/src/jdk/nashorn/api/scripting/Formatter.java
index 3b47d34..5cb19ed 100644
--- a/nashorn/src/jdk/nashorn/api/scripting/Formatter.java
+++ b/nashorn/src/jdk/nashorn/api/scripting/Formatter.java
@@ -46,7 +46,7 @@
  * <p>Pattern and the logic for parameter position: java.util.Formatter
  *
  */
-public final class Formatter {
+final class Formatter {
 
     private Formatter() {
     }
@@ -59,8 +59,8 @@
      * @param args arguments referenced by the format specifiers in format
      * @return a formatted string
      */
-    public static String format(final String format, final Object[] args) {
-        Matcher m = FS_PATTERN.matcher(format);
+    static String format(final String format, final Object[] args) {
+        final Matcher m = FS_PATTERN.matcher(format);
         int positionalParameter = 1;
 
         while (m.find()) {
@@ -143,7 +143,7 @@
     /**
      * Method to parse the integer of the argument index.
      *
-     * @param s
+     * @param s string to parse
      * @return -1 if parsing failed, 0 if string is null, > 0 integer
      */
     private static int index(final String s) {
@@ -166,7 +166,7 @@
      * Method to check if a string contains '&lt;'. This is used to find out if
      * previous parameter is used.
      *
-     * @param s
+     * @param s string to check
      * @return true if '&lt;' is in the string, else false
      */
     private static boolean isPreviousArgument(final String s) {
diff --git a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java
index ce16a7e..55967bb 100644
--- a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java
+++ b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngine.java
@@ -32,6 +32,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
+import java.lang.reflect.Method;
 import java.net.URL;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
@@ -179,14 +180,14 @@
     }
 
     private <T> T getInterfaceInner(final Object self, final Class<T> clazz) {
-        final Object realSelf;
+        final ScriptObject realSelf;
         final ScriptObject ctxtGlobal = getNashornGlobalFrom(context);
         if(self == null) {
             realSelf = ctxtGlobal;
         } else if (!(self instanceof ScriptObject)) {
-            realSelf = ScriptObjectMirror.unwrap(self, ctxtGlobal);
+            realSelf = (ScriptObject)ScriptObjectMirror.unwrap(self, ctxtGlobal);
         } else {
-            realSelf = self;
+            realSelf = (ScriptObject)self;
         }
         try {
             final ScriptObject oldGlobal = getNashornGlobal();
@@ -194,6 +195,10 @@
                 if(oldGlobal != ctxtGlobal) {
                     setNashornGlobal(ctxtGlobal);
                 }
+
+                if (! isInterfaceImplemented(clazz, realSelf)) {
+                    return null;
+                }
                 return clazz.cast(JavaAdapterFactory.getConstructor(realSelf.getClass(), clazz).invoke(realSelf));
             } finally {
                 if(oldGlobal != ctxtGlobal) {
@@ -394,14 +399,6 @@
             setContextVariables(ctxt);
             final Object val = ctxt.getAttribute(ScriptEngine.FILENAME);
             final String fileName = (val != null) ? val.toString() : "<eval>";
-
-            // NOTE: FIXME: If this is jrunscript's init.js, we want to run the replacement.
-            // This should go away once we fix jrunscript's copy of init.js.
-            if ("<system-init>".equals(fileName)) {
-                evalSupportScript("resources/init.js", "nashorn:engine/resources/init.js");
-                return null;
-            }
-
             Object res = ScriptRuntime.apply(script, ctxtGlobal);
             return ScriptObjectMirror.translateUndefined(ScriptObjectMirror.wrap(res, ctxtGlobal));
         } catch (final Exception e) {
@@ -471,6 +468,21 @@
         }
     }
 
+    private static boolean isInterfaceImplemented(final Class<?> iface, final ScriptObject sobj) {
+        for (final Method method : iface.getMethods()) {
+            // ignore methods of java.lang.Object class
+            if (method.getDeclaringClass() == Object.class) {
+                continue;
+            }
+
+            Object obj = sobj.get(method.getName());
+            if (! (obj instanceof ScriptFunction)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     // don't make this public!!
     static ScriptObject getNashornGlobal() {
         return Context.getGlobal();
diff --git a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java
index 47a0c59..e38284d 100644
--- a/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java
+++ b/nashorn/src/jdk/nashorn/api/scripting/NashornScriptEngineFactory.java
@@ -147,6 +147,7 @@
      * @return newly created script engine.
      */
     public ScriptEngine getScriptEngine(final ClassLoader appLoader) {
+        checkConfigPermission();
         return new NashornScriptEngine(this, appLoader);
     }
 
@@ -157,6 +158,7 @@
      * @return newly created script engine.
      */
     public ScriptEngine getScriptEngine(final String[] args) {
+        checkConfigPermission();
         return new NashornScriptEngine(this, args, getAppClassLoader());
     }
 
@@ -168,11 +170,19 @@
      * @return newly created script engine.
      */
     public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader) {
+        checkConfigPermission();
         return new NashornScriptEngine(this, args, appLoader);
     }
 
     // -- Internals only below this point
 
+    private static void checkConfigPermission() {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(new RuntimePermission("nashorn.setConfig"));
+        }
+    }
+
     private static final List<String> names;
     private static final List<String> mimeTypes;
     private static final List<String> extensions;
diff --git a/nashorn/src/jdk/nashorn/api/scripting/ScriptUtils.java b/nashorn/src/jdk/nashorn/api/scripting/ScriptUtils.java
new file mode 100644
index 0000000..ccd5879
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/api/scripting/ScriptUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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.api.scripting;
+
+import jdk.nashorn.internal.runtime.ScriptRuntime;
+
+/**
+ * Utilities that are to be called from script code
+ */
+public final class ScriptUtils {
+    private ScriptUtils() {}
+
+    /**
+     * Returns AST as JSON compatible string. This is used to
+     * implement "parse" function in resources/parse.js script.
+     *
+     * @param code code to be parsed
+     * @param name name of the code source (used for location)
+     * @param includeLoc tells whether to include location information for nodes or not
+     * @return JSON string representation of AST of the supplied code
+     */
+    public static String parse(final String code, final String name, final boolean includeLoc) {
+        return ScriptRuntime.parse(code, name, includeLoc);
+    }
+
+    /**
+     * Method which converts javascript types to java types for the
+     * String.format method (jrunscript function sprintf).
+     *
+     * @param format a format string
+     * @param args arguments referenced by the format specifiers in format
+     * @return a formatted string
+     */
+    public static String format(final String format, final Object[] args) {
+        return Formatter.format(format, args);
+    }
+}
diff --git a/nashorn/src/jdk/nashorn/api/scripting/resources/engine.js b/nashorn/src/jdk/nashorn/api/scripting/resources/engine.js
index 65b82df..e956072 100644
--- a/nashorn/src/jdk/nashorn/api/scripting/resources/engine.js
+++ b/nashorn/src/jdk/nashorn/api/scripting/resources/engine.js
@@ -46,3 +46,49 @@
     }
     writer.println(String(str));
 }
+
+/**
+ * This is C-like printf
+ *
+ * @param format string to format the rest of the print items
+ * @param args variadic argument list
+ */
+Object.defineProperty(this, "printf", {
+    configurable: true,
+    enumerable: false,
+    writable: true,
+    value: function (format, args/*, more args*/) {
+        print(sprintf.apply(this, arguments));
+    }
+});
+
+/**
+ * This is C-like sprintf
+ *
+ * @param format string to format the rest of the print items
+ * @param args variadic argument list
+ */
+Object.defineProperty(this, "sprintf", {
+    configurable: true,
+    enumerable: false,
+    writable: true,
+    value: function (format, args/*, more args*/) {
+        var len = arguments.length - 1;
+        var array = [];
+
+        if (len < 0) {
+            return "";
+        }
+
+        for (var i = 0; i < len; i++) {
+            if (arguments[i+1] instanceof Date) {
+                array[i] = arguments[i+1].getTime();
+            } else {
+                array[i] = arguments[i+1];
+            }
+        }
+
+        array = Java.toJavaArray(array);
+        return Packages.jdk.nashorn.api.scripting.ScriptUtils.format(format, array);
+    }
+});
diff --git a/nashorn/src/jdk/nashorn/api/scripting/resources/init.js b/nashorn/src/jdk/nashorn/api/scripting/resources/init.js
deleted file mode 100644
index 18cde92..0000000
--- a/nashorn/src/jdk/nashorn/api/scripting/resources/init.js
+++ /dev/null
@@ -1,939 +0,0 @@
-/*
- * Copyright (c) 2005, 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.
- */
-
-/**
- * jrunscript JavaScript built-in functions and objects.
- */
-
-/**
- * Creates an object that delegates all method calls on
- * it to the 'invoke' method on the given delegate object.<br>
- *
- * Example:
- * <pre>
- * <code>
- *     var x  = { invoke: function(name, args) { //code...}
- *     var y = new JSInvoker(x);
- *     y.func(3, 3); // calls x.invoke('func', args); where args is array of arguments
- * </code>
- * </pre>
- * @param obj object to be wrapped by JSInvoker
- * @constructor
- */
-function JSInvoker(obj) {
-    return new JSAdapter({
-        __get__ : function(name) {
-            return function() {
-                return obj.invoke(name, arguments);
-            }
-        }
-    });
-}
-
-/**
- * This variable represents OS environment. Environment
- * variables can be accessed as fields of this object. For
- * example, env.PATH will return PATH value configured.
- */
-var env = new JSAdapter({
-    __get__ : function (name) {
-        return java.lang.System.getenv(name);
-    },
-    __has__ : function (name) {
-        return java.lang.System.getenv().containsKey(name);
-    },
-    __getIds__ : function() {
-        return java.lang.System.getenv().keySet().toArray();
-    },
-    __delete__ : function(name) {
-        println("can't delete env item");
-    },
-    __put__ : function (name, value) {
-        println("can't change env item");
-    },
-    toString: function() {
-        return java.lang.System.getenv().toString();
-    }
-});
-
-/**
- * Creates a convenient script object to deal with java.util.Map instances.
- * The result script object's field names are keys of the Map. For example,
- * scriptObj.keyName can be used to access value associated with given key.<br>
- * Example:
- * <pre>
- * <code>
- *     var x = java.lang.SystemProperties();
- *     var y = jmap(x);
- *     println(y['java.class.path']); // prints java.class.path System property
- *     delete y['java.class.path']; // remove java.class.path System property
- * </code>
- * </pre>
- *
- * @param map java.util.Map instance that will be wrapped
- * @constructor
- */
-function jmap(map) {
-    return new JSAdapter({
-        __get__ : function(name) {
-            if (map.containsKey(name)) {
-                return map.get(name);
-            } else {
-                return undefined;
-            }
-        },
-        __has__ :  function(name) {
-            return map.containsKey(name);
-        },
-
-        __delete__ : function (name) {
-            return map.remove(name);
-        },
-        __put__ : function(name, value) {
-            map.put(name, value);
-        },
-        __getIds__ : function() {
-            return map.keySet().toArray();
-        },
-        toString: function() {
-            return map.toString();
-        }
-    });
-}
-
-/**
- * Creates a convenient script object to deal with java.util.List instances.
- * The result script object behaves like an array. For example,
- * scriptObj[index] syntax can be used to access values in the List instance.
- * 'length' field gives size of the List. <br>
- *
- * Example:
- * <pre>
- * <code>
- *    var x = new java.util.ArrayList(4);
- *    x.add('Java');
- *    x.add('JavaScript');
- *    x.add('SQL');
- *    x.add('XML');
- *
- *    var y = jlist(x);
- *    println(y[2]); // prints third element of list
- *    println(y.length); // prints size of the list
- *
- * @param map java.util.List instance that will be wrapped
- * @constructor
- */
-function jlist(list) {
-    function isValid(index) {
-        return typeof(index) == 'number' &&
-            index > -1 && index < list.size();
-    }
-    return new JSAdapter({
-        __get__ :  function(name) {
-            if (isValid(name)) {
-                return list.get(name);
-            } else if (name == 'length') {
-                return list.size();
-            } else {
-                return undefined;
-            }
-        },
-        __has__ : function (name) {
-            return isValid(name) || name == 'length';
-        },
-        __delete__ : function(name) {
-            if (isValid(name)) {
-                list.remove(name);
-            }
-        },
-        __put__ : function(name, value) {
-            if (isValid(name)) {
-                list.set(name, value);
-            }
-        },
-        __getIds__: function() {
-            var res = new Array(list.size());
-            for (var i = 0; i < res.length; i++) {
-                res[i] = i;
-            }
-            return res;
-        },
-        toString: function() {
-            return list.toString();
-        }
-    });
-}
-
-/**
- * This is java.lang.System properties wrapped by JSAdapter.
- * For eg. to access java.class.path property, you can use
- * the syntax sysProps["java.class.path"]
- */
-var sysProps = new JSAdapter({
-    __get__ : function (name) {
-        return java.lang.System.getProperty(name);
-    },
-    __has__ : function (name) {
-        return java.lang.System.getProperty(name) != null;
-    },
-    __getIds__ : function() {
-        return java.lang.System.getProperties().keySet().toArray();
-    },
-    __delete__ : function(name) {
-        java.lang.System.clearProperty(name);
-        return true;
-    },
-    __put__ : function (name, value) {
-        java.lang.System.setProperty(name, value);
-    },
-    toString: function() {
-        return "<system properties>";
-    }
-});
-
-// stdout, stderr & stdin
-var out = java.lang.System.out;
-var err = java.lang.System.err;
-// can't use 'in' because it is a JavaScript keyword :-(
-var inp = java.lang.System["in"];
-
-var BufferedInputStream = java.io.BufferedInputStream;
-var BufferedOutputStream = java.io.BufferedOutputStream;
-var BufferedReader = java.io.BufferedReader;
-var DataInputStream = java.io.DataInputStream;
-var File = java.io.File;
-var FileInputStream = java.io.FileInputStream;
-var FileOutputStream = java.io.FileOutputStream;
-var InputStream = java.io.InputStream;
-var InputStreamReader = java.io.InputStreamReader;
-var OutputStream = java.io.OutputStream;
-var Reader = java.io.Reader;
-var URL = java.net.URL;
-
-/**
- * Generic any object to input stream mapper
- * @param str input file name, URL or InputStream
- * @return InputStream object
- * @private
- */
-function inStream(str) {
-    if (typeof(str) == "string") {
-        // '-' means standard input
-        if (str == '-') {
-            return java.lang.System["in"];
-        }
-        // try file first
-        var file = null;
-        try {
-            file = pathToFile(str);
-        } catch (e) {
-        }
-        if (file && file.exists()) {
-            return new FileInputStream(file);
-        } else {
-            try {
-                // treat the string as URL
-                return new URL(str).openStream();
-            } catch (e) {
-                throw 'file or URL ' + str + ' not found';
-            }
-        }
-    } else {
-        if (str instanceof InputStream) {
-            return str;
-        } else if (str instanceof URL) {
-            return str.openStream();
-        } else if (str instanceof File) {
-            return new FileInputStream(str);
-        }
-    }
-    // everything failed, just give input stream
-    return java.lang.System["in"];
-}
-
-/**
- * Generic any object to output stream mapper
- *
- * @param out output file name or stream
- * @return OutputStream object
- * @private
- */
-function outStream(out) {
-    if (typeof(out) == "string") {
-        if (out == '>') {
-            return java.lang.System.out;
-        } else {
-            // treat it as file
-            return new FileOutputStream(pathToFile(out));
-        }
-    } else {
-        if (out instanceof OutputStream) {
-            return out;
-        } else if (out instanceof File) {
-            return new FileOutputStream(out);
-        }
-    }
-
-    // everything failed, just return System.out
-    return java.lang.System.out;
-}
-
-/**
- * stream close takes care not to close stdin, out & err.
- * @private
- */
-function streamClose(stream) {
-    if (stream) {
-        if (stream != java.lang.System["in"] &&
-            stream != java.lang.System.out &&
-            stream != java.lang.System.err) {
-            try {
-                stream.close();
-            } catch (e) {
-                println(e);
-            }
-        }
-    }
-}
-
-/**
- * Loads and evaluates JavaScript code from a stream or file or URL<br>
- *
- * Examples:
- * <pre>
- * <code>
- *    load('test.js'); // load script file 'test.js'
- *    load('http://java.sun.com/foo.js'); // load from a URL
- * </code>
- * </pre>
- *
- * @param str input from which script is loaded and evaluated
- */
-if (typeof(load) == 'undefined') {
-    var load = function(str) {
-        var stream = inStream(str);
-        var bstream = new BufferedInputStream(stream);
-        var reader = new BufferedReader(new InputStreamReader(bstream));
-        var oldFilename = engine.get(engine.FILENAME);
-        engine.put(engine.FILENAME, str);
-        try {
-            engine.eval(reader);
-        } finally {
-            engine.put(engine.FILENAME, oldFilename);
-            streamClose(stream);
-        }
-    }
-}
-
-// file system utilities
-
-/**
- * Creates a Java byte[] of given length
- * @param len size of the array to create
- * @private
- */
-function javaByteArray(len) {
-    return java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, len);
-}
-
-var curDir = new File('.');
-
-/**
- * Print present working directory
- */
-function pwd() {
-    println(curDir.getAbsolutePath());
-}
-
-/**
- * Changes present working directory to given directory
- * @param target directory to change to. optional, defaults to user's HOME
- */
-function cd(target) {
-    if (target == undefined) {
-        target = sysProps["user.home"];
-    }
-    if (!(target instanceof File)) {
-        target = pathToFile(target);
-    }
-    if (target.exists() && target.isDirectory()) {
-        curDir = target;
-    } else {
-        println(target + " is not a directory");
-    }
-}
-
-/**
- * Converts path to java.io.File taking care of shell present working dir
- *
- * @param pathname file path to be converted
- * @private
- */
-function pathToFile(pathname) {
-    var tmp = pathname;
-    if (!(tmp instanceof File)) {
-        tmp = new File(tmp);
-    }
-    if (!tmp.isAbsolute()) {
-        return new File(curDir, pathname);
-    } else {
-        return tmp;
-    }
-}
-
-/**
- * Copies a file or URL or stream to another file or stream
- *
- * @param from input file or URL or stream
- * @param to output stream or file
- */
-function cp(from, to) {
-    if (from == to) {
-        println("file " + from + " cannot be copied onto itself!");
-        return;
-    }
-    var inp = inStream(from);
-    var out = outStream(to);
-    var binp = new BufferedInputStream(inp);
-    var bout = new BufferedOutputStream(out);
-    var buff = javaByteArray(1024);
-    var len;
-    while ((len = binp.read(buff)) > 0 )
-        bout.write(buff, 0, len);
-
-    bout.flush();
-    streamClose(inp);
-    streamClose(out);
-}
-
-/**
- * Shows the content of a file or URL or any InputStream<br>
- * Examples:
- * <pre>
- * <code>
- *    cat('test.txt'); // show test.txt file contents
- *    cat('http://java.net'); // show the contents from the URL http://java.net
- * </code>
- * </pre>
- * @param obj input to show
- * @param pattern optional. show only the lines matching the pattern
- */
-function cat(obj, pattern) {
-    if (obj instanceof File && obj.isDirectory()) {
-        ls(obj);
-        return;
-    }
-
-    var inp = null;
-    if (!(obj instanceof Reader)) {
-        inp = inStream(obj);
-        obj = new BufferedReader(new InputStreamReader(inp));
-    }
-    var line;
-    if (pattern) {
-        var count = 1;
-        while ((line=obj.readLine()) != null) {
-            if (line.match(pattern)) {
-                println(count + "\t: " + line);
-            }
-            count++;
-        }
-    } else {
-        while ((line=obj.readLine()) != null) {
-            println(line);
-        }
-    }
-}
-
-/**
- * Returns directory part of a filename
- *
- * @param pathname input path name
- * @return directory part of the given file name
- */
-function dirname(pathname) {
-    var dirName = ".";
-    // Normalize '/' to local file separator before work.
-    var i = pathname.replace('/', File.separatorChar ).lastIndexOf(
-        File.separator );
-    if ( i != -1 )
-        dirName = pathname.substring(0, i);
-    return dirName;
-}
-
-/**
- * Creates a new dir of given name
- *
- * @param dir name of the new directory
- */
-function mkdir(dir) {
-    dir = pathToFile(dir);
-    println(dir.mkdir()? "created" : "can not create dir");
-}
-
-/**
- * Creates the directory named by given pathname, including
- * any necessary but nonexistent parent directories.
- *
- * @param dir input path name
- */
-function mkdirs(dir) {
-    dir = pathToFile(dir);
-    println(dir.mkdirs()? "created" : "can not create dirs");
-}
-
-/**
- * Removes a given file
- *
- * @param pathname name of the file
- */
-function rm(pathname) {
-    var file = pathToFile(pathname);
-    if (!file.exists()) {
-        println("file not found: " + pathname);
-        return false;
-    }
-    // note that delete is a keyword in JavaScript!
-    println(file["delete"]()? "deleted" : "can not delete");
-}
-
-/**
- * Removes a given directory
- *
- * @param pathname name of the directory
- */
-function rmdir(pathname) {
-    rm(pathname);
-}
-
-/**
- * Synonym for 'rm'
- */
-function del(pathname) {
-    rm(pathname);
-}
-
-/**
- * Moves a file to another
- *
- * @param from original name of the file
- * @param to new name for the file
- */
-function mv(from, to) {
-    println(pathToFile(from).renameTo(pathToFile(to))?
-        "moved" : "can not move");
-}
-
-/**
- * Synonym for 'mv'.
- */
-function ren(from, to) {
-    mv(from, to);
-}
-
-var months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ];
-
-/**
- * Helper function called by ls
- * @private
- */
-function printFile(f) {
-    var sb = new java.lang.StringBuffer();
-    sb.append(f.isDirectory()? "d" : "-");
-    sb.append(f.canRead() ? "r": "-" );
-    sb.append(f.canWrite() ? "w": "-" );
-    sb.append(" ");
-
-    var d = new java.util.Date(f.lastModified());
-    var c = new java.util.GregorianCalendar();
-    c.setTime(d);
-    var day    = c.get(java.util.Calendar.DAY_OF_MONTH);
-    sb.append(months[c.get(java.util.Calendar.MONTH)]
-         + " " + day );
-    if (day < 10) {
-        sb.append(" ");
-    }
-
-    // to get fixed length 'length' field
-    var fieldlen = 8;
-    var len = new java.lang.StringBuffer();
-    for(var j=0; j<fieldlen; j++)
-        len.append(" ");
-    len.insert(0, java.lang.Long.toString(f.length()));
-    len.setLength(fieldlen);
-    // move the spaces to the front
-    var si = len.toString().indexOf(" ");
-    if ( si != -1 ) {
-        var pad = len.toString().substring(si);
-        len.setLength(si);
-        len.insert(0, pad);
-    }
-    sb.append(len.toString());
-    sb.append(" ");
-    sb.append(f.getName());
-    if (f.isDirectory()) {
-        sb.append('/');
-    }
-    println(sb.toString());
-}
-
-/**
- * Lists the files in a directory
- *
- * @param dir directory from which to list the files. optional, default to pwd
- * @param filter pattern to filter the files listed. optional, default is '.'.
- */
-function ls(dir, filter) {
-    if (dir) {
-        dir = pathToFile(dir);
-    } else {
-        dir = curDir;
-    }
-    if (dir.isDirectory()) {
-        var files = dir.listFiles();
-        for (var i in files) {
-            var f = files[i];
-            if (filter) {
-                if(!f.getName().match(filter)) {
-                    continue;
-                }
-            }
-            printFile(f);
-        }
-    } else {
-        printFile(dir);
-    }
-}
-
-/**
- * Synonym for 'ls'.
- */
-function dir(d, filter) {
-    ls(d, filter);
-}
-
-/**
- * Unix-like grep, but accepts JavaScript regex patterns
- *
- * @param pattern to search in files
- * @param files one or more files
- */
-function grep(pattern, files /*, one or more files */) {
-    if (arguments.length < 2) return;
-    for (var i = 1; i < arguments.length; i++) {
-        println(arguments[i] + ":");
-        cat(arguments[i], pattern);
-    }
-}
-
-/**
- * Find in files. Calls arbitrary callback function
- * for each matching file.<br>
- *
- * Examples:
- * <pre>
- * <code>
- *    find('.')
- *    find('.', '.*\.class', rm);  // remove all .class files
- *    find('.', '.*\.java');       // print fullpath of each .java file
- *    find('.', '.*\.java', cat);  // print all .java files
- * </code>
- * </pre>
- *
- * @param dir directory to search files
- * @param pattern to search in the files
- * @param callback function to call for matching files
- */
-function find(dir, pattern, callback) {
-    dir = pathToFile(dir);
-    if (!callback) callback = print;
-    var files = dir.listFiles();
-    for (var f in files) {
-        var file = files[f];
-        if (file.isDirectory()) {
-            find(file, pattern, callback);
-        } else {
-            if (pattern) {
-                if (file.getName().match(pattern)) {
-                    callback(file);
-                }
-            } else {
-                callback(file);
-            }
-        }
-    }
-}
-
-// process utilities
-
-/**
- * Exec's a child process, waits for completion &amp; returns exit code
- *
- * @param cmd command to execute in child process
- */
-function exec(cmd) {
-    var process = java.lang.Runtime.getRuntime().exec(cmd);
-    var inp = new DataInputStream(process.getInputStream());
-    var line = null;
-    while ((line = inp.readLine()) != null) {
-        println(line);
-    }
-    process.waitFor();
-    $exit = process.exitValue();
-}
-
-// XML utilities
-
-/**
- * Converts input to DOM Document object
- *
- * @param inp file or reader. optional, without this param,
- * this function returns a new DOM Document.
- * @return returns a DOM Document object
- */
-function XMLDocument(inp) {
-    var factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
-    var builder = factory.newDocumentBuilder();
-    if (inp) {
-        if (typeof(inp) == "string") {
-            return builder.parse(pathToFile(inp));
-        } else {
-            return builder.parse(inp);
-        }
-    } else {
-        return builder.newDocument();
-    }
-}
-
-/**
- * Converts arbitrary stream, file, URL to XMLSource
- *
- * @param inp input stream or file or URL
- * @return XMLSource object
- */
-function XMLSource(inp) {
-    if (inp instanceof javax.xml.transform.Source) {
-        return inp;
-    } else if (inp instanceof Packages.org.w3c.dom.Document) {
-        return new javax.xml.transform.dom.DOMSource(inp);
-    } else {
-        inp = new BufferedInputStream(inStream(inp));
-        return new javax.xml.transform.stream.StreamSource(inp);
-    }
-}
-
-/**
- * Converts arbitrary stream, file to XMLResult
- *
- * @param inp output stream or file
- * @return XMLResult object
- */
-function XMLResult(out) {
-    if (out instanceof javax.xml.transform.Result) {
-        return out;
-    } else if (out instanceof Packages.org.w3c.dom.Document) {
-        return new javax.xml.transform.dom.DOMResult(out);
-    } else {
-        out = new BufferedOutputStream(outStream(out));
-        return new javax.xml.transform.stream.StreamResult(out);
-    }
-}
-
-/**
- * Perform XSLT transform
- *
- * @param inp Input XML to transform (URL, File or InputStream)
- * @param style XSL Stylesheet to be used (URL, File or InputStream). optional.
- * @param out Output XML (File or OutputStream
- */
-function XSLTransform(inp, style, out) {
-    switch (arguments.length) {
-    case 2:
-        inp = arguments[0];
-        out = arguments[1];
-        break;
-    case 3:
-        inp = arguments[0];
-        style = arguments[1];
-        out = arguments[2];
-        break;
-    default:
-        println("XSL tranform requires 2 or 3 arguments");
-        return;
-    }
-
-    var factory = javax.xml.transform.TransformerFactory.newInstance();
-    var transformer;
-    if (style) {
-        transformer = factory.newTransformer(XMLSource(style));
-    } else {
-        transformer = factory.newTransformer();
-    }
-    var source = XMLSource(inp);
-    var result = XMLResult(out);
-    transformer.transform(source, result);
-    if (source.getInputStream) {
-        streamClose(source.getInputStream());
-    }
-    if (result.getOutputStream) {
-        streamClose(result.getOutputStream());
-    }
-}
-
-// miscellaneous utilities
-
-/**
- * Prints which command is selected from PATH
- *
- * @param cmd name of the command searched from PATH
- */
-function which(cmd) {
-    var st = new java.util.StringTokenizer(env.PATH, File.pathSeparator);
-    while (st.hasMoreTokens()) {
-        var file = new File(st.nextToken(), cmd);
-        if (file.exists()) {
-            println(file.getAbsolutePath());
-            return;
-        }
-    }
-}
-
-/**
- * Prints IP addresses of given domain name
- *
- * @param name domain name
- */
-function ip(name) {
-    var addrs = InetAddress.getAllByName(name);
-    for (var i in addrs) {
-        println(addrs[i]);
-    }
-}
-
-/**
- * Prints current date in current locale
- */
-function date() {
-    println(new Date().toLocaleString());
-}
-
-/**
- * Echoes the given string arguments
- */
-function echo(x) {
-    for (var i = 0; i < arguments.length; i++) {
-        println(arguments[i]);
-    }
-}
-
-/**
- * Reads one or more lines from stdin after printing prompt
- *
- * @param prompt optional, default is '>'
- * @param multiline to tell whether to read single line or multiple lines
- */
-function read(prompt, multiline) {
-    if (!prompt) {
-        prompt = '>';
-    }
-    var inp = java.lang.System["in"];
-    var reader = new BufferedReader(new InputStreamReader(inp));
-    if (multiline) {
-        var line = '';
-        while (true) {
-            java.lang.System.err.print(prompt);
-            java.lang.System.err.flush();
-            var tmp = reader.readLine();
-            if (tmp == '' || tmp == null) break;
-            line += tmp + '\n';
-        }
-        return line;
-    } else {
-        java.lang.System.err.print(prompt);
-        java.lang.System.err.flush();
-        return reader.readLine();
-    }
-}
-
-if (typeof(println) == 'undefined') {
-    var print = function(str, newline) {
-        if (typeof(str) == 'undefined') {
-            str = 'undefined';
-        } else if (str == null) {
-            str = 'null';
-        }
-
-        if (!(out instanceof java.io.PrintWriter)) {
-            out = new java.io.PrintWriter(out);
-        }
-
-        out.print(String(str));
-        if (newline) {
-            out.print('\n');
-        }
-        out.flush();
-    }
-
-    var println = function(str) {
-        print(str, true);
-    };
-}
-
-/**
- * This is C-like printf
- *
- * @param format string to format the rest of the print items
- * @param args variadic argument list
- */
-function printf(format, args/*, more args*/) {
-    print(sprintf.apply(this, arguments));
-}
-
-/**
- * This is C-like sprintf
- *
- * @param format string to format the rest of the print items
- * @param args variadic argument list
- */
-function sprintf(format, args/*, more args*/) {
-    var len = arguments.length - 1;
-    var array = [];
-
-    if (len < 0) {
-        return "";
-    }
-
-    for (var i = 0; i < len; i++) {
-        if (arguments[i+1] instanceof Date) {
-            array[i] = arguments[i+1].getTime();
-        } else {
-            array[i] = arguments[i+1];
-        }
-    }
-
-    array = Java.toJavaArray(array);
-    return Packages.jdk.nashorn.api.scripting.Formatter.format(format, array);
-}
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
index 211140e..9ecf7c8 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Attr.java
@@ -37,13 +37,16 @@
 import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL;
 import static jdk.nashorn.internal.ir.Symbol.IS_LET;
 import static jdk.nashorn.internal.ir.Symbol.IS_PARAM;
+import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE;
 import static jdk.nashorn.internal.ir.Symbol.IS_THIS;
 import static jdk.nashorn.internal.ir.Symbol.IS_VAR;
+import static jdk.nashorn.internal.ir.Symbol.KINDMASK;
 
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.LinkedList;
+import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Set;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.AccessNode;
@@ -55,14 +58,15 @@
 import jdk.nashorn.internal.ir.CatchNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ObjectNode;
 import jdk.nashorn.internal.ir.PropertyNode;
-import jdk.nashorn.internal.ir.ReferenceNode;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.RuntimeNode.Request;
@@ -115,6 +119,8 @@
      */
     private Set<String> localUses;
 
+    private final LexicalContext lexicalContext = new LexicalContext();
+
     private static final DebugLogger LOG   = new DebugLogger("attr");
     private static final boolean     DEBUG = LOG.isEnabled();
 
@@ -135,14 +141,15 @@
     }
 
     @Override
-    public Node leave(final AccessNode accessNode) {
+    public Node leaveAccessNode(final AccessNode accessNode) {
         newTemporary(Type.OBJECT, accessNode);  //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this
         end(accessNode);
         return accessNode;
     }
 
     @Override
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
+        lexicalContext.push(block);
         start(block);
 
         final Set<String> savedLocalDefs = localDefs;
@@ -158,9 +165,7 @@
             localDefs = new HashSet<>(savedLocalDefs);
             localUses = new HashSet<>(savedLocalUses);
 
-            for (final Node statement : block.getStatements()) {
-                statement.accept(this);
-            }
+            block.visitStatements(this);
         } finally {
             localDefs = savedLocalDefs;
             localUses = savedLocalUses;
@@ -170,11 +175,12 @@
 
         end(block);
 
+        lexicalContext.pop(block);
         return null;
     }
 
     @Override
-    public Node enter(final CallNode callNode) {
+    public Node enterCallNode(final CallNode callNode) {
         start(callNode);
 
         callNode.getFunction().accept(this);
@@ -195,8 +201,7 @@
             evalArgs.setThis(thisNode);
         }
 
-        newTemporary(Type.OBJECT, callNode); // object type here, access specialization in FinalizeTypes may narrow it later
-        newType(callNode.getFunction().getSymbol(), Type.OBJECT);
+        newTemporary(callNode.getType(), callNode); // access specialization in FinalizeTypes may narrow it further later
 
         end(callNode);
 
@@ -204,29 +209,106 @@
     }
 
     @Override
-    public Node enter(final CatchNode catchNode) {
+    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 = block.defineSymbol(exception.getName(), IS_VAR | IS_LET, exception);
+        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 enter(final FunctionNode functionNode) {
+    public Node enterFunctionNode(final FunctionNode functionNode) {
         start(functionNode, false);
         if (functionNode.isLazy()) {
-            LOG.info("LAZY: " + functionNode.getName());
+            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();
 
@@ -242,24 +324,36 @@
         initScope(functionNode);
         initReturn(functionNode);
 
-        // Add all nested functions as symbols in this function
-        for (final FunctionNode nestedFunction : functionNode.getFunctions()) {
+        // Add all nested declared functions as symbols in this function
+        for (final FunctionNode nestedFunction : functionNode.getDeclaredFunctions()) {
             final IdentNode ident = nestedFunction.getIdent();
-            if (ident != null && nestedFunction.isStatement()) {
-                final Symbol functionSymbol = functionNode.defineSymbol(ident.getName(), IS_VAR, nestedFunction);
+            if (ident != null) {
+                assert nestedFunction.isDeclared();
+                final Symbol functionSymbol = defineSymbol(functionNode, ident.getName(), IS_VAR, nestedFunction);
                 newType(functionSymbol, Type.typeFor(ScriptFunction.class));
             }
         }
 
-        if (functionNode.isScript()) {
+        if (functionNode.isProgram()) {
             initFromPropertyMap(functionNode);
         }
 
         // Add function name as local symbol
-        if (!functionNode.isStatement() && !functionNode.isAnonymous() && !functionNode.isScript()) {
-            final Symbol selfSymbol = functionNode.defineSymbol(functionNode.getIdent().getName(), IS_VAR, functionNode);
-            newType(selfSymbol, Type.OBJECT);
-            selfSymbol.setNode(functionNode);
+        if (!functionNode.isDeclared() && !functionNode.isProgram()) {
+            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);
+            } else {
+                selfSymbol = defineSymbol(functionNode, functionNode.getIdent().getName(), IS_VAR, functionNode);
+                newType(selfSymbol, Type.OBJECT);
+                selfSymbol.setNode(functionNode);
+            }
         }
 
         /*
@@ -280,32 +374,26 @@
          */
 
         final List<Symbol> declaredSymbols = new ArrayList<>();
-        for (final VarNode decl : functionNode.getDeclarations()) {
-            final IdentNode ident = decl.getName();
-            // any declared symbols that aren't visited need to be typed as well, hence the list
-            declaredSymbols.add(functionNode.defineSymbol(ident.getName(), IS_VAR, new IdentNode(ident)));
-        }
-
-        // Every nested function needs a definition in the outer function with its name. Add these.
-        for (final FunctionNode nestedFunction : functionNode.getFunctions()) {
-            final VarNode varNode = nestedFunction.getFunctionVarNode();
-            if (varNode != null) {
-                varNode.accept(this);
-                assert varNode.isFunctionVarNode() : varNode + " should be function var node";
+        // 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() {
+            @Override
+            public Node enterFunctionNode(FunctionNode nestedFn) {
+                // Don't descend into nested functions
+                return nestedFn == functionNode ? nestedFn : null;
             }
-        }
-
-        for (final Node statement : functionNode.getStatements()) {
-            if (statement instanceof VarNode && ((VarNode)statement).isFunctionVarNode()) {
-                continue; //var nodes have already been processed, skip or they will generate additional defs/uses and false "can be undefined"
+            @Override
+            public Node enterVarNode(VarNode varNode) {
+                if(varNode.isStatement() && !varNode.isFunctionDeclaration()) {
+                    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)));
+                }
+                return null;
             }
-            statement.accept(this);
-        }
+        });
 
-        for (final FunctionNode nestedFunction : functionNode.getFunctions()) {
-            LOG.info("Going into nested function " + functionNode.getName() + " -> " + nestedFunction.getName());
-            nestedFunction.accept(this);
-        }
+        visitFunctionStatements(functionNode);
 
         //unknown parameters are promoted to object type.
         finalizeParameters(functionNode);
@@ -332,13 +420,28 @@
             functionNode.setNeedsSelfSymbol(functionNode.getSelfSymbolInit().accept(this));
         }
 
+        if (functionNode.hasLazyChildren()) {
+            objectifySymbols(functionNode);
+        }
+
         functionNode.popFrame();
 
+        functionNode.setState(CompilationState.ATTR);
+
         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);
+    }
+
     @Override
     public Node leaveCONVERT(final UnaryNode unaryNode) {
         assert false : "There should be no convert operators in IR during Attribution";
@@ -347,7 +450,7 @@
     }
 
     @Override
-    public Node enter(final IdentNode identNode) {
+    public Node enterIdentNode(final IdentNode identNode) {
         final String name = identNode.getName();
 
         start(identNode);
@@ -364,7 +467,7 @@
         final Block  block     = getCurrentBlock();
         final Symbol oldSymbol = identNode.getSymbol();
 
-        Symbol symbol = block.findSymbol(name);
+        Symbol symbol = findSymbol(block, name);
 
         //If an existing symbol with the name is found, use that otherwise, declare a new one
         if (symbol != null) {
@@ -388,22 +491,13 @@
             }
 
             identNode.setSymbol(symbol);
-            if (!getCurrentFunctionNode().isLocal(symbol)) {
-                // non-local: we need to put symbol in scope (if it isn't already)
-                if (!symbol.isScope()) {
-                    final List<Block> lookupBlocks = findLookupBlocksHelper(getCurrentFunctionNode(), symbol.findFunction());
-                    for (final Block lookupBlock : lookupBlocks) {
-                        final Symbol refSymbol = lookupBlock.findSymbol(name);
-                        if (refSymbol != null) { // See NASHORN-837, function declaration in lexical scope: try {} catch (x){ function f() { use(x) } } f()
-                            LOG.finest("Found a ref symbol that must be scope " + refSymbol);
-                            refSymbol.setIsScope();
-                        }
-                    }
-                }
+            // non-local: we need to put symbol in scope (if it isn't already)
+            if (!isLocal(getCurrentFunctionNode(), symbol) && !symbol.isScope()) {
+                symbol.setIsScope();
             }
         } else {
             LOG.info("No symbol exists. Declare undefined: " + symbol);
-            symbol = block.useSymbol(name, identNode);
+            symbol = useSymbol(block, name, identNode);
             // we have never seen this before, it can be undefined
             newType(symbol, Type.OBJECT); // TODO unknown -we have explicit casts anyway?
             symbol.setCanBeUndefined();
@@ -412,9 +506,10 @@
 
         assert symbol != null;
         if(symbol.isGlobal()) {
-            getCurrentFunctionNode().setUsesGlobalSymbol();
+            setUsesGlobalSymbol();
         } else if(symbol.isScope()) {
-            getCurrentFunctionNode().setUsesScopeSymbol(symbol);
+            final Iterator<Block> blocks = lexicalContext.getBlocks();
+            blocks.next().setUsesScopeSymbol(symbol, blocks);
         }
 
         if (symbol != oldSymbol && !identNode.isInitializedHere()) {
@@ -427,15 +522,68 @@
         return null;
     }
 
+    /**
+     * Marks the current function as one using any global symbol. The function and all its parent functions will all be
+     * marked as needing parent scope.
+     * @see #needsParentScope()
+     */
+    private void setUsesGlobalSymbol() {
+        for(final Iterator<FunctionNode> fns = lexicalContext.getFunctions(); fns.hasNext();) {
+            fns.next().setUsesAncestorScope();
+        }
+    }
+
+    /**
+     * 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.
+     */
+    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();) {
+            // Find name.
+            final Symbol symbol = blocks.next().getExistingSymbol(name);
+            // If found then we are good.
+            if(symbol != null) {
+                return symbol;
+            }
+        }
+        return null;
+    }
+
     @Override
-    public Node leave(final IndexNode indexNode) {
-        newTemporary(Type.OBJECT, indexNode); //TORO
+    public Node leaveIndexNode(final IndexNode indexNode) {
+        newTemporary(Type.OBJECT, indexNode); //TODO
         return indexNode;
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enter(final LiteralNode literalNode) {
+    public Node 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
@@ -464,14 +612,14 @@
     }
 
     @Override
-    public Node leave(final ObjectNode objectNode) {
+    public Node leaveObjectNode(final ObjectNode objectNode) {
         newTemporary(Type.OBJECT, objectNode);
         end(objectNode);
         return objectNode;
     }
 
     @Override
-    public Node enter(final PropertyNode propertyNode) {
+    public Node enterPropertyNode(final PropertyNode propertyNode) {
         // assign a pseudo symbol to property name, see NASHORN-710
         propertyNode.setSymbol(new Symbol(propertyNode.getKeyName(), 0, Type.OBJECT));
         end(propertyNode);
@@ -479,31 +627,7 @@
     }
 
     @Override
-    public Node enter(final ReferenceNode referenceNode) {
-        final FunctionNode functionNode = referenceNode.getReference();
-        if (functionNode != null) {
-            functionNode.addReferencingParentBlock(getCurrentBlock());
-        }
-        return referenceNode;
-    }
-
-    @Override
-    public Node leave(final ReferenceNode referenceNode) {
-        newTemporary(Type.OBJECT, referenceNode); //reference node type is always an object, i.e. the scriptFunction. the function return type varies though
-
-        final FunctionNode functionNode = referenceNode.getReference();
-        //assert !functionNode.getType().isUnknown() || functionNode.isLazy() : functionNode.getType();
-        if (functionNode.isLazy()) {
-            LOG.info("Lazy function node call reference: " + functionNode.getName() + " => Promoting to OBJECT");
-            functionNode.setReturnType(Type.OBJECT);
-        }
-        end(referenceNode);
-
-        return referenceNode;
-    }
-
-    @Override
-    public Node leave(final ReturnNode returnNode) {
+    public Node leaveReturnNode(final ReturnNode returnNode) {
         final Node expr = returnNode.getExpression();
 
         if (expr != null) {
@@ -522,7 +646,7 @@
     }
 
     @Override
-    public Node leave(final SwitchNode switchNode) {
+    public Node leaveSwitchNode(final SwitchNode switchNode) {
         Type type = Type.UNKNOWN;
 
         for (final CaseNode caseNode : switchNode.getCases()) {
@@ -559,7 +683,7 @@
     }
 
     @Override
-    public Node leave(final TryNode tryNode) {
+    public Node leaveTryNode(final TryNode tryNode) {
         tryNode.setException(exceptionSymbol());
 
         if (tryNode.getFinallyBody() != null) {
@@ -572,13 +696,13 @@
     }
 
     @Override
-    public Node enter(final VarNode varNode) {
+    public Node enterVarNode(final VarNode varNode) {
         start(varNode);
 
         final IdentNode ident = varNode.getName();
         final String    name  = ident.getName();
 
-        final Symbol symbol = getCurrentBlock().defineSymbol(name, IS_VAR, ident);
+        final Symbol symbol = defineSymbol(getCurrentBlock(), name, IS_VAR, ident);
         assert symbol != null;
 
         LOG.info("VarNode " + varNode + " set symbol " + symbol);
@@ -590,23 +714,15 @@
             symbol.setCanBeUndefined();
         }
 
-        if (varNode.getInit() != null) {
-            varNode.getInit().accept(this);
-        }
-
         return varNode;
     }
 
     @Override
-    public Node leave(final VarNode varNode) {
+    public Node leaveVarNode(final VarNode varNode) {
         final Node      init  = varNode.getInit();
         final IdentNode ident = varNode.getName();
         final String    name  = ident.getName();
 
-        if (init != null) {
-            addLocalDef(name);
-        }
-
         if (init == null) {
             // var x; with no init will be treated like a use of x by
             // visit(IdentNode) unless we remove the name
@@ -615,8 +731,10 @@
             return varNode;
         }
 
+        addLocalDef(name);
+
         final Symbol  symbol   = varNode.getSymbol();
-        final boolean isScript = symbol.getBlock().getFunction().isScript(); //see NASHORN-56
+        final boolean isScript = lexicalContext.getFunction(symbol.getBlock()).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());
@@ -710,11 +828,9 @@
         runtimeNode = new RuntimeNode(unaryNode, request, args);
         assert runtimeNode.getSymbol() == unaryNode.getSymbol(); //clone constructor should do this
 
-        runtimeNode.accept(this);
-        return runtimeNode;
+        return leaveRuntimeNode(runtimeNode);
     }
 
-
     @Override
     public Node leaveNEW(final UnaryNode unaryNode) {
         newTemporary(Type.OBJECT, unaryNode);
@@ -747,7 +863,7 @@
         runtimeNode = new RuntimeNode(unaryNode, Request.TYPEOF, args);
         assert runtimeNode.getSymbol() == unaryNode.getSymbol();
 
-        runtimeNode.accept(this);
+        runtimeNode = (RuntimeNode)leaveRuntimeNode(runtimeNode);
 
         end(unaryNode);
 
@@ -755,7 +871,7 @@
     }
 
     @Override
-    public Node leave(final RuntimeNode runtimeNode) {
+    public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
         newTemporary(runtimeNode.getRequest().getReturnType(), runtimeNode);
         return runtimeNode;
     }
@@ -815,12 +931,12 @@
             final IdentNode ident = (IdentNode)lhs;
             final String    name  = ident.getName();
 
-            Symbol symbol = getCurrentBlock().findSymbol(name);
+            Symbol symbol = findSymbol(getCurrentBlock(), name);
 
             if (symbol == null) {
-                symbol = block.defineSymbol(name, IS_GLOBAL, ident);
+                symbol = defineSymbol(block, name, IS_GLOBAL, ident);
                 binaryNode.setSymbol(symbol);
-            } else if (!getCurrentFunctionNode().isLocal(symbol)) {
+            } else if (!isLocal(getCurrentFunctionNode(), symbol)) {
                 symbol.setIsScope();
             }
 
@@ -830,6 +946,12 @@
         return binaryNode;
     }
 
+    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;
+    }
+
     @Override
     public Node enterASSIGN(final BinaryNode binaryNode) {
         return enterAssignmentNode(binaryNode);
@@ -957,20 +1079,17 @@
 
     @Override
     public Node leaveBIT_AND(final BinaryNode binaryNode) {
-        newTemporary(Type.INT, binaryNode);
-        return binaryNode;
+        return end(coerce(binaryNode, Type.INT));
     }
 
     @Override
     public Node leaveBIT_OR(final BinaryNode binaryNode) {
-        newTemporary(Type.INT, binaryNode);
-        return binaryNode;
+        return end(coerce(binaryNode, Type.INT));
     }
 
     @Override
     public Node leaveBIT_XOR(final BinaryNode binaryNode) {
-        newTemporary(Type.INT, binaryNode);
-        return binaryNode;
+        return end(coerce(binaryNode, Type.INT));
     }
 
     @Override
@@ -990,7 +1109,7 @@
         return leaveBinaryArithmetic(binaryNode);
     }
 
-    private Node leaveCmp(final BinaryNode binaryNode, final RuntimeNode.Request request) {
+    private Node leaveCmp(final BinaryNode binaryNode) {
         final Node lhs = binaryNode.lhs();
         final Node rhs = binaryNode.rhs();
 
@@ -1002,49 +1121,64 @@
         return binaryNode;
     }
 
+    private Node coerce(final BinaryNode binaryNode, final Type operandType, final Type destType) {
+        // TODO we currently don't support changing inferred type based on uses, only on
+        // definitions. we would need some additional logic. We probably want to do that
+        // in the future, if e.g. a specialized method gets parameter that is only used
+        // as, say, an int : function(x) { return x & 4711 }, and x is not defined in
+        // the function. to make this work, uncomment the following two type inferences
+        // and debug.
+
+        //newType(binaryNode.lhs().getSymbol(), operandType);
+        //newType(binaryNode.rhs().getSymbol(), operandType);
+        newTemporary(destType, binaryNode);
+        return binaryNode;
+    }
+
+    private Node coerce(final BinaryNode binaryNode, final Type type) {
+        return coerce(binaryNode, type, type);
+    }
+
     //leave a binary node and inherit the widest type of lhs , rhs
     private Node leaveBinaryArithmetic(final BinaryNode binaryNode) {
-        if (!Compiler.shouldUseIntegerArithmetic()) {
-            newTemporary(Type.NUMBER, binaryNode);
-            return binaryNode;
-        }
-        newTemporary(Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType(), Type.NUMBER), binaryNode);
-        return binaryNode;
+        assert !Compiler.shouldUseIntegerArithmetic();
+        return end(coerce(binaryNode, Type.NUMBER));
     }
 
     @Override
     public Node leaveEQ(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.EQ);
+        return leaveCmp(binaryNode);
     }
 
     @Override
     public Node leaveEQ_STRICT(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.EQ_STRICT);
+        return leaveCmp(binaryNode);
     }
 
     @Override
     public Node leaveGE(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.GE);
+        return leaveCmp(binaryNode);
     }
 
     @Override
     public Node leaveGT(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.GT);
+        return leaveCmp(binaryNode);
     }
 
     @Override
     public Node leaveIN(final BinaryNode binaryNode) {
-        try {
-            return new RuntimeNode(binaryNode, Request.IN).accept(this);
-        } finally {
-            end(binaryNode);
-        }
+        return leaveBinaryRuntimeOperator(binaryNode, Request.IN);
     }
 
     @Override
     public Node leaveINSTANCEOF(final BinaryNode binaryNode) {
+        return leaveBinaryRuntimeOperator(binaryNode, Request.INSTANCEOF);
+    }
+
+    private Node leaveBinaryRuntimeOperator(final BinaryNode binaryNode, final Request request) {
         try {
-            return new RuntimeNode(binaryNode, Request.INSTANCEOF).accept(this);
+            // Don't do a full RuntimeNode.accept, as we don't want to double-visit the binary node operands
+            return leaveRuntimeNode(new RuntimeNode(binaryNode, request));
         } finally {
             end(binaryNode);
         }
@@ -1052,12 +1186,12 @@
 
     @Override
     public Node leaveLE(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.LE);
+        return leaveCmp(binaryNode);
     }
 
     @Override
     public Node leaveLT(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.LT);
+        return leaveCmp(binaryNode);
     }
 
     @Override
@@ -1072,12 +1206,12 @@
 
     @Override
     public Node leaveNE(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.NE);
+        return leaveCmp(binaryNode);
     }
 
     @Override
     public Node leaveNE_STRICT(final BinaryNode binaryNode) {
-        return leaveCmp(binaryNode, Request.NE_STRICT);
+        return leaveCmp(binaryNode);
     }
 
     @Override
@@ -1089,23 +1223,17 @@
 
     @Override
     public Node leaveSAR(final BinaryNode binaryNode) {
-        newTemporary(Type.INT, binaryNode);
-        end(binaryNode);
-        return binaryNode;
+        return end(coerce(binaryNode, Type.INT));
     }
 
     @Override
     public Node leaveSHL(final BinaryNode binaryNode) {
-        newTemporary(Type.INT, binaryNode);
-        end(binaryNode);
-        return binaryNode;
+        return end(coerce(binaryNode, Type.INT));
     }
 
     @Override
     public Node leaveSHR(final BinaryNode binaryNode) {
-        newTemporary(Type.LONG, binaryNode);
-        end(binaryNode);
-        return binaryNode;
+        return end(coerce(binaryNode, Type.LONG));
     }
 
     @Override
@@ -1114,9 +1242,9 @@
     }
 
     @Override
-    public Node leave(final ForNode forNode) {
+    public Node leaveForNode(final ForNode forNode) {
         if (forNode.isForIn()) {
-            forNode.setIterator(newInternal(getCurrentFunctionNode(), getCurrentFunctionNode().uniqueName(ITERATOR_PREFIX.tag()), Type.OBJECT)); //NASHORN-73
+            forNode.setIterator(newInternal(getCurrentFunctionNode().uniqueName(ITERATOR_PREFIX.tag()), Type.OBJECT)); //NASHORN-73
             /*
              * Iterators return objects, so we need to widen the scope of the
              * init variable if it, for example, has been assigned double type
@@ -1131,7 +1259,7 @@
     }
 
     @Override
-    public Node leave(final TernaryNode ternaryNode) {
+    public Node leaveTernaryNode(final TernaryNode ternaryNode) {
         final Node lhs  = ternaryNode.rhs();
         final Node rhs  = ternaryNode.third();
 
@@ -1146,24 +1274,24 @@
         return ternaryNode;
     }
 
-    private static void initThis(final FunctionNode functionNode) {
-        final Symbol thisSymbol = functionNode.defineSymbol(THIS.tag(), IS_PARAM | IS_THIS, null);
+    private void initThis(final FunctionNode functionNode) {
+        final Symbol thisSymbol = defineSymbol(functionNode, THIS.tag(), IS_PARAM | IS_THIS, null);
         newType(thisSymbol, Type.OBJECT);
         thisSymbol.setNeedsSlot(true);
         functionNode.getThisNode().setSymbol(thisSymbol);
         LOG.info("Initialized scope symbol: " + thisSymbol);
     }
 
-    private static void initScope(final FunctionNode functionNode) {
-        final Symbol scopeSymbol = functionNode.defineSymbol(SCOPE.tag(), IS_VAR | IS_INTERNAL, null);
+    private void initScope(final FunctionNode functionNode) {
+        final Symbol scopeSymbol = defineSymbol(functionNode, SCOPE.tag(), 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 static void initReturn(final FunctionNode functionNode) {
-        final Symbol returnSymbol = functionNode.defineSymbol(SCRIPT_RETURN.tag(), IS_VAR | IS_INTERNAL, null);
+    private void initReturn(final FunctionNode functionNode) {
+        final Symbol returnSymbol = defineSymbol(functionNode, SCRIPT_RETURN.tag(), IS_VAR | IS_INTERNAL, null);
         newType(returnSymbol, Type.OBJECT);
         returnSymbol.setNeedsSlot(true);
         functionNode.getResultNode().setSymbol(returnSymbol);
@@ -1173,7 +1301,7 @@
 
     private void initVarArg(final FunctionNode functionNode) {
         if (functionNode.isVarArg()) {
-            final Symbol varArgsSymbol = functionNode.defineSymbol(VARARGS.tag(), IS_PARAM | IS_INTERNAL, null);
+            final Symbol varArgsSymbol = defineSymbol(functionNode, VARARGS.tag(), IS_PARAM | IS_INTERNAL, null);
             varArgsSymbol.setTypeOverride(Type.OBJECT_ARRAY);
             varArgsSymbol.setNeedsSlot(true);
             functionNode.getVarArgsNode().setSymbol(varArgsSymbol);
@@ -1181,7 +1309,7 @@
 
             if (functionNode.needsArguments()) {
                 final String    argumentsName   = functionNode.getArgumentsNode().getName();
-                final Symbol    argumentsSymbol = functionNode.defineSymbol(argumentsName, IS_VAR | IS_INTERNAL, null);
+                final Symbol    argumentsSymbol = defineSymbol(functionNode, argumentsName, IS_VAR | IS_INTERNAL, null);
                 newType(argumentsSymbol, Type.typeFor(ScriptObject.class));
                 argumentsSymbol.setNeedsSlot(true);
                 functionNode.getArgumentsNode().setSymbol(argumentsSymbol);
@@ -1191,9 +1319,9 @@
         }
     }
 
-    private static void initCallee(final FunctionNode functionNode) {
+    private void initCallee(final FunctionNode functionNode) {
         assert functionNode.getCalleeNode() != null : functionNode + " has no callee";
-        final Symbol calleeSymbol = functionNode.defineSymbol(CALLEE.tag(), IS_PARAM | IS_INTERNAL, null);
+        final Symbol calleeSymbol = defineSymbol(functionNode, CALLEE.tag(), IS_PARAM | IS_INTERNAL, null);
         newType(calleeSymbol, Type.typeFor(ScriptFunction.class));
         calleeSymbol.setNeedsSlot(true);
         functionNode.getCalleeNode().setSymbol(calleeSymbol);
@@ -1211,11 +1339,17 @@
         // type or its parameters with the widest (OBJECT) type for safety.
         functionNode.setReturnType(Type.UNKNOWN);
 
-        for (final IdentNode ident : functionNode.getParameters()) {
-            addLocalDef(ident.getName());
-            final Symbol paramSymbol = functionNode.defineSymbol(ident.getName(), IS_PARAM, ident);
+        for (final IdentNode param : functionNode.getParameters()) {
+            addLocalDef(param.getName());
+            final Symbol paramSymbol = defineSymbol(functionNode, param.getName(), IS_PARAM, param);
             if (paramSymbol != null) {
-                newType(paramSymbol, Type.UNKNOWN);
+                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.");
+                }
+                newType(paramSymbol, callSiteParamType == null ? Type.UNKNOWN : callSiteParamType);
             }
 
             LOG.info("Initialized param " + paramSymbol);
@@ -1229,36 +1363,29 @@
      * @param functionNode functionNode
      */
     private static void finalizeParameters(final FunctionNode functionNode) {
-        boolean nonObjectParams = false;
-        List<Type> paramSpecializations = new ArrayList<>();
+        final boolean isVarArg = functionNode.isVarArg();
 
         for (final IdentNode ident : functionNode.getParameters()) {
             final Symbol paramSymbol = ident.getSymbol();
-            if (paramSymbol != null) {
-                Type type = paramSymbol.getSymbolType();
-                if (type.isUnknown()) {
-                    type = Type.OBJECT;
-                }
-                paramSpecializations.add(type);
-                if (!type.isObject()) {
-                    nonObjectParams = true;
-                }
-                newType(paramSymbol, Type.OBJECT);
+
+            assert paramSymbol != null;
+            Type type = functionNode.getSpecializedType(ident);
+            if (type == null) {
+                type = Type.OBJECT;
             }
-        }
 
-        if (!nonObjectParams) {
-            paramSpecializations = null;
-            // Later, when resolving a call to this method, the linker can say "I have a double, an int and an object" as parameters
-            // here. If the callee has parameter specializations, we can regenerate it with those particular types for speed.
-        } else {
-            LOG.info("parameter specialization possible: " + functionNode.getName() + " " + paramSpecializations);
-        }
+            // if we know that a parameter is only used as a certain type throughout
+            // 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());
+            }
 
-        // 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);
+            newType(paramSymbol, Type.widest(type, paramSymbol.getSymbolType()));
+
+            // parameters should not be slots for a function that uses variable arity signature
+            if (isVarArg) {
+                paramSymbol.setNeedsSlot(false);
             }
         }
     }
@@ -1267,15 +1394,15 @@
      * Move any properties from a global map into the scope of this method
      * @param functionNode the function node for which to init scope vars
      */
-    private static void initFromPropertyMap(final FunctionNode functionNode) {
+    private void initFromPropertyMap(final FunctionNode functionNode) {
         // For a script, add scope symbols as defined in the property map
-        assert functionNode.isScript();
+        assert functionNode.isProgram();
 
         final PropertyMap map = Context.getGlobalMap();
 
         for (final Property property : map.getProperties()) {
             final String key    = property.getKey();
-            final Symbol symbol = functionNode.defineSymbol(key, IS_GLOBAL, null);
+            final Symbol symbol = defineSymbol(functionNode, key, IS_GLOBAL, null);
             newType(symbol, Type.OBJECT);
             LOG.info("Added global symbol from property map " + symbol);
         }
@@ -1342,9 +1469,14 @@
     private static void ensureAssignmentSlots(final FunctionNode functionNode, final Node assignmentDest) {
         assignmentDest.accept(new NodeVisitor() {
             @Override
-            public Node leave(final IndexNode indexNode) {
+            public Node leaveIndexNode(final IndexNode indexNode) {
+                assert indexNode.getSymbol().isTemp();
                 final Node index = indexNode.getIndex();
-                index.getSymbol().setNeedsSlot(!index.getSymbol().isConstant());
+                //only temps can be set as needing slots. the others will self resolve
+                //it is illegal to take a scope var and force it to be a slot, that breaks
+                if (index.getSymbol().isTemp() && !index.getSymbol().isConstant()) {
+                     index.getSymbol().setNeedsSlot(true);
+                }
                 return indexNode;
             }
         });
@@ -1387,7 +1519,7 @@
                 }
 
                 @Override
-                public Node enter(final FunctionNode node) {
+                public Node enterFunctionNode(final FunctionNode node) {
                     return node.isLazy() ? null : node;
                 }
 
@@ -1407,7 +1539,7 @@
                  */
                 @SuppressWarnings("fallthrough")
                 @Override
-                public Node leave(final BinaryNode binaryNode) {
+                public Node leaveBinaryNode(final BinaryNode binaryNode) {
                     final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType());
                     switch (binaryNode.tokenType()) {
                     default:
@@ -1465,22 +1597,6 @@
         return binaryNode;
     }
 
-    private static List<Block> findLookupBlocksHelper(final FunctionNode currentFunction, final FunctionNode topFunction) {
-        if (currentFunction.findParentFunction() == topFunction) {
-            final List<Block> blocks = new LinkedList<>();
-
-            blocks.add(currentFunction.getParent());
-            blocks.addAll(currentFunction.getReferencingParentBlocks());
-            return blocks;
-        }
-        /*
-         * assumption: all parent blocks of an inner function will always be in the same outer function;
-         * therefore we can simply skip through intermediate functions.
-         * @see FunctionNode#addReferencingParentBlock(Block)
-         */
-        return findLookupBlocksHelper(currentFunction.findParentFunction(), topFunction);
-    }
-
     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());
@@ -1497,16 +1613,12 @@
         return newTemporary(getCurrentFunctionNode(), type, node);
     }
 
-    private Symbol newInternal(final FunctionNode functionNode, final String name, final Type type) {
-        final Symbol iter = getCurrentFunctionNode().defineSymbol(name, IS_VAR | IS_INTERNAL, null);
+    private Symbol newInternal(final String name, final Type type) {
+        final Symbol iter = defineSymbol(getCurrentFunctionNode(), name, IS_VAR | IS_INTERNAL, null);
         iter.setType(type); // NASHORN-73
         return iter;
     }
 
-    private Symbol newInternal(final String name, final Type type) {
-        return newInternal(getCurrentFunctionNode(), name, type);
-    }
-
     private static void newType(final Symbol symbol, final Type type) {
         final Type oldType = symbol.getSymbolType();
         symbol.setType(type);
@@ -1548,6 +1660,39 @@
         localUses.add(name);
     }
 
+    /**
+     * Pessimistically promote all symbols in current function node to Object types
+     * 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
+     */
+    private static void objectifySymbols(final FunctionNode functionNode) {
+        functionNode.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);
+                }
+            }
+
+            @Override
+            public Node enterBlock(final Block block) {
+                toObject(block);
+                return block;
+            }
+
+            @Override
+            public Node enterFunctionNode(final FunctionNode node) {
+                toObject(node);
+                if (node.isLazy()) {
+                    return null;
+                }
+                return node;
+            }
+        });
+    }
+
     private static String name(final Node node) {
         final String cn = node.getClass().getName();
         int lastDot = cn.lastIndexOf('.');
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/BranchOptimizer.java b/nashorn/src/jdk/nashorn/internal/codegen/BranchOptimizer.java
index 84cef43..ee92211 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/BranchOptimizer.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/BranchOptimizer.java
@@ -32,7 +32,6 @@
 import static jdk.nashorn.internal.codegen.Condition.LT;
 import static jdk.nashorn.internal.codegen.Condition.NE;
 
-import jdk.nashorn.internal.codegen.Label;
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Node;
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
index 35e7482..7ca7f99 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/ClassEmitter.java
@@ -195,6 +195,14 @@
     }
 
     /**
+     * Returns the name of the compile unit class name.
+     * @return the name of the compile unit class name.
+     */
+    String getUnitClassName() {
+        return unitClassName;
+    }
+
+    /**
      * Convert a binary name to a package/class name.
      *
      * @param name Binary name.
@@ -244,7 +252,7 @@
             // $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);
             getMapMethod.begin();
-            getMapMethod.loadConstants(unitClassName)
+            getMapMethod.loadConstants()
                         .load(Type.INT, 0)
                         .arrayload()
                         .checkcast(PropertyMap.class)
@@ -254,7 +262,7 @@
             // $setMap - overwrite an existing map.
             final MethodEmitter setMapMethod = method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), SET_MAP.tag(), void.class, int.class, PropertyMap.class);
             setMapMethod.begin();
-            setMapMethod.loadConstants(unitClassName)
+            setMapMethod.loadConstants()
                         .load(Type.INT, 0)
                         .load(Type.OBJECT, 1)
                         .arraystore();
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
index 2ffb5bd..1d09e9c 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CodeGenerator.java
@@ -25,10 +25,8 @@
 
 package jdk.nashorn.internal.codegen;
 
-import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.HANDLE_STATIC;
 import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.PRIVATE;
 import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC;
-import static jdk.nashorn.internal.codegen.CompilerConstants.ALLOCATE;
 import static jdk.nashorn.internal.codegen.CompilerConstants.GET_MAP;
 import static jdk.nashorn.internal.codegen.CompilerConstants.GET_STRING;
 import static jdk.nashorn.internal.codegen.CompilerConstants.LEAF;
@@ -50,7 +48,6 @@
 import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT;
 
 import java.io.PrintWriter;
-import java.lang.invoke.MethodHandle;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumSet;
@@ -79,9 +76,11 @@
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LineNumberNode;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
@@ -89,7 +88,6 @@
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ObjectNode;
 import jdk.nashorn.internal.ir.PropertyNode;
-import jdk.nashorn.internal.ir.ReferenceNode;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.RuntimeNode.Request;
@@ -108,14 +106,14 @@
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.Lexer.RegexToken;
 import jdk.nashorn.internal.parser.TokenType;
-import jdk.nashorn.internal.runtime.CodeInstaller;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.ECMAException;
 import jdk.nashorn.internal.runtime.Property;
 import jdk.nashorn.internal.runtime.PropertyMap;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
 import jdk.nashorn.internal.runtime.Scope;
 import jdk.nashorn.internal.runtime.ScriptFunction;
-import jdk.nashorn.internal.runtime.ScriptFunctionData;
 import jdk.nashorn.internal.runtime.ScriptObject;
 import jdk.nashorn.internal.runtime.ScriptRuntime;
 import jdk.nashorn.internal.runtime.Source;
@@ -149,8 +147,6 @@
     /** Name of the ScriptFunctionImpl, cannot be referred to as .class @see FunctionObjectCreator */
     private static final String SCRIPTFUNCTION_IMPL_OBJECT = Compiler.OBJECTS_PACKAGE + '/' + "ScriptFunctionImpl";
 
-    private static final String SCRIPTFUNCTION_TRAMPOLINE_OBJECT = Compiler.OBJECTS_PACKAGE + '/' + "ScriptFunctionTrampolineImpl";
-
     /** Constant data & installation. The only reason the compiler keeps this is because it is assigned
      *  by reflection in class installation */
     private final Compiler compiler;
@@ -161,12 +157,20 @@
     /** 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();
+
     /** When should we stop caching regexp expressions in fields to limit bytecode size? */
     private static final int MAX_REGEX_FIELDS = 2 * 1024;
 
+    private static final DebugLogger LOG   = new DebugLogger("codegen", "nashorn.codegen.debug");
+
     /**
      * Constructor.
      *
@@ -215,7 +219,7 @@
             final int flags = CALLSITE_SCOPE | getCallSiteFlags();
             method.loadScope();
 
-            if (symbol.isFastScope(getCurrentFunctionNode())) {
+            if (isFastScope(symbol)) {
                 // Only generate shared scope getter for fast-scope symbols so we know we can dial in correct scope.
                 if (symbol.getUseCount() > SharedScopeCall.FAST_SCOPE_GET_THRESHOLD) {
                     return loadSharedScopeVar(identNode.getType(), symbol, flags);
@@ -226,8 +230,28 @@
         }
     }
 
+    /**
+     * Check if this symbol can be accessed directly with a putfield or getfield or dynamic load
+     *
+     * @param function function to check for fast scope
+     * @return true if fast scope
+     */
+    private boolean isFastScope(final Symbol symbol) {
+        if (!symbol.isScope() || !symbol.getBlock().needsScope()) {
+            return false;
+        }
+        // Allow fast scope access if no function contains with or eval
+        for(final Iterator<FunctionNode> it = lexicalContext.getFunctions(getCurrentFunctionNode()); it.hasNext();) {
+            final FunctionNode func = it.next();
+            if (func.hasWith() || func.hasEval()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) {
-        method.load(symbol.isFastScope(getCurrentFunctionNode()) ? getScopeProtoDepth(getCurrentBlock(), symbol) : -1);
+        method.load(isFastScope(symbol) ? getScopeProtoDepth(getCurrentBlock(), symbol) : -1);
         final SharedScopeCall scopeCall = getScopeGet(valueType, symbol, flags | CALLSITE_FAST_SCOPE);
         scopeCall.generateInvoke(method);
         return method;
@@ -245,30 +269,18 @@
         return method;
     }
 
-    private static int getScopeProtoDepth(final Block currentBlock, final Symbol symbol) {
-        if (currentBlock == symbol.getBlock()) {
-            return 0;
-        }
-
-        final int   delta       = currentBlock.needsScope() ? 1 : 0;
-        final Block parentBlock = currentBlock.getParent();
-
-        if (parentBlock != null) {
-            final int result = getScopeProtoDepth(parentBlock, symbol);
-            if (result != -1) {
-                return delta + result;
+    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 Block currentBlock = blocks.next();
+            if (currentBlock == definingBlock) {
+                return depth;
+            }
+            if (currentBlock.needsScope()) {
+                ++depth;
             }
         }
-
-        if (currentBlock instanceof FunctionNode) {
-            for (final Block lookupBlock : ((FunctionNode)currentBlock).getReferencingParentBlocks()) {
-                final int result = getScopeProtoDepth(lookupBlock, symbol);
-                if (result != -1) {
-                    return delta + result;
-                }
-            }
-        }
-
         return -1;
     }
 
@@ -318,13 +330,13 @@
 
         node.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
             @Override
-            public Node enter(final IdentNode identNode) {
+            public Node enterIdentNode(final IdentNode identNode) {
                 loadIdent(identNode);
                 return null;
             }
 
             @Override
-            public Node enter(final AccessNode accessNode) {
+            public Node enterAccessNode(final AccessNode accessNode) {
                 if (!baseAlreadyOnStack) {
                     load(accessNode.getBase()).convert(Type.OBJECT);
                 }
@@ -334,7 +346,7 @@
             }
 
             @Override
-            public Node enter(final IndexNode indexNode) {
+            public Node enterIndexNode(final IndexNode indexNode) {
                 if (!baseAlreadyOnStack) {
                     load(indexNode.getBase()).convert(Type.OBJECT);
                     load(indexNode.getIndex());
@@ -344,6 +356,14 @@
             }
 
             @Override
+            public Node 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;
+            }
+
+            @Override
             public Node 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)
@@ -355,7 +375,7 @@
     }
 
     @Override
-    public Node enter(final AccessNode accessNode) {
+    public Node enterAccessNode(final AccessNode accessNode) {
         if (accessNode.testResolved()) {
             return null;
         }
@@ -427,10 +447,11 @@
     }
 
     @Override
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
         if (block.testResolved()) {
             return null;
         }
+        lexicalContext.push(block);
 
         method.label(block.getEntryLabel());
         initLocals(block);
@@ -439,14 +460,14 @@
     }
 
     @Override
-    public Node leave(final Block block) {
+    public Node leaveBlock(final Block block) {
         method.label(block.getBreakLabel());
         symbolInfo(block);
 
         if (block.needsScope()) {
             popBlockScope(block);
         }
-
+        lexicalContext.pop(block);
         return block;
     }
 
@@ -472,7 +493,7 @@
     }
 
     @Override
-    public Node enter(final BreakNode breakNode) {
+    public Node enterBreakNode(final BreakNode breakNode) {
         if (breakNode.testResolved()) {
             return null;
         }
@@ -520,14 +541,13 @@
     }
 
     @Override
-    public Node enter(final CallNode callNode) {
+    public Node enterCallNode(final CallNode callNode) {
         if (callNode.testResolved()) {
             return null;
         }
 
         final List<Node>   args            = callNode.getArgs();
         final Node         function        = callNode.getFunction();
-        final FunctionNode currentFunction = getCurrentFunctionNode();
         final Block        currentBlock    = getCurrentBlock();
 
         function.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
@@ -536,7 +556,7 @@
                 final Symbol symbol = identNode.getSymbol();
                 int    scopeCallFlags = flags;
                 method.loadScope();
-                if (symbol.isFastScope(currentFunction)) {
+                if (isFastScope(symbol)) {
                     method.load(getScopeProtoDepth(currentBlock, symbol));
                     scopeCallFlags |= CALLSITE_FAST_SCOPE;
                 } else {
@@ -598,7 +618,7 @@
             }
 
             @Override
-            public Node enter(final IdentNode node) {
+            public Node enterIdentNode(final IdentNode node) {
                 final Symbol symbol = node.getSymbol();
 
                 if (symbol.isScope()) {
@@ -611,7 +631,7 @@
                     if (callNode.isEval()) {
                         evalCall(node, flags);
                     } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD
-                            || (!symbol.isFastScope(currentFunction) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD)
+                            || (!isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD)
                             || callNode.inWithBlock()) {
                         scopeCall(node, flags);
                     } else {
@@ -626,7 +646,7 @@
             }
 
             @Override
-            public Node enter(final AccessNode node) {
+            public Node enterAccessNode(final AccessNode node) {
                 load(node.getBase());
                 method.convert(Type.OBJECT);
                 method.dup();
@@ -639,8 +659,7 @@
             }
 
             @Override
-            public Node enter(final ReferenceNode node) {
-                final FunctionNode callee   = node.getReference();
+            public Node enterFunctionNode(final FunctionNode callee) {
                 final boolean      isVarArg = callee.isVarArg();
                 final int          argCount = isVarArg ? -1 : callee.getParameters().size();
 
@@ -658,12 +677,13 @@
                 loadArgs(args, signature, isVarArg, argCount);
                 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;
             }
 
             @Override
-            public Node enter(final IndexNode node) {
+            public Node enterIndexNode(final IndexNode node) {
                 load(node.getBase());
                 method.convert(Type.OBJECT);
                 method.dup();
@@ -699,7 +719,7 @@
     }
 
     @Override
-    public Node enter(final ContinueNode continueNode) {
+    public Node enterContinueNode(final ContinueNode continueNode) {
         if (continueNode.testResolved()) {
             return null;
         }
@@ -714,17 +734,17 @@
     }
 
     @Override
-    public Node enter(final DoWhileNode doWhileNode) {
-        return enter((WhileNode)doWhileNode);
+    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
+        return enterWhileNode(doWhileNode);
     }
 
     @Override
-    public Node enter(final EmptyNode emptyNode) {
+    public Node enterEmptyNode(final EmptyNode emptyNode) {
         return null;
     }
 
     @Override
-    public Node enter(final ExecuteNode executeNode) {
+    public Node enterExecuteNode(final ExecuteNode executeNode) {
         if (executeNode.testResolved()) {
             return null;
         }
@@ -736,7 +756,7 @@
     }
 
     @Override
-    public Node enter(final ForNode forNode) {
+    public Node enterForNode(final ForNode forNode) {
         if (forNode.testResolved()) {
             return null;
         }
@@ -818,7 +838,7 @@
      * @param block block with local vars.
      */
     private void initLocals(final Block block) {
-        final FunctionNode function       = block.getFunction();
+        final FunctionNode function       = lexicalContext.getFunction(block);
         final boolean      isFunctionNode = block == function;
 
         /*
@@ -920,7 +940,7 @@
             foc.makeObject(method);
 
             // runScript(): merge scope into global
-            if (isFunctionNode && function.isScript()) {
+            if (isFunctionNode && function.isProgram()) {
                 method.invoke(ScriptRuntime.MERGE_SCOPE);
             }
 
@@ -963,31 +983,42 @@
     }
 
     @Override
-    public Node enter(final FunctionNode functionNode) {
-        if (functionNode.isLazy()) {
-            return null;
-        }
+    public Node enterFunctionNode(final FunctionNode functionNode) {
+        final boolean isCallee = functionNodeIsCallee;
+        functionNodeIsCallee = false;
 
         if (functionNode.testResolved()) {
             return null;
         }
 
+        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;
 
-        method = getCurrentCompileUnit().getClassEmitter().method(functionNode);
+        setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(functionNode));
         functionNode.setMethodEmitter(method);
         // Mark end for variable tables.
         method.begin();
         method.label(functionNode.getEntryLabel());
 
         initLocals(functionNode);
+        functionNode.setState(CompilationState.EMITTED);
 
         return functionNode;
     }
 
     @Override
-    public Node leave(final FunctionNode functionNode) {
+    public Node leaveFunctionNode(final FunctionNode functionNode) {
         // Mark end for variable tables.
         method.label(functionNode.getBreakLabel());
 
@@ -1005,16 +1036,18 @@
             throw e;
         }
 
+        lexicalContext.pop(functionNode);
+        LOG.info("=== END " + functionNode.getName());
         return functionNode;
     }
 
     @Override
-    public Node enter(final IdentNode identNode) {
+    public Node enterIdentNode(final IdentNode identNode) {
         return null;
     }
 
     @Override
-    public Node enter(final IfNode ifNode) {
+    public Node enterIfNode(final IfNode ifNode) {
         if (ifNode.testResolved()) {
             return null;
         }
@@ -1053,7 +1086,7 @@
     }
 
     @Override
-    public Node enter(final IndexNode indexNode) {
+    public Node enterIndexNode(final IndexNode indexNode) {
         if (indexNode.testResolved()) {
             return null;
         }
@@ -1064,7 +1097,7 @@
     }
 
     @Override
-    public Node enter(final LineNumberNode lineNumberNode) {
+    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
         if (lineNumberNode.testResolved()) {
             return null;
         }
@@ -1072,7 +1105,6 @@
         final Label label = new Label("line:" + lineNumberNode.getLineNumber() + " (" + getCurrentFunctionNode().getName() + ")");
         method.label(label);
         method.lineNumber(lineNumberNode.getLineNumber(), label);
-
         return null;
     }
 
@@ -1110,7 +1142,7 @@
                     final String name      = getCurrentFunctionNode().uniqueName(SPLIT_PREFIX.tag());
                     final String signature = methodDescriptor(type, Object.class, ScriptFunction.class, ScriptObject.class, type);
 
-                    method = getCurrentCompileUnit().getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature);
+                    setCurrentMethodEmitter(getCurrentCompileUnit().getClassEmitter().method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), name, signature));
                     method.setFunctionNode(getCurrentFunctionNode());
                     method.begin();
 
@@ -1216,7 +1248,7 @@
             method.invokestatic(unitClassName, methodName, methodDescriptor(cls, int.class));
             classEmitter.needGetConstantMethod(cls);
         } else {
-            method.loadConstants(unitClassName).load(index).arrayload();
+            method.loadConstants().load(index).arrayload();
             if (cls != Object.class) {
                 method.checkcast(cls);
             }
@@ -1296,14 +1328,14 @@
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enter(final LiteralNode literalNode) {
+    public Node enterLiteralNode(final LiteralNode literalNode) {
         assert literalNode.getSymbol() != null : literalNode + " has no symbol";
         load(literalNode).store(literalNode.getSymbol());
         return null;
     }
 
     @Override
-    public Node enter(final ObjectNode objectNode) {
+    public Node enterObjectNode(final ObjectNode objectNode) {
         if (objectNode.testResolved()) {
             return null;
         }
@@ -1376,10 +1408,10 @@
         }
 
         for (final Node element : elements) {
-            final PropertyNode  propertyNode = (PropertyNode)element;
-            final Object        key          = propertyNode.getKey();
-            final ReferenceNode getter       = (ReferenceNode)propertyNode.getGetter();
-            final ReferenceNode setter       = (ReferenceNode)propertyNode.getSetter();
+            final PropertyNode propertyNode = (PropertyNode)element;
+            final Object       key          = propertyNode.getKey();
+            final FunctionNode getter       = (FunctionNode)propertyNode.getGetter();
+            final FunctionNode setter       = (FunctionNode)propertyNode.getSetter();
 
             if (getter == null && setter == null) {
                 continue;
@@ -1408,18 +1440,7 @@
     }
 
     @Override
-    public Node enter(final ReferenceNode referenceNode) {
-        if (referenceNode.testResolved()) {
-            return null;
-        }
-
-        newFunctionObject(referenceNode.getReference());
-
-        return null;
-    }
-
-    @Override
-    public Node enter(final ReturnNode returnNode) {
+    public Node enterReturnNode(final ReturnNode returnNode) {
         if (returnNode.testResolved()) {
             return null;
         }
@@ -1560,7 +1581,7 @@
     }
 
     @Override
-    public Node enter(final RuntimeNode runtimeNode) {
+    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
         if (runtimeNode.testResolved()) {
             return null;
         }
@@ -1641,7 +1662,7 @@
     }
 
     @Override
-    public Node enter(final SplitNode splitNode) {
+    public Node enterSplitNode(final SplitNode splitNode) {
         if (splitNode.testResolved()) {
             return null;
         }
@@ -1710,7 +1731,7 @@
     }
 
     @Override
-    public Node leave(final SplitNode splitNode) {
+    public Node leaveSplitNode(final SplitNode splitNode) {
         try {
             // Wrap up this method.
             method.loadResult();
@@ -1767,7 +1788,7 @@
     }
 
     @Override
-    public Node enter(final SwitchNode switchNode) {
+    public Node enterSwitchNode(final SwitchNode switchNode) {
         if (switchNode.testResolved()) {
             return null;
         }
@@ -1899,7 +1920,7 @@
     }
 
     @Override
-    public Node enter(final ThrowNode throwNode) {
+    public Node enterThrowNode(final ThrowNode throwNode) {
         if (throwNode.testResolved()) {
             return null;
         }
@@ -1926,7 +1947,7 @@
     }
 
     @Override
-    public Node enter(final TryNode tryNode) {
+    public Node enterTryNode(final TryNode tryNode) {
         if (tryNode.testResolved()) {
             return null;
         }
@@ -1959,7 +1980,7 @@
             setCurrentBlock(catchBlock);
 
             try {
-                enter(catchBlock);
+                enterBlock(catchBlock);
 
                 final CatchNode catchNode          = (CatchNode)catchBlocks.get(i).getStatements().get(0);
                 final IdentNode exception          = catchNode.getException();
@@ -1970,6 +1991,7 @@
                     // Generate catch body (inlined finally) and rethrow exception
                     catchBody.accept(this);
                     method.load(symbol).athrow();
+                    lexicalContext.pop(catchBlock);
                     continue;
                 }
 
@@ -2016,7 +2038,7 @@
                     }
                 }
 
-                leave(catchBlock);
+                leaveBlock(catchBlock);
             } finally {
                 setCurrentBlock(saveBlock);
             }
@@ -2031,7 +2053,7 @@
     }
 
     @Override
-    public Node enter(final VarNode varNode) {
+    public Node enterVarNode(final VarNode varNode) {
         final Node init = varNode.getInit();
 
         if (varNode.testResolved() || init == null) {
@@ -2053,7 +2075,7 @@
             int flags = CALLSITE_SCOPE | getCallSiteFlags();
             final IdentNode identNode = varNode.getName();
             final Type type = identNode.getType();
-            if (varSymbol.isFastScope(getCurrentFunctionNode())) {
+            if (isFastScope(varSymbol)) {
                 storeFastScopeVar(type, varSymbol, flags);
             } else {
                 method.dynamicSet(type, identNode.getName(), flags);
@@ -2069,7 +2091,7 @@
     }
 
     @Override
-    public Node enter(final WhileNode whileNode) {
+    public Node enterWhileNode(final WhileNode whileNode) {
         if (whileNode.testResolved()) {
             return null;
         }
@@ -2102,7 +2124,7 @@
     }
 
     @Override
-    public Node enter(final WithNode withNode) {
+    public Node enterWithNode(final WithNode withNode) {
         if (withNode.testResolved()) {
             return null;
         }
@@ -2868,7 +2890,7 @@
      * Ternary visits.
      */
     @Override
-    public Node enter(final TernaryNode ternaryNode) {
+    public Node enterTernaryNode(final TernaryNode ternaryNode) {
         if (ternaryNode.testResolved()) {
             return null;
         }
@@ -3064,7 +3086,7 @@
 
             target.accept(new NodeVisitor(getCurrentCompileUnit(), method) {
                 @Override
-                public Node enter(final IdentNode node) {
+                public Node enterIdentNode(final IdentNode node) {
                     if (targetSymbol.isScope()) {
                         method.load(scopeSymbol);
                         depth++;
@@ -3087,13 +3109,13 @@
                 }
 
                 @Override
-                public Node enter(final AccessNode node) {
+                public Node enterAccessNode(final AccessNode node) {
                     enterBaseNode();
                     return null;
                 }
 
                 @Override
-                public Node enter(final IndexNode node) {
+                public Node enterIndexNode(final IndexNode node) {
                     enterBaseNode();
 
                     final Node index = node.getIndex();
@@ -3159,8 +3181,6 @@
         }
 
         private void epilogue() {
-            final FunctionNode currentFunction = getCurrentFunctionNode();
-
             /**
              * Take the original target args from the stack and use them
              * together with the value to be stored to emit the store code
@@ -3178,7 +3198,7 @@
                 }
 
                 @Override
-                public Node enter(final UnaryNode node) {
+                public Node enterUnaryNode(final UnaryNode node) {
                     if(node.tokenType() == TokenType.CONVERT && node.getSymbol() != null) {
                         method.convert(node.rhs().getType());
                     }
@@ -3186,11 +3206,11 @@
                 }
 
                 @Override
-                public Node enter(final IdentNode node) {
+                public Node enterIdentNode(final IdentNode node) {
                     final Symbol symbol = node.getSymbol();
                     assert symbol != null;
                     if (symbol.isScope()) {
-                        if (symbol.isFastScope(currentFunction)) {
+                        if (isFastScope(symbol)) {
                             storeFastScopeVar(node.getType(), symbol, CALLSITE_SCOPE | getCallSiteFlags());
                         } else {
                             method.dynamicSet(node.getType(), node.getName(), CALLSITE_SCOPE | getCallSiteFlags());
@@ -3203,13 +3223,13 @@
                 }
 
                 @Override
-                public Node enter(final AccessNode node) {
+                public Node enterAccessNode(final AccessNode node) {
                     method.dynamicSet(node.getProperty().getType(), node.getProperty().getName(), getCallSiteFlags());
                     return null;
                 }
 
                 @Override
-                public Node enter(final IndexNode node) {
+                public Node enterIndexNode(final IndexNode node) {
                     method.dynamicSetIndex(getCallSiteFlags());
                     return null;
                 }
@@ -3234,42 +3254,22 @@
     }
 
     private void newFunctionObject(final FunctionNode functionNode) {
-        final boolean isLazy = functionNode.isLazy();
-        final Class<?>[] cparams = new Class<?>[] { ScriptFunctionData.class, ScriptObject.class, MethodHandle.class };
+        final boolean isLazy  = functionNode.isLazy();
 
         new ObjectCreator(this, new ArrayList<String>(), new ArrayList<Symbol>(), false, false) {
             @Override
-            protected void makeObject(final MethodEmitter method) {
-                final String className = isLazy ? SCRIPTFUNCTION_TRAMPOLINE_OBJECT : SCRIPTFUNCTION_IMPL_OBJECT;
+            protected void makeObject(final MethodEmitter m) {
+                final String className = SCRIPTFUNCTION_IMPL_OBJECT;
 
-                method._new(className).dup();
-                if (isLazy) {
-                    loadConstant(compiler.getCodeInstaller());
-                    loadConstant(functionNode);
-                } else {
-                    final String signature = new FunctionSignature(true, functionNode.needsCallee(), functionNode.getReturnType(), functionNode.isVarArg() ? null : functionNode.getParameters()).toString();
-                    method.loadHandle(functionNode.getCompileUnit().getUnitClassName(), functionNode.getName(), signature, EnumSet.of(HANDLE_STATIC)); // function
-                }
-                loadConstant(new ScriptFunctionData(functionNode, makeMap()));
+                m._new(className).dup();
+                loadConstant(new RecompilableScriptFunctionData(functionNode, compiler.getCodeInstaller(), Compiler.binaryName(getClassName()), makeMap()));
 
                 if (isLazy || functionNode.needsParentScope()) {
-                    method.loadScope();
+                    m.loadScope();
                 } else {
-                    method.loadNull();
+                    m.loadNull();
                 }
-
-                method.loadHandle(getClassName(), ALLOCATE.tag(), methodDescriptor(ScriptObject.class, PropertyMap.class), EnumSet.of(HANDLE_STATIC));
-
-                final List<Class<?>> cparamList = new ArrayList<>();
-                if (isLazy) {
-                    cparamList.add(CodeInstaller.class);
-                    cparamList.add(FunctionNode.class);
-                } else {
-                    cparamList.add(MethodHandle.class);
-                }
-                cparamList.addAll(Arrays.asList(cparams));
-
-                method.invoke(constructorNoLookup(className, cparamList.toArray(new Class<?>[cparamList.size()])));
+                m.invoke(constructorNoLookup(className, RecompilableScriptFunctionData.class, ScriptObject.class));
             }
         }.makeObject(method);
     }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
index e22454c..8b905f8 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompilationPhase.java
@@ -2,10 +2,10 @@
 
 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.ATTR;
 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.CONSTANT_FOLDED;
-import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.EMITTED;
 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.FINALIZED;
 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.INITIALIZED;
 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
+import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
 import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
 
 import java.io.File;
@@ -14,16 +14,16 @@
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Set;
-
 import jdk.nashorn.internal.codegen.types.Type;
 import jdk.nashorn.internal.ir.CallNode;
 import jdk.nashorn.internal.ir.FunctionNode;
 import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.ReferenceNode;
-import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.ir.debug.ASTWriter;
 import jdk.nashorn.internal.ir.debug.PrintVisitor;
+import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
+import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.ECMAErrors;
 import jdk.nashorn.internal.runtime.ScriptEnvironment;
 import jdk.nashorn.internal.runtime.Timing;
@@ -39,7 +39,7 @@
      * default policy. The will get trampolines and only be generated when
      * called
      */
-    LAZY_INITIALIZATION_PHASE(EnumSet.of(FunctionNode.CompilationState.INITIALIZED)) {
+    LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
         @Override
         void transform(final Compiler compiler, final FunctionNode fn) {
 
@@ -65,23 +65,25 @@
             outermostFunctionNode.accept(new NodeVisitor() {
                 // self references are done with invokestatic and thus cannot have trampolines - never lazy
                 @Override
-                public Node enter(final CallNode node) {
+                public Node enterCallNode(final CallNode node) {
                     final Node callee = node.getFunction();
-                    if (callee instanceof ReferenceNode) {
-                        neverLazy.add(((ReferenceNode)callee).getReference());
+                    if (callee instanceof FunctionNode) {
+                        neverLazy.add(((FunctionNode)callee));
                         return null;
                     }
                     return node;
                 }
 
                 @Override
-                public Node enter(final FunctionNode node) {
+                public Node enterFunctionNode(final FunctionNode node) {
                     if (node == outermostFunctionNode) {
                         return node;
                     }
-                    assert Compiler.LAZY_JIT;
+                    assert compiler.isLazy();
                     lazy.add(node);
 
+                    //also needs scope, potentially needs arguments etc etc
+
                     return node;
                 }
             });
@@ -92,15 +94,24 @@
                 lazy.remove(node);
             }
 
-            for (final FunctionNode node : lazy) {
-                Compiler.LOG.fine("Marking " + node.getName() + " as lazy");
-                node.setIsLazy(true);
-                final FunctionNode parent = node.findParentFunction();
-                if (parent != null) {
-                    Compiler.LOG.fine("Marking " + parent.getName() + " as having lazy children - it needs scope for all variables");
-                    parent.setHasLazyChildren();
+            outermostFunctionNode.accept(new NodeOperatorVisitor() {
+                private final LexicalContext lexicalContext = new LexicalContext();
+                @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();
+                    }
+                    return functionNode;
                 }
-            }
+                @Override
+                public Node leaveFunctionNode(FunctionNode functionNode) {
+                    lexicalContext.pop(functionNode);
+                    return functionNode;
+                }
+            });
         }
 
         @Override
@@ -113,7 +124,7 @@
      * Constant folding pass
      *   Simple constant folding that will make elementary constructs go away
      */
-    CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED), CONSTANT_FOLDED) {
+    CONSTANT_FOLDING_PHASE(EnumSet.of(INITIALIZED, PARSED)) {
         @Override
         void transform(final Compiler compiler, final FunctionNode fn) {
             fn.accept(new FoldConstants());
@@ -134,7 +145,7 @@
      *   as runtime nodes where applicable.
      *
      */
-    LOWERING_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED), LOWERED) {
+    LOWERING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED)) {
         @Override
         void transform(final Compiler compiler, final FunctionNode fn) {
             fn.accept(new Lower());
@@ -150,19 +161,10 @@
      * Attribution
      *   Assign symbols and types to all nodes.
      */
-    ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED), ATTR) {
+    ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) {
         @Override
         void transform(final Compiler compiler, final FunctionNode fn) {
-            final ScriptEnvironment env = compiler.getEnv();
-
             fn.accept(new Attr());
-            if (env._print_lower_ast) {
-                env.getErr().println(new ASTWriter(fn));
-            }
-
-            if (env._print_lower_parse) {
-                env.getErr().println(new PrintVisitor(fn));
-           }
         }
 
         @Override
@@ -178,7 +180,7 @@
      *   a + b a ScriptRuntime.ADD with call overhead or a dadd with much
      *   less). Split IR can lead to scope information being changed.
      */
-    SPLITTING_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED, ATTR), SPLIT) {
+    SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) {
         @Override
         void transform(final Compiler compiler, final FunctionNode fn) {
             final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName());
@@ -212,10 +214,20 @@
      * Contract: all variables must have slot assignments and scope assignments
      * before type finalization.
      */
-    TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT), FINALIZED) {
+    TYPE_FINALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT)) {
         @Override
         void transform(final Compiler compiler, final FunctionNode fn) {
+            final ScriptEnvironment env = compiler.getEnv();
+
             fn.accept(new FinalizeTypes());
+
+            if (env._print_lower_ast) {
+                env.getErr().println(new ASTWriter(fn));
+            }
+
+            if (env._print_lower_parse) {
+                env.getErr().println(new PrintVisitor(fn));
+           }
         }
 
         @Override
@@ -229,7 +241,7 @@
      *
      *   Generate the byte code class(es) resulting from the compiled FunctionNode
      */
-    BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED), EMITTED) {
+    BYTECODE_GENERATION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR, SPLIT, FINALIZED)) {
         @Override
         void transform(final Compiler compiler, final FunctionNode fn) {
             final ScriptEnvironment env = compiler.getEnv();
@@ -238,6 +250,16 @@
                 final CodeGenerator codegen = new CodeGenerator(compiler);
                 fn.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) {
@@ -306,18 +328,12 @@
     };
 
     private final EnumSet<CompilationState> pre;
-    private final CompilationState post;
     private long startTime;
     private long endTime;
     private boolean isFinished;
 
     private CompilationPhase(final EnumSet<CompilationState> pre) {
-        this(pre, null);
-    }
-
-    private CompilationPhase(final EnumSet<CompilationState> pre, final CompilationState post) {
-        this.pre  = pre;
-        this.post = post;
+        this.pre = pre;
     }
 
     boolean isApplicable(final FunctionNode functionNode) {
@@ -343,10 +359,6 @@
         endTime = System.currentTimeMillis();
         Timing.accumulateTime(toString(), endTime - startTime);
 
-        if (post != null) {
-            functionNode.setState(post);
-        }
-
         isFinished = true;
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java b/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java
index 5e62116..ff88fa9 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/CompileUnit.java
@@ -37,6 +37,8 @@
 
     private long weight;
 
+    private Class<?> clazz;
+
     CompileUnit(final String className, final ClassEmitter classEmitter) {
         this(className, classEmitter, 0L);
     }
@@ -48,6 +50,24 @@
     }
 
     /**
+     * Return the class that contains the code for this unit, null if not
+     * generated yet
+     *
+     * @return class with compile unit code
+     */
+    public Class<?> getCode() {
+        return clazz;
+    }
+
+    /**
+     * Set class when it exists. Only accessible from compiler
+     * @param clazz class with code for this compile unit
+     */
+    void setCode(final Class<?> clazz) {
+        this.clazz = clazz;
+    }
+
+    /**
      * Add weight to this compile unit
      * @param w weight to add
      */
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
index ca23c42..397f39a 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Compiler.java
@@ -45,11 +45,14 @@
 import java.util.Map;
 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;
@@ -71,8 +74,6 @@
     /** Name of the objects package */
     public static final String OBJECTS_PACKAGE = "jdk/nashorn/internal/objects";
 
-    static final boolean LAZY_JIT = Options.getBooleanProperty("nashorn.compiler.lazy");
-
     private final Map<String, byte[]> bytecode;
 
     private final Set<CompileUnit> compileUnits;
@@ -164,7 +165,7 @@
      * and JIT it at once. This can lead to long startup time and fewer type
      * specializations
      */
-    final static CompilationSequence SEQUENCE_NORMAL = new CompilationSequence(
+    final static CompilationSequence SEQUENCE_EAGER = new CompilationSequence(
         CompilationPhase.CONSTANT_FOLDING_PHASE,
         CompilationPhase.LOWERING_PHASE,
         CompilationPhase.ATTRIBUTION_PHASE,
@@ -173,12 +174,15 @@
         CompilationPhase.BYTECODE_GENERATION_PHASE);
 
     final static CompilationSequence SEQUENCE_LAZY =
-        SEQUENCE_NORMAL.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE);
+        SEQUENCE_EAGER.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE);
 
-    final static CompilationSequence SEQUENCE_DEFAULT =
-        LAZY_JIT ?
-            SEQUENCE_LAZY :
-            SEQUENCE_NORMAL;
+    private static CompilationSequence sequence(final boolean lazy) {
+        return lazy ? SEQUENCE_LAZY : SEQUENCE_EAGER;
+    }
+
+    boolean isLazy() {
+        return sequence == SEQUENCE_LAZY;
+    }
 
     private static String lazyTag(final FunctionNode functionNode) {
         if (functionNode.isLazy()) {
@@ -212,11 +216,6 @@
                 append(safeSourceName(functionNode.getSource()));
 
         this.scriptName = sb.toString();
-
-        LOG.info("Initializing compiler for '" + functionNode.getName() + "' scriptName = " + scriptName + ", root function: '" + functionNode.getName() + "'");
-        if (functionNode.isLazy()) {
-            LOG.info(">>> This is a lazy recompilation triggered by a trampoline");
-        }
     }
 
     /**
@@ -227,7 +226,7 @@
      * @param strict       should this compilation use strict mode semantics
      */
     public Compiler(final CodeInstaller<ScriptEnvironment> installer, final FunctionNode functionNode, final boolean strict) {
-        this(installer.getOwner(), installer, functionNode, SEQUENCE_DEFAULT, strict);
+        this(installer.getOwner(), installer, functionNode, sequence(installer.getOwner()._lazy_compilation), strict);
     }
 
     /**
@@ -237,7 +236,7 @@
      * @param functionNode function node (in any available {@link CompilationState}) to compile
      */
     public Compiler(final CodeInstaller<ScriptEnvironment> installer, final FunctionNode functionNode) {
-        this(installer.getOwner(), installer, functionNode, SEQUENCE_DEFAULT, installer.getOwner()._strict);
+        this(installer.getOwner(), installer, functionNode, sequence(installer.getOwner()._lazy_compilation), installer.getOwner()._strict);
     }
 
     /**
@@ -247,28 +246,104 @@
      * @param functionNode functionNode to compile
      */
     public Compiler(final ScriptEnvironment env, final FunctionNode functionNode) {
-        this(env, null, functionNode, SEQUENCE_DEFAULT, env._strict);
+        this(env, null, functionNode, sequence(env._lazy_compilation), env._strict);
     }
 
     /**
      * 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
      */
-    public void compile() throws CompilationException {
+    public Compiler compile() throws CompilationException {
+        return compile(null);
+    }
+
+    /**
+     * 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
+     */
+    public Compiler compile(final Class<?> paramTypes) throws CompilationException {
         for (final String reservedName : RESERVED_NAMES) {
             functionNode.uniqueName(reservedName);
         }
 
+        final boolean fine = !LOG.levelAbove(Level.FINE);
+        final boolean info = !LOG.levelAbove(Level.INFO);
+
+        long time = 0L;
+
         for (final CompilationPhase phase : sequence) {
             phase.apply(this, functionNode);
-            final String end = phase.toString() + " done for function '" + functionNode.getName() + "'";
-            if (Timing.isEnabled()) {
-                final long duration = phase.getEndTime() - phase.getStartTime();
-                LOG.info(end + " in " + duration + " ms");
-            } else {
-                LOG.info(end);
+
+            final long duration = Timing.isEnabled() ? (phase.getEndTime() - phase.getStartTime()) : 0L;
+            time += duration;
+
+            if (fine) {
+                final StringBuilder sb = new StringBuilder();
+
+                sb.append(phase.toString()).
+                    append(" done for function '").
+                    append(functionNode.getName()).
+                    append('\'');
+
+                if (duration > 0L) {
+                    sb.append(" in ").
+                        append(duration).
+                        append(" ms ");
+                }
+
+                LOG.fine(sb.toString());
             }
         }
+
+        if (info) {
+            final StringBuilder sb = new StringBuilder();
+            sb.append("Compile job for '").
+                append(functionNode.getName()).
+                append("' finished");
+
+            if (time > 0L) {
+                sb.append(" in ").
+                    append(time).
+                    append(" ms");
+            }
+
+            LOG.info(sb.toString());
+        }
+
+        return this;
+    }
+
+    private Class<?> install(final String className, final byte[] code) {
+        LOG.fine("Installing class " + className);
+
+        final Class<?> clazz = installer.install(Compiler.binaryName(className), code);
+
+        try {
+            final Source   source    = getSource();
+            final Object[] constants = getConstantData().toArray();
+            // Need doPrivileged because these fields are private
+            AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+                @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());
+                    sourceField.setAccessible(true);
+                    constantsField.setAccessible(true);
+                    sourceField.set(null, source);
+                    constantsField.set(null, constants);
+                    return null;
+                }
+            });
+        } catch (final PrivilegedActionException e) {
+            throw new RuntimeException(e);
+        }
+
+        return clazz;
     }
 
     /**
@@ -280,46 +355,68 @@
 
         assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " has no bytecode and cannot be installed";
 
-        Class<?> rootClass = null;
+        final Map<String, Class<?>> installedClasses = new HashMap<>();
+
+        final String   rootClassName = firstCompileUnitName();
+        final byte[]   rootByteCode  = bytecode.get(rootClassName);
+        final Class<?> rootClass     = install(rootClassName, rootByteCode);
+
+        int length = rootByteCode.length;
+
+        installedClasses.put(rootClassName, rootClass);
 
         for (final Entry<String, byte[]> entry : bytecode.entrySet()) {
-            final String     className = entry.getKey();
-            LOG.fine("Installing class " + className);
-
-            final byte[]     code  = entry.getValue();
-            final Class<?>   clazz = installer.install(Compiler.binaryName(className), code);
-
-            if (rootClass == null && firstCompileUnitName().equals(className)) {
-                rootClass = clazz;
+            final String className = entry.getKey();
+            if (className.equals(rootClassName)) {
+                continue;
             }
+            final byte[] code = entry.getValue();
+            length += code.length;
 
-            try {
-                final Source source = getSource();
-                final Object[] constants = getConstantData().toArray();
-                // Need doPrivileged because these fields are private
-                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
-                    @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());
-                        sourceField.setAccessible(true);
-                        constantsField.setAccessible(true);
-                        sourceField.set(null, source);
-                        constantsField.set(null, constants);
-                        return null;
-                    }
-                });
-            } catch (final PrivilegedActionException e) {
-                throw new RuntimeException(e);
-            }
+            installedClasses.put(className, install(className, code));
         }
 
-        LOG.info("Installed root class: " + rootClass + " and " + bytecode.size() + " compile unit classes");
+        for (final CompileUnit unit : compileUnits) {
+            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();
+            sb.append("Installed class '").
+                append(rootClass.getSimpleName()).
+                append('\'').
+                append(" bytes=").
+                append(length).
+                append('.');
+            if (bytecode.size() > 1) {
+                sb.append(' ').append(bytecode.size()).append(" compile units.");
+            }
+        } else {
+            sb = null;
+        }
+
         if (Timing.isEnabled()) {
             final long duration = System.currentTimeMillis() - t0;
             Timing.accumulateTime("[Code Installation]", duration);
-            LOG.info("Installation time: " + duration + " ms");
+            if (sb != null) {
+                sb.append(" Install time: ").append(duration).append(" ms");
+            }
+        }
+
+        if (sb != null) {
+            LOG.info(sb.toString());
         }
 
         return rootClass;
@@ -444,8 +541,6 @@
      * TODO: We currently generate no overflow checks so this is
      * disabled
      *
-     * @see #shouldUseIntegers()
-     *
      * @return true if arithmetic operations should not widen integer
      *   operands by default.
      */
@@ -460,4 +555,5 @@
         assert !USE_INT_ARITH : "Integer arithmetic is not enabled";
     }
 
+
 }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java b/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
index 28dfda7..cd18524 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FinalizeTypes.java
@@ -40,13 +40,14 @@
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode;
 import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.ReferenceNode;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.RuntimeNode.Request;
@@ -84,11 +85,13 @@
 
     private static final DebugLogger LOG = new DebugLogger("finalize");
 
+    private final LexicalContext lexicalContext = new LexicalContext();
+
     FinalizeTypes() {
     }
 
     @Override
-    public Node leave(final CallNode callNode) {
+    public Node leaveCallNode(final CallNode callNode) {
         final EvalArgs evalArgs = callNode.getEvalArgs();
         if (evalArgs != null) {
             evalArgs.setCode(evalArgs.getCode().accept(this));
@@ -96,15 +99,14 @@
 
         // AccessSpecializer - call return type may change the access for this location
         final Node function = callNode.getFunction();
-        if (function instanceof ReferenceNode) {
-            setTypeOverride(callNode, ((ReferenceNode)function).getReference().getType());
+        if (function instanceof FunctionNode) {
+            return setTypeOverride(callNode, ((FunctionNode)function).getReturnType());
         }
         return callNode;
     }
 
     private Node leaveUnary(final UnaryNode unaryNode) {
-        unaryNode.setRHS(convert(unaryNode.rhs(), unaryNode.getType()));
-        return unaryNode;
+        return unaryNode.setRHS(convert(unaryNode.rhs(), unaryNode.getType()));
     }
 
     @Override
@@ -125,8 +127,7 @@
 
     @Override
     public Node leaveDECINC(final UnaryNode unaryNode) {
-        specialize(unaryNode);
-        return unaryNode;
+        return specialize(unaryNode).node;
     }
 
     @Override
@@ -158,9 +159,7 @@
             }
         }
 
-        binaryNode.setLHS(convert(lhs, type));
-        binaryNode.setRHS(convert(rhs, type));
-        return binaryNode;
+        return binaryNode.setLHS(convert(lhs, type)).setRHS(convert(rhs, type));
     }
 
     @Override
@@ -170,12 +169,13 @@
 
     @Override
     public Node leaveASSIGN(final BinaryNode binaryNode) {
-        Type destType = specialize(binaryNode);
+        final SpecializedNode specialized = specialize(binaryNode);
+        final BinaryNode specBinaryNode = (BinaryNode)specialized.node;
+        Type destType = specialized.type;
         if (destType == null) {
-            destType = binaryNode.getType();
+            destType = specBinaryNode.getType();
         }
-        binaryNode.setRHS(convert(binaryNode.rhs(), destType));
-        return binaryNode;
+        return specBinaryNode.setRHS(convert(specBinaryNode.rhs(), destType));
     }
 
     @Override
@@ -235,40 +235,40 @@
 
     @Override
     public Node leaveBIT_AND(BinaryNode binaryNode) {
-        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger() : binaryNode.getSymbol();
+        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger() : "int coercion expected: " + binaryNode.getSymbol();
         return leaveBinary(binaryNode, Type.INT, Type.INT);
     }
 
     @Override
     public Node leaveBIT_OR(BinaryNode binaryNode) {
-        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger();
+        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger() : "int coercion expected: " + binaryNode.getSymbol();
         return leaveBinary(binaryNode, Type.INT, Type.INT);
     }
 
     @Override
     public Node leaveBIT_XOR(BinaryNode binaryNode) {
-        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger();
+        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isInteger() : "int coercion expected: " + binaryNode.getSymbol();
         return leaveBinary(binaryNode, Type.INT, Type.INT);
     }
 
     @Override
     public Node leaveCOMMALEFT(final BinaryNode binaryNode) {
         assert binaryNode.getSymbol() != null;
-        binaryNode.setRHS(discard(binaryNode.rhs()));
-        // AccessSpecializer - the type of rhs, which is the remaining value of this node may have changed
+        final BinaryNode newBinaryNode = (BinaryNode)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(binaryNode, binaryNode.lhs().getType());
-        return binaryNode;
+        propagateType(newBinaryNode, newBinaryNode.lhs().getType());
+        return newBinaryNode;
     }
 
     @Override
     public Node leaveCOMMARIGHT(final BinaryNode binaryNode) {
         assert binaryNode.getSymbol() != null;
-        binaryNode.setLHS(discard(binaryNode.lhs()));
+        final BinaryNode newBinaryNode = binaryNode.setLHS(discard(binaryNode.lhs()));
         // AccessSpecializer - the type of rhs, which is the remaining value of this node may have changed
         // in that case, update the node type as well
-        propagateType(binaryNode, binaryNode.rhs().getType());
-        return binaryNode;
+        propagateType(newBinaryNode, newBinaryNode.rhs().getType());
+        return newBinaryNode;
     }
 
     @Override
@@ -344,7 +344,7 @@
 
     @Override
     public Node leaveSHR(final BinaryNode binaryNode) {
-        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isLong();
+        assert binaryNode.getSymbol() != null && binaryNode.getSymbol().getSymbolType().isLong() : "long coercion expected: " + binaryNode.getSymbol();
         return leaveBinary(binaryNode, Type.INT, Type.INT);
     }
 
@@ -354,13 +354,20 @@
     }
 
     @Override
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
+        lexicalContext.push(block);
         updateSymbols(block);
         return block;
     }
 
     @Override
-    public Node leave(final CatchNode catchNode) {
+    public Node leaveBlock(Block block) {
+        lexicalContext.pop(block);
+        return super.leaveBlock(block);
+    }
+
+    @Override
+    public Node leaveCatchNode(final CatchNode catchNode) {
         final Node exceptionCondition = catchNode.getExceptionCondition();
         if (exceptionCondition != null) {
             catchNode.setExceptionCondition(convert(exceptionCondition, Type.BOOLEAN));
@@ -369,23 +376,23 @@
     }
 
     @Override
-    public Node enter(final DoWhileNode doWhileNode) {
-        return enter((WhileNode)doWhileNode);
+    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
+        return enterWhileNode(doWhileNode);
     }
 
     @Override
-    public Node leave(final DoWhileNode doWhileNode) {
-        return leave((WhileNode)doWhileNode);
+    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
+        return leaveWhileNode(doWhileNode);
     }
 
     @Override
-    public Node leave(final ExecuteNode executeNode) {
+    public Node leaveExecuteNode(final ExecuteNode executeNode) {
         executeNode.setExpression(discard(executeNode.getExpression()));
         return executeNode;
     }
 
     @Override
-    public Node leave(final ForNode forNode) {
+    public Node leaveForNode(final ForNode forNode) {
         final Node init   = forNode.getInit();
         final Node test   = forNode.getTest();
         final Node modify = forNode.getModify();
@@ -413,11 +420,12 @@
     }
 
     @Override
-    public Node enter(final FunctionNode functionNode) {
+    public Node enterFunctionNode(final FunctionNode functionNode) {
         if (functionNode.isLazy()) {
             return null;
         }
 
+        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.
@@ -432,18 +440,26 @@
         }
 
         updateSymbols(functionNode);
+        functionNode.setState(CompilationState.FINALIZED);
+
         return functionNode;
     }
 
     @Override
-    public Node leave(final IfNode ifNode) {
+    public Node leaveFunctionNode(FunctionNode functionNode) {
+        lexicalContext.pop(functionNode);
+        return super.leaveFunctionNode(functionNode);
+    }
+
+    @Override
+    public Node leaveIfNode(final IfNode ifNode) {
         ifNode.setTest(convert(ifNode.getTest(), Type.BOOLEAN));
         return ifNode;
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enter(final LiteralNode literalNode) {
+    public Node enterLiteralNode(final LiteralNode literalNode) {
         if (literalNode instanceof ArrayLiteralNode) {
             final ArrayLiteralNode arrayLiteralNode = (ArrayLiteralNode)literalNode;
             final Node[]           array            = arrayLiteralNode.getValue();
@@ -461,7 +477,7 @@
     }
 
     @Override
-    public Node leave(final ReturnNode returnNode) {
+    public Node leaveReturnNode(final ReturnNode returnNode) {
         final Node expr = returnNode.getExpression();
         if (expr != null) {
             returnNode.setExpression(convert(expr, getCurrentFunctionNode().getReturnType()));
@@ -470,7 +486,7 @@
     }
 
     @Override
-    public Node leave(final RuntimeNode runtimeNode) {
+    public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
         final List<Node> args = runtimeNode.getArgs();
         for (final Node arg : args) {
             assert !arg.getType().isUnknown();
@@ -479,7 +495,7 @@
     }
 
     @Override
-    public Node leave(final SwitchNode switchNode) {
+    public Node leaveSwitchNode(final SwitchNode switchNode) {
         final Node           expression  = switchNode.getExpression();
         final List<CaseNode> cases       = switchNode.getCases();
         final boolean        allInteger  = switchNode.getTag().getSymbolType().isInteger();
@@ -498,34 +514,34 @@
     }
 
     @Override
-    public Node leave(final TernaryNode ternaryNode) {
-        ternaryNode.setLHS(convert(ternaryNode.lhs(), Type.BOOLEAN));
-        return ternaryNode;
+    public Node leaveTernaryNode(final TernaryNode ternaryNode) {
+        return ternaryNode.setLHS(convert(ternaryNode.lhs(), Type.BOOLEAN));
     }
 
     @Override
-    public Node leave(final ThrowNode throwNode) {
+    public Node leaveThrowNode(final ThrowNode throwNode) {
         throwNode.setExpression(convert(throwNode.getExpression(), Type.OBJECT));
         return throwNode;
     }
 
     @Override
-    public Node leave(final VarNode varNode) {
-
+    public Node leaveVarNode(final VarNode varNode) {
         final Node rhs = varNode.getInit();
         if (rhs != null) {
-            Type destType = specialize(varNode);
+            final SpecializedNode specialized = specialize(varNode);
+            final VarNode specVarNode = (VarNode)specialized.node;
+            Type destType = specialized.type;
             if (destType == null) {
-                destType = varNode.getType();
+                destType = specVarNode.getType();
             }
-            assert varNode.hasType() : varNode + " doesn't have a type";
-            varNode.setInit(convert(rhs, destType));
+            assert specVarNode.hasType() : specVarNode + " doesn't have a type";
+            return specVarNode.setInit(convert(rhs, destType));
         }
         return varNode;
     }
 
     @Override
-    public Node leave(final WhileNode whileNode) {
+    public Node leaveWhileNode(final WhileNode whileNode) {
         final Node test = whileNode.getTest();
         if (test != null) {
             whileNode.setTest(convert(test, Type.BOOLEAN));
@@ -534,7 +550,7 @@
     }
 
     @Override
-    public Node leave(final WithNode withNode) {
+    public Node leaveWithNode(final WithNode withNode) {
         withNode.setExpression(convert(withNode.getExpression(), Type.OBJECT));
         return withNode;
     }
@@ -553,14 +569,14 @@
      * that scope and slot information is correct for every symbol
      * @param block block for which to to finalize type info.
      */
-    private static void updateSymbols(final Block block) {
+    private void updateSymbols(final Block block) {
         if (!block.needsScope()) {
             return; // nothing to do
         }
 
-        assert !(block instanceof FunctionNode) || block.getFunction() == block;
+        final FunctionNode functionNode = lexicalContext.getFunction(block);
+        assert !(block instanceof FunctionNode) || functionNode == block;
 
-        final FunctionNode functionNode   = block.getFunction();
         final List<Symbol> symbols        = block.getFrame().getSymbols();
         final boolean      allVarsInScope = functionNode.allVarsInScope();
         final boolean      isVarArg       = functionNode.isVarArg();
@@ -629,10 +645,7 @@
             break;
         }
 
-        binaryNode.setLHS(convert(lhs, widest));
-        binaryNode.setRHS(convert(rhs, widest));
-
-        return binaryNode;
+        return binaryNode.setLHS(convert(lhs, widest)).setRHS(convert(rhs, widest));
     }
 
     /**
@@ -654,9 +667,7 @@
     }
 
     private Node leaveBinary(final BinaryNode binaryNode, final Type lhsType, final Type rhsType) {
-        binaryNode.setLHS(convert(binaryNode.lhs(), lhsType));
-        binaryNode.setRHS(convert(binaryNode.rhs(), rhsType));
-        return binaryNode;
+        return binaryNode.setLHS(convert(binaryNode.lhs(), lhsType)).setRHS(convert(binaryNode.rhs(), rhsType));
     }
 
     /**
@@ -677,7 +688,7 @@
             }
 
             @Override
-            public Node enter(final IdentNode identNode) {
+            public Node enterIdentNode(final IdentNode identNode) {
                 if (!exclude.contains(identNode)) {
                     setCanBePrimitive(identNode.getSymbol());
                 }
@@ -685,26 +696,36 @@
             }
 
             @Override
-            public Node enter(final AccessNode accessNode) {
+            public Node enterAccessNode(final AccessNode accessNode) {
                 setCanBePrimitive(accessNode.getProperty().getSymbol());
                 return null;
             }
 
             @Override
-            public Node enter(final IndexNode indexNode) {
+            public Node 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;
             }
         });
     }
 
-    private static Type specialize(final Assignment<?> assignment) {
+    private static class SpecializedNode {
+        final Node node;
+        final Type type;
+
+        SpecializedNode(Node node, Type type) {
+            this.node = node;
+            this.type = type;
+        }
+    }
+
+    private static <T extends Node> SpecializedNode specialize(final Assignment<T> assignment) {
         final Node node = ((Node)assignment);
-        final Node lhs = assignment.getAssignmentDest();
+        final T lhs = assignment.getAssignmentDest();
         final Node rhs = assignment.getAssignmentSource();
 
         if (!canHaveCallSiteType(lhs)) {
-            return null;
+            return new SpecializedNode(node, null);
         }
 
         final Type to;
@@ -716,13 +737,13 @@
 
         if (!isSupportedCallSiteType(to)) {
             //meaningless to specialize to boolean or object
-            return null;
+            return new SpecializedNode(node, null);
         }
 
-        setTypeOverride(lhs, to);
-        propagateType(node, to);
+        final Node newNode = assignment.setAssignmentDest(setTypeOverride(lhs, to));
+        propagateType(newNode, to);
 
-        return to;
+        return new SpecializedNode(newNode, to);
     }
 
 
@@ -734,7 +755,7 @@
      * @return true if node can have a callsite type
      */
     private static boolean canHaveCallSiteType(final Node node) {
-        return node instanceof TypeOverride && ((TypeOverride)node).canHaveCallSiteType();
+        return node instanceof TypeOverride && ((TypeOverride<?>)node).canHaveCallSiteType();
     }
 
     /**
@@ -760,7 +781,8 @@
      * @param node    node for which to change type
      * @param to      new type
      */
-    private static void setTypeOverride(final Node node, final Type to) {
+    @SuppressWarnings("unchecked")
+    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);
@@ -769,7 +791,7 @@
             }
         }
         LOG.info("Type override for lhs in '" + node + "' => " + to);
-        ((TypeOverride)node).setType(to);
+        return ((TypeOverride<T>)node).setType(to);
     }
 
     /**
@@ -814,8 +836,8 @@
             }
         } else {
             if (canHaveCallSiteType(node) && isSupportedCallSiteType(to)) {
-                setTypeOverride(node, to);
-                return resultNode;
+                assert node instanceof TypeOverride;
+                return setTypeOverride(node, to);
             }
             resultNode = new UnaryNode(node.getSource(), Token.recast(node.getToken(), TokenType.CONVERT), node);
         }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java b/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
index 4ea53a0..fbc6264 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FoldConstants.java
@@ -30,6 +30,8 @@
 import jdk.nashorn.internal.ir.Block;
 import jdk.nashorn.internal.ir.EmptyNode;
 import jdk.nashorn.internal.ir.ExecuteNode;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.Node;
@@ -52,7 +54,7 @@
     }
 
     @Override
-    public Node leave(final UnaryNode unaryNode) {
+    public Node leaveUnaryNode(final UnaryNode unaryNode) {
         final LiteralNode<?> literalNode = new UnaryNodeConstantEvaluator(unaryNode).eval();
         if (literalNode != null) {
             LOG.info("Unary constant folded " + unaryNode + " to " + literalNode);
@@ -62,7 +64,7 @@
     }
 
     @Override
-    public Node leave(final BinaryNode binaryNode) {
+    public Node leaveBinaryNode(final BinaryNode binaryNode) {
         final LiteralNode<?> literalNode = new BinaryNodeConstantEvaluator(binaryNode).eval();
         if (literalNode != null) {
             LOG.info("Binary constant folded " + binaryNode + " to " + literalNode);
@@ -72,7 +74,21 @@
     }
 
     @Override
-    public Node leave(final IfNode ifNode) {
+    public Node enterFunctionNode(final FunctionNode functionNode) {
+        if (functionNode.isLazy()) {
+            return null;
+        }
+        return functionNode;
+    }
+
+    @Override
+    public Node leaveFunctionNode(final FunctionNode functionNode) {
+        functionNode.setState(CompilationState.CONSTANT_FOLDED);
+        return functionNode;
+    }
+
+    @Override
+    public Node leaveIfNode(final IfNode ifNode) {
         final Node test = ifNode.getTest();
         if (test instanceof LiteralNode) {
             final Block shortCut = ((LiteralNode<?>)test).isTrue() ? ifNode.getPass() : ifNode.getFail();
@@ -85,7 +101,7 @@
     }
 
     @Override
-    public Node leave(final TernaryNode ternaryNode) {
+    public Node leaveTernaryNode(final TernaryNode ternaryNode) {
         final Node test = ternaryNode.lhs();
         if (test instanceof LiteralNode) {
             return ((LiteralNode<?>)test).isTrue() ? ternaryNode.rhs() : ternaryNode.third();
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
index ded12b0..057d2d4 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/FunctionSignature.java
@@ -146,7 +146,7 @@
 
     /**
      * Create a function signature given a function node, using as much
-     * type information for parameters and return types that is availabe
+     * type information for parameters and return types that is available
      *
      * @param functionNode the function node
      */
@@ -155,7 +155,7 @@
             true,
             functionNode.needsCallee(),
             functionNode.getReturnType(),
-            (functionNode.isVarArg() && !functionNode.isScript()) ?
+            (functionNode.isVarArg() && !functionNode.isProgram()) ?
                 null :
                 functionNode.getParameters());
     }
@@ -202,6 +202,14 @@
         return methodType;
     }
 
+    /**
+     * Return the return type for this function signature
+     * @return the return type
+     */
+    public Type getReturnType() {
+        return returnType;
+    }
+
     private static Type[] objectArgs(final int nArgs) {
         final Type[] array = new Type[nArgs];
         for (int i = 0; i < nArgs; i++) {
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
index 7a83469..715ddc6 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Lower.java
@@ -37,8 +37,8 @@
 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.AccessNode;
 import jdk.nashorn.internal.ir.BaseNode;
 import jdk.nashorn.internal.ir.BinaryNode;
 import jdk.nashorn.internal.ir.Block;
@@ -52,11 +52,12 @@
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
-import jdk.nashorn.internal.ir.IndexNode;
 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.Node;
@@ -102,6 +103,8 @@
 
     private List<Node> statements;
 
+    private LexicalContext lexicalContext = new LexicalContext();
+
     /**
      * Constructor.
      *
@@ -113,14 +116,15 @@
     }
 
     @Override
-    public Node enter(final Block block) {
+    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(this);
+                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
@@ -132,7 +136,7 @@
                  */
                 if (lastStatement != null && lastStatement.isTerminal()) {
                     copyTerminal(block, lastStatement);
-                    break;
+                    visitor = new DeadCodeVarDeclarationVisitor();
                 }
             }
             block.setStatements(statements);
@@ -140,18 +144,19 @@
         } finally {
             this.statements = savedStatements;
             this.lastStatement = savedLastStatement;
+            lexicalContext.pop(block);
         }
 
         return null;
     }
 
     @Override
-    public Node enter(final BreakNode breakNode) {
+    public Node enterBreakNode(final BreakNode breakNode) {
         return enterBreakOrContinue(breakNode);
     }
 
     @Override
-    public Node enter(final CallNode callNode) {
+    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
@@ -159,44 +164,44 @@
     }
 
     @Override
-    public Node leave(final CaseNode caseNode) {
+    public Node leaveCaseNode(final CaseNode caseNode) {
         caseNode.copyTerminalFlags(caseNode.getBody());
         return caseNode;
     }
 
     @Override
-    public Node leave(final CatchNode catchNode) {
+    public Node leaveCatchNode(final CatchNode catchNode) {
         catchNode.copyTerminalFlags(catchNode.getBody());
         addStatement(catchNode);
         return catchNode;
     }
 
     @Override
-    public Node enter(final ContinueNode continueNode) {
+    public Node enterContinueNode(final ContinueNode continueNode) {
         return enterBreakOrContinue(continueNode);
     }
 
     @Override
-    public Node enter(final DoWhileNode doWhileNode) {
-        return enter((WhileNode)doWhileNode);
+    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
+        return enterWhileNode(doWhileNode);
     }
 
     @Override
-    public Node leave(final DoWhileNode doWhileNode) {
-        return leave((WhileNode)doWhileNode);
+    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
+        return leaveWhileNode(doWhileNode);
     }
 
     @Override
-    public Node enter(final EmptyNode emptyNode) {
+    public Node enterEmptyNode(final EmptyNode emptyNode) {
         return null;
     }
 
     @Override
-    public Node leave(final ExecuteNode executeNode) {
+    public Node leaveExecuteNode(final ExecuteNode executeNode) {
         final Node expr = executeNode.getExpression();
 
-        if (getCurrentFunctionNode().isScript()) {
-            if (!(expr instanceof Block)) {
+        if (getCurrentFunctionNode().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(),
@@ -212,13 +217,13 @@
     }
 
     @Override
-    public Node enter(final ForNode forNode) {
+    public Node enterForNode(final ForNode forNode) {
         nest(forNode);
         return forNode;
     }
 
     @Override
-    public Node leave(final ForNode forNode) {
+    public Node leaveForNode(final ForNode forNode) {
         final Node  test = forNode.getTest();
         final Block body = forNode.getBody();
 
@@ -236,6 +241,7 @@
 
         if (!forNode.isForIn() && conservativeAlwaysTrue(test)) {
             forNode.setTest(null);
+            setHasGoto(forNode);
             setTerminal(forNode, !escapes);
         }
 
@@ -245,18 +251,16 @@
     }
 
     @Override
-    public Node enter(final FunctionNode functionNode) {
+    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);
 
-        Node initialEvalResult = LiteralNode.newInstance(functionNode, ScriptRuntime.UNDEFINED);
-
         nest(functionNode);
 
         /*
@@ -270,60 +274,40 @@
         statements    = new ArrayList<>();
         lastStatement = null;
 
-        // for initial eval result is the last declared function
-        for (final FunctionNode nestedFunction : functionNode.getFunctions()) {
-            final IdentNode ident = nestedFunction.getIdent();
-            if (ident != null && nestedFunction.isStatement()) {
-                initialEvalResult = new IdentNode(ident);
-            }
-        }
-
         if (functionNode.needsSelfSymbol()) {
             //function needs to start with var funcIdent = __callee_;
             statements.add(functionNode.getSelfSymbolInit().accept(this));
         }
 
+        NodeVisitor visitor = this;
         try {
-            // Every nested function needs a definition in the outer function with its name. Add these.
-            for (final FunctionNode nestedFunction : functionNode.getFunctions()) {
-                final VarNode varNode = nestedFunction.getFunctionVarNode();
-                if (varNode != null) {
-                    final LineNumberNode lineNumberNode = nestedFunction.getFunctionVarLineNumberNode();
-                    if (lineNumberNode != null) {
-                        lineNumberNode.accept(this);
-                    }
-                    varNode.accept(this);
-                    varNode.setIsFunctionVarNode();
-                }
-            }
-
-            if (functionNode.isScript()) {
-                new ExecuteNode(functionNode.getSource(), functionNode.getFirstToken(), functionNode.getFinish(), initialEvalResult).accept(this);
-            }
-
             //do the statements - this fills the block with code
+            boolean needsInitialEvalResult = functionNode.isProgram();
             for (final Node statement : functionNode.getStatements()) {
-                statement.accept(this);
+                // 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);
-                    break;
+                    assert !needsInitialEvalResult;
+                    visitor = new DeadCodeVarDeclarationVisitor();
                 }
             }
-
+            if(needsInitialEvalResult) {
+                addInitialEvalResult(functionNode);
+            }
             functionNode.setStatements(statements);
 
             if (!functionNode.isTerminal()) {
                 guaranteeReturn(functionNode);
             }
-
-            //lower all nested functions
-            for (final FunctionNode nestedFunction : functionNode.getFunctions()) {
-                nestedFunction.accept(this);
-            }
-
         } finally {
             statements    = savedStatements;
             lastStatement = savedLastStatement;
@@ -331,17 +315,67 @@
 
         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);
+    }
+
     @Override
-    public Node enter(final IfNode ifNode) {
+    public Node enterIfNode(final IfNode ifNode) {
         return nest(ifNode);
     }
 
     @Override
-    public Node leave(final IfNode ifNode) {
+    public Node leaveIfNode(final IfNode ifNode) {
         final Node pass = ifNode.getPass();
         final Node fail = ifNode.getFail();
 
@@ -356,7 +390,7 @@
     }
 
     @Override
-    public Node enter(LabelNode labelNode) {
+    public Node enterLabelNode(LabelNode labelNode) {
         final Block body = labelNode.getBody();
         body.accept(this);
         copyTerminal(labelNode, body);
@@ -365,13 +399,13 @@
     }
 
     @Override
-    public Node enter(final LineNumberNode lineNumberNode) {
+    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
         addStatement(lineNumberNode, false); // don't put it in lastStatement cache
         return null;
     }
 
     @Override
-    public Node enter(final ReturnNode returnNode) {
+    public Node enterReturnNode(final ReturnNode returnNode) {
         final TryNode tryNode = returnNode.getTryChain();
         final Node    expr    = returnNode.getExpression();
 
@@ -409,19 +443,19 @@
     }
 
     @Override
-    public Node leave(final ReturnNode returnNode) {
+    public Node leaveReturnNode(final ReturnNode returnNode) {
         addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor
         return returnNode;
     }
 
     @Override
-    public Node enter(final SwitchNode switchNode) {
+    public Node enterSwitchNode(final SwitchNode switchNode) {
         nest(switchNode);
         return switchNode;
     }
 
     @Override
-    public Node leave(final SwitchNode switchNode) {
+    public Node leaveSwitchNode(final SwitchNode switchNode) {
         unnest(switchNode);
 
         final List<CaseNode> cases       = switchNode.getCases();
@@ -442,13 +476,13 @@
     }
 
     @Override
-    public Node leave(final ThrowNode throwNode) {
+    public Node leaveThrowNode(final ThrowNode throwNode) {
         addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
         return throwNode;
     }
 
     @Override
-    public Node enter(final TryNode tryNode) {
+    public Node enterTryNode(final TryNode tryNode) {
         final Block  finallyBody = tryNode.getFinallyBody();
         final long   token       = tryNode.getToken();
         final int    finish      = tryNode.getFinish();
@@ -534,26 +568,19 @@
 
             // set outer tryNode's body to innerTryNode
             final Block outerBody;
-            outerBody = new Block(source, token, finish, tryNode.getBody().getParent(), getCurrentFunctionNode());
+            outerBody = new Block(source, token, finish);
             outerBody.setStatements(new ArrayList<Node>(Arrays.asList(innerTryNode)));
             tryNode.setBody(outerBody);
             tryNode.setCatchBlocks(null);
-
-            // now before we go on, we have to fix the block parents
-            // (we repair the block tree after the insertion so that all references are intact)
-            innerTryNode.getBody().setParent(tryNode.getBody());
-            for (final Block block : innerTryNode.getCatchBlocks()) {
-                block.setParent(tryNode.getBody());
-            }
         }
 
         // create a catch-all that inlines finally and rethrows
 
-        final Block catchBlock      = new Block(source, token, finish, getCurrentBlock(), getCurrentFunctionNode());
+        final Block catchBlock      = new Block(source, token, finish);
         //this catch block should get define symbol
 
-        final Block catchBody       = new Block(source, token, finish, catchBlock, getCurrentFunctionNode());
-        final Node  catchAllFinally = finallyBody.clone();
+        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);
@@ -580,7 +607,7 @@
     }
 
     @Override
-    public Node leave(final TryNode tryNode) {
+    public Node leaveTryNode(final TryNode tryNode) {
         final Block finallyBody   = tryNode.getFinallyBody();
 
         boolean allTerminal = tryNode.getBody().isTerminal() && (finallyBody == null || finallyBody.isTerminal());
@@ -604,18 +631,18 @@
     }
 
     @Override
-    public Node leave(final VarNode varNode) {
+    public Node leaveVarNode(final VarNode varNode) {
         addStatement(varNode);
         return varNode;
     }
 
     @Override
-    public Node enter(final WhileNode whileNode) {
+    public Node enterWhileNode(final WhileNode whileNode) {
         return nest(whileNode);
     }
 
     @Override
-    public Node leave(final WhileNode whileNode) {
+    public Node leaveWhileNode(final WhileNode whileNode) {
         final Node test = whileNode.getTest();
 
         if (test == null) {
@@ -636,7 +663,7 @@
             } else if (conservativeAlwaysTrue(test)) {
                 node = new ForNode(whileNode.getSource(), whileNode.getToken(), whileNode.getFinish());
                 ((ForNode)node).setBody(body);
-                ((ForNode)node).accept(this);
+                node.accept(this);
                 setTerminal(node, !escapes);
             }
         }
@@ -649,7 +676,7 @@
     }
 
     @Override
-    public Node leave(final WithNode withNode) {
+    public Node leaveWithNode(final WithNode withNode) {
         if (withNode.getBody().isTerminal()) {
             setTerminal(withNode,  true);
         }
@@ -678,28 +705,10 @@
      */
     private static Node markerFunction(final Node function) {
         if (function instanceof IdentNode) {
-            return new IdentNode((IdentNode)function) {
-                @Override
-                public boolean isFunction() {
-                    return true;
-                }
-            };
-        } else if (function instanceof AccessNode) {
-            return new AccessNode((AccessNode)function) {
-                @Override
-                public boolean isFunction() {
-                    return true;
-                }
-            };
-        } else if (function instanceof IndexNode) {
-            return new IndexNode((IndexNode)function) {
-                @Override
-                public boolean isFunction() {
-                    return true;
-                }
-            };
+            return ((IdentNode)function).setIsFunction();
+        } else if (function instanceof BaseNode) {
+            return ((BaseNode)function).setIsFunction();
         }
-
         return function;
     }
 
@@ -742,7 +751,7 @@
             if (args.size() >= 1 && EVAL.tag().equals(callee.getName())) {
                 final CallNode.EvalArgs evalArgs =
                     new CallNode.EvalArgs(
-                        args.get(0).clone().accept(this), //clone as we use this for the "is eval case". original evaluated separately for "is not eval case"
+                        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(),
                         evalLocation(callee),
                         getCurrentFunctionNode().isStrictMode());
@@ -769,13 +778,13 @@
 
         loopBody.accept(new NodeVisitor() {
             @Override
-            public Node leave(final BreakNode node) {
+            public Node leaveBreakNode(final BreakNode node) {
                 escapes.add(node);
                 return node;
             }
 
             @Override
-            public Node leave(final ContinueNode node) {
+            public Node leaveContinueNode(final ContinueNode node) {
                 // all inner loops have been popped.
                 if (nesting.contains(node.getTargetNode())) {
                     escapes.add(node);
@@ -790,7 +799,7 @@
     private void guaranteeReturn(final FunctionNode functionNode) {
         Node resultNode;
 
-        if (functionNode.isScript()) {
+        if (functionNode.isProgram()) {
             resultNode = functionNode.getResultNode(); // the eval result, symbol assigned in Attr
         } else {
             if (lastStatement != null && lastStatement.isTerminal() || lastStatement instanceof ReturnNode) {
@@ -855,18 +864,15 @@
      * @return true if try block is inside the target, false otherwise.
      */
     private boolean isNestedTry(final TryNode tryNode, final Block target) {
-        for (Block current = getCurrentBlock(); current != target; current = current.getParent()) {
-            if (tryNode.getBody() == current) {
+        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;
             }
-
-            for (final Block catchBlock : tryNode.getCatchBlocks()) {
-                if (catchBlock == current) {
-                    return true;
-                }
-            }
         }
-
         return false;
     }
 
@@ -895,7 +901,7 @@
                 continue;
             }
 
-            finallyBody = (Block)finallyBody.clone();
+            finallyBody = (Block)finallyBody.copy();
             final boolean hasTerminalFlags = finallyBody.hasTerminalFlags();
 
             new ExecuteNode(finallyBody.getSource(), finallyBody.getToken(), finallyBody.getFinish(), finallyBody).accept(this);
@@ -970,6 +976,3 @@
     }
 
 }
-
-
-
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java b/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java
index 4cd0f52..ae40ed3 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/MethodEmitter.java
@@ -651,11 +651,10 @@
 
     /**
      * Load the constants array
-     * @param unitClassName name of the compile unit from which to load constants
      * @return this method emitter
      */
-    MethodEmitter loadConstants(final String unitClassName) {
-        getStatic(unitClassName, CONSTANTS.tag(), CONSTANTS.descriptor());
+    MethodEmitter loadConstants() {
+        getStatic(classEmitter.getUnitClassName(), CONSTANTS.tag(), CONSTANTS.descriptor());
         assert peekType().isArray() : peekType();
         return this;
     }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
index b92f238..f9a84f9 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/Splitter.java
@@ -39,7 +39,9 @@
 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;
@@ -48,6 +50,7 @@
 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;
@@ -69,6 +72,8 @@
     /** 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);
 
@@ -92,15 +97,16 @@
      */
     void split() {
         if (functionNode.isLazy()) {
-            LOG.fine("Postponing split of '" + functionNode.getName() + "' as it's lazy");
+            LOG.finest("Postponing split of '" + functionNode.getName() + "' as it's lazy");
             return;
         }
-        LOG.fine("Initiating split of '" + functionNode.getName() + "'");
+
+        LOG.finest("Initiating split of '" + functionNode.getName() + "'");
 
         long weight = WeighNodes.weigh(functionNode);
 
         if (weight >= SPLIT_THRESHOLD) {
-            LOG.fine("Splitting '" + functionNode.getName() + "' as its weight " + weight + " exceeds split threshold " + SPLIT_THRESHOLD);
+            LOG.finest("Splitting '" + functionNode.getName() + "' as its weight " + weight + " exceeds split threshold " + SPLIT_THRESHOLD);
 
             functionNode.accept(this);
 
@@ -110,7 +116,7 @@
             }
 
             if (weight >= SPLIT_THRESHOLD) {
-                weight = splitBlock(functionNode);
+                weight = splitBlock(functionNode, functionNode);
             }
 
             if (functionNode.isSplit()) {
@@ -130,9 +136,22 @@
         }
 
         // Recursively split nested functions
-        for (final FunctionNode function : functionNode.getFunctions()) {
-            new Splitter(compiler, function, outermostCompileUnit).split();
-        }
+        functionNode.accept(new NodeOperatorVisitor() {
+            @Override
+            public Node enterFunctionNode(FunctionNode function) {
+                if(function == functionNode) {
+                    // Don't process outermost function (it was already processed) but descend into it to find nested
+                    // functions.
+                    return function;
+                }
+                // 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;
+            }
+        });
+
+        functionNode.setState(CompilationState.SPLIT);
     }
 
     /**
@@ -151,7 +170,7 @@
      *
      * @return new weight for the resulting block.
      */
-    private long splitBlock(final Block block) {
+    private long splitBlock(final Block block, final FunctionNode function) {
         functionNode.setIsSplit();
 
         final List<Node> splits = new ArrayList<>();
@@ -163,7 +182,7 @@
 
             if (statementsWeight + weight >= SPLIT_THRESHOLD || statement.isTerminal()) {
                 if (!statements.isEmpty()) {
-                    splits.add(createBlockSplitNode(block, statements, statementsWeight));
+                    splits.add(createBlockSplitNode(block, function, statements, statementsWeight));
                     statements = new ArrayList<>();
                     statementsWeight = 0;
                 }
@@ -179,7 +198,7 @@
         }
 
         if (!statements.isEmpty()) {
-            splits.add(createBlockSplitNode(block, statements, statementsWeight));
+            splits.add(createBlockSplitNode(block, function, statements, statementsWeight));
         }
 
         block.setStatements(splits);
@@ -195,13 +214,13 @@
      *
      * @return New split node.
      */
-    private SplitNode createBlockSplitNode(final Block parent, final List<Node> statements, final long weight) {
+    private SplitNode createBlockSplitNode(final Block parent, final FunctionNode function, final List<Node> statements, final long weight) {
         final Source source = parent.getSource();
         final long   token  = parent.getToken();
         final int    finish = parent.getFinish();
-        final String name   = parent.getFunction().uniqueName(SPLIT_PREFIX.tag());
+        final String name   = function.uniqueName(SPLIT_PREFIX.tag());
 
-        final Block newBlock = new Block(source, token, finish, parent, functionNode);
+        final Block newBlock = new Block(source, token, finish);
         newBlock.setFrame(new Frame(parent.getFrame()));
         newBlock.setStatements(statements);
 
@@ -213,15 +232,17 @@
     }
 
     @Override
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
         if (block.isCatchBlock()) {
             return null;
         }
+        lexicalContext.push(block);
 
         final long weight = WeighNodes.weigh(block, weightCache);
 
         if (weight < SPLIT_THRESHOLD) {
             weightCache.put(block, weight);
+            lexicalContext.pop(block);
             return null;
         }
 
@@ -229,23 +250,24 @@
     }
 
     @Override
-    public Node leave(final Block block) {
+    public Node leaveBlock(final Block block) {
         assert !block.isCatchBlock();
 
         // 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);
+            weight = splitBlock(block, lexicalContext.getFunction(block));
         }
         weightCache.put(block, weight);
 
+        lexicalContext.pop(block);
         return block;
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node leave(final LiteralNode literal) {
+    public Node leaveLiteralNode(final LiteralNode literal) {
         long weight = WeighNodes.weigh(literal);
 
         if (weight < SPLIT_THRESHOLD) {
@@ -290,17 +312,12 @@
     }
 
     @Override
-    public Node enter(final FunctionNode node) {
-        if (node.isLazy()) {
-            return null;
+    public Node enterFunctionNode(final FunctionNode node) {
+        if(node == functionNode && !node.isLazy()) {
+            lexicalContext.push(node);
+            node.visitStatements(this);
+            lexicalContext.pop(node);
         }
-
-        final List<Node> statements = node.getStatements();
-
-        for (final Node statement : statements) {
-            statement.accept(this);
-        }
-
         return null;
     }
 
@@ -317,38 +334,38 @@
         }
 
         @Override
-        public Node enter(final LabelNode labelNode) {
+        public Node enterLabelNode(final LabelNode labelNode) {
             registerJumpTarget(labelNode.getBreakNode());
             registerJumpTarget(labelNode.getContinueNode());
             return labelNode;
         }
 
         @Override
-        public Node enter(final WhileNode whileNode) {
+        public Node enterWhileNode(final WhileNode whileNode) {
             registerJumpTarget(whileNode);
             return whileNode;
         }
 
         @Override
-        public Node enter(final DoWhileNode doWhileNode) {
+        public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
             registerJumpTarget(doWhileNode);
             return doWhileNode;
         }
 
         @Override
-        public Node enter(final ForNode forNode) {
+        public Node enterForNode(final ForNode forNode) {
             registerJumpTarget(forNode);
             return forNode;
         }
 
         @Override
-        public Node enter(final SwitchNode switchNode) {
+        public Node enterSwitchNode(final SwitchNode switchNode) {
             registerJumpTarget(switchNode);
             return switchNode;
         }
 
         @Override
-        public Node enter(final ReturnNode returnNode) {
+        public Node enterReturnNode(final ReturnNode returnNode) {
             for (final SplitNode split : splitStack) {
                 split.setHasReturn(true);
             }
@@ -356,25 +373,25 @@
         }
 
         @Override
-        public Node enter(final ContinueNode continueNode) {
+        public Node enterContinueNode(final ContinueNode continueNode) {
             searchJumpTarget(continueNode.getTargetNode(), continueNode.getTargetLabel());
             return continueNode;
         }
 
         @Override
-        public Node enter(final BreakNode breakNode) {
+        public Node enterBreakNode(final BreakNode breakNode) {
             searchJumpTarget(breakNode.getTargetNode(), breakNode.getTargetLabel());
             return breakNode;
         }
 
         @Override
-        public Node enter(final SplitNode splitNode) {
+        public Node enterSplitNode(final SplitNode splitNode) {
             splitStack.addFirst(splitNode);
             return splitNode;
         }
 
         @Override
-        public Node leave(final SplitNode splitNode) {
+        public Node leaveSplitNode(final SplitNode splitNode) {
             assert splitNode == splitStack.peekFirst();
             splitStack.removeFirst();
             return splitNode;
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
index 9f2e8be..18bd955 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/WeighNodes.java
@@ -47,7 +47,6 @@
 import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode.ArrayUnit;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.PropertyNode;
-import jdk.nashorn.internal.ir.ReferenceNode;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.SplitNode;
@@ -80,7 +79,7 @@
     private static final long LITERAL_WEIGHT   = 10;
     private static final long LOOP_WEIGHT      = 4;
     private static final long NEW_WEIGHT       = 6;
-    private static final long REFERENCE_WEIGHT = 20;
+    private static final long FUNC_EXPR_WEIGHT = 20;
     private static final long RETURN_WEIGHT    = 2;
     private static final long SPLIT_WEIGHT     = 40;
     private static final long SWITCH_WEIGHT    = 8;
@@ -94,36 +93,37 @@
     /** Optional cache for weight of block nodes. */
     private final Map<Node, Long> weightCache;
 
-    /*
+    private final FunctionNode topFunction;
+
+    /**
      * Constructor
      *
      * @param weightCache cache of already calculated block weights
      */
-    private WeighNodes(final Map<Node, Long> weightCache) {
+    private WeighNodes(FunctionNode topFunction, final Map<Node, Long> weightCache) {
         super(null, null);
+        this.topFunction = topFunction;
         this.weightCache = weightCache;
     }
 
     static long weigh(final Node node) {
-        final WeighNodes weighNodes = new WeighNodes(null);
-        node.accept(weighNodes);
-        return weighNodes.weight;
+        return weigh(node, null);
     }
 
     static long weigh(final Node node, final Map<Node, Long> weightCache) {
-        final WeighNodes weighNodes = new WeighNodes(weightCache);
+        final WeighNodes weighNodes = new WeighNodes(node instanceof FunctionNode ? (FunctionNode)node : null, weightCache);
         node.accept(weighNodes);
         return weighNodes.weight;
     }
 
     @Override
-    public Node leave(final AccessNode accessNode) {
+    public Node leaveAccessNode(final AccessNode accessNode) {
         weight += ACCESS_WEIGHT;
         return accessNode;
     }
 
     @Override
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
         if (weightCache != null && weightCache.containsKey(block)) {
             weight += weightCache.get(block);
             return null;
@@ -133,78 +133,79 @@
     }
 
     @Override
-    public Node leave(final BreakNode breakNode) {
+    public Node leaveBreakNode(final BreakNode breakNode) {
         weight += BREAK_WEIGHT;
         return breakNode;
     }
 
     @Override
-    public Node leave(final CallNode callNode) {
+    public Node leaveCallNode(final CallNode callNode) {
         weight += CALL_WEIGHT;
         return callNode;
     }
 
     @Override
-    public Node leave(final CatchNode catchNode) {
+    public Node leaveCatchNode(final CatchNode catchNode) {
         weight += CATCH_WEIGHT;
         return catchNode;
     }
 
     @Override
-    public Node leave(final ContinueNode continueNode) {
+    public Node leaveContinueNode(final ContinueNode continueNode) {
         weight += CONTINUE_WEIGHT;
         return continueNode;
     }
 
     @Override
-    public Node leave(final DoWhileNode doWhileNode) {
+    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
         weight += LOOP_WEIGHT;
         return doWhileNode;
     }
 
     @Override
-    public Node leave(final ExecuteNode executeNode) {
+    public Node leaveExecuteNode(final ExecuteNode executeNode) {
         return executeNode;
     }
 
     @Override
-    public Node leave(final ForNode forNode) {
+    public Node leaveForNode(final ForNode forNode) {
         weight += LOOP_WEIGHT;
         return forNode;
     }
 
     @Override
-    public Node enter(final FunctionNode functionNode) {
-        final List<Node> statements = functionNode.getStatements();
-
-        for (final Node statement : statements) {
-            statement.accept(this);
+    public Node 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 null;
     }
 
     @Override
-    public Node leave(final IdentNode identNode) {
+    public Node leaveIdentNode(final IdentNode identNode) {
         weight += ACCESS_WEIGHT + identNode.getName().length() * 2;
         return identNode;
     }
 
     @Override
-    public Node leave(final IfNode ifNode) {
+    public Node leaveIfNode(final IfNode ifNode) {
         weight += IF_WEIGHT;
         return ifNode;
     }
 
     @Override
-    public Node leave(final IndexNode indexNode) {
+    public Node leaveIndexNode(final IndexNode indexNode) {
         weight += ACCESS_WEIGHT;
         return indexNode;
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enter(final LiteralNode literalNode) {
+    public Node enterLiteralNode(final LiteralNode literalNode) {
         weight += LITERAL_WEIGHT;
 
         if (literalNode instanceof ArrayLiteralNode) {
@@ -230,67 +231,61 @@
     }
 
     @Override
-    public Node leave(final PropertyNode propertyNode) {
+    public Node leavePropertyNode(final PropertyNode propertyNode) {
         weight += LITERAL_WEIGHT;
         return propertyNode;
     }
 
     @Override
-    public Node leave(final ReferenceNode referenceNode) {
-        weight += REFERENCE_WEIGHT;
-        return referenceNode;
-    }
-
-    @Override
-    public Node leave(final ReturnNode returnNode) {
+    public Node leaveReturnNode(final ReturnNode returnNode) {
         weight += RETURN_WEIGHT;
         return returnNode;
     }
 
     @Override
-    public Node leave(final RuntimeNode runtimeNode) {
+    public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
         weight += CALL_WEIGHT;
         return runtimeNode;
     }
 
     @Override
-    public Node enter(final SplitNode splitNode) {
+    public Node enterSplitNode(final SplitNode splitNode) {
         weight += SPLIT_WEIGHT;
         return null;
     }
 
     @Override
-    public Node leave(final SwitchNode switchNode) {
+    public Node leaveSwitchNode(final SwitchNode switchNode) {
         weight += SWITCH_WEIGHT;
         return switchNode;
     }
 
     @Override
-    public Node leave(final ThrowNode throwNode) {
+    public Node leaveThrowNode(final ThrowNode throwNode) {
         weight += THROW_WEIGHT;
         return throwNode;
     }
 
     @Override
-    public Node leave(final TryNode tryNode) {
+    public Node leaveTryNode(final TryNode tryNode) {
         weight += THROW_WEIGHT;
         return tryNode;
     }
 
     @Override
-    public Node leave(final VarNode varNode) {
+    public Node leaveVarNode(final VarNode varNode) {
         weight += VAR_WEIGHT;
         return varNode;
     }
 
     @Override
-    public Node leave(final WhileNode whileNode) {
+    public Node leaveWhileNode(final WhileNode whileNode) {
         weight += LOOP_WEIGHT;
         return whileNode;
     }
 
     @Override
-    public Node leave(final WithNode withNode) {
+    public Node leaveWithNode(final WithNode withNode) {
         weight += WITH_WEIGHT;
         return withNode;
     }
diff --git a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java
index d36dd20..7755883 100644
--- a/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java
+++ b/nashorn/src/jdk/nashorn/internal/codegen/types/Type.java
@@ -647,21 +647,20 @@
     }
 
     private static void swap(final MethodVisitor method, final Type above, final Type below) {
-        final MethodVisitor mv = method;
         if (below.isCategory2()) {
             if (above.isCategory2()) {
-                mv.visitInsn(DUP2_X2);
-                mv.visitInsn(POP2);
+                method.visitInsn(DUP2_X2);
+                method.visitInsn(POP2);
             } else {
-                mv.visitInsn(DUP_X2);
-                mv.visitInsn(POP);
+                method.visitInsn(DUP_X2);
+                method.visitInsn(POP);
             }
         } else {
             if (above.isCategory2()) {
-                mv.visitInsn(DUP2_X1);
-                mv.visitInsn(POP2);
+                method.visitInsn(DUP2_X1);
+                method.visitInsn(POP2);
             } else {
-                mv.visitInsn(SWAP);
+                method.visitInsn(SWAP);
             }
         }
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java b/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java
index 481e385..b7b7668 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/AccessNode.java
@@ -36,7 +36,7 @@
  * IR representation of a property access (period operator.)
  *
  */
-public class AccessNode extends BaseNode implements TypeOverride {
+public class AccessNode extends BaseNode implements TypeOverride<AccessNode> {
     /** Property ident. */
     private IdentNode property;
 
@@ -56,9 +56,7 @@
         super(source, token, finish, base);
 
         this.start    = base.getStart();
-        this.property = property;
-
-        this.property.setIsPropertyName();
+        this.property = property.setIsPropertyName();
     }
 
     /**
@@ -106,10 +104,10 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterAccessNode(this) != null) {
             base = base.accept(visitor);
             property = (IdentNode)property.accept(visitor);
-            return visitor.leave(this);
+            return visitor.leaveAccessNode(this);
         }
 
         return this;
@@ -150,13 +148,14 @@
     }
 
     @Override
-    public void setType(final Type type) {
+    public AccessNode setType(final Type type) {
         if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
             ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType());
         }
-        property.setType(type);
+        property = property.setType(type);
         getSymbol().setTypeOverride(type); //always a temp so this is fine.
         hasCallSiteType = true;
+        return this;
     }
 
     @Override
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Assignment.java b/nashorn/src/jdk/nashorn/internal/ir/Assignment.java
index 8107a87..0c531bc 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Assignment.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Assignment.java
@@ -46,4 +46,11 @@
      * @return get the assignment source node
      */
     public Node getAssignmentSource();
+
+    /**
+     * Set assignment destination node.
+     * @param n the assignment destination node.
+     * @return a node equivalent to this one except for the requested change.
+     */
+    public Node setAssignmentDest(D n);
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java b/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java
index ea632d1..26a2836 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/BaseNode.java
@@ -38,6 +38,8 @@
     /** Base Node. */
     protected Node base;
 
+    private boolean function;
+
     /**
      * Constructor
      *
@@ -96,6 +98,15 @@
 
     @Override
     public boolean isFunction() {
-        return false;
+        return function;
+    }
+
+    /**
+     * 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;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java b/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java
index 26964ba..42c7a6c 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/BinaryNode.java
@@ -35,7 +35,7 @@
  */
 public class BinaryNode extends UnaryNode {
     /** Left hand side argument. */
-    protected Node lhs;
+    private Node lhs;
 
     /**
      * Constructor
@@ -140,6 +140,11 @@
     }
 
     @Override
+    public Node setAssignmentDest(Node n) {
+        return setLHS(n);
+    }
+
+    @Override
     public Node getAssignmentSource() {
         return rhs();
     }
@@ -163,10 +168,9 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            lhs = lhs.accept(visitor);
-            rhs = rhs.accept(visitor);
-            return visitor.leave(this);
+        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)));
         }
 
         return this;
@@ -229,8 +233,12 @@
     /**
      * 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 void setLHS(final Node lhs) {
-        this.lhs = lhs;
+    public BinaryNode setLHS(final Node lhs) {
+        if(this.lhs == lhs) return this;
+        final BinaryNode n = (BinaryNode)clone();
+        n.lhs = lhs;
+        return n;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Block.java b/nashorn/src/jdk/nashorn/internal/ir/Block.java
index 9449be5..6f138f8 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Block.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Block.java
@@ -25,41 +25,24 @@
 
 package jdk.nashorn.internal.ir;
 
-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;
-import static jdk.nashorn.internal.ir.Symbol.IS_PARAM;
-import static jdk.nashorn.internal.ir.Symbol.IS_SCOPE;
-import static jdk.nashorn.internal.ir.Symbol.IS_VAR;
-import static jdk.nashorn.internal.ir.Symbol.KINDMASK;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.ListIterator;
 import jdk.nashorn.internal.codegen.Frame;
 import jdk.nashorn.internal.codegen.Label;
-import jdk.nashorn.internal.ir.annotations.Ignore;
-import jdk.nashorn.internal.ir.annotations.ParentNode;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
 /**
  * IR representation for a list of statements and functions. All provides the
  * basis for script body.
- *
  */
 public class Block extends Node {
-    /** Parent context */
-    @ParentNode @Ignore
-    private Block parent;
-
-    /** Owning function. */
-    @Ignore //don't print it, it is apparent in the tree
-    protected FunctionNode function;
-
     /** List of statements */
     protected List<Node> statements;
 
@@ -84,14 +67,10 @@
      * @param source   source code
      * @param token    token
      * @param finish   finish
-     * @param parent   reference to parent block
-     * @param function function node this block is in
      */
-    public Block(final Source source, final long token, final int finish, final Block parent, final FunctionNode function) {
+    public Block(final Source source, final long token, final int finish) {
         super(source, token, finish);
 
-        this.parent     = parent;
-        this.function   = function;
         this.statements = new ArrayList<>();
         this.symbols    = new HashMap<>();
         this.entryLabel = new Label("block_entry");
@@ -107,8 +86,6 @@
     protected Block(final Block block, final CopyState cs) {
         super(block);
 
-        this.parent     = block.parent;
-        this.function   = block.function;
         this.statements = new ArrayList<>();
         for (final Node statement : block.getStatements()) {
             statements.add(cs.existingOrCopy(statement));
@@ -123,55 +100,7 @@
 
     @Override
     protected Node copy(final CopyState cs) {
-        return fixBlockChain(new Block(this, cs));
-    }
-
-    /**
-     * Whenever a clone that contains a hierarchy of blocks is created,
-     * this function has to be called to ensure that the parents point
-     * to the correct parent blocks or two different ASTs would not
-     * be completely separated.
-     *
-     * @return the argument
-     */
-    static Block fixBlockChain(final Block root) {
-        root.accept(new NodeVisitor() {
-            private Block        parent   = root.getParent();
-            private final FunctionNode function = root.getFunction();
-
-            @Override
-            public Node enter(final Block block) {
-                assert block.getFunction() == function;
-                block.setParent(parent);
-                parent = block;
-
-                return block;
-            }
-
-            @Override
-            public Node leave(final Block block) {
-                parent = block.getParent();
-
-                return block;
-            }
-
-            @Override
-            public Node enter(final FunctionNode functionNode) {
-                assert functionNode.getFunction() == function;
-
-                return enter((Block)functionNode);
-            }
-
-            @Override
-            public Node leave(final FunctionNode functionNode) {
-                assert functionNode.getFunction() == function;
-
-                return leave((Block)functionNode);
-            }
-
-        });
-
-        return root;
+        return new Block(this, cs);
     }
 
     /**
@@ -189,17 +118,12 @@
     }
 
     /**
-     * Prepend a statement to the statement list
+     * Prepend statements to the statement list
      *
-     * @param statement Statement node to add
+     * @param prepended statement to add
      */
-    public void prependStatement(final Node statement) {
-        if (statement != null) {
-            final List<Node> newStatements = new ArrayList<>();
-            newStatements.add(statement);
-            newStatements.addAll(statements);
-            setStatements(newStatements);
-        }
+    public void prependStatements(final List<Node> prepended) {
+        statements.addAll(0, prepended);
     }
 
     /**
@@ -212,39 +136,6 @@
     }
 
     /**
-     * Add a new function to the function list.
-     *
-     * @param functionNode Function node to add.
-     */
-    public void addFunction(final FunctionNode functionNode) {
-        assert parent != null : "Parent context missing.";
-
-        parent.addFunction(functionNode);
-    }
-
-    /**
-     * Add a list of functions to the function list.
-     *
-     * @param functionNodes Function nodes to add.
-     */
-    public void addFunctions(final List<FunctionNode> functionNodes) {
-        assert parent != null : "Parent context missing.";
-
-        parent.addFunctions(functionNodes);
-    }
-
-    /**
-     * Set the function list to a new one
-     *
-     * @param functionNodes the nodes to set
-     */
-    public void setFunctions(final List<FunctionNode> functionNodes) {
-        assert parent != null : "Parent context missing.";
-
-        parent.setFunctions(functionNodes);
-    }
-
-    /**
      * Assist in IR navigation.
      *
      * @param visitor IR navigating visitor.
@@ -258,13 +149,9 @@
         try {
             // Ignore parent to avoid recursion.
 
-            if (visitor.enter(this) != null) {
-                for (int i = 0, count = statements.size(); i < count; i++) {
-                    final Node statement = statements.get(i);
-                    statements.set(i, statement.accept(visitor));
-                }
-
-                return visitor.leave(this);
+            if (visitor.enterBlock(this) != null) {
+                visitStatements(visitor);
+                return visitor.leaveBlock(this);
             }
         } finally {
             visitor.setCurrentBlock(saveBlock);
@@ -274,51 +161,21 @@
     }
 
     /**
-     * Search for symbol.
-     *
-     * @param name Symbol name.
-     *
-     * @return Found symbol or null if not found.
+     * Get an iterator for all the symbols defined in this block
+     * @return symbol iterator
      */
-    public Symbol findSymbol(final String name) {
-        // Search up block chain to locate symbol.
-
-        for (Block block = this; block != null; block = block.getParent()) {
-            // Find name.
-            final Symbol symbol = block.symbols.get(name);
-            // If found then we are good.
-            if (symbol != null) {
-                return symbol;
-            }
-        }
-        return null;
+    public Iterator<Symbol> symbolIterator() {
+        return symbols.values().iterator();
     }
 
     /**
-     * Search for symbol in current function.
-     *
-     * @param name Symbol name.
-     *
-     * @return Found symbol or null if not found.
+     * Retrieves an existing symbol defined in the current block.
+     * @param name the name of the symbol
+     * @return an existing symbol with the specified name defined in the current block, or null if this block doesn't
+     * define a symbol with this name.
      */
-    public Symbol findLocalSymbol(final String name) {
-        // Search up block chain to locate symbol.
-        for (Block block = this; block != null; block = block.getParent()) {
-            // Find name.
-            final Symbol symbol = block.symbols.get(name);
-            // If found then we are good.
-            if (symbol != null) {
-                return symbol;
-            }
-
-            // If searched function then we are done.
-            if (block == block.function) {
-                break;
-            }
-        }
-
-        // Not found.
-        return null;
+    public Symbol getExistingSymbol(final String name) {
+        return symbols.get(name);
     }
 
     /**
@@ -331,122 +188,6 @@
         return statements.size() == 1 && statements.get(0) instanceof CatchNode;
     }
 
-    /**
-     * Test to see if a symbol is local to the function.
-     *
-     * @param symbol Symbol to test.
-     * @return True if a local symbol.
-     */
-    public boolean isLocal(final Symbol symbol) {
-        // some temp symbols have no block, so can be assumed local
-        final Block block = symbol.getBlock();
-        return block == null || block.getFunction() == function;
-    }
-
-    /**
-     * 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.
-     */
-    public Symbol defineSymbol(final String name, final int symbolFlags, final Node node) {
-        int    flags  = symbolFlags;
-        Symbol symbol = findSymbol(name); // Locate symbol.
-
-        if ((flags & KINDMASK) == IS_GLOBAL) {
-            flags |= IS_SCOPE;
-        }
-
-        if (symbol != null) {
-            // Symbol was already defined. Check if it needs to be redefined.
-            if ((flags & KINDMASK) == IS_PARAM) {
-                if (!function.isLocal(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 & Symbol.IS_LET) == Symbol.IS_LET) {
-                    assert !((flags & IS_LET) == IS_LET && symbol.getBlock() == this) : "duplicate let variable in block";
-                    // Always create a new definition.
-                    symbol = null;
-                } else {
-                    // Not defined in this function. Create a new definition.
-                    if (!function.isLocal(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 = this;
-            } else {
-                symbolBlock = getFunction();
-            }
-
-            // 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;
-    }
-
-    /**
-     * Declare the use of a symbol.
-     *
-     * @param name Name of symbol.
-     * @param node Using node
-     *
-     * @return Symbol for given name.
-     */
-    public Symbol useSymbol(final String name, final Node node) {
-        Symbol symbol = findSymbol(name);
-
-        if (symbol == null) {
-            // If not found, declare as a free var.
-            symbol = defineSymbol(name, IS_GLOBAL, node);
-        } else {
-            node.setSymbol(symbol);
-        }
-
-        return symbol;
-    }
-
-    /**
-     * Add parent name to the builder.
-     *
-     * @param sb String bulder.
-     */
-    public void addParentName(final StringBuilder sb) {
-        if (parent != null) {
-            parent.addParentName(sb);
-        }
-    }
-
     @Override
     public void toString(final StringBuilder sb) {
         for (final Node statement : statements) {
@@ -505,16 +246,6 @@
     }
 
     /**
-     * Get the FunctionNode for this block, i.e. the function it
-     * belongs to
-     *
-     * @return the function node
-     */
-    public FunctionNode getFunction() {
-        return function;
-    }
-
-    /**
      * Reset the frame for this block
      *
      * @param frame  the new frame
@@ -524,24 +255,6 @@
     }
 
     /**
-     * Get the parent block
-     *
-     * @return parent block, or null if none exists
-     */
-    public Block getParent() {
-        return parent;
-    }
-
-    /**
-     * Set the parent block
-     *
-     * @param parent the new parent block
-     */
-    public void setParent(final Block parent) {
-        this.parent = parent;
-    }
-
-    /**
      * Get the list of statements in this block
      *
      * @return a list of statements
@@ -551,6 +264,15 @@
     }
 
     /**
+     * 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
@@ -585,4 +307,29 @@
         needsScope = true;
     }
 
+    /**
+     * 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
+     */
+    public void setUsesScopeSymbol(final Symbol symbol, Iterator<Block> ancestors) {
+        if(symbol.getBlock() == this) {
+            setNeedsScope();
+        } else {
+            setUsesParentScopeSymbol(symbol, ancestors);
+        }
+    }
+
+    /**
+     * 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);
+        }
+    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java b/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java
index 81c572d..7ad0dc6 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/BreakNode.java
@@ -64,8 +64,8 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            return visitor.leave(this);
+        if (visitor.enterBreakNode(this) != null) {
+            return visitor.leaveBreakNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/CallNode.java b/nashorn/src/jdk/nashorn/internal/ir/CallNode.java
index 387369e..3410709 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/CallNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/CallNode.java
@@ -37,7 +37,7 @@
  * IR representation for a function call.
  *
  */
-public class CallNode extends Node implements TypeOverride {
+public class CallNode extends Node implements TypeOverride<CallNode> {
 
     private Type type;
 
@@ -176,13 +176,13 @@
         if (hasCallSiteType()) {
             return type;
         }
-        assert !function.getType().isUnknown();
-        return function.getType();
+        return function instanceof FunctionNode ? ((FunctionNode)function).getReturnType() : Type.OBJECT;
     }
 
     @Override
-    public void setType(final Type type) {
+    public CallNode setType(final Type type) {
         this.type = type;
+        return this;
     }
 
     private boolean hasCallSiteType() {
@@ -208,14 +208,14 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        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));
             }
 
-            return visitor.leave(this);
+            return visitor.leaveCallNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java
index 928ba5b..61b8921 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/CaseNode.java
@@ -79,7 +79,7 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterCaseNode(this) != null) {
             if (test != null) {
                 test = test.accept(visitor);
             }
@@ -87,7 +87,7 @@
                 body = (Block)body.accept(visitor);
             }
 
-            return visitor.leave(this);
+            return visitor.leaveCaseNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java b/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java
index 187c394..005ffa8 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/CatchNode.java
@@ -84,7 +84,7 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterCatchNode(this) != null) {
             exception = (IdentNode)exception.accept(visitor);
 
             if (exceptionCondition != null) {
@@ -92,7 +92,7 @@
             }
 
             body = (Block)body.accept(visitor);
-            return visitor.leave(this);
+            return visitor.leaveCatchNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java b/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java
index 2063d6b..cbc7bff 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ContinueNode.java
@@ -61,8 +61,8 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            return visitor.leave(this);
+        if (visitor.enterContinueNode(this) != null) {
+            return visitor.leaveContinueNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/DoWhileNode.java b/nashorn/src/jdk/nashorn/internal/ir/DoWhileNode.java
index 476643a..3939795 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/DoWhileNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/DoWhileNode.java
@@ -63,11 +63,11 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterDoWhileNode(this) != null) {
             body = (Block)body.accept(visitor);
             test = test.accept(visitor);
 
-            return visitor.leave(this);
+            return visitor.leaveDoWhileNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java b/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java
index 0bfacd5..15330a3 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/EmptyNode.java
@@ -57,8 +57,8 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            return visitor.leave(this);
+        if (visitor.enterEmptyNode(this) != null) {
+            return visitor.leaveEmptyNode(this);
         }
         return this;
     }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java b/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java
index 501468b..8ae7d55 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ExecuteNode.java
@@ -85,9 +85,9 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterExecuteNode(this) != null) {
             setExpression(expression.accept(visitor));
-            return visitor.leave(this);
+            return visitor.leaveExecuteNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ForNode.java b/nashorn/src/jdk/nashorn/internal/ir/ForNode.java
index 53b5675..e55054d 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ForNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ForNode.java
@@ -76,7 +76,7 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterForNode(this) != null) {
             if (init != null) {
                 init = init.accept(visitor);
             }
@@ -91,7 +91,7 @@
 
             body = (Block)body.accept(visitor);
 
-            return visitor.leave(this);
+            return visitor.leaveForNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
index d928aa7..2b0e109 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/FunctionNode.java
@@ -33,8 +33,10 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
-import java.util.LinkedList;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Stack;
 import jdk.nashorn.internal.codegen.CompileUnit;
 import jdk.nashorn.internal.codegen.Compiler;
@@ -45,16 +47,18 @@
 import jdk.nashorn.internal.ir.annotations.Ignore;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.parser.Parser;
+import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.Source;
 import jdk.nashorn.internal.runtime.UserAccessorProperty;
 import jdk.nashorn.internal.runtime.linker.LinkerCallSite;
 
 /**
  * IR representation for function (or script.)
- *
  */
 public class FunctionNode extends Block {
 
+    private static final Type FUNCTION_TYPE = Type.typeFor(ScriptFunction.class);
+
     /** Function kinds */
     public enum Kind {
         /** a normal function - nothing special */
@@ -86,7 +90,9 @@
         /** method has had its types finalized */
         FINALIZED,
         /** method has been emitted to bytecode */
-        EMITTED
+        EMITTED,
+        /** code installed in a class loader */
+        INSTALLED
     }
 
     /** External function identifier. */
@@ -108,9 +114,6 @@
     /** List of parameters. */
     private List<IdentNode> parameters;
 
-    /** List of nested functions. */
-    private List<FunctionNode> functions;
-
     /** First token of function. **/
     private long firstToken;
 
@@ -153,10 +156,6 @@
     /** Pending control list. */
     private final Stack<Node> controlStack;
 
-    /** Variable declarations in the function's scope */
-    @Ignore
-    private final List<VarNode> declarations;
-
     /** VarNode for this function statement */
     @Ignore //this is explicit code anyway and should not be traversed after lower
     private VarNode funcVarNode;
@@ -173,37 +172,42 @@
     @Ignore
     private final EnumSet<CompilationState> compilationState;
 
+    /** Type hints, e.g based on parameters at call site */
+    private final Map<IdentNode, Type> specializedTypes;
+
     /** Function flags. */
     private int flags;
 
     /** Is anonymous function flag. */
-    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_ANONYMOUS                = 1 << 0;
+    /** Is the function created in a function declaration (as opposed to a function expression) */
+    private static final int IS_DECLARED                 = 1 << 1;
     /** is this a strict mode function? */
-    private static final int IS_STRICT_MODE              = 0b0000_0000_0000_0100;
+    private static final int IS_STRICT_MODE              = 1 << 2;
     /** Does the function use the "arguments" identifier ? */
-    private static final int USES_ARGUMENTS              = 0b0000_0000_0000_1000;
+    private static final int USES_ARGUMENTS              = 1 << 3;
     /** Are we lowered ? */
-    private static final int IS_LOWERED                  = 0b0000_0000_0001_0000;
+    private static final int IS_LOWERED                  = 1 << 4;
     /** 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                    = 1 << 5;
     /** Does the function call eval? */
-    private static final int HAS_EVAL                    = 0b0000_0000_0100_0000;
+    private static final int HAS_EVAL                    = 1 << 6;
     /** Does the function contain a with block ? */
-    private static final int HAS_WITH                    = 0b0000_0000_1000_0000;
+    private static final int HAS_WITH                    = 1 << 7;
     /** Does a descendant function contain a with or eval? */
-    private static final int HAS_DESCENDANT_WITH_OR_EVAL = 0b0000_0001_0000_0000;
+    private static final int HAS_DESCENDANT_WITH_OR_EVAL = 1 << 8;
     /** Does the function define "arguments" identifier as a parameter of nested function name? */
-    private static final int DEFINES_ARGUMENTS           = 0b0000_0010_0000_0000;
+    private static final int DEFINES_ARGUMENTS           = 1 << 9;
     /** Does the function need a self symbol? */
-    private static final int NEEDS_SELF_SYMBOL           = 0b0000_0100_0000_0000;
+    private static final int NEEDS_SELF_SYMBOL           = 1 << 10;
     /** 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         = 0b0000_1000_0000_0000;
+    private static final int USES_ANCESTOR_SCOPE         = 1 << 11;
     /** Is this function lazily compiled? */
-    private static final int IS_LAZY                     = 0b0001_0000_0000_0000;
+    private static final int IS_LAZY                     = 1 << 12;
     /** Does this function have lazy, yet uncompiled children */
-    private static final int HAS_LAZY_CHILDREN           = 0b0010_0000_0000_0000;
+    private static final int HAS_LAZY_CHILDREN           = 1 << 13;
+    /** Does this function have lazy, yet uncompiled children */
+    private static final int IS_PROGRAM                   = 1 << 14;
 
     /** Does this function or any nested functions contain a with or an eval? */
     private static final int HAS_DEEP_WITH_OR_EVAL = HAS_EVAL | HAS_WITH | HAS_DESCENDANT_WITH_OR_EVAL;
@@ -211,54 +215,39 @@
     private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_WITH_OR_EVAL | IS_SPLIT | HAS_LAZY_CHILDREN;
     /** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */
     private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL;
-    /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep with or eval. */
-    private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_WITH_OR_EVAL;
+    /** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep with or eval.
+     *  We also pessimistically need a parent scope if we have lazy children that have not yet been compiled */
+    private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_WITH_OR_EVAL | HAS_LAZY_CHILDREN;
 
     /** What is the return type of this function? */
     private Type returnType = Type.UNKNOWN;
 
     /**
-     * Used to keep track of a function's parent blocks.
-     * This is needed when a (finally body) block is cloned than contains inner functions.
-     * Does not include function.getParent().
-     */
-    @Ignore
-    private List<Block> referencingParentBlocks;
-
-    /**
      * Constructor
      *
      * @param source    the source
      * @param token     token
      * @param finish    finish
      * @param namespace the namespace
-     * @param parent    the parent block
      * @param ident     the identifier
      * @param name      the name of the function
      */
-    @SuppressWarnings("LeakingThisInConstructor")
-    public FunctionNode(final Source source, final long token, final int finish, final Namespace namespace, final Block parent, final IdentNode ident, final String name) {
-        super(source, token, finish, parent, null);
+    public FunctionNode(final Source source, final long token, final int finish, final Namespace namespace, final IdentNode ident, final String name) {
+        super(source, token, finish);
 
         this.ident             = ident;
         this.name              = name;
         this.kind              = Kind.NORMAL;
         this.parameters        = new ArrayList<>();
-        this.functions         = new ArrayList<>();
         this.firstToken        = token;
         this.lastToken         = token;
         this.namespace         = namespace;
         this.labelStack        = new Stack<>();
         this.controlStack      = new Stack<>();
-        this.declarations      = new ArrayList<>();
-        // my block -> function is this. We added @SuppressWarnings("LeakingThisInConstructor") as NetBeans identifies
-        // it as such a leak - this is a false positive as we're setting this into a field of the object being
-        // constructed, so it can't be seen from other threads.
-        this.function          = this;
         this.compilationState  = EnumSet.of(CompilationState.INITIALIZED);
+        this.specializedTypes  = new HashMap<>();
     }
 
-    @SuppressWarnings("LeakingThisInConstructor")
     private FunctionNode(final FunctionNode functionNode, final CopyState cs) {
         super(functionNode, cs);
 
@@ -268,10 +257,9 @@
 
         this.parameters = new ArrayList<>();
         for (final IdentNode param : functionNode.getParameters()) {
-            this.parameters.add((IdentNode) cs.existingOrCopy(param));
+            this.parameters.add((IdentNode)cs.existingOrCopy(param));
         }
 
-        this.functions         = new ArrayList<>();
         this.firstToken        = functionNode.firstToken;
         this.lastToken         = functionNode.lastToken;
         this.namespace         = functionNode.getNamespace();
@@ -283,43 +271,34 @@
         this.calleeNode        = (IdentNode)cs.existingOrCopy(functionNode.calleeNode);
         this.labelStack        = new Stack<>();
         this.controlStack      = new Stack<>();
-        this.declarations      = new ArrayList<>();
-
-        for (final VarNode decl : functionNode.getDeclarations()) {
-            declarations.add((VarNode) cs.existingOrCopy(decl)); //TODO same?
-        }
 
         this.flags = functionNode.flags;
 
         this.funcVarNode = (VarNode)cs.existingOrCopy(functionNode.funcVarNode);
         /** VarNode for this function statement */
 
-        // my block -> function is this. We added @SuppressWarnings("LeakingThisInConstructor") as NetBeans identifies
-        // it as such a leak - this is a false positive as we're setting this into a field of the object being
-        // constructed, so it can't be seen from other threads.
-        this.function = this;
-
         this.compilationState = EnumSet.copyOf(functionNode.compilationState);
+        this.specializedTypes = new HashMap<>();
     }
 
     @Override
     protected Node copy(final CopyState cs) {
         // deep clone all parent blocks
-        return fixBlockChain(new FunctionNode(this, cs));
+        return new FunctionNode(this, cs);
     }
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        final FunctionNode saveFunctionNode = visitor.getCurrentFunctionNode();
-        final Block        saveBlock        = visitor.getCurrentBlock();
+        final FunctionNode  saveFunctionNode  = visitor.getCurrentFunctionNode();
+        final Block         saveBlock         = visitor.getCurrentBlock();
+        final MethodEmitter saveMethodEmitter = visitor.getCurrentMethodEmitter();
+        final CompileUnit   saveCompileUnit   = visitor.getCurrentCompileUnit();
 
         visitor.setCurrentFunctionNode(this);
-        visitor.setCurrentCompileUnit(getCompileUnit());
-        visitor.setCurrentMethodEmitter(getMethodEmitter());
         visitor.setCurrentBlock(this);
 
         try {
-            if (visitor.enter(this) != null) {
+            if (visitor.enterFunctionNode(this) != null) {
                 if (ident != null) {
                     ident = (IdentNode)ident.accept(visitor);
                 }
@@ -328,51 +307,25 @@
                     parameters.set(i, (IdentNode)parameters.get(i).accept(visitor));
                 }
 
-                for (int i = 0, count = functions.size(); i < count; i++) {
-                    functions.set(i, (FunctionNode)functions.get(i).accept(visitor));
-                }
-
                 for (int i = 0, count = statements.size(); i < count; i++) {
                     statements.set(i, statements.get(i).accept(visitor));
                 }
 
-                return visitor.leave(this);
+                return visitor.leaveFunctionNode(this);
             }
         } finally {
             visitor.setCurrentBlock(saveBlock);
             visitor.setCurrentFunctionNode(saveFunctionNode);
-            visitor.setCurrentCompileUnit(saveFunctionNode != null ? saveFunctionNode.getCompileUnit() : null);
-            visitor.setCurrentMethodEmitter(saveFunctionNode != null ? saveFunctionNode.getMethodEmitter() : null);
+            visitor.setCurrentCompileUnit(saveCompileUnit);
+            visitor.setCurrentMethodEmitter(saveMethodEmitter);
         }
 
         return this;
     }
 
-    /**
-     * Locate the parent function.
-     *
-     * @return Parent function.
-     */
-    public FunctionNode findParentFunction() {
-        return getParent() != null ? getParent().getFunction() : null;
-    }
-
-    /**
-     * Add parent name to the builder.
-     *
-     * @param sb String builder.
-     */
-    @Override
-    public void addParentName(final StringBuilder sb) {
-        if (!isScript()) {
-            sb.append(getName());
-            sb.append("$");
-        }
-    }
-
     @Override
     public boolean needsScope() {
-        return super.needsScope() || isScript();
+        return super.needsScope() || isProgram();
     }
 
     /**
@@ -530,12 +483,18 @@
     }
 
     /**
-     * Determine if script function.
-     *
-     * @return True if script function.
+     * Returns true if the function is the top-level program.
+     * @return True if this function node represents the top-level program.
      */
-    public boolean isScript() {
-        return getParent() == null;
+    public boolean isProgram() {
+        return (flags & IS_PROGRAM) != 0;
+    }
+
+    /**
+     * Marks the function as representing the top-level program.
+     */
+    public void setProgram() {
+        flags |= IS_PROGRAM;
     }
 
     /**
@@ -575,31 +534,31 @@
 
     /**
      * Flag this function as using the {@code with} keyword
+     * @param ancestors the iterator over functions in this functions's containing lexical context
      */
-    public void setHasWith() {
+    public void setHasWith(final Iterator<FunctionNode> ancestors) {
         if(!hasWith()) {
             this.flags |= HAS_WITH;
             // with requires scope in parents.
             // TODO: refine this. with should not force all variables in parents to be in scope, only those that are
             // actually referenced as identifiers by name
-            markParentForWithOrEval();
+            markParentForWithOrEval(ancestors);
         }
     }
 
-    private void markParentForWithOrEval() {
+    private void markParentForWithOrEval(final Iterator<FunctionNode> ancestors) {
         // If this is invoked, then either us or a descendant uses with or eval, meaning we must have our own scope.
         setNeedsScope();
 
-        final FunctionNode parentFunction = findParentFunction();
-        if(parentFunction != null) {
-            parentFunction.setDescendantHasWithOrEval();
+        if(ancestors.hasNext()) {
+            ancestors.next().setDescendantHasWithOrEval(ancestors);
         }
     }
 
-    private void setDescendantHasWithOrEval() {
+    private void setDescendantHasWithOrEval(final Iterator<FunctionNode> ancestors) {
         if((flags & HAS_DESCENDANT_WITH_OR_EVAL) == 0) {
             flags |= HAS_DESCENDANT_WITH_OR_EVAL;
-            markParentForWithOrEval();
+            markParentForWithOrEval(ancestors);
         }
     }
 
@@ -614,11 +573,12 @@
 
     /**
      * Flag this function as calling the {@code eval} function
+     * @param ancestors the iterator over functions in this functions's containing lexical context
      */
-    public void setHasEval() {
+    public void setHasEval(final Iterator<FunctionNode> ancestors) {
         if(!hasEval()) {
             this.flags |= HAS_EVAL;
-            markParentForWithOrEval();
+            markParentForWithOrEval(ancestors);
         }
     }
 
@@ -651,11 +611,34 @@
     }
 
     /**
-     * Get all nested functions
-     * @return list of nested functions in this function
+     * Returns a list of functions declared by this function. Only includes declared functions, and does not include any
+     * function expressions that might occur in its body.
+     * @return a list of functions declared by this function.
      */
-    public List<FunctionNode> getFunctions() {
-        return Collections.unmodifiableList(functions);
+    public List<FunctionNode> getDeclaredFunctions() {
+        // Note that the function does not have a dedicated list of declared functions, but rather relies on the
+        // invariant that all function declarations are at the beginning of the statement list as VarNode with a
+        // FunctionNode marked as statement with its variable initializer. Every VarNode is also preceded by a
+        // LineNumberNode. This invariant is established by the parser and has to be preserved in visitors.
+        final List<FunctionNode> fns = new ArrayList<>();
+        for (final Node stmt : statements) {
+            if(stmt instanceof LineNumberNode) {
+                continue;
+            } else if(stmt instanceof VarNode) {
+                final Node init = ((VarNode)stmt).getInit();
+                if(init instanceof FunctionNode) {
+                    final FunctionNode fn = (FunctionNode)init;
+                    if(fn.isDeclared()) {
+                        fns.add(fn);
+                        continue;
+                    }
+                }
+            }
+            // Node is neither a LineNumberNode, nor a function declaration VarNode. Since all function declarations are
+            // at the start of the function, we've reached the end of function declarations.
+            break;
+        }
+        return fns;
     }
 
     /**
@@ -710,6 +693,7 @@
      * 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() {
@@ -786,7 +770,7 @@
     public boolean needsArguments() {
         // uses "arguments" or calls eval, but it does not redefine "arguments", and finally, it's not a script, since
         // for top-level script, "arguments" is picked up from Context by Global.init() instead.
-        return (flags & MAYBE_NEEDS_ARGUMENTS) != 0 && (flags & DEFINES_ARGUMENTS) == 0 && !isScript();
+        return (flags & MAYBE_NEEDS_ARGUMENTS) != 0 && (flags & DEFINES_ARGUMENTS) == 0 && !isProgram();
     }
 
     /**
@@ -805,7 +789,7 @@
      * @return true if the function needs parent scope.
      */
     public boolean needsParentScope() {
-        return (flags & NEEDS_PARENT_SCOPE) != 0 || isScript();
+        return (flags & NEEDS_PARENT_SCOPE) != 0 || isProgram();
     }
 
     /**
@@ -865,7 +849,7 @@
      * @return true if all variables should be in scope
      */
     public boolean allVarsInScope() {
-        return isScript() || (flags & HAS_ALL_VARS_IN_SCOPE) != 0;
+        return isProgram() || (flags & HAS_ALL_VARS_IN_SCOPE) != 0;
     }
 
     /**
@@ -919,6 +903,27 @@
     }
 
     /**
+     * Get a specialized type for an identity, if one exists
+     * @param node node to check specialized type for
+     * @return null if no specialization exists, otherwise type
+     */
+    public Type getSpecializedType(final IdentNode node) {
+        return specializedTypes.get(node);
+    }
+
+    /**
+     * Set parameter type hints for specialization.
+     * @param types types array of length equal to parameter list size
+     */
+    public void setParameterTypes(final Class<?>[] types) {
+        assert types.length == parameters.size() : "Type vector length doesn't correspond to parameter types";
+        //diff - skip the callee and this etc, they are not explicit params in the parse tree
+        for (int i = 0; i < types.length ; i++) {
+            specializedTypes.put(parameters.get(i), Type.typeFor(types[i]));
+        }
+    }
+
+    /**
      * Get the identifier for the variable in which the function return value
      * should be stored
      * @return an IdentNode representing the return value
@@ -953,19 +958,19 @@
     }
 
     /**
-     * Check if this function is a statement
-     * @return true if function is a statement
+     * Check if this function is created as a function declaration (as opposed to function expression)
+     * @return true if function is declared.
      */
-    public boolean isStatement() {
-        return (flags & IS_STATEMENT) != 0;
+    public boolean isDeclared() {
+        return (flags & IS_DECLARED) != 0;
     }
 
     /**
-     * Flag this function as a statement
+     * Flag this function as being created as a function declaration (as opposed to a function expression).
      * @see Parser
      */
-    public void setIsStatement() {
-        this.flags |= IS_STATEMENT;
+    public void setIsDeclared() {
+        this.flags |= IS_DECLARED;
     }
 
     /**
@@ -1013,35 +1018,16 @@
     }
 
     /**
-     * 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()
+     * Marks this function as using any of its ancestors' scopes.
      */
-    public void setUsesGlobalSymbol() {
+    public void setUsesAncestorScope() {
         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);
-            }
-        }
+    @Override
+    void setUsesParentScopeSymbol(Symbol symbol, Iterator<Block> ancestors) {
+        setUsesAncestorScope();
+        super.setUsesParentScopeSymbol(symbol, ancestors);
     }
 
     /**
@@ -1116,7 +1102,7 @@
 
     @Override
     public Type getType() {
-        return getReturnType();
+        return FUNCTION_TYPE;
     }
 
     /**
@@ -1176,56 +1162,6 @@
     }
 
     /**
-     * Add a new function to the function list.
-     *
-     * @param functionNode Function node to add.
-     */
-    @Override
-    public void addFunction(final FunctionNode functionNode) {
-        assert functionNode != null;
-        functions.add(functionNode);
-    }
-
-    /**
-     * Add a list of functions to the function list.
-     *
-     * @param functionNodes  Function nodes to add.
-     */
-    @Override
-    public void addFunctions(final List<FunctionNode> functionNodes) {
-        functions.addAll(functionNodes);
-    }
-
-    /**
-     * Set a function list
-     *
-     * @param functionNodes to set
-     */
-    @Override
-    public void setFunctions(final List<FunctionNode> functionNodes) {
-        this.functions = functionNodes;
-    }
-
-    /**
-     * Add a variable declaration that should be visible to the entire function
-     * scope. Parser does this.
-     *
-     * @param varNode a var node
-     */
-    public void addDeclaration(final VarNode varNode) {
-        declarations.add(varNode);
-    }
-
-    /**
-     * Return all variable declarations from this function scope
-     *
-     * @return all VarNodes in scope
-     */
-    public List<VarNode> getDeclarations() {
-        return Collections.unmodifiableList(declarations);
-    }
-
-    /**
      * Get the compile unit used to compile this function
      * @see Compiler
      * @return the compile unit
@@ -1258,32 +1194,4 @@
     public void setMethodEmitter(final MethodEmitter method) {
         this.method = method;
     }
-
-    /**
-     * Each FunctionNode maintains a list of reference to its parent blocks.
-     * Add a parent block to this function.
-     *
-     * @param parentBlock  a block to remember as parent
-     */
-    public void addReferencingParentBlock(final Block parentBlock) {
-        assert parentBlock.getFunction() == function.findParentFunction(); // all parent blocks must be in the same function
-        if (parentBlock != function.getParent()) {
-            if (referencingParentBlocks == null) {
-                referencingParentBlocks = new LinkedList<>();
-            }
-            referencingParentBlocks.add(parentBlock);
-        }
-    }
-
-    /**
-     * Get the known parent blocks to this function
-     *
-     * @return list of parent blocks
-     */
-    public List<Block> getReferencingParentBlocks() {
-        if (referencingParentBlocks == null) {
-            return Collections.emptyList();
-        }
-        return Collections.unmodifiableList(referencingParentBlocks);
-    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java
index b0570a2..889a870 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/IdentNode.java
@@ -38,18 +38,18 @@
 /**
  * IR representation for an identifier.
  */
-public class IdentNode extends Node implements PropertyKey, TypeOverride, FunctionCall {
+public class IdentNode extends Node implements PropertyKey, TypeOverride<IdentNode>, FunctionCall {
+    private static final int PROPERTY_NAME    = 1 << 0;
+    private static final int INITIALIZED_HERE = 1 << 1;
+    private static final int FUNCTION         = 1 << 2;
+
     /** Identifier. */
     private final String name;
 
     /** Type for a callsite, e.g. X in a get()X or a set(X)V */
     private Type callSiteType;
 
-    /** flag for an ident that is the property name of an AccessNode. */
-    private boolean isPropertyName;
-
-    /** flag for an ident on the left hand side of <code>var lhs = rhs;</code>. */
-    private boolean isInitializedHere;
+    private byte flags;
 
     /**
      * Constructor
@@ -71,9 +71,8 @@
      */
     public IdentNode(final IdentNode identNode) {
         super(identNode);
-        this.name              = identNode.getName();
-        this.isPropertyName    = identNode.isPropertyName;
-        this.isInitializedHere = identNode.isInitializedHere;
+        this.name  = identNode.getName();
+        this.flags = identNode.flags;
     }
 
     @Override
@@ -92,12 +91,17 @@
     }
 
     @Override
-    public void setType(final Type type) {
+    public IdentNode setType(final Type type) {
         if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
             ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType());
         }
-        this.callSiteType = type;
         // do NOT, repeat NOT touch the symbol here. it might be a local variable or whatever. This is the override if it isn't
+        if(this.callSiteType == type) {
+            return this;
+        }
+        final IdentNode n = (IdentNode)clone();
+        n.callSiteType = type;
+        return n;
     }
 
     @Override
@@ -131,8 +135,8 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            return visitor.leave(this);
+        if (visitor.enterIdentNode(this) != null) {
+            return visitor.leaveIdentNode(this);
         }
 
         return this;
@@ -179,14 +183,18 @@
      * @return true if this is a property name
      */
     public boolean isPropertyName() {
-        return isPropertyName;
+        return (flags & PROPERTY_NAME) != 0;
     }
 
     /**
      * Flag this IdentNode as a property name
+     * @return a node equivalent to this one except for the requested change.
      */
-    public void setIsPropertyName() {
-        isPropertyName = true;
+    public IdentNode setIsPropertyName() {
+        if(isPropertyName()) return this;
+        final IdentNode n = (IdentNode)clone();
+        n.flags |= PROPERTY_NAME;
+        return n;
     }
 
     /**
@@ -194,14 +202,18 @@
      * @return true if IdentNode is initialized on creation
      */
     public boolean isInitializedHere() {
-        return isInitializedHere;
+        return (flags & INITIALIZED_HERE) != 0;
     }
 
     /**
      * Flag IdentNode to be initialized on creation
+     * @return a node equivalent to this one except for the requested change.
      */
-    public void setIsInitializedHere() {
-        isInitializedHere = true;
+    public IdentNode setIsInitializedHere() {
+        if(isInitializedHere()) return this;
+        final IdentNode n = (IdentNode)clone();
+        n.flags |= INITIALIZED_HERE;
+        return n;
     }
 
     /**
@@ -216,6 +228,17 @@
 
     @Override
     public boolean isFunction() {
-        return false;
+        return (flags & FUNCTION) != 0;
+    }
+
+    /**
+     * Mark this node as being the callee operand of a {@link CallNode}.
+     * @return an ident node identical to this one in all aspects except with its function flag set.
+     */
+    public IdentNode setIsFunction() {
+        if(isFunction()) return this;
+        final IdentNode n = (IdentNode)clone();
+        n.flags |= FUNCTION;
+        return n;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/IfNode.java b/nashorn/src/jdk/nashorn/internal/ir/IfNode.java
index 81148c3..3ddcf1d 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/IfNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/IfNode.java
@@ -75,7 +75,7 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterIfNode(this) != null) {
             test = test.accept(visitor);
 
             pass = (Block)pass.accept(visitor);
@@ -84,7 +84,7 @@
                 fail = (Block)fail.accept(visitor);
             }
 
-            return visitor.leave(this);
+            return visitor.leaveIfNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java b/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java
index 9feb5eb..4745bf6 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/IndexNode.java
@@ -36,7 +36,7 @@
  * IR representation of an indexed access (brackets operator.)
  *
  */
-public class IndexNode extends BaseNode implements TypeOverride {
+public class IndexNode extends BaseNode implements TypeOverride<IndexNode> {
     /** Property ident. */
     private Node index;
 
@@ -92,10 +92,10 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterIndexNode(this) != null) {
             base = base.accept(visitor);
             index = index.accept(visitor);
-            return visitor.leave(this);
+            return visitor.leaveIndexNode(this);
         }
 
         return this;
@@ -144,12 +144,13 @@
     }
 
     @Override
-    public void setType(final Type type) {
+    public IndexNode setType(final Type type) {
         if (DEBUG_FIELDS && !Type.areEquivalent(getSymbol().getSymbolType(), type)) {
             ObjectClassGenerator.LOG.info(getClass().getName() + " " + this + " => " + type + " instead of " + getType());
         }
         hasCallSiteType = true;
         getSymbol().setTypeOverride(type);
+        return this;
     }
 
     @Override
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java b/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java
index c61bfcb..756ea2d 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LabelNode.java
@@ -81,10 +81,10 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterLabelNode(this) != null) {
             label = (IdentNode)label.accept(visitor);
             body  = (Block)body.accept(visitor);
-            return visitor.leave(this);
+            return visitor.leaveLabelNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java b/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java
new file mode 100644
index 0000000..2db1f79
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/ir/LexicalContext.java
@@ -0,0 +1,198 @@
+package jdk.nashorn.internal.ir;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * A class that tracks the current lexical context of node visitation as a stack of {@link Block} nodes. Has special
+ * methods to retrieve useful subsets of the context.
+ */
+public class LexicalContext implements Cloneable {
+    private final Deque<Block> lexicalContext;
+
+    /**
+     * Creates a new empty lexical context.
+     */
+    public LexicalContext() {
+        lexicalContext = new ArrayDeque<>();
+    }
+
+    /**
+     * Pushes a new block on top of the context, making it the innermost open block.
+     * @param block the new block
+     */
+    public void push(Block block) {
+        //new Exception(block.toString()).printStackTrace();
+        lexicalContext.push(block);
+    }
+
+    /**
+     * Pops the innermost block off the context.
+     * @param the block expected to be popped, used to detect unbalanced pushes/pops
+     */
+    public void pop(Block block) {
+        final Block popped = lexicalContext.pop();
+        assert popped == block;
+    }
+
+    /**
+     * Returns an iterator over all blocks in the context, with the top block (innermost lexical context) first.
+     * @return an iterator over all blocks in the context.
+     */
+    public Iterator<Block> getBlocks() {
+        return lexicalContext.iterator();
+    }
+
+    /**
+     * Returns an iterator over all functions in the context, with the top (innermost open) function first.
+     * @return an iterator over all functions in the context.
+     */
+    public Iterator<FunctionNode> getFunctions() {
+        return new FunctionIterator(getBlocks());
+    }
+
+    private static final class FunctionIterator implements Iterator<FunctionNode> {
+        private final Iterator<Block> it;
+        private FunctionNode next;
+
+        FunctionIterator(Iterator<Block> it) {
+            this.it = it;
+            next = findNext();
+        }
+
+        @Override
+        public boolean hasNext() {
+            return next != null;
+        }
+
+        @Override
+        public FunctionNode next() {
+            if(next == null) {
+                throw new NoSuchElementException();
+            }
+            FunctionNode lnext = next;
+            next = findNext();
+            return lnext;
+        }
+
+        private FunctionNode findNext() {
+            while(it.hasNext()) {
+                final Block block = it.next();
+                if(block instanceof FunctionNode) {
+                    return ((FunctionNode)block);
+                }
+            }
+            return null;
+        }
+
+        @Override
+        public void remove() {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    /**
+     * Returns an iterator over all ancestors block of the given block, with its parent block first.
+     * @param block the block whose ancestors are returned
+     * @return an iterator over all ancestors block of the given block.
+     */
+    public Iterator<Block> getAncestorBlocks(Block block) {
+        final Iterator<Block> it = getBlocks();
+        while(it.hasNext()) {
+            final Block b = it.next();
+            if(block == b) {
+                return it;
+            }
+        }
+        throw new AssertionError("Block is not on the current lexical context stack");
+    }
+
+    /**
+     * Returns an iterator over a block and all its ancestors blocks, with the block first.
+     * @param block the block that is the starting point of the iteration.
+     * @return an iterator over a block and all its ancestors.
+     */
+    public Iterator<Block> getBlocks(final Block block) {
+        final Iterator<Block> it = getAncestorBlocks(block);
+        return new Iterator<Block>() {
+            boolean blockReturned = false;
+            @Override
+            public boolean hasNext() {
+                return it.hasNext() || !blockReturned;
+            }
+            @Override
+            public Block next() {
+                if(blockReturned) {
+                    return it.next();
+                }
+                blockReturned = true;
+                return block;
+            }
+            @Override
+            public void remove() {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    /**
+     * Returns the closest function node to the block. If the block is itself a function, it is returned.
+     * @param block the block
+     * @return the function closest to the block.
+     * @see #getParentFunction(Block)
+     */
+    public FunctionNode getFunction(Block block) {
+        if(block instanceof FunctionNode) {
+            return (FunctionNode)block;
+        }
+        return getParentFunction(block);
+    }
+
+    /**
+     * Returns the closest function node to the block and all its ancestor functions. If the block is itself a function,
+     * it is returned too.
+     * @param block the block
+     * @return the closest function node to the block and all its ancestor functions.
+     */
+    public Iterator<FunctionNode> getFunctions(final Block block) {
+        return new FunctionIterator(getBlocks(block));
+    }
+
+    /**
+     * Returns the containing function of the block. If the block is itself a function, its parent function is returned.
+     * @param block the block
+     * @return the containing function of the block.
+     * @see #getFunction(Block)
+     */
+    public FunctionNode getParentFunction(Block block) {
+        return getFirstFunction(getAncestorBlocks(block));
+    }
+
+    private static FunctionNode getFirstFunction(Iterator<Block> it) {
+        while(it.hasNext()) {
+            final Block ancestor = it.next();
+            if(ancestor instanceof FunctionNode) {
+                return (FunctionNode)ancestor;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the innermost block in the context.
+     * @return the innermost block in the context.
+     */
+    public Block getCurrentBlock() {
+        return lexicalContext.element();
+    }
+
+    /**
+     * Returns the innermost function in the context.
+     * @return the innermost function in the context.
+     */
+    public FunctionNode getCurrentFunction() {
+        return getFirstFunction(getBlocks());
+    }
+}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java b/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java
index ef1c05e..c7912ff 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LineNumberNode.java
@@ -63,8 +63,8 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            return visitor.leave(this);
+        if (visitor.enterLineNumberNode(this) != null) {
+            return visitor.leaveLineNumberNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java
index f1bf1b8..cc424b7a 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/LiteralNode.java
@@ -46,7 +46,7 @@
  */
 public abstract class LiteralNode<T> extends Node implements PropertyKey {
     /** Literal value */
-    protected T value;
+    protected final T value;
 
      /**
      * Constructor
@@ -67,8 +67,17 @@
      * @param literalNode source node
      */
     protected LiteralNode(final LiteralNode<T> literalNode) {
+        this(literalNode, literalNode.value);
+    }
+
+    /**
+     * A copy constructor with value change.
+     * @param literalNode the original literal node
+     * @param newValue new value for this node
+     */
+    protected LiteralNode(final LiteralNode<T> literalNode, final T newValue) {
         super(literalNode);
-        this.value = literalNode.value;
+        this.value = newValue;
     }
 
     @Override
@@ -217,8 +226,8 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            return visitor.leave(this);
+        if (visitor.enterLiteralNode(this) != null) {
+            return visitor.leaveLiteralNode(this);
         }
 
         return this;
@@ -544,6 +553,10 @@
             super(literalNode);
         }
 
+        private NodeLiteralNode(final LiteralNode<Node> literalNode, final Node value) {
+            super(literalNode, value);
+        }
+
         @Override
         protected Node copy(final CopyState cs) {
             return new NodeLiteralNode(this);
@@ -551,11 +564,14 @@
 
         @Override
         public Node accept(final NodeVisitor visitor) {
-            if (visitor.enter(this) != null) {
+            if (visitor.enterLiteralNode(this) != null) {
                 if (value != null) {
-                    value = value.accept(visitor);
+                    final Node newValue = value.accept(visitor);
+                    if(value != newValue) {
+                        return visitor.leaveLiteralNode(new NodeLiteralNode(this, newValue));
+                    }
                 }
-                return visitor.leave(this);
+                return visitor.leaveLiteralNode(this);
             }
 
             return this;
@@ -878,14 +894,14 @@
 
         @Override
         public Node accept(final NodeVisitor visitor) {
-            if (visitor.enter(this) != null) {
+            if (visitor.enterLiteralNode(this) != null) {
                 for (int i = 0; i < value.length; i++) {
                     final Node element = value[i];
                     if (element != null) {
                         value[i] = element.accept(visitor);
                     }
                 }
-                return visitor.leave(this);
+                return visitor.leaveLiteralNode(this);
             }
             return this;
         }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Location.java b/nashorn/src/jdk/nashorn/internal/ir/Location.java
index 16ee680..c8e01dd 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Location.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Location.java
@@ -65,7 +65,11 @@
 
     @Override
     protected Object clone() {
-        return new Location(this);
+        try {
+            return super.clone();
+        } catch(CloneNotSupportedException e) {
+            throw new AssertionError(e);
+        }
     }
 
     @Override
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Node.java b/nashorn/src/jdk/nashorn/internal/ir/Node.java
index 24ad879..c5f0133 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Node.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Node.java
@@ -165,12 +165,19 @@
             return true;
         }
 
-        setIsResolved();
+        setIsResolved(true);
 
         return false;
     }
 
     /**
+     * Reset the resolved flag.
+     */
+    public void resetResolved() {
+        setIsResolved(false);
+    }
+
+    /**
      * Is this a debug info node like LineNumberNode etc?
      *
      * @return true if this is a debug node
@@ -234,8 +241,7 @@
      *
      * @return Deep copy of the  Node.
      */
-    @Override
-    public final Node clone() {
+    public final Node copy() {
         return copy(new CopyState());
     }
 
@@ -349,10 +355,10 @@
     }
 
     /**
-     * Flag this node as resolved, i.e. code has been generated for it
+     * Flag this node as resolved or not, i.e. code has been generated for it
      */
-    public void setIsResolved() {
-        this.isResolved = true;
+    private void setIsResolved(boolean isResolved) {
+        this.isResolved = isResolved;
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java b/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java
index ab7e49e..f6724a6 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ObjectNode.java
@@ -28,7 +28,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import jdk.nashorn.internal.ir.annotations.Ignore;
 import jdk.nashorn.internal.ir.visitor.NodeVisitor;
 import jdk.nashorn.internal.runtime.Source;
 
@@ -36,9 +35,6 @@
  * IR representation of an object literal.
  */
 public class ObjectNode extends Node {
-    /** Literal context. */
-    @Ignore
-    private Block context;
 
     /** Literal elements. */
     private final List<Node> elements;
@@ -49,13 +45,11 @@
      * @param source   the source
      * @param token    token
      * @param finish   finish
-     * @param context  the block for this ObjectNode
      * @param elements the elements used to initialize this ObjectNode
      */
-    public ObjectNode(final Source source, final long token, final int finish, final Block context, final List<Node> elements) {
+    public ObjectNode(final Source source, final long token, final int finish, final List<Node> elements) {
         super(source, token, finish);
 
-        this.context  = context;
         this.elements = elements;
     }
 
@@ -68,7 +62,6 @@
             newElements.add(cs.existingOrCopy(element));
         }
 
-        this.context  = (Block)cs.existingOrCopy(objectNode.context);
         this.elements = newElements;
     }
 
@@ -79,16 +72,12 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            if (context != null) {
-                context = (Block)context.accept(visitor);
-            }
-
+        if (visitor.enterObjectNode(this) != null) {
             for (int i = 0, count = elements.size(); i < count; i++) {
                 elements.set(i, elements.get(i).accept(visitor));
             }
 
-            return visitor.leave(this);
+            return visitor.leaveObjectNode(this);
         }
 
         return this;
@@ -117,14 +106,6 @@
     }
 
     /**
-     * Get the block that is this ObjectNode's literal context
-     * @return the block
-     */
-    public Block getContext() {
-        return context;
-    }
-
-    /**
      * Get the elements of this literal node
      * @return a list of elements
      */
diff --git a/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java b/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java
index 7510337..a6bc49d 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/PropertyNode.java
@@ -88,7 +88,7 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterPropertyNode(this) != null) {
             key = (PropertyKey)((Node)key).accept(visitor);
 
             if (value != null) {
@@ -103,7 +103,7 @@
                 setter = setter.accept(visitor);
             }
 
-            return visitor.leave(this);
+            return visitor.leavePropertyNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ReferenceNode.java b/nashorn/src/jdk/nashorn/internal/ir/ReferenceNode.java
deleted file mode 100644
index 3cae7b5..0000000
--- a/nashorn/src/jdk/nashorn/internal/ir/ReferenceNode.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.nashorn.internal.ir;
-
-import jdk.nashorn.internal.ir.annotations.Reference;
-import jdk.nashorn.internal.ir.visitor.NodeVisitor;
-import jdk.nashorn.internal.runtime.Source;
-
-/**
- * IR representation of a reference to another entity (function.)
- */
-public class ReferenceNode extends Node {
-    /** Node referenced. */
-    @Reference
-    private final FunctionNode reference;
-
-    /**
-     * Constructor
-     *
-     * @param source the source
-     * @param token  token
-     * @param finish finish
-     * @param reference the function node to reference
-     */
-    public ReferenceNode(final Source source, final long token, final int finish, final FunctionNode reference) {
-        super(source, token, finish);
-
-        this.reference = reference;
-    }
-
-    private ReferenceNode(final ReferenceNode referenceNode) {
-        super(referenceNode);
-
-        this.reference = referenceNode.reference;
-    }
-
-    @Override
-    protected Node copy(final CopyState cs) {
-        return new ReferenceNode(this);
-    }
-
-    @Override
-    public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            return visitor.leave(this);
-        }
-
-        return this;
-    }
-
-    @Override
-    public void toString(final StringBuilder sb) {
-        if (reference == null) {
-            sb.append("null");
-        } else {
-            reference.toString(sb);
-        }
-    }
-
-    /**
-     * Get there function node reference that this node points tp
-     * @return a function node reference
-     */
-    public FunctionNode getReference() {
-        return reference;
-    }
-
-}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java b/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java
index 35395b3..1400f39 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ReturnNode.java
@@ -100,12 +100,12 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterReturnNode(this) != null) {
             if (expression != null) {
                 expression = expression.accept(visitor);
             }
 
-            return visitor.leave(this);
+            return visitor.leaveReturnNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java b/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java
index bfc47d1..461007c 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/RuntimeNode.java
@@ -38,7 +38,7 @@
  * IR representation for a runtime call.
  *
  */
-public class RuntimeNode extends Node implements TypeOverride {
+public class RuntimeNode extends Node implements TypeOverride<RuntimeNode> {
 
     /**
      * Request enum used for meta-information about the runtime request
@@ -393,8 +393,9 @@
     }
 
     @Override
-    public void setType(final Type type) {
+    public RuntimeNode setType(final Type type) {
         this.callSiteType = type;
+        return this;
     }
 
     @Override
@@ -408,12 +409,12 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterRuntimeNode(this) != null) {
             for (int i = 0, count = args.size(); i < count; i++) {
                 args.set(i, args.get(i).accept(visitor));
             }
 
-            return visitor.leave(this);
+            return visitor.leaveRuntimeNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java b/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java
index c09a4f0..b751cdc 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/SplitNode.java
@@ -108,10 +108,10 @@
         visitor.setCurrentMethodEmitter(getMethodEmitter());
 
         try {
-            if (visitor.enter(this) != null) {
+            if (visitor.enterSplitNode(this) != null) {
                 body = body.accept(visitor);
 
-                return visitor.leave(this);
+                return visitor.leaveSplitNode(this);
             }
         } finally {
             visitor.setCurrentCompileUnit(saveCompileUnit);
diff --git a/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java b/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java
index 068398e..23d9c7e 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/SwitchNode.java
@@ -85,7 +85,7 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterSwitchNode(this) != null) {
             expression = expression.accept(visitor);
 
             for (int i = 0, count = cases.size(); i < count; i++) {
@@ -94,7 +94,7 @@
 
             //the default case is in the cases list and should not be explicitly traversed!
 
-            return visitor.leave(this);
+            return visitor.leaveSwitchNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/Symbol.java b/nashorn/src/jdk/nashorn/internal/ir/Symbol.java
index a58a7c1..603b8b0 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/Symbol.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/Symbol.java
@@ -38,31 +38,31 @@
  */
 
 public final class Symbol implements Comparable<Symbol> {
-    /** Symbol flags. Kind ordered by precedence. */
-    public static final int IS_TEMP     = 0b0000_0001;
+    /** Symbol kinds. Kind ordered by precedence. */
+    public static final int IS_TEMP     = 1;
     /** Is this Global */
-    public static final int IS_GLOBAL   = 0b0000_0010;
+    public static final int IS_GLOBAL   = 2;
     /** Is this a variable */
-    public static final int IS_VAR      = 0b0000_0011;
+    public static final int IS_VAR      = 3;
     /** Is this a parameter */
-    public static final int IS_PARAM    = 0b0000_0100;
+    public static final int IS_PARAM    = 4;
     /** Is this a constant */
-    public static final int IS_CONSTANT = 0b0000_0101;
-
-    static final int KINDMASK = 0b0000_1111;
+    public static final int IS_CONSTANT = 5;
+    /** Mask for kind flags */
+    public static final int KINDMASK = (1 << 3) - 1; // Kinds are represented by lower three bits
 
     /** Is this scope */
-    public static final int IS_SCOPE         = 0b0000_0001_0000;
+    public static final int IS_SCOPE         = 1 << 4;
     /** Is this a this symbol */
-    public static final int IS_THIS          = 0b0000_0010_0000;
+    public static final int IS_THIS          = 1 << 5;
     /** Can this symbol ever be undefined */
-    public static final int CAN_BE_UNDEFINED = 0b0000_0100_0000;
+    public static final int CAN_BE_UNDEFINED = 1 << 6;
     /** Can this symbol ever have primitive types */
-    public static final int CAN_BE_PRIMITIVE = 0b0000_1000_0000;
+    public static final int CAN_BE_PRIMITIVE = 1 << 7;
     /** Is this a let */
-    public static final int IS_LET           = 0b0001_0000_0000;
+    public static final int IS_LET           = 1 << 8;
     /** Is this an internal symbol, never represented explicitly in source code */
-    public static final int IS_INTERNAL      = 0b0010_0000_0000;
+    public static final int IS_INTERNAL      = 1 << 9;
 
     /** Null or name identifying symbol. */
     private final String name;
@@ -269,15 +269,6 @@
         return type.isCategory2() ? 2 : 1;
     }
 
-    /**
-     * Return the defining function (scope.)
-     *
-     * @return Defining function.
-     */
-    public FunctionNode findFunction() {
-        return block != null ? block.getFunction() : null;
-    }
-
     @Override
     public boolean equals(final Object other) {
         if (!(other instanceof Symbol)) {
@@ -487,27 +478,6 @@
     }
 
     /**
-     * Check if this symbol can be accessed directly with a putfield or getfield or dynamic load
-     *
-     * @param currentFunction function to check for fast scope
-     * @return true if fast scope
-     */
-    public boolean isFastScope(final FunctionNode currentFunction) {
-        if (!isScope() || !block.needsScope()) {
-            return false;
-        }
-        // Allow fast scope access if no parent function contains with or eval
-        FunctionNode func = currentFunction;
-        while (func != null) {
-            if (func.hasWith() || func.hasEval()) {
-                return false;
-            }
-            func = func.findParentFunction();
-        }
-        return true;
-    }
-
-    /**
      * Get the block in which the symbol is defined
      * @return a block
      */
@@ -651,7 +621,7 @@
      * @return true if this this is a global scope symbol
      */
     public boolean isTopLevel() {
-        return block instanceof FunctionNode && ((FunctionNode) block).isScript();
+        return block instanceof FunctionNode && ((FunctionNode) block).isProgram();
     }
 
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java b/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java
index a82991f..de33385 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/TernaryNode.java
@@ -77,11 +77,11 @@
 
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            lhs = lhs.accept(visitor);
-            rhs = rhs.accept(visitor);
-            third = third.accept(visitor);
-            return visitor.leave(this);
+        if (visitor.enterTernaryNode(this) != null) {
+            final Node newLhs = lhs().accept(visitor);
+            final Node newRhs = rhs().accept(visitor);
+            final Node newThird = third.accept(visitor);
+            return visitor.leaveTernaryNode((TernaryNode)setThird(newThird).setLHS(newLhs).setRHS(newRhs));
         }
 
         return this;
@@ -133,8 +133,12 @@
     /**
      * Reset the "third" node for this ternary expression, i.e. "z" in x ? y : z
      * @param third a node
+     * @return a node equivalent to this one except for the requested change.
      */
-    public void setThird(final Node third) {
-        this.third = third;
+    public TernaryNode setThird(final Node third) {
+        if(this.third == third) return this;
+        final TernaryNode n = (TernaryNode)clone();
+        n.third = third;
+        return n;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java b/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java
index efa63d9..ab6d59e 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/ThrowNode.java
@@ -75,9 +75,9 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterThrowNode(this) != null) {
             setExpression(expression.accept(visitor));
-            return visitor.leave(this);
+            return visitor.leaveThrowNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/TryNode.java b/nashorn/src/jdk/nashorn/internal/ir/TryNode.java
index b6aa439..7d3864b 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/TryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/TryNode.java
@@ -101,7 +101,7 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterTryNode(this) != null) {
             // Need to do first for termination analysis.
             if (finallyBody != null) {
                 finallyBody = (Block)finallyBody.accept(visitor);
@@ -115,7 +115,7 @@
             }
             this.catchBlocks = newCatchBlocks;
 
-            return visitor.leave(this);
+            return visitor.leaveTryNode(this);
         }
 
         return this;
@@ -155,6 +155,15 @@
     }
 
     /**
+     * Returns true if the specified block is the body of this try block, or any of its catch blocks.
+     * @param block the block
+     * @return true if the specified block is the body of this try block, or any of its catch blocks.
+     */
+    public boolean isChildBlock(Block block) {
+        return body == block || catchBlocks.contains(block);
+    }
+
+    /**
      * Get the catch blocks for this try block
      * @return a list of blocks
      */
diff --git a/nashorn/src/jdk/nashorn/internal/ir/TypeOverride.java b/nashorn/src/jdk/nashorn/internal/ir/TypeOverride.java
index 8321318..61c1fe2 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/TypeOverride.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/TypeOverride.java
@@ -36,15 +36,17 @@
  * by using JSType.toInt32. Especially in scenarios where the field is already stored
  * as a primitive, this will be much faster than the "object is all I see" scope
  * available in the method
+ * @param <T> the type of the node implementing the interface
  */
 
-public interface TypeOverride {
+public interface TypeOverride<T extends Node> {
     /**
      * Set the override type
      *
      * @param type  the type
+     * @return a node equivalent to this one except for the requested change.
      */
-    public void setType(final Type type);
+    public T setType(final Type type);
 
     /**
      * Returns true if this node can have a callsite override, e.g. all scope ident nodes
diff --git a/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java b/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java
index 7b608ed..d823c05 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/UnaryNode.java
@@ -41,7 +41,7 @@
  */
 public class UnaryNode extends Node implements Assignment<Node> {
     /** Right hand side argument. */
-    protected Node rhs;
+    private Node rhs;
 
     /**
      * Constructor
@@ -104,6 +104,11 @@
     }
 
     @Override
+    public Node setAssignmentDest(Node n) {
+        return setRHS(n);
+    }
+
+    @Override
     public Node getAssignmentSource() {
         return getAssignmentDest();
     }
@@ -132,9 +137,8 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            rhs = rhs.accept(visitor);
-            return visitor.leave(this);
+        if (visitor.enterUnaryNode(this) != null) {
+            return visitor.leaveUnaryNode(setRHS(rhs.accept(visitor)));
         }
 
         return this;
@@ -152,9 +156,9 @@
 
         if (isConvert) {
             convertPos = sb.length();
-            sb.append("((");
+            sb.append("(");
             sb.append(getType());
-            sb.append(")");
+            sb.append(")(");
         }
 
         if (!isPostfix && !isConvert) {
@@ -191,8 +195,6 @@
                 sb.setCharAt(convertPos, ' ');
             }
         }
-
-        //TODO - conversions still have too many parenthesis - makes --print-lower-parse hard to read
     }
 
     /**
@@ -214,10 +216,12 @@
      * @see BinaryNode
      *
      * @param rhs right hand side or expression node
+     * @return a node equivalent to this one except for the requested change.
      */
-    public void setRHS(final Node rhs) {
-        this.rhs = rhs;
+    public UnaryNode setRHS(final Node rhs) {
+        if(this.rhs == rhs) return this;
+        final UnaryNode n = (UnaryNode)clone();
+        n.rhs = rhs;
+        return n;
     }
-
-
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/VarNode.java b/nashorn/src/jdk/nashorn/internal/ir/VarNode.java
index 07b6f8e..b719c99 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/VarNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/VarNode.java
@@ -38,8 +38,8 @@
     /** Initialization expression. */
     private Node init;
 
-    /** Is this a function var node */
-    private boolean isFunctionVarNode;
+    /** Is this a var statement (as opposed to a "var" in a for loop statement) */
+    private final boolean isStatement;
 
     /**
      * Constructor
@@ -51,20 +51,34 @@
      * @param init   init node or null if just a declaration
      */
     public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init) {
+        this(source, token, finish, name, init, true);
+    }
+
+    /**
+     * Constructor
+     *
+     * @param source the source
+     * @param token  token
+     * @param finish finish
+     * @param name   name of variable
+     * @param init   init node or null if just a declaration
+     * @param isStatement if this is a var statement (true), or a for-loop initializer (false)
+     */
+    public VarNode(final Source source, final long token, final int finish, final IdentNode name, final Node init, boolean isStatement) {
         super(source, token, finish);
 
-        this.name  = name;
+        this.name  = init == null ? name : name.setIsInitializedHere();
         this.init  = init;
-        if (init != null) {
-            this.name.setIsInitializedHere();
-        }
+        this.isStatement = isStatement;
     }
 
+
     private VarNode(final VarNode varNode, final CopyState cs) {
         super(varNode);
 
         this.name = (IdentNode)cs.existingOrCopy(varNode.name);
         this.init = cs.existingOrCopy(varNode.init);
+        this.isStatement = varNode.isStatement;
     }
 
     @Override
@@ -83,6 +97,11 @@
     }
 
     @Override
+    public Node setAssignmentDest(IdentNode n) {
+        return setName(n);
+    }
+
+    @Override
     public Node getAssignmentSource() {
         return isAssignment() ? getInit() : null;
     }
@@ -127,16 +146,19 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
-            name = (IdentNode)name.accept(visitor);
-
-            if (init != null) {
-                init = init.accept(visitor);
+        if (visitor.enterVarNode(this) != null) {
+            final IdentNode newName = (IdentNode)name.accept(visitor);
+            final Node newInit = init == null ? null : init.accept(visitor);
+            final VarNode newThis;
+            if(name != newName || init != newInit) {
+                newThis = (VarNode)clone();
+                newThis.init = newInit;
+                newThis.name = newInit == null ? newName : newName.setIsInitializedHere();
+            } else {
+                newThis = this;
             }
-
-            return visitor.leave(this);
+            return visitor.leaveVarNode(newThis);
         }
-
         return this;
     }
 
@@ -162,9 +184,13 @@
     /**
      * Reset the initialization expression
      * @param init new initialization expression
+     * @return a node equivalent to this one except for the requested change.
      */
-    public void setInit(final Node init) {
-        this.init = init;
+    public VarNode setInit(final Node init) {
+        if(this.init == init) return this;
+        final VarNode n = (VarNode)clone();
+        n.init = init;
+        return n;
     }
 
     /**
@@ -179,30 +205,26 @@
      * Reset the identifier for this VarNode
      * @param name new IdentNode representing the variable being set or declared
      */
-    public void setName(final IdentNode name) {
-        this.name = name;
+    private VarNode setName(final IdentNode name) {
+        if(this.name == name) return this;
+        final VarNode n = (VarNode)clone();
+        n.name = name;
+        return n;
     }
 
     /**
-     * Check if this is a virtual assignment of a function node. Function nodes declared
-     * with a name are hoisted to the top of the scope and appear as symbols too. This is
-     * implemented by representing them as virtual VarNode assignments added to the code
-     * during lowering
-     *
-     * @see FunctionNode
-     *
-     * @return true if this is a virtual function declaration
+     * Returns true if this is a var statement (as opposed to a var initializer in a for loop).
+     * @return true if this is a var statement (as opposed to a var initializer in a for loop).
      */
-    public boolean isFunctionVarNode() {
-        return isFunctionVarNode;
+    public boolean isStatement() {
+        return isStatement;
     }
 
     /**
-     * Flag this var node as a virtual function var node assignment as described in
-     * {@link VarNode#isFunctionVarNode()}
+     * Returns true if this is a function declaration.
+     * @return true if this is a function declaration.
      */
-    public void setIsFunctionVarNode() {
-        this.isFunctionVarNode = true;
+    public boolean isFunctionDeclaration() {
+        return init instanceof FunctionNode && ((FunctionNode)init).isDeclared();
     }
-
 }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java b/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java
index c51659b..8db31c0 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/WhileNode.java
@@ -88,11 +88,11 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterWhileNode(this) != null) {
             test = test.accept(visitor);
             body = (Block)body.accept(visitor);
 
-            return visitor.leave(this);
+            return visitor.leaveWhileNode(this);
         }
         return this;
     }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/WithNode.java b/nashorn/src/jdk/nashorn/internal/ir/WithNode.java
index 52f4e9b..f5ad3b1 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/WithNode.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/WithNode.java
@@ -73,10 +73,10 @@
      */
     @Override
     public Node accept(final NodeVisitor visitor) {
-        if (visitor.enter(this) != null) {
+        if (visitor.enterWithNode(this) != null) {
             expression = expression.accept(visitor);
             body = (Block)body.accept(visitor);
-            return visitor.leave(this);
+            return visitor.leaveWithNode(this);
         }
 
         return this;
diff --git a/nashorn/src/jdk/nashorn/internal/ir/annotations/ChildNode.java b/nashorn/src/jdk/nashorn/internal/ir/annotations/ChildNode.java
deleted file mode 100644
index 181c63d..0000000
--- a/nashorn/src/jdk/nashorn/internal/ir/annotations/ChildNode.java
+++ /dev/null
@@ -1,42 +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.annotations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * This is a child node, a real node, not a reference, to an IR node that should
- * be traversed.
- * <p>
- * TODO Currently not in use.  Would make e.g. accept methods simple and unified
- * @see jdk.nashorn.internal.ir.Node
- */
-@Retention(value=RetentionPolicy.RUNTIME)
-public @interface ChildNode {
-    /** order of traversal compared to other children */
-    public int order() default -1;
-}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/annotations/ParentNode.java b/nashorn/src/jdk/nashorn/internal/ir/annotations/ParentNode.java
deleted file mode 100644
index 6a9de3a..0000000
--- a/nashorn/src/jdk/nashorn/internal/ir/annotations/ParentNode.java
+++ /dev/null
@@ -1,44 +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.annotations;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Signifies a parent of a node, i.e. node that should not be traversed if we
- * go down the AST. In automatic parsing this can be handled by @Reference
- * annotations instead, as all parents are references.
- * <p>
- * TODO The use case is automating and creating one implementation of something like
- * Node.getParent()
- *
- * @see jdk.nashorn.internal.ir.Node
- */
-@Retention(value=RetentionPolicy.RUNTIME)
-public @interface ParentNode {
-    // EMPTY
-}
diff --git a/nashorn/src/jdk/nashorn/internal/ir/annotations/Reference.java b/nashorn/src/jdk/nashorn/internal/ir/annotations/Reference.java
index 1dd002c..20c8ffc 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/annotations/Reference.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/annotations/Reference.java
@@ -33,7 +33,6 @@
  * AST traversal and cloning. Cloning currently as a rule uses
  * existingOrSame for references and otherwise existingOrCopy
  * <p>
- * TODO this could probably be automated using the @Reference annotation.
  */
 
 @Retention(value=RetentionPolicy.RUNTIME)
diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java
index 3c66aa1..7bbe383 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/ASTWriter.java
@@ -27,6 +27,7 @@
 
 import java.lang.reflect.Field;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Deque;
 import java.util.Iterator;
@@ -36,10 +37,10 @@
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.TernaryNode;
 import jdk.nashorn.internal.ir.annotations.Ignore;
-import jdk.nashorn.internal.ir.annotations.ParentNode;
 import jdk.nashorn.internal.ir.annotations.Reference;
 import jdk.nashorn.internal.parser.Token;
 import jdk.nashorn.internal.runtime.Context;
+import jdk.nashorn.internal.runtime.Debug;
 
 /**
  * AST-as-text visualizer. Sometimes you want tree form and not source
@@ -47,7 +48,6 @@
  *
  * see the flags --print-ast and --print-ast-lower
  */
-
 public final class ASTWriter {
     /** Root node from which to start the traversal */
     private final Node root;
@@ -71,12 +71,22 @@
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
-        printAST(sb, null, "root", root, 0);
+        printAST(sb, null, null, "root", root, 0);
         return sb.toString();
     }
 
+    /**
+     * Return the visited nodes in an ordered list
+     * @return the list of nodes in order
+     */
+    public Node[] toArray() {
+        final List<Node> preorder = new ArrayList<>();
+        printAST(new StringBuilder(), preorder, null, "root", root, 0);
+        return preorder.toArray(new Node[preorder.size()]);
+    }
+
     @SuppressWarnings("unchecked")
-    private void printAST(final StringBuilder sb, final Field field, final String name, final Node node, final int indent) {
+    private void printAST(final StringBuilder sb, final List<Node> preorder, final Field field, final String name, final Node node, final int indent) {
         ASTWriter.indent(sb, indent);
         if (node == null) {
             sb.append("[Object ");
@@ -85,13 +95,23 @@
             return;
         }
 
+        if (preorder != null) {
+            preorder.add(node);
+        }
+
         final boolean isReference = field != null && field.getAnnotation(Reference.class) != null;
 
         Class<?> clazz = node.getClass();
         String   type  = clazz.getName();
 
         type = type.substring(type.lastIndexOf('.') + 1, type.length());
-//        type += "@" + Debug.id(node) + "#" + node.getSymbol();
+        if (isReference) {
+            type = "ref: " + type;
+        }
+        type += "@" + Debug.id(node);
+        if (node.getSymbol() != null) {
+            type += "#" + node.getSymbol();
+        }
 
         final List<Field> children = new LinkedList<>();
 
@@ -153,9 +173,7 @@
                 append('\n');
 
             for (final Field child : children) {
-                if (child.getAnnotation(ParentNode.class) != null) {
-                    continue;
-                } else if (child.getAnnotation(Ignore.class) != null) {
+                if (child.getAnnotation(Ignore.class) != null) {
                     continue;
                 }
 
@@ -168,7 +186,7 @@
                 }
 
                 if (value instanceof Node) {
-                    printAST(sb, child, child.getName(), (Node)value, indent + 1);
+                    printAST(sb, preorder, child, child.getName(), (Node)value, indent + 1);
                 } else if (value instanceof Collection) {
                     int pos = 0;
                     ASTWriter.indent(sb, indent + 1);
@@ -180,7 +198,7 @@
                         append('\n');
 
                     for (final Node member : (Collection<Node>)value) {
-                        printAST(sb, child, child.getName() + "[" + pos++ + "]", member, indent + 2);
+                        printAST(sb, preorder, child, child.getName() + "[" + pos++ + "]", member, indent + 2);
                     }
                 }
             }
diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
index 68fb68b..a8c3c4a 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/JSONWriter.java
@@ -112,7 +112,7 @@
     }
 
     @Override
-    public Node enter(final AccessNode accessNode) {
+    public Node enterAccessNode(final AccessNode accessNode) {
         enterDefault(accessNode);
 
         type("MemberExpression");
@@ -132,7 +132,7 @@
     }
 
     @Override
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
         enterDefault(block);
 
         type("BlockStatement");
@@ -154,7 +154,7 @@
     }
 
     @Override
-    public Node enter(final BinaryNode binaryNode) {
+    public Node enterBinaryNode(final BinaryNode binaryNode) {
         enterDefault(binaryNode);
 
         final String name;
@@ -183,7 +183,7 @@
     }
 
     @Override
-    public Node enter(final BreakNode breakNode) {
+    public Node enterBreakNode(final BreakNode breakNode) {
         enterDefault(breakNode);
 
         type("BreakStatement");
@@ -201,7 +201,7 @@
     }
 
     @Override
-    public Node enter(final CallNode callNode) {
+    public Node enterCallNode(final CallNode callNode) {
         enterDefault(callNode);
 
         type("CallExpression");
@@ -217,7 +217,7 @@
     }
 
     @Override
-    public Node enter(final CaseNode caseNode) {
+    public Node enterCaseNode(final CaseNode caseNode) {
         enterDefault(caseNode);
 
         type("SwitchCase");
@@ -238,7 +238,7 @@
     }
 
     @Override
-    public Node enter(final CatchNode catchNode) {
+    public Node enterCatchNode(final CatchNode catchNode) {
         enterDefault(catchNode);
 
         type("CatchClause");
@@ -264,7 +264,7 @@
     }
 
     @Override
-    public Node enter(final ContinueNode continueNode) {
+    public Node enterContinueNode(final ContinueNode continueNode) {
         enterDefault(continueNode);
 
         type("ContinueStatement");
@@ -282,7 +282,7 @@
     }
 
     @Override
-    public Node enter(final DoWhileNode doWhileNode) {
+    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
         enterDefault(doWhileNode);
 
         type("DoWhileStatement");
@@ -299,7 +299,7 @@
     }
 
     @Override
-    public Node enter(final EmptyNode emptyNode) {
+    public Node enterEmptyNode(final EmptyNode emptyNode) {
         enterDefault(emptyNode);
 
         type("EmptyStatement");
@@ -308,7 +308,7 @@
     }
 
     @Override
-    public Node enter(final ExecuteNode executeNode) {
+    public Node enterExecuteNode(final ExecuteNode executeNode) {
         enterDefault(executeNode);
 
         type("ExpressionStatement");
@@ -321,7 +321,7 @@
     }
 
     @Override
-    public Node enter(final ForNode forNode) {
+    public Node enterForNode(final ForNode forNode) {
         enterDefault(forNode);
 
         if (forNode.isForIn() || (forNode.isForEach() && forNode.getInit() != null)) {
@@ -384,14 +384,14 @@
     }
 
     @Override
-    public Node enter(final FunctionNode functionNode) {
+    public Node enterFunctionNode(final FunctionNode functionNode) {
         enterDefault(functionNode);
 
-        final boolean program = functionNode.isScript();
+        final boolean program = functionNode.isProgram();
         final String name;
         if (program) {
             name = "Program";
-        } else if (functionNode.isStatement()) {
+        } else if (functionNode.isDeclared()) {
             name = "FunctionDeclaration";
         } else {
             name = "FunctionExpression";
@@ -419,20 +419,11 @@
         }
 
         // body consists of nested functions and statements
-        final List<FunctionNode> funcs = functionNode.getFunctions();
         final List<Node> stats = functionNode.getStatements();
-        final int size = stats.size() + funcs.size();
+        final int size = stats.size();
         int idx = 0;
         arrayStart("body");
 
-        for (final Node func : funcs) {
-            func.accept(this);
-            if (idx != (size - 1)) {
-                comma();
-            }
-            idx++;
-        }
-
         for (final Node stat : stats) {
             if (! stat.isDebug()) {
                 stat.accept(this);
@@ -448,7 +439,7 @@
     }
 
     @Override
-    public Node enter(final IdentNode identNode) {
+    public Node enterIdentNode(final IdentNode identNode) {
         enterDefault(identNode);
 
         final String name = identNode.getName();
@@ -464,7 +455,7 @@
     }
 
     @Override
-    public Node enter(final IfNode ifNode) {
+    public Node enterIfNode(final IfNode ifNode) {
         enterDefault(ifNode);
 
         type("IfStatement");
@@ -490,7 +481,7 @@
     }
 
     @Override
-    public Node enter(final IndexNode indexNode) {
+    public Node enterIndexNode(final IndexNode indexNode) {
         enterDefault(indexNode);
 
         type("MemberExpression");
@@ -510,7 +501,7 @@
     }
 
     @Override
-    public Node enter(final LabelNode labelNode) {
+    public Node enterLabelNode(final LabelNode labelNode) {
         enterDefault(labelNode);
 
         type("LabeledStatement");
@@ -527,13 +518,13 @@
     }
 
     @Override
-    public Node enter(final LineNumberNode lineNumberNode) {
+    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
         return null;
     }
 
     @SuppressWarnings("rawtypes")
     @Override
-    public Node enter(final LiteralNode literalNode) {
+    public Node enterLiteralNode(final LiteralNode literalNode) {
         enterDefault(literalNode);
 
         if (literalNode instanceof LiteralNode.ArrayLiteralNode) {
@@ -569,7 +560,7 @@
     }
 
     @Override
-    public Node enter(final ObjectNode objectNode) {
+    public Node enterObjectNode(final ObjectNode objectNode) {
         enterDefault(objectNode);
 
         type("ObjectExpression");
@@ -581,7 +572,7 @@
     }
 
     @Override
-    public Node enter(final PropertyNode propertyNode) {
+    public Node enterPropertyNode(final PropertyNode propertyNode) {
         final Node key = propertyNode.getKey();
 
         final Node value = propertyNode.getValue();
@@ -647,7 +638,7 @@
     }
 
     @Override
-    public Node enter(final ReturnNode returnNode) {
+    public Node enterReturnNode(final ReturnNode returnNode) {
         enterDefault(returnNode);
 
         type("ReturnStatement");
@@ -665,7 +656,7 @@
     }
 
     @Override
-    public Node enter(final RuntimeNode runtimeNode) {
+    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
         final RuntimeNode.Request req = runtimeNode.getRequest();
 
         if (req == RuntimeNode.Request.DEBUGGER) {
@@ -680,12 +671,12 @@
     }
 
     @Override
-    public Node enter(final SplitNode splitNode) {
+    public Node enterSplitNode(final SplitNode splitNode) {
         return null;
     }
 
     @Override
-    public Node enter(final SwitchNode switchNode) {
+    public Node enterSwitchNode(final SwitchNode switchNode) {
         enterDefault(switchNode);
 
         type("SwitchStatement");
@@ -701,7 +692,7 @@
     }
 
     @Override
-    public Node enter(final TernaryNode ternaryNode) {
+    public Node enterTernaryNode(final TernaryNode ternaryNode) {
         enterDefault(ternaryNode);
 
         type("ConditionalExpression");
@@ -722,7 +713,7 @@
     }
 
     @Override
-    public Node enter(final ThrowNode throwNode) {
+    public Node enterThrowNode(final ThrowNode throwNode) {
         enterDefault(throwNode);
 
         type("ThrowStatement");
@@ -735,7 +726,7 @@
     }
 
     @Override
-    public Node enter(final TryNode tryNode) {
+    public Node enterTryNode(final TryNode tryNode) {
         enterDefault(tryNode);
 
         type("TryStatement");
@@ -760,7 +751,7 @@
     }
 
     @Override
-    public Node enter(final UnaryNode unaryNode) {
+    public Node enterUnaryNode(final UnaryNode unaryNode) {
         enterDefault(unaryNode);
 
         final TokenType tokenType = unaryNode.tokenType();
@@ -816,7 +807,7 @@
     }
 
     @Override
-    public Node enter(final VarNode varNode) {
+    public Node enterVarNode(final VarNode varNode) {
         enterDefault(varNode);
 
         type("VariableDeclaration");
@@ -852,7 +843,7 @@
     }
 
     @Override
-    public Node enter(final WhileNode whileNode) {
+    public Node enterWhileNode(final WhileNode whileNode) {
         enterDefault(whileNode);
 
         type("WhileStatement");
@@ -869,7 +860,7 @@
     }
 
     @Override
-    public Node enter(final WithNode withNode) {
+    public Node enterWithNode(final WithNode withNode) {
         enterDefault(withNode);
 
         type("WithStatement");
diff --git a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java
index 11b8dbd..d2f40d1 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/debug/PrintVisitor.java
@@ -42,7 +42,6 @@
 import jdk.nashorn.internal.ir.LabelNode;
 import jdk.nashorn.internal.ir.LineNumberNode;
 import jdk.nashorn.internal.ir.Node;
-import jdk.nashorn.internal.ir.ReferenceNode;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.SplitNode;
@@ -138,13 +137,13 @@
      * Visits.
      */
     @Override
-    public Node enter(final AccessNode accessNode) {
+    public Node enterAccessNode(final AccessNode accessNode) {
         accessNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
         sb.append(' ');
         sb.append('{');
 
@@ -152,21 +151,6 @@
 
         final boolean isFunction = block instanceof FunctionNode;
 
-        if (isFunction) {
-            final FunctionNode       function  = (FunctionNode)block;
-            final List<FunctionNode> functions = function.getFunctions();
-
-            for (final FunctionNode f : functions) {
-                sb.append(EOLN);
-                indent();
-                f.accept(this);
-            }
-
-            if (!functions.isEmpty()) {
-                sb.append(EOLN);
-            }
-        }
-
         final List<Node> statements = block.getStatements();
 
         boolean lastLineNumber = false;
@@ -224,25 +208,25 @@
     }
 
     @Override
-    public Node enter(final BreakNode breakNode) {
+    public Node enterBreakNode(final BreakNode breakNode) {
         breakNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final CallNode callNode) {
+    public Node enterCallNode(final CallNode callNode) {
         callNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final ContinueNode continueNode) {
+    public Node enterContinueNode(final ContinueNode continueNode) {
         continueNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final DoWhileNode doWhileNode) {
+    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
         sb.append("do");
         doWhileNode.getBody().accept(this);
         sb.append(' ');
@@ -252,7 +236,7 @@
     }
 
     @Override
-    public Node enter(final ExecuteNode executeNode) {
+    public Node enterExecuteNode(final ExecuteNode executeNode) {
         final Node expression = executeNode.getExpression();
 
         if (expression instanceof UnaryNode) {
@@ -265,7 +249,7 @@
     }
 
     @Override
-    public Node enter(final ForNode forNode) {
+    public Node enterForNode(final ForNode forNode) {
         forNode.toString(sb);
         forNode.getBody().accept(this);
 
@@ -273,15 +257,15 @@
     }
 
     @Override
-    public Node enter(final FunctionNode functionNode) {
+    public Node enterFunctionNode(final FunctionNode functionNode) {
         functionNode.toString(sb);
-        enter((Block)functionNode);
+        enterBlock(functionNode);
 
         return null;
     }
 
     @Override
-    public Node enter(final IfNode ifNode) {
+    public Node enterIfNode(final IfNode ifNode) {
         ifNode.toString(sb);
         ifNode.getPass().accept(this);
 
@@ -296,13 +280,13 @@
     }
 
     @Override
-    public Node enter(final IndexNode indexNode) {
+    public Node enterIndexNode(final IndexNode indexNode) {
         indexNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final LabelNode labeledNode) {
+    public Node enterLabelNode(final LabelNode labeledNode) {
         indent -= TABWIDTH;
         indent();
         indent += TABWIDTH;
@@ -313,7 +297,7 @@
     }
 
     @Override
-    public Node enter(final LineNumberNode lineNumberNode) {
+    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
         if (printLineNumbers) {
             lineNumberNode.toString(sb);
         }
@@ -323,25 +307,19 @@
 
 
     @Override
-    public Node enter(final ReferenceNode referenceNode) {
-        referenceNode.toString(sb);
-        return null;
-    }
-
-    @Override
-    public Node enter(final ReturnNode returnNode) {
+    public Node enterReturnNode(final ReturnNode returnNode) {
         returnNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final RuntimeNode runtimeNode) {
+    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
         runtimeNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final SplitNode splitNode) {
+    public Node enterSplitNode(final SplitNode splitNode) {
         splitNode.toString(sb);
         sb.append(EOLN);
         indent += TABWIDTH;
@@ -350,7 +328,7 @@
     }
 
     @Override
-    public Node leave(final SplitNode splitNode) {
+    public Node leaveSplitNode(final SplitNode splitNode) {
         sb.append("</split>");
         sb.append(EOLN);
         indent -= TABWIDTH;
@@ -359,7 +337,7 @@
     }
 
     @Override
-    public Node enter(final SwitchNode switchNode) {
+    public Node enterSwitchNode(final SwitchNode switchNode) {
         switchNode.toString(sb);
         sb.append(" {");
 
@@ -383,13 +361,13 @@
    }
 
     @Override
-    public Node enter(final ThrowNode throwNode) {
+    public Node enterThrowNode(final ThrowNode throwNode) {
         throwNode.toString(sb);
         return null;
     }
 
     @Override
-    public Node enter(final TryNode tryNode) {
+    public Node enterTryNode(final TryNode tryNode) {
         tryNode.toString(sb);
         tryNode.getBody().accept(this);
 
@@ -412,13 +390,19 @@
     }
 
     @Override
-    public Node enter(final VarNode varNode) {
-        varNode.toString(sb);
+    public Node enterVarNode(final VarNode varNode) {
+        sb.append("var ");
+        varNode.getName().toString(sb);
+        final Node init = varNode.getInit();
+        if(init != null) {
+            sb.append(" = ");
+            init.accept(this);
+        }
         return null;
     }
 
     @Override
-    public Node enter(final WhileNode whileNode) {
+    public Node enterWhileNode(final WhileNode whileNode) {
         whileNode.toString(sb);
         whileNode.getBody().accept(this);
 
@@ -426,7 +410,7 @@
     }
 
     @Override
-    public Node enter(final WithNode withNode) {
+    public Node enterWithNode(final WithNode withNode) {
         withNode.toString(sb);
         withNode.getBody().accept(this);
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java
index 6856282..0021b7d 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeOperatorVisitor.java
@@ -53,7 +53,7 @@
     }
 
     @Override
-    public final Node enter(final UnaryNode unaryNode) {
+    public final Node enterUnaryNode(final UnaryNode unaryNode) {
         switch (unaryNode.tokenType()) {
         case ADD:
             return enterADD(unaryNode);
@@ -81,12 +81,12 @@
         case INCPOSTFIX:
             return enterDECINC(unaryNode);
         default:
-            return super.enter(unaryNode);
+            return super.enterUnaryNode(unaryNode);
         }
     }
 
     @Override
-    public final Node leave(final UnaryNode unaryNode) {
+    public final Node leaveUnaryNode(final UnaryNode unaryNode) {
         switch (unaryNode.tokenType()) {
         case ADD:
             return leaveADD(unaryNode);
@@ -114,12 +114,12 @@
         case INCPOSTFIX:
             return leaveDECINC(unaryNode);
         default:
-            return super.leave(unaryNode);
+            return super.leaveUnaryNode(unaryNode);
         }
     }
 
     @Override
-    public final Node enter(final BinaryNode binaryNode) {
+    public final Node enterBinaryNode(final BinaryNode binaryNode) {
         switch (binaryNode.tokenType()) {
         case ADD:
             return enterADD(binaryNode);
@@ -198,12 +198,12 @@
         case SUB:
             return enterSUB(binaryNode);
         default:
-            return super.enter(binaryNode);
+            return super.enterBinaryNode(binaryNode);
         }
     }
 
     @Override
-    public final Node leave(final BinaryNode binaryNode) {
+    public final Node leaveBinaryNode(final BinaryNode binaryNode) {
         switch (binaryNode.tokenType()) {
         case ADD:
             return leaveADD(binaryNode);
@@ -282,7 +282,7 @@
         case SUB:
             return leaveSUB(binaryNode);
         default:
-            return super.leave(binaryNode);
+            return super.leaveBinaryNode(binaryNode);
         }
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
index 9ce6fd0..f10d8c0 100644
--- a/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
+++ b/nashorn/src/jdk/nashorn/internal/ir/visitor/NodeVisitor.java
@@ -49,7 +49,6 @@
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ObjectNode;
 import jdk.nashorn.internal.ir.PropertyNode;
-import jdk.nashorn.internal.ir.ReferenceNode;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.SplitNode;
@@ -153,7 +152,7 @@
      * @param  accessNode the node
      * @return processed node, null if traversal should end, null if traversal should end
      */
-    public Node enter(final AccessNode accessNode) {
+    public Node enterAccessNode(final AccessNode accessNode) {
         return enterDefault(accessNode);
     }
 
@@ -163,7 +162,7 @@
      * @param  accessNode the node
      * @return processed node, null if traversal should end
      */
-    public Node leave(final AccessNode accessNode) {
+    public Node leaveAccessNode(final AccessNode accessNode) {
         return leaveDefault(accessNode);
     }
 
@@ -173,7 +172,7 @@
      * @param  block     the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final Block block) {
+    public Node enterBlock(final Block block) {
         return enterDefault(block);
     }
 
@@ -183,7 +182,7 @@
      * @param  block the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final Block block) {
+    public Node leaveBlock(final Block block) {
         return leaveDefault(block);
     }
 
@@ -193,7 +192,7 @@
      * @param  binaryNode  the node
      * @return processed   node
      */
-    public Node enter(final BinaryNode binaryNode) {
+    public Node enterBinaryNode(final BinaryNode binaryNode) {
         return enterDefault(binaryNode);
     }
 
@@ -203,7 +202,7 @@
      * @param  binaryNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final BinaryNode binaryNode) {
+    public Node leaveBinaryNode(final BinaryNode binaryNode) {
         return leaveDefault(binaryNode);
     }
 
@@ -213,7 +212,7 @@
      * @param  breakNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final BreakNode breakNode) {
+    public Node enterBreakNode(final BreakNode breakNode) {
         return enterDefault(breakNode);
     }
 
@@ -223,7 +222,7 @@
      * @param  breakNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final BreakNode breakNode) {
+    public Node leaveBreakNode(final BreakNode breakNode) {
         return leaveDefault(breakNode);
     }
 
@@ -233,7 +232,7 @@
      * @param  callNode  the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final CallNode callNode) {
+    public Node enterCallNode(final CallNode callNode) {
         return enterDefault(callNode);
     }
 
@@ -243,7 +242,7 @@
      * @param  callNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final CallNode callNode) {
+    public Node leaveCallNode(final CallNode callNode) {
         return leaveDefault(callNode);
     }
 
@@ -253,7 +252,7 @@
      * @param  caseNode  the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final CaseNode caseNode) {
+    public Node enterCaseNode(final CaseNode caseNode) {
         return enterDefault(caseNode);
     }
 
@@ -263,7 +262,7 @@
      * @param  caseNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final CaseNode caseNode) {
+    public Node leaveCaseNode(final CaseNode caseNode) {
         return leaveDefault(caseNode);
     }
 
@@ -273,7 +272,7 @@
      * @param  catchNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final CatchNode catchNode) {
+    public Node enterCatchNode(final CatchNode catchNode) {
         return enterDefault(catchNode);
     }
 
@@ -283,7 +282,7 @@
      * @param  catchNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final CatchNode catchNode) {
+    public Node leaveCatchNode(final CatchNode catchNode) {
         return leaveDefault(catchNode);
     }
 
@@ -293,7 +292,7 @@
      * @param  continueNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final ContinueNode continueNode) {
+    public Node enterContinueNode(final ContinueNode continueNode) {
         return enterDefault(continueNode);
     }
 
@@ -303,7 +302,7 @@
      * @param  continueNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final ContinueNode continueNode) {
+    public Node leaveContinueNode(final ContinueNode continueNode) {
         return leaveDefault(continueNode);
     }
 
@@ -313,7 +312,7 @@
      * @param  doWhileNode the node
      * @return processed   node
      */
-    public Node enter(final DoWhileNode doWhileNode) {
+    public Node enterDoWhileNode(final DoWhileNode doWhileNode) {
         return enterDefault(doWhileNode);
     }
 
@@ -323,7 +322,7 @@
      * @param  doWhileNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final DoWhileNode doWhileNode) {
+    public Node leaveDoWhileNode(final DoWhileNode doWhileNode) {
         return leaveDefault(doWhileNode);
     }
 
@@ -333,7 +332,7 @@
      * @param  emptyNode   the node
      * @return processed   node
      */
-    public Node enter(final EmptyNode emptyNode) {
+    public Node enterEmptyNode(final EmptyNode emptyNode) {
         return enterDefault(emptyNode);
     }
 
@@ -343,7 +342,7 @@
      * @param  emptyNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final EmptyNode emptyNode) {
+    public Node leaveEmptyNode(final EmptyNode emptyNode) {
         return leaveDefault(emptyNode);
     }
 
@@ -353,7 +352,7 @@
      * @param  executeNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final ExecuteNode executeNode) {
+    public Node enterExecuteNode(final ExecuteNode executeNode) {
         return enterDefault(executeNode);
     }
 
@@ -363,7 +362,7 @@
      * @param  executeNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final ExecuteNode executeNode) {
+    public Node leaveExecuteNode(final ExecuteNode executeNode) {
         return leaveDefault(executeNode);
     }
 
@@ -373,7 +372,7 @@
      * @param  forNode   the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final ForNode forNode) {
+    public Node enterForNode(final ForNode forNode) {
         return enterDefault(forNode);
     }
 
@@ -383,7 +382,7 @@
      * @param  forNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final ForNode forNode) {
+    public Node leaveForNode(final ForNode forNode) {
         return leaveDefault(forNode);
     }
 
@@ -393,7 +392,7 @@
      * @param  functionNode the node
      * @return processed    node
      */
-    public Node enter(final FunctionNode functionNode) {
+    public Node enterFunctionNode(final FunctionNode functionNode) {
         return enterDefault(functionNode);
     }
 
@@ -403,7 +402,7 @@
      * @param  functionNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final FunctionNode functionNode) {
+    public Node leaveFunctionNode(final FunctionNode functionNode) {
         return leaveDefault(functionNode);
     }
 
@@ -413,7 +412,7 @@
      * @param  identNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final IdentNode identNode) {
+    public Node enterIdentNode(final IdentNode identNode) {
         return enterDefault(identNode);
     }
 
@@ -423,7 +422,7 @@
      * @param  identNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final IdentNode identNode) {
+    public Node leaveIdentNode(final IdentNode identNode) {
         return leaveDefault(identNode);
     }
 
@@ -433,7 +432,7 @@
      * @param  ifNode    the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final IfNode ifNode) {
+    public Node enterIfNode(final IfNode ifNode) {
         return enterDefault(ifNode);
     }
 
@@ -443,7 +442,7 @@
      * @param  ifNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final IfNode ifNode) {
+    public Node leaveIfNode(final IfNode ifNode) {
         return leaveDefault(ifNode);
     }
 
@@ -453,7 +452,7 @@
      * @param  indexNode  the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final IndexNode indexNode) {
+    public Node enterIndexNode(final IndexNode indexNode) {
         return enterDefault(indexNode);
     }
 
@@ -463,7 +462,7 @@
      * @param  indexNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final IndexNode indexNode) {
+    public Node leaveIndexNode(final IndexNode indexNode) {
         return leaveDefault(indexNode);
     }
 
@@ -473,7 +472,7 @@
      * @param  labelNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final LabelNode labelNode) {
+    public Node enterLabelNode(final LabelNode labelNode) {
         return enterDefault(labelNode);
     }
 
@@ -483,7 +482,7 @@
      * @param  labelNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final LabelNode labelNode) {
+    public Node leaveLabelNode(final LabelNode labelNode) {
         return leaveDefault(labelNode);
     }
 
@@ -493,7 +492,7 @@
      * @param  lineNumberNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final LineNumberNode lineNumberNode) {
+    public Node enterLineNumberNode(final LineNumberNode lineNumberNode) {
         return enterDefault(lineNumberNode);
     }
 
@@ -503,7 +502,7 @@
      * @param  lineNumberNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final LineNumberNode lineNumberNode) {
+    public Node leaveLineNumberNode(final LineNumberNode lineNumberNode) {
         return leaveDefault(lineNumberNode);
     }
 
@@ -513,8 +512,7 @@
      * @param  literalNode the node
      * @return processed   node
      */
-    @SuppressWarnings("rawtypes")
-    public Node enter(final LiteralNode literalNode) {
+    public Node enterLiteralNode(final LiteralNode<?> literalNode) {
         return enterDefault(literalNode);
     }
 
@@ -524,8 +522,7 @@
      * @param  literalNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    @SuppressWarnings("rawtypes")
-    public Node leave(final LiteralNode literalNode) {
+    public Node leaveLiteralNode(final LiteralNode<?> literalNode) {
         return leaveDefault(literalNode);
     }
 
@@ -535,7 +532,7 @@
      * @param  objectNode the node
      * @return processed  node
      */
-    public Node enter(final ObjectNode objectNode) {
+    public Node enterObjectNode(final ObjectNode objectNode) {
         return enterDefault(objectNode);
     }
 
@@ -545,7 +542,7 @@
      * @param  objectNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final ObjectNode objectNode) {
+    public Node leaveObjectNode(final ObjectNode objectNode) {
         return leaveDefault(objectNode);
     }
 
@@ -555,7 +552,7 @@
      * @param  propertyNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final PropertyNode propertyNode) {
+    public Node enterPropertyNode(final PropertyNode propertyNode) {
         return enterDefault(propertyNode);
     }
 
@@ -565,37 +562,17 @@
      * @param  propertyNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final PropertyNode propertyNode) {
+    public Node leavePropertyNode(final PropertyNode propertyNode) {
         return leaveDefault(propertyNode);
     }
 
     /**
-     * Callback for entering a ReferenceNode
-     *
-     * @param  referenceNode the node
-     * @return processed node, null if traversal should end
-     */
-    public Node enter(final ReferenceNode referenceNode) {
-        return enterDefault(referenceNode);
-    }
-
-    /**
-     * Callback for leaving a ReferenceNode
-     *
-     * @param  referenceNode the node
-     * @return processed node, which will replace the original one, or the original node
-     */
-    public Node leave(final ReferenceNode referenceNode) {
-        return leaveDefault(referenceNode);
-    }
-
-    /**
      * Callback for entering a ReturnNode
      *
      * @param  returnNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final ReturnNode returnNode) {
+    public Node enterReturnNode(final ReturnNode returnNode) {
         return enterDefault(returnNode);
     }
 
@@ -605,7 +582,7 @@
      * @param  returnNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final ReturnNode returnNode) {
+    public Node leaveReturnNode(final ReturnNode returnNode) {
         return leaveDefault(returnNode);
     }
 
@@ -615,7 +592,7 @@
      * @param  runtimeNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final RuntimeNode runtimeNode) {
+    public Node enterRuntimeNode(final RuntimeNode runtimeNode) {
         return enterDefault(runtimeNode);
     }
 
@@ -625,7 +602,7 @@
      * @param  runtimeNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final RuntimeNode runtimeNode) {
+    public Node leaveRuntimeNode(final RuntimeNode runtimeNode) {
         return leaveDefault(runtimeNode);
     }
 
@@ -635,7 +612,7 @@
      * @param  splitNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final SplitNode splitNode) {
+    public Node enterSplitNode(final SplitNode splitNode) {
         return enterDefault(splitNode);
     }
 
@@ -645,7 +622,7 @@
      * @param  splitNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final SplitNode splitNode) {
+    public Node leaveSplitNode(final SplitNode splitNode) {
         return leaveDefault(splitNode);
     }
 
@@ -655,7 +632,7 @@
      * @param  switchNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final SwitchNode switchNode) {
+    public Node enterSwitchNode(final SwitchNode switchNode) {
         return enterDefault(switchNode);
     }
 
@@ -665,7 +642,7 @@
      * @param  switchNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final SwitchNode switchNode) {
+    public Node leaveSwitchNode(final SwitchNode switchNode) {
         return leaveDefault(switchNode);
     }
 
@@ -675,7 +652,7 @@
      * @param  ternaryNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final TernaryNode ternaryNode) {
+    public Node enterTernaryNode(final TernaryNode ternaryNode) {
         return enterDefault(ternaryNode);
     }
 
@@ -685,7 +662,7 @@
      * @param  ternaryNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final TernaryNode ternaryNode) {
+    public Node leaveTernaryNode(final TernaryNode ternaryNode) {
         return leaveDefault(ternaryNode);
     }
 
@@ -695,7 +672,7 @@
      * @param  throwNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final ThrowNode throwNode) {
+    public Node enterThrowNode(final ThrowNode throwNode) {
         return enterDefault(throwNode);
     }
 
@@ -705,7 +682,7 @@
      * @param  throwNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final ThrowNode throwNode) {
+    public Node leaveThrowNode(final ThrowNode throwNode) {
         return leaveDefault(throwNode);
     }
 
@@ -715,7 +692,7 @@
      * @param  tryNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final TryNode tryNode) {
+    public Node enterTryNode(final TryNode tryNode) {
         return enterDefault(tryNode);
     }
 
@@ -725,7 +702,7 @@
      * @param  tryNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final TryNode tryNode) {
+    public Node leaveTryNode(final TryNode tryNode) {
         return leaveDefault(tryNode);
     }
 
@@ -735,7 +712,7 @@
      * @param  unaryNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final UnaryNode unaryNode) {
+    public Node enterUnaryNode(final UnaryNode unaryNode) {
         return enterDefault(unaryNode);
     }
 
@@ -745,7 +722,7 @@
      * @param  unaryNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final UnaryNode unaryNode) {
+    public Node leaveUnaryNode(final UnaryNode unaryNode) {
         return leaveDefault(unaryNode);
     }
 
@@ -755,7 +732,7 @@
      * @param  varNode   the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final VarNode varNode) {
+    public Node enterVarNode(final VarNode varNode) {
         return enterDefault(varNode);
     }
 
@@ -765,7 +742,7 @@
      * @param  varNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final VarNode varNode) {
+    public Node leaveVarNode(final VarNode varNode) {
         return leaveDefault(varNode);
     }
 
@@ -775,7 +752,7 @@
      * @param  whileNode the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final WhileNode whileNode) {
+    public Node enterWhileNode(final WhileNode whileNode) {
         return enterDefault(whileNode);
     }
 
@@ -785,7 +762,7 @@
      * @param  whileNode the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final WhileNode whileNode) {
+    public Node leaveWhileNode(final WhileNode whileNode) {
         return leaveDefault(whileNode);
     }
 
@@ -795,7 +772,7 @@
      * @param  withNode  the node
      * @return processed node, null if traversal should end
      */
-    public Node enter(final WithNode withNode) {
+    public Node enterWithNode(final WithNode withNode) {
         return enterDefault(withNode);
     }
 
@@ -805,7 +782,7 @@
      * @param  withNode  the node
      * @return processed node, which will replace the original one, or the original node
      */
-    public Node leave(final WithNode withNode) {
+    public Node leaveWithNode(final WithNode withNode) {
         return leaveDefault(withNode);
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/objects/Global.java b/nashorn/src/jdk/nashorn/internal/objects/Global.java
index b89d207..e079248 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/Global.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/Global.java
@@ -34,6 +34,9 @@
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.ref.SoftReference;
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
@@ -256,13 +259,29 @@
     @Property(name = "Packages", attributes = Attribute.NOT_ENUMERABLE)
     public volatile Object packages;
 
+    /** Nashorn extension: Java access - global.com */
+    @Property(attributes = Attribute.NOT_ENUMERABLE)
+    public volatile Object com;
+
+    /** Nashorn extension: Java access - global.edu */
+    @Property(attributes = Attribute.NOT_ENUMERABLE)
+    public volatile Object edu;
+
     /** Nashorn extension: Java access - global.java */
     @Property(attributes = Attribute.NOT_ENUMERABLE)
     public volatile Object java;
 
+    /** Nashorn extension: Java access - global.javafx */
+    @Property(attributes = Attribute.NOT_ENUMERABLE)
+    public volatile Object javafx;
+
     /** Nashorn extension: Java access - global.javax */
     @Property(attributes = Attribute.NOT_ENUMERABLE)
-    public Object javax;
+    public volatile Object javax;
+
+    /** Nashorn extension: Java access - global.org */
+    @Property(attributes = Attribute.NOT_ENUMERABLE)
+    public volatile Object org;
 
     /** Nashorn extension: Java access - global.javaImporter */
     @Property(name = "JavaImporter", attributes = Attribute.NOT_ENUMERABLE)
@@ -317,8 +336,12 @@
     private ScriptFunction builtinTypeError;
     private ScriptFunction builtinURIError;
     private ScriptObject   builtinPackages;
+    private ScriptObject   builtinCom;
+    private ScriptObject   builtinEdu;
     private ScriptObject   builtinJava;
+    private ScriptObject   builtinJavafx;
     private ScriptObject   builtinJavax;
+    private ScriptObject   builtinOrg;
     private ScriptObject   builtinJavaImporter;
     private ScriptObject   builtinJavaApi;
     private ScriptObject   builtinArrayBuffer;
@@ -1479,8 +1502,12 @@
     private void initJavaAccess() {
         final ScriptObject objectProto = getObjectPrototype();
         this.builtinPackages = new NativeJavaPackage("", objectProto);
+        this.builtinCom = new NativeJavaPackage("com", objectProto);
+        this.builtinEdu = new NativeJavaPackage("edu", objectProto);
         this.builtinJava = new NativeJavaPackage("java", objectProto);
+        this.builtinJavafx = new NativeJavaPackage("javafx", objectProto);
         this.builtinJavax = new NativeJavaPackage("javax", objectProto);
+        this.builtinOrg = new NativeJavaPackage("org", objectProto);
         this.builtinJavaImporter = initConstructor("JavaImporter");
         this.builtinJavaApi = initConstructor("Java");
     }
@@ -1503,8 +1530,10 @@
         addOwnProperty("echo", Attribute.NOT_ENUMERABLE, value);
 
         // Nashorn extension: global.$OPTIONS (scripting-mode-only)
-        value = context.getEnv();
-        addOwnProperty("$OPTIONS", Attribute.NOT_ENUMERABLE, value);
+        final ScriptObject options = newEmptyInstance();
+        final ScriptEnvironment scriptEnv = context.getEnv();
+        copyOptions(options, scriptEnv);
+        addOwnProperty("$OPTIONS", Attribute.NOT_ENUMERABLE, options);
 
         // Nashorn extension: global.$ENV (scripting-mode-only)
         if (System.getSecurityManager() == null) {
@@ -1523,6 +1552,22 @@
         addOwnProperty(ScriptingFunctions.EXIT_NAME, Attribute.NOT_ENUMERABLE, UNDEFINED);
     }
 
+    private static void copyOptions(final ScriptObject options, final ScriptEnvironment scriptEnv) {
+        AccessController.doPrivileged(new PrivilegedAction<Void>() {
+            @Override
+            public Void run() {
+                for (Field f : scriptEnv.getClass().getFields()) {
+                    try {
+                        options.set(f.getName(), f.get(scriptEnv), false);
+                    } catch (final IllegalArgumentException | IllegalAccessException exp) {
+                        throw new RuntimeException(exp);
+                    }
+                }
+                return null;
+            }
+        });
+    }
+
     private void initTypedArray() {
         this.builtinArrayBuffer       = initConstructor("ArrayBuffer");
         this.builtinInt8Array         = initConstructor("Int8Array");
@@ -1545,8 +1590,12 @@
         this.function          = this.builtinFunction;
         this.jsadapter         = this.builtinJSAdapter;
         this.json              = this.builtinJSON;
+        this.com               = this.builtinCom;
+        this.edu               = this.builtinEdu;
         this.java              = this.builtinJava;
+        this.javafx            = this.builtinJavafx;
         this.javax             = this.builtinJavax;
+        this.org               = this.builtinOrg;
         this.javaImporter      = this.builtinJavaImporter;
         this.javaApi           = this.builtinJavaApi;
         this.math              = this.builtinMath;
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeArray.java b/nashorn/src/jdk/nashorn/internal/objects/NativeArray.java
index 31c1d97..16e237b 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeArray.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeArray.java
@@ -605,7 +605,7 @@
             final boolean strict    = sobj.isStrictContext();
 
             if (bulkable(sobj)) {
-                return ((NativeArray)sobj).getArray().pop();
+                return sobj.getArray().pop();
             }
 
             final long len = JSType.toUint32(sobj.getLength());
@@ -725,7 +725,7 @@
             first = sobj.get(0);
 
             if (bulkable(sobj)) {
-                ((NativeArray) sobj).getArray().shiftLeft(1);
+                sobj.getArray().shiftLeft(1);
             } else {
                 for (long k = 1; k < len; k++) {
                     sobj.set(k - 1, sobj.get(k), strict);
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeDebug.java b/nashorn/src/jdk/nashorn/internal/objects/NativeDebug.java
index 3d6fcbc..b311981 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeDebug.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeDebug.java
@@ -66,7 +66,7 @@
     public static Object getContext(final Object self) {
         final SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
-            sm.checkPermission(new RuntimePermission("getNashornContext"));
+            sm.checkPermission(new RuntimePermission("nashorn.getContext"));
         }
         return Global.getThisContext();
     }
@@ -162,21 +162,6 @@
     }
 
     /**
-     * Nashorn extension: get invocation handle from {@link ScriptFunction}
-     *
-     * @param self self reference
-     * @param obj script function
-     * @return the invocation handle for the given ScriptFunction
-     */
-    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
-    public static Object methodHandle(final Object self, final Object obj) {
-        if (obj instanceof ScriptFunction) {
-            return ((ScriptFunction)obj).getInvokeHandle();
-        }
-        return UNDEFINED;
-    }
-
-    /**
      * Check object identity comparison regardless of type
      *
      * @param self self reference
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeError.java b/nashorn/src/jdk/nashorn/internal/objects/NativeError.java
index 8cea0c7..433f931 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeError.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeError.java
@@ -317,7 +317,7 @@
             return name;
         }
         // Step 10 : return name + ": " + msg
-        return (String)name + ": " + (String)msg;
+        return name + ": " + msg;
     }
 
     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java b/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java
index fc16962..2519c02 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeJava.java
@@ -223,6 +223,23 @@
     }
 
     /**
+     * Returns name of a java type {@link StaticClass}.
+     * @param self not used
+     * @param type the type whose name is returned
+     * @return name of the given type
+     */
+    @Function(attributes = Attribute.NOT_ENUMERABLE, where = Where.CONSTRUCTOR)
+    public static Object typeName(final Object self, final Object type) {
+        if (type instanceof StaticClass) {
+            return ((StaticClass)type).getRepresentedClass().getName();
+        } else if (type instanceof Class) {
+            return ((Class<?>)type).getName();
+        } else {
+            return UNDEFINED;
+        }
+    }
+
+    /**
      * Given a JavaScript array and a Java type, returns a Java array with the same initial contents, and with the
      * specified component type. Example:
      * <pre>
diff --git a/nashorn/src/jdk/nashorn/internal/objects/NativeString.java b/nashorn/src/jdk/nashorn/internal/objects/NativeString.java
index b2b1cf2..5f48ad9 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeString.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeString.java
@@ -122,6 +122,19 @@
         return value.length();
     }
 
+    // This is to support length as method call as well.
+    @Override
+    protected GuardedInvocation findGetMethod(final CallSiteDescriptor desc, final LinkRequest request, final String operator) {
+        final String name = desc.getNameToken(2);
+
+        // if str.length(), then let the bean linker handle it
+        if ("length".equals(name) && "getMethod".equals(operator)) {
+            return null;
+        }
+
+        return super.findGetMethod(desc, request, operator);
+    }
+
     // This is to provide array-like access to string characters without creating a NativeString wrapper.
     @Override
     protected GuardedInvocation findGetIndexMethod(final CallSiteDescriptor desc, final LinkRequest request) {
diff --git a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java
index 0514bd2..46b353f 100644
--- a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java
+++ b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionImpl.java
@@ -31,6 +31,7 @@
 import jdk.nashorn.internal.runtime.GlobalFunctions;
 import jdk.nashorn.internal.runtime.Property;
 import jdk.nashorn.internal.runtime.PropertyMap;
+import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData;
 import jdk.nashorn.internal.runtime.ScriptFunction;
 import jdk.nashorn.internal.runtime.ScriptFunctionData;
 import jdk.nashorn.internal.runtime.ScriptObject;
@@ -86,8 +87,8 @@
      * @param builtin is this a built-in function
      * @param isConstructor can the function be used as a constructor (most can; some built-ins are restricted).
      */
-    ScriptFunctionImpl(final String name, final MethodHandle methodHandle, final ScriptObject scope, final MethodHandle[] specs, final boolean strict, final boolean builtin, final boolean isConstructor) {
-        super(name, methodHandle, getMap(strict), scope, specs, strict, builtin, isConstructor);
+    ScriptFunctionImpl(final String name, final MethodHandle methodHandle, final ScriptObject scope, final MethodHandle[] specs, final boolean isStrict, final boolean isBuiltin, final boolean isConstructor) {
+        super(name, methodHandle, getMap(isStrict), scope, specs, isStrict, isBuiltin, isConstructor);
         init();
     }
 
@@ -95,14 +96,10 @@
      * Constructor called by (compiler) generated code for {@link ScriptObject}s.
      *
      * @param data static function data
-     * @param methodHandle handle for invocation
      * @param scope scope object
-     * @param allocator instance constructor for function
      */
-    public ScriptFunctionImpl(final MethodHandle methodHandle, final ScriptFunctionData data, final ScriptObject scope, final MethodHandle allocator) {
+    public ScriptFunctionImpl(final RecompilableScriptFunctionData data, final ScriptObject scope) {
         super(data, getMap(data.isStrict()), scope);
-        // Set method handles in script data
-        data.setMethodHandles(methodHandle, allocator);
         init();
     }
 
diff --git a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionTrampolineImpl.java b/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionTrampolineImpl.java
deleted file mode 100644
index 4d3df50..0000000
--- a/nashorn/src/jdk/nashorn/internal/objects/ScriptFunctionTrampolineImpl.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package jdk.nashorn.internal.objects;
-
-import static jdk.nashorn.internal.lookup.Lookup.MH;
-
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
-import jdk.nashorn.internal.codegen.CompilationException;
-import jdk.nashorn.internal.codegen.Compiler;
-import jdk.nashorn.internal.codegen.FunctionSignature;
-import jdk.nashorn.internal.codegen.types.Type;
-import jdk.nashorn.internal.ir.FunctionNode;
-import jdk.nashorn.internal.runtime.CodeInstaller;
-import jdk.nashorn.internal.runtime.Context;
-import jdk.nashorn.internal.runtime.ScriptEnvironment;
-import jdk.nashorn.internal.runtime.ScriptFunction;
-import jdk.nashorn.internal.runtime.ScriptFunctionData;
-import jdk.nashorn.internal.runtime.ScriptObject;
-
-/**
- * A trampoline is a promise to compile a {@link ScriptFunction} later. It just looks like
- * the call to the script function, but when invoked it will compile the script function
- * (in a new compile unit) and invoke it
- */
-public final class ScriptFunctionTrampolineImpl extends ScriptFunctionImpl {
-
-    private CodeInstaller<ScriptEnvironment> installer;
-
-    /** Function node to lazily recompile when trampoline is hit */
-    private FunctionNode functionNode;
-
-    /**
-     * Constructor
-     *
-     * @param installer    opaque code installer from context
-     * @param functionNode function node to lazily compile when trampoline is hit
-     * @param data         {@link ScriptFunctionData} for function
-     * @param scope        scope
-     * @param allocator    allocator
-     */
-    public ScriptFunctionTrampolineImpl(final CodeInstaller<ScriptEnvironment> installer, final FunctionNode functionNode, final ScriptFunctionData data, final ScriptObject scope, final MethodHandle allocator) {
-        super(null, data, scope, allocator);
-
-        this.installer    = installer;
-        this.functionNode = functionNode;
-
-        data.setMethodHandles(makeTrampoline(), allocator);
-    }
-
-    private final MethodHandle makeTrampoline() {
-        final MethodType mt =
-            new FunctionSignature(
-                true,
-                functionNode.needsCallee(),
-                Type.OBJECT,
-                functionNode.getParameters().size()).
-            getMethodType();
-
-        return
-            MH.bindTo(
-                MH.asCollector(
-                    findOwnMH(
-                        "trampoline",
-                        Object.class,
-                        Object[].class),
-                    Object[].class,
-                    mt.parameterCount()),
-                this);
-    }
-
-    private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
-        return MH.findVirtual(MethodHandles.lookup(), ScriptFunctionTrampolineImpl.class, name, MH.type(rtype, types));
-    }
-
-    @Override
-    protected ScriptFunction makeBoundFunction(final ScriptFunctionData data) {
-        //prevent trampoline recompilation cycle if a function is bound before use
-        compile();
-        return super.makeBoundFunction(data);
-    }
-
-    private MethodHandle compile() throws CompilationException {
-        final Compiler compiler = new Compiler(installer, functionNode);
-
-        compiler.compile();
-
-        final Class<?> clazz = compiler.install();
-        /* compute function signature for lazy method. this can be done first after compilation, as only then do we know
-         * the final state about callees, scopes and specialized parameter types */
-        final FunctionSignature signature = new FunctionSignature(true, functionNode.needsCallee(), Type.OBJECT, functionNode.getParameters().size());
-        final MethodType        mt        = signature.getMethodType();
-
-        MethodHandle mh = MH.findStatic(MethodHandles.publicLookup(), clazz, functionNode.getName(), mt);
-        if (functionNode.needsCallee()) {
-            mh = MH.bindTo(mh, this);
-        }
-
-        // now the invoker method looks like the one our superclass is expecting
-        resetInvoker(mh);
-
-        return mh;
-    }
-
-    @SuppressWarnings("unused")
-    private Object trampoline(final Object... args) throws CompilationException {
-        Compiler.LOG.info(">>> TRAMPOLINE: Hitting trampoline for '" + functionNode.getName() + "'");
-        MethodHandle mh = compile();
-
-        Compiler.LOG.info("<<< COMPILED TO: " + mh);
-        // spread the array to invididual args of the correct type
-        mh = MH.asSpreader(mh, Object[].class, mh.type().parameterCount());
-
-        try {
-            //invoke the real method the trampoline points to. this only happens once
-            return mh.invoke(args);
-        } catch (final RuntimeException | Error e) {
-            throw e;
-        } catch (final Throwable t) {
-            throw new RuntimeException(t);
-        }
-    }
-}
diff --git a/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java b/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java
index 39cf549..5468ca3 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/JSONParser.java
@@ -313,7 +313,7 @@
         }
 
         // Construct new object literal.
-        return new ObjectNode(source, objectToken, finish, null, elements);
+        return new ObjectNode(source, objectToken, finish, elements);
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/parser/Parser.java b/nashorn/src/jdk/nashorn/internal/parser/Parser.java
index 41149e4..b810043 100644
--- a/nashorn/src/jdk/nashorn/internal/parser/Parser.java
+++ b/nashorn/src/jdk/nashorn/internal/parser/Parser.java
@@ -56,6 +56,7 @@
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Stack;
@@ -75,17 +76,18 @@
 import jdk.nashorn.internal.ir.ExecuteNode;
 import jdk.nashorn.internal.ir.ForNode;
 import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
 import jdk.nashorn.internal.ir.IdentNode;
 import jdk.nashorn.internal.ir.IfNode;
 import jdk.nashorn.internal.ir.IndexNode;
 import jdk.nashorn.internal.ir.LabelNode;
+import jdk.nashorn.internal.ir.LexicalContext;
 import jdk.nashorn.internal.ir.LineNumberNode;
 import jdk.nashorn.internal.ir.LiteralNode;
 import jdk.nashorn.internal.ir.Node;
 import jdk.nashorn.internal.ir.ObjectNode;
 import jdk.nashorn.internal.ir.PropertyKey;
 import jdk.nashorn.internal.ir.PropertyNode;
-import jdk.nashorn.internal.ir.ReferenceNode;
 import jdk.nashorn.internal.ir.ReturnNode;
 import jdk.nashorn.internal.ir.RuntimeNode;
 import jdk.nashorn.internal.ir.SwitchNode;
@@ -96,7 +98,6 @@
 import jdk.nashorn.internal.ir.VarNode;
 import jdk.nashorn.internal.ir.WhileNode;
 import jdk.nashorn.internal.ir.WithNode;
-import jdk.nashorn.internal.runtime.Context;
 import jdk.nashorn.internal.runtime.DebugLogger;
 import jdk.nashorn.internal.runtime.ErrorManager;
 import jdk.nashorn.internal.runtime.JSErrorType;
@@ -116,19 +117,13 @@
     /** Is scripting mode. */
     private final boolean scripting;
 
-    /** Top level script being parsed. */
-    private FunctionNode script;
-
-    /** Current function being parsed. */
-    private FunctionNode function;
-
-    /** Current parsing block. */
-    private Block block;
+    private final LexicalContext lexicalContext = new LexicalContext();
+    private List<Node> functionDeclarations;
 
     /** Namespace for function names where not explicitly given */
     private final Namespace namespace;
 
-    private static DebugLogger LOG = new DebugLogger("parser");
+    private static final DebugLogger LOG = new DebugLogger("parser");
 
     /**
      * Constructor
@@ -279,7 +274,9 @@
      * @return New block.
      */
     private Block newBlock() {
-        return block = new Block(source, token, Token.descPosition(token), block, function);
+        final Block block = new Block(source, token, Token.descPosition(token));
+        lexicalContext.push(block);
+        return block;
     }
 
     /**
@@ -292,18 +289,23 @@
         // Build function name.
         final StringBuilder sb = new StringBuilder();
 
-        if (block != null) {
-            block.addParentName(sb);
+        final FunctionNode parentFunction = getFunction();
+        if(parentFunction != null && !parentFunction.isProgram()) {
+            sb.append(parentFunction.getName()).append('$');
         }
 
         sb.append(ident != null ? ident.getName() : FUNCTION_PREFIX.tag());
         final String name = namespace.uniqueName(sb.toString());
-        assert function != null || name.equals(RUN_SCRIPT.tag())  : "name = " + name;// must not rename runScript().
+        assert parentFunction != null || name.equals(RUN_SCRIPT.tag())  : "name = " + name;// must not rename runScript().
 
         // Start new block.
-        final FunctionNode functionBlock = new FunctionNode(source, token, Token.descPosition(token), namespace, block, ident, name);
-        block = function = functionBlock;
-        function.setStrictMode(isStrictMode);
+        final FunctionNode functionBlock = new FunctionNode(source, token, Token.descPosition(token), namespace, ident, name);
+        if(parentFunction == null) {
+            functionBlock.setProgram();
+        }
+        functionBlock.setStrictMode(isStrictMode);
+        functionBlock.setState(errors.hasErrors() ? CompilationState.PARSE_ERROR : CompilationState.PARSED);
+        lexicalContext.push(functionBlock);
 
         return functionBlock;
     }
@@ -311,9 +313,8 @@
     /**
      * Restore the current block.
      */
-    private void restoreBlock() {
-        block = block.getParent();
-        function = block.getFunction();
+    private void restoreBlock(Block block) {
+        lexicalContext.pop(block);
     }
 
     /**
@@ -323,22 +324,25 @@
     private Block getBlock(final boolean needsBraces) {
         // Set up new block. Captures LBRACE.
         final Block newBlock = newBlock();
-        pushControlNode(newBlock);
-
-        // Block opening brace.
-        if (needsBraces) {
-            expect(LBRACE);
-        }
-
         try {
-            // Accumulate block statements.
-            statementList();
+            pushControlNode(newBlock);
+
+            // Block opening brace.
+            if (needsBraces) {
+                expect(LBRACE);
+            }
+
+            try {
+                // Accumulate block statements.
+                statementList();
+            } finally {
+                popControlNode();
+            }
         } finally {
-            restoreBlock();
-            popControlNode();
+            restoreBlock(newBlock);
         }
 
-    final int possibleEnd = Token.descPosition(token) + Token.descLength(token);
+        final int possibleEnd = Token.descPosition(token) + Token.descLength(token);
 
         // Block closing brace.
         if (needsBraces) {
@@ -365,7 +369,7 @@
             // Accumulate statements.
             statement();
         } finally {
-            restoreBlock();
+            restoreBlock(newBlock);
         }
 
         return newBlock;
@@ -379,7 +383,10 @@
         final String name = ident.getName();
 
         if (EVAL.tag().equals(name)) {
-            function.setHasEval();
+            final Iterator<FunctionNode> it = lexicalContext.getFunctions();
+            if(it.hasNext()) {
+                it.next().setHasEval(it);
+            }
         }
     }
 
@@ -391,7 +398,7 @@
         final String name = ident.getName();
 
         if (ARGUMENTS.tag().equals(name)) {
-            function.setUsesArguments();
+            getFunction().setUsesArguments();
         }
     }
 
@@ -438,7 +445,7 @@
             }
 
             if (lhs instanceof IdentNode) {
-                if (! checkIdentLValue((IdentNode)lhs)) {
+                if (!checkIdentLValue((IdentNode)lhs)) {
                     return referenceError(lhs, rhs);
                 }
                 verifyStrictIdent((IdentNode)lhs, "assignment");
@@ -482,7 +489,7 @@
      * @return null or the found label node.
      */
     private LabelNode findLabel(final IdentNode ident) {
-        for (final LabelNode labelNode : function.getLabelStack()) {
+        for (final LabelNode labelNode : getFunction().getLabelStack()) {
             if (labelNode.getLabel().equals(ident)) {
                 return labelNode;
             }
@@ -496,14 +503,14 @@
      * @param labelNode Label to add.
      */
     private void pushLabel(final LabelNode labelNode) {
-        function.getLabelStack().push(labelNode);
+        getFunction().getLabelStack().push(labelNode);
     }
 
     /**
       * Remove a label from the label stack.
       */
     private void popLabel() {
-        function.getLabelStack().pop();
+        getFunction().getLabelStack().pop();
     }
 
     /**
@@ -513,6 +520,7 @@
     private void pushControlNode(final Node node) {
         final boolean isLoop = node instanceof WhileNode;
         final boolean isBreakable = node instanceof BreakableNode || node instanceof Block;
+        final FunctionNode function = getFunction();
         function.getControlStack().push(node);
 
         for (final LabelNode labelNode : function.getLabelStack()) {
@@ -531,7 +539,7 @@
      */
     private void popControlNode() {
         // Get control stack.
-        final Stack<Node> controlStack = function.getControlStack();
+        final Stack<Node> controlStack = getFunction().getControlStack();
 
         // Can be empty if missing brace.
         if (!controlStack.isEmpty()) {
@@ -541,7 +549,7 @@
 
     private void popControlNode(final Node node) {
         // Get control stack.
-        final Stack<Node> controlStack = function.getControlStack();
+        final Stack<Node> controlStack = getFunction().getControlStack();
 
         // Can be empty if missing brace.
         if (!controlStack.isEmpty() && controlStack.peek() == node) {
@@ -550,7 +558,7 @@
     }
 
     private boolean isInWithBlock() {
-        final Stack<Node> controlStack = function.getControlStack();
+        final Stack<Node> controlStack = getFunction().getControlStack();
         for (int i = controlStack.size() - 1; i >= 0; i--) {
             final Node node = controlStack.get(i);
 
@@ -563,7 +571,7 @@
     }
 
     private <T extends Node> T findControl(final Class<T> ctype) {
-        final Stack<Node> controlStack = function.getControlStack();
+        final Stack<Node> controlStack = getFunction().getControlStack();
         for (int i = controlStack.size() - 1; i >= 0; i--) {
             final Node node = controlStack.get(i);
 
@@ -577,7 +585,7 @@
 
     private <T extends Node> List<T> findControls(final Class<T> ctype, final Node to) {
         final List<T> nodes = new ArrayList<>();
-        final Stack<Node> controlStack = function.getControlStack();
+        final Stack<Node> controlStack = getFunction().getControlStack();
         for (int i = controlStack.size() - 1; i >= 0; i--) {
             final Node node = controlStack.get(i);
 
@@ -621,15 +629,16 @@
         // Make a fake token for the script.
         final long functionToken = Token.toDesc(FUNCTION, 0, source.getLength());
         // Set up the script to append elements.
-        script = newFunctionBlock(new IdentNode(source, functionToken, Token.descPosition(functionToken), scriptName));
-        // set kind to be SCRIPT
+
+        final FunctionNode script = newFunctionBlock(new IdentNode(source, functionToken, Token.descPosition(functionToken), scriptName));
+
         script.setKind(FunctionNode.Kind.SCRIPT);
-        // Set the first token of the script.
         script.setFirstToken(functionToken);
-        // Gather source elements.
+        functionDeclarations = new ArrayList<>();
         sourceElements();
+        script.prependStatements(functionDeclarations);
+        functionDeclarations = null;
         expect(EOF);
-        // Set the last token of the script.
         script.setLastToken(token);
         script.setFinish(source.getLength() - 1);
 
@@ -707,7 +716,7 @@
                     // check for directive prologues
                     if (checkDirective) {
                         // skip any debug statement like line number to get actual first line
-                        final Node lastStatement = lastStatement(block.getStatements());
+                        final Node lastStatement = lastStatement(getBlock().getStatements());
 
                         // get directive prologue, if any
                         final String directive = getDirective(lastStatement);
@@ -727,6 +736,7 @@
                             // handle use strict directive
                             if ("use strict".equals(directive)) {
                                 isStrictMode = true;
+                                final FunctionNode function = getFunction();
                                 function.setStrictMode(true);
 
                                 // We don't need to check these, if lexical environment is already strict
@@ -799,11 +809,11 @@
             if (isStrictMode && !topLevel) {
                 error(AbstractParser.message("strict.no.func.here"), token);
             }
-            functionExpression(true);
+            functionExpression(true, topLevel);
             return;
         }
 
-        block.addStatement(lineNumberNode);
+        getBlock().addStatement(lineNumberNode);
 
         switch (type) {
         case LBRACE:
@@ -889,7 +899,7 @@
         // Force block execution.
         final ExecuteNode executeNode = new ExecuteNode(source, newBlock.getToken(), finish, newBlock);
 
-        block.addStatement(executeNode);
+        getBlock().addStatement(executeNode);
     }
 
     /**
@@ -984,13 +994,9 @@
 
             // Allocate var node.
             final VarNode var = new VarNode(source, varToken, finish, name, init);
-            if (isStatement) {
-                function.addDeclaration(var);
-            }
-
             vars.add(var);
             // Add to current block.
-            block.addStatement(var);
+            getBlock().addStatement(var);
 
             if (type != COMMARIGHT) {
                 break;
@@ -1003,7 +1009,7 @@
             boolean semicolon = type == SEMICOLON;
             endOfLine();
             if (semicolon) {
-                block.setFinish(finish);
+                getBlock().setFinish(finish);
             }
         }
 
@@ -1020,7 +1026,7 @@
      */
     private void emptyStatement() {
         if (env._empty_statements) {
-            block.addStatement(new EmptyNode(source, token,
+            getBlock().addStatement(new EmptyNode(source, token,
                     Token.descPosition(token) + Token.descLength(token)));
         }
 
@@ -1046,7 +1052,7 @@
         ExecuteNode executeNode = null;
         if (expression != null) {
             executeNode = new ExecuteNode(source, expressionToken, finish, expression);
-            block.addStatement(executeNode);
+            getBlock().addStatement(executeNode);
         } else {
             expect(null);
         }
@@ -1055,7 +1061,7 @@
 
         if (executeNode != null) {
             executeNode.setFinish(finish);
-            block.setFinish(finish);
+            getBlock().setFinish(finish);
         }
     }
 
@@ -1097,7 +1103,7 @@
         // Construct and add new if node.
         final IfNode ifNode = new IfNode(source, ifToken, fail != null ? fail.getFinish() : pass.getFinish(), test, pass, fail);
 
-        block.addStatement(ifNode);
+        getBlock().addStatement(ifNode);
     }
 
     /**
@@ -1146,13 +1152,13 @@
             outer.setFinish(body.getFinish());
 
             // Add for to current block.
-            block.addStatement(forNode);
+            getBlock().addStatement(forNode);
         } finally {
-            restoreBlock();
+            restoreBlock(outer);
             popControlNode();
         }
 
-        block.addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
+        getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
      }
 
     /**
@@ -1231,7 +1237,7 @@
                 }
 
                 if (init instanceof IdentNode) {
-                    if (! checkIdentLValue((IdentNode)init)) {
+                    if (!checkIdentLValue((IdentNode)init)) {
                         error(AbstractParser.message("not.lvalue.for.in.loop"), init.getToken());
                     }
                     verifyStrictIdent((IdentNode)init, "for-in iterator");
@@ -1286,7 +1292,7 @@
             whileNode.setFinish(statements.getFinish());
 
             // Add WHILE node.
-            block.addStatement(whileNode);
+            getBlock().addStatement(whileNode);
         } finally {
             popControlNode();
         }
@@ -1333,7 +1339,7 @@
             doWhileNode.setFinish(finish);
 
             // Add DO node.
-            block.addStatement(doWhileNode);
+            getBlock().addStatement(doWhileNode);
         } finally {
             popControlNode();
         }
@@ -1385,7 +1391,7 @@
         final ContinueNode continueNode = new ContinueNode(source, continueToken, finish, labelNode, targetNode, findControl(TryNode.class));
         continueNode.setScopeNestingLevel(countControls(WithNode.class, targetNode));
 
-        block.addStatement(continueNode);
+        getBlock().addStatement(continueNode);
     }
 
     /**
@@ -1433,7 +1439,7 @@
         final BreakNode breakNode = new BreakNode(source, breakToken, finish, labelNode, targetNode, findControl(TryNode.class));
         breakNode.setScopeNestingLevel(countControls(WithNode.class, targetNode));
 
-        block.addStatement(breakNode);
+        getBlock().addStatement(breakNode);
     }
 
     /**
@@ -1446,7 +1452,7 @@
      */
     private void returnStatement() {
         // check for return outside function
-        if (function.getKind() == FunctionNode.Kind.SCRIPT) {
+        if (getFunction().getKind() == FunctionNode.Kind.SCRIPT) {
             error(AbstractParser.message("invalid.return"));
         }
 
@@ -1473,7 +1479,7 @@
 
         // Construct and add RETURN node.
         final ReturnNode returnNode = new ReturnNode(source, returnToken, finish, expression, findControl(TryNode.class));
-        block.addStatement(returnNode);
+        getBlock().addStatement(returnNode);
     }
 
     /**
@@ -1508,7 +1514,7 @@
 
         // Construct and add YIELD node.
         final ReturnNode yieldNode = new ReturnNode(source, yieldToken, finish, expression, findControl(TryNode.class));
-        block.addStatement(yieldNode);
+        getBlock().addStatement(yieldNode);
     }
 
     /**
@@ -1532,7 +1538,10 @@
 
         // Get WITH expression.
         final WithNode withNode = new WithNode(source, withToken, finish, null, null);
-        function.setHasWith();
+        final Iterator<FunctionNode> it = lexicalContext.getFunctions();
+        if(it.hasNext()) {
+            it.next().setHasWith(it);
+        }
 
         try {
             pushControlNode(withNode);
@@ -1552,7 +1561,7 @@
             popControlNode(withNode);
         }
 
-        block.addStatement(withNode);
+        getBlock().addStatement(withNode);
     }
 
     /**
@@ -1652,7 +1661,7 @@
 
             switchNode.setFinish(finish);
 
-            block.addStatement(switchNode);
+            getBlock().addStatement(switchNode);
         } finally {
             popControlNode();
         }
@@ -1687,7 +1696,7 @@
             labelNode.setBody(statements);
             labelNode.setFinish(finish);
 
-            block.addStatement(labelNode);
+            getBlock().addStatement(labelNode);
         } finally {
             // Remove label.
             popLabel();
@@ -1730,7 +1739,7 @@
 
         // Construct and add THROW node.
         final ThrowNode throwNode = new ThrowNode(source, throwToken, finish, expression, findControl(TryNode.class));
-        block.addStatement(throwNode);
+        getBlock().addStatement(throwNode);
     }
 
     /**
@@ -1796,18 +1805,18 @@
 
                 expect(RPAREN);
 
+                final Block catchBlock = newBlock();
                 try {
-                    final Block catchBlock = newBlock();
 
                     // Get CATCH body.
                     final Block catchBody = getBlock(true);
 
                     // Create and add catch.
                     final CatchNode catchNode = new CatchNode(source, catchToken, finish, exception, ifExpression, catchBody);
-                    block.addStatement(catchNode);
+                    getBlock().addStatement(catchNode);
                     catchBlocks.add(catchBlock);
                 } finally {
-                    restoreBlock();
+                    restoreBlock(catchBlock);
                 }
 
                 // If unconditional catch then should to be the end.
@@ -1843,11 +1852,11 @@
             outer.addStatement(tryNode);
         } finally {
             popControlNode(tryNode);
-            restoreBlock();
+            restoreBlock(outer);
             popControlNode(outer);
         }
 
-        block.addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
+        getBlock().addStatement(new ExecuteNode(source, outer.getToken(), outer.getFinish(), outer));
     }
 
     /**
@@ -1867,7 +1876,7 @@
         endOfLine();
 
         final RuntimeNode runtimeNode = new RuntimeNode(source, debuggerToken, finish, RuntimeNode.Request.DEBUGGER, new ArrayList<Node>());
-        block.addStatement(runtimeNode);
+        getBlock().addStatement(runtimeNode);
     }
 
     /**
@@ -2026,7 +2035,7 @@
                 break;
 
             default:
-                if (! elision) {
+                if (!elision) {
                     error(AbstractParser.message("expected.comma", type.getNameOrType()));
                 }
                 // Add expression element.
@@ -2067,15 +2076,11 @@
         next();
 
         // Object context.
-        Block objectContext = null;
         // Prepare to accumulate elements.
         final List<Node> elements = new ArrayList<>();
         final Map<Object, PropertyNode> map = new HashMap<>();
 
-        try {
-            // Create a block for the object literal.
-            objectContext = newBlock();
-
+        // Create a block for the object literal.
             boolean commaSeen = true;
 loop:
             while (true) {
@@ -2090,97 +2095,90 @@
                     break;
 
                 default:
-                    if (! commaSeen) {
+                    if (!commaSeen) {
                         error(AbstractParser.message("expected.comma", type.getNameOrType()));
-                    }
-
-                    commaSeen = false;
-                    // Get and add the next property.
-                    final PropertyNode property = propertyAssignment();
-                    final Object key = property.getKeyName();
-                    final PropertyNode existingProperty = map.get(key);
-
-                    if (existingProperty != null) {
-                        // ECMA section 11.1.5 Object Initialiser
-                        // point # 4 on property assignment production
-                        final Node value  = property.getValue();
-                        final Node getter = property.getGetter();
-                        final Node setter = property.getSetter();
-
-                        final Node prevValue  = existingProperty.getValue();
-                        final Node prevGetter = existingProperty.getGetter();
-                        final Node prevSetter = existingProperty.getSetter();
-
-                        boolean redefinitionOk = true;
-                        // ECMA 11.1.5 strict mode restrictions
-                        if (isStrictMode) {
-                            if (value != null && prevValue != null) {
-                                redefinitionOk = false;
-                            }
-                        }
-
-                        final boolean isPrevAccessor = prevGetter != null || prevSetter != null;
-                        final boolean isAccessor = getter != null || setter != null;
-
-                        // data property redefined as accessor property
-                        if (prevValue != null && isAccessor) {
-                            redefinitionOk = false;
-                        }
-
-                        // accessor property redefined as data
-                        if (isPrevAccessor && value != null) {
-                            redefinitionOk = false;
-                        }
-
-                        if (isAccessor && isPrevAccessor) {
-                            if (getter != null && prevGetter != null ||
-                                setter != null && prevSetter != null) {
-                                redefinitionOk = false;
-                            }
-                        }
-
-                        if (! redefinitionOk) {
-                            error(AbstractParser.message("property.redefinition", key.toString()), property.getToken());
-                        }
-
-                        if (value != null) {
-                            final Node existingValue = existingProperty.getValue();
-
-                            if (existingValue == null) {
-                                existingProperty.setValue(value);
-                            } else {
-                                final long propertyToken = Token.recast(existingProperty.getToken(), COMMARIGHT);
-                                existingProperty.setValue(new BinaryNode(source, propertyToken, existingValue, value));
-                            }
-
-                            existingProperty.setGetter(null);
-                            existingProperty.setSetter(null);
-                        }
-
-                        if (getter != null) {
-                            existingProperty.setGetter(getter);
-                        }
-
-                        if (setter != null) {
-                            existingProperty.setSetter(setter);
-                        }
-                    } else {
-                        map.put(key, property);
-                        elements.add(property);
-                    }
-
-                    break;
                 }
+
+                commaSeen = false;
+                // Get and add the next property.
+                final PropertyNode property = propertyAssignment();
+                final Object key = property.getKeyName();
+                final PropertyNode existingProperty = map.get(key);
+
+                if (existingProperty != null) {
+                    // ECMA section 11.1.5 Object Initialiser
+                    // point # 4 on property assignment production
+                    final Node value  = property.getValue();
+                    final Node getter = property.getGetter();
+                    final Node setter = property.getSetter();
+
+                    final Node prevValue  = existingProperty.getValue();
+                    final Node prevGetter = existingProperty.getGetter();
+                    final Node prevSetter = existingProperty.getSetter();
+
+                    boolean redefinitionOk = true;
+                    // ECMA 11.1.5 strict mode restrictions
+                    if (isStrictMode) {
+                        if (value != null && prevValue != null) {
+                            redefinitionOk = false;
+                        }
+                    }
+
+                    final boolean isPrevAccessor = prevGetter != null || prevSetter != null;
+                    final boolean isAccessor = getter != null || setter != null;
+
+                    // data property redefined as accessor property
+                    if (prevValue != null && isAccessor) {
+                        redefinitionOk = false;
+                    }
+
+                    // accessor property redefined as data
+                    if (isPrevAccessor && value != null) {
+                        redefinitionOk = false;
+                    }
+
+                    if (isAccessor && isPrevAccessor) {
+                        if (getter != null && prevGetter != null ||
+                            setter != null && prevSetter != null) {
+                            redefinitionOk = false;
+                        }
+                    }
+
+                    if (!redefinitionOk) {
+                        error(AbstractParser.message("property.redefinition", key.toString()), property.getToken());
+                    }
+
+                    if (value != null) {
+                        final Node existingValue = existingProperty.getValue();
+
+                        if (existingValue == null) {
+                            existingProperty.setValue(value);
+                        } else {
+                            final long propertyToken = Token.recast(existingProperty.getToken(), COMMARIGHT);
+                            existingProperty.setValue(new BinaryNode(source, propertyToken, existingValue, value));
+                        }
+
+                        existingProperty.setGetter(null);
+                        existingProperty.setSetter(null);
+                    }
+
+                    if (getter != null) {
+                        existingProperty.setGetter(getter);
+                    }
+
+                    if (setter != null) {
+                        existingProperty.setSetter(setter);
+                    }
+                } else {
+                    map.put(key, property);
+                    elements.add(property);
+                }
+
+                break;
             }
-        } finally {
-            restoreBlock();
         }
 
-        // Construct new object literal.
-        objectContext.setFinish(finish);
-        objectContext.setStart(Token.descPosition(objectToken));
-
-        return new ObjectNode(source, objectToken, finish, objectContext, elements);
+        return new ObjectNode(source, objectToken, finish, elements);
     }
 
     /**
@@ -2258,7 +2256,7 @@
                     parameters = new ArrayList<>();
                     functionNode = functionBody(getSetToken, getNameNode, parameters, FunctionNode.Kind.GETTER);
                     propertyNode = new PropertyNode(source, propertyToken, finish, getIdent, null);
-                    propertyNode.setGetter(new ReferenceNode(source, propertyToken, finish, functionNode));
+                    propertyNode.setGetter(functionNode);
                     return propertyNode;
 
                 case "set":
@@ -2273,7 +2271,7 @@
                     parameters.add(argIdent);
                     functionNode = functionBody(getSetToken, setNameNode, parameters, FunctionNode.Kind.SETTER);
                     propertyNode = new PropertyNode(source, propertyToken, finish, setIdent, null);
-                    propertyNode.setSetter(new ReferenceNode(source, propertyToken, finish, functionNode));
+                    propertyNode.setSetter(functionNode);
                     return propertyNode;
 
                 default:
@@ -2454,7 +2452,7 @@
 
         case FUNCTION:
             // Get function expression.
-            lhs = functionExpression(false);
+            lhs = functionExpression(false, false);
             break;
 
         default:
@@ -2559,7 +2557,7 @@
      *
      * @return Expression node.
      */
-    private Node functionExpression(final boolean isStatement) {
+    private Node functionExpression(final boolean isStatement, final boolean topLevel) {
         final LineNumberNode lineNumber = lineNumber();
 
         final long functionToken = token;
@@ -2594,10 +2592,12 @@
 
         final FunctionNode functionNode = functionBody(functionToken, name, parameters, FunctionNode.Kind.NORMAL);
 
-        if (isStatement && !isInWithBlock()) {
-            functionNode.setIsStatement();
+        if (isStatement) {
+            if(topLevel) {
+                functionNode.setIsDeclared();
+            }
             if(ARGUMENTS.tag().equals(name.getName())) {
-                functionNode.findParentFunction().setDefinesArguments();
+                getFunction().setDefinesArguments();
             }
         }
 
@@ -2605,8 +2605,6 @@
             functionNode.setIsAnonymous();
         }
 
-        final ReferenceNode referenceNode = new ReferenceNode(source, functionToken, finish, functionNode);
-
         final int arity = parameters.size();
 
         final boolean strict = functionNode.isStrictMode();
@@ -2642,17 +2640,18 @@
         }
 
         if (isStatement) {
-            final VarNode var = new VarNode(source, functionToken, finish, name, referenceNode);
-            if (isInWithBlock()) {
-                function.addDeclaration(var);
-                // Add to current block.
-                block.addStatement(var);
+            final VarNode varNode = new VarNode(source, functionToken, finish, name, functionNode, true);
+            if(topLevel) {
+                functionDeclarations.add(lineNumber);
+                functionDeclarations.add(varNode);
             } else {
-                functionNode.setFunctionVarNode(var, lineNumber);
+                final Block block = getBlock();
+                block.addStatement(lineNumber);
+                block.addStatement(varNode);
             }
         }
 
-        return referenceNode;
+        return functionNode;
     }
 
     /**
@@ -2735,7 +2734,14 @@
                 expect(LBRACE);
 
                 // Gather the function elements.
-                sourceElements();
+                final List<Node> prevFunctionDecls = functionDeclarations;
+                functionDeclarations = new ArrayList<>();
+                try {
+                    sourceElements();
+                    functionNode.prependStatements(functionDeclarations);
+                } finally {
+                    functionDeclarations = prevFunctionDecls;
+                }
 
                 functionNode.setLastToken(token);
                 expect(RBRACE);
@@ -2743,12 +2749,9 @@
 
             }
         } finally {
-            restoreBlock();
+            restoreBlock(functionNode);
         }
 
-        // Add the body of the function to the current block.
-        block.addFunction(functionNode);
-
         return functionNode;
     }
 
@@ -2845,7 +2848,7 @@
             }
 
             if (lhs instanceof IdentNode) {
-                if (! checkIdentLValue((IdentNode)lhs)) {
+                if (!checkIdentLValue((IdentNode)lhs)) {
                     return referenceError(lhs, null);
                 }
                 verifyStrictIdent((IdentNode)lhs, "operand for " + opType.getName() + " operator");
@@ -2872,7 +2875,7 @@
                     return referenceError(lhs, null);
                 }
                 if (lhs instanceof IdentNode) {
-                    if (! checkIdentLValue((IdentNode)lhs)) {
+                    if (!checkIdentLValue((IdentNode)lhs)) {
                         next();
                         return referenceError(lhs, null);
                     }
@@ -3083,4 +3086,12 @@
     public String toString() {
         return "[JavaScript Parsing]";
     }
+
+    private Block getBlock() {
+        return lexicalContext.getCurrentBlock();
+    }
+
+    private FunctionNode getFunction() {
+        return lexicalContext.getCurrentFunction();
+    }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java b/nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java
index 9f6867d..996bea3 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/AccessorProperty.java
@@ -164,7 +164,6 @@
         super(key, flags, slot);
 
         /*
-         *
          * primitiveGetter and primitiveSetter are only used in dual fields mode. Setting them to null also
          * works in dual field mode, it only means that the property never has a primitive
          * representation.
@@ -348,11 +347,10 @@
 
     private MethodHandle debug(final MethodHandle mh, final Class<?> forType, final Class<?> type, final String tag) {
         if (DEBUG_FIELDS) {
-           final MethodHandle mhd = MethodHandleFactory.addDebugPrintout(
+           return MethodHandleFactory.addDebugPrintout(
                LOG,
                mh,
                tag + " '" + getKey() + "' (property="+ Debug.id(this) + ", forType=" + stripName(forType) + ", type=" + stripName(type) + ')');
-           return mhd;
         }
         return mh;
     }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java b/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java
index 5fd1652..80fac17 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CodeInstaller.java
@@ -25,6 +25,8 @@
 
 package jdk.nashorn.internal.runtime;
 
+import jdk.nashorn.internal.codegen.ClassEmitter;
+
 /**
  * Interface for installing classes passed to the compiler.
  * As only the code generating package (i.e. Context) knows about
@@ -52,12 +54,12 @@
      */
     public Class<?> install(final String className, final byte[] bytecode);
 
-    /*
+    /**
      * Verify generated bytecode before emission. This is called back from the
      * {@link ClassEmitter} or the {@link Compiler}. If the "--verify-code" parameter
      * hasn't been given, this is a nop
      *
-     * @param bytecode bytecode to verify
+     * @param code bytecode to verify
      */
     public void verify(final byte[] code);
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java
new file mode 100644
index 0000000..3cc9f09
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunction.java
@@ -0,0 +1,162 @@
+/*
+ * 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 java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodType;
+
+import jdk.nashorn.internal.codegen.types.Type;
+
+/**
+ * An version of a JavaScript function, native or JavaScript.
+ * Supports lazily generating a constructor version of the invocation.
+ */
+final class CompiledFunction implements Comparable<CompiledFunction> {
+
+    private final MethodHandle invoker;
+    private MethodHandle constructor;
+
+    CompiledFunction(final MethodHandle invoker) {
+        this(invoker, null);
+    }
+
+    CompiledFunction(final MethodHandle invoker, final MethodHandle constructor) {
+        this.invoker = invoker;
+        this.constructor = constructor; //isConstructor
+    }
+
+    @Override
+    public String toString() {
+        return "<invoker=" + invoker + " ctor=" + constructor + ">";
+    }
+
+    MethodHandle getInvoker() {
+        return invoker;
+    }
+
+    MethodHandle getConstructor() {
+        return constructor;
+    }
+
+    void setConstructor(final MethodHandle constructor) {
+        this.constructor = constructor;
+    }
+
+    boolean hasConstructor() {
+        return constructor != null;
+    }
+
+    MethodType type() {
+        return invoker.type();
+    }
+
+    @Override
+    public int compareTo(final CompiledFunction o) {
+        return weight() - o.weight();
+    }
+
+    private int weight() {
+        return weight(type());
+    }
+
+    private static int weight(final MethodType type) {
+        if (isVarArgsType(type)) {
+            return Integer.MAX_VALUE; //if there is a varargs it should be the heavist and last fallback
+        }
+
+        int weight = Type.typeFor(type.returnType()).getWeight();
+        for (final Class<?> paramType : type.parameterArray()) {
+            final int pweight = Type.typeFor(paramType).getWeight();
+            weight += pweight;
+        }
+        return weight;
+    }
+
+    private static boolean isVarArgsType(final MethodType type) {
+        assert type.parameterCount() >= 1 : type;
+        return type.parameterType(type.parameterCount() - 1) == Object[].class;
+    }
+
+    boolean moreGenericThan(final CompiledFunction o) {
+        return weight() > o.weight();
+    }
+
+    boolean moreGenericThan(final MethodType type) {
+        return weight() > weight(type);
+    }
+
+    /**
+     * Check whether a given method descriptor is compatible with this invocation.
+     * It is compatible if the types are narrower than the invocation type so that
+     * a semantically equivalent linkage can be performed.
+     *
+     * @param typesc
+     * @return
+     */
+    boolean typeCompatible(final MethodType type) {
+        final Class<?>[] wantedParams   = type.parameterArray();
+        final Class<?>[] existingParams = type().parameterArray();
+
+        //if we are not examining a varargs type, the number of parameters must be the same
+        if (wantedParams.length != existingParams.length && !isVarArgsType(type)) {
+            return false;
+        }
+
+        //we only go as far as the shortest array. the only chance to make this work if
+        //parameters lengths do not match is if our type ends with a varargs argument.
+        //then every trailing parameter in the given callsite can be folded into it, making
+        //us compatible (albeit slower than a direct specialization)
+        final int lastParamIndex = Math.min(wantedParams.length, existingParams.length);
+        for (int i = 0; i < lastParamIndex; i++) {
+            final Type w = Type.typeFor(wantedParams[i]);
+            final Type e = Type.typeFor(existingParams[i]);
+
+            //don't specialize on booleans, we have the "true" vs int 1 ambiguity in resolution
+            //we also currently don't support boolean as a javascript function callsite type.
+            //it will always box.
+            if (w.isBoolean()) {
+                return false;
+            }
+
+            //This callsite type has a vararg here. it will swallow all remaining args.
+            //for consistency, check that it's the last argument
+            if (e.isArray()) {
+                return true;
+            }
+
+            //Our arguments must be at least as wide as the wanted one, if not wider
+            if (Type.widest(w, e) != e) {
+                //e.g. this invocation takes double and callsite says "object". reject. won't fit
+                //but if invocation takes a double and callsite says "int" or "long" or "double", that's fine
+                return false;
+            }
+        }
+
+        return true; // anything goes for return type, take the convenient one and it will be upcasted thru dynalink magic.
+    }
+
+
+
+}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunctions.java b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunctions.java
new file mode 100644
index 0000000..ff660d3
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/runtime/CompiledFunctions.java
@@ -0,0 +1,73 @@
+/*
+ * 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 java.lang.invoke.MethodType;
+import java.util.Iterator;
+import java.util.TreeSet;
+
+/**
+ * This is a list of code versions of a function.
+ * The list is sorted in ascending order of generic descriptors
+ */
+@SuppressWarnings("serial")
+final class CompiledFunctions extends TreeSet<CompiledFunction> {
+
+    CompiledFunction best(final MethodType type) {
+        final Iterator<CompiledFunction> iter = iterator();
+        while (iter.hasNext()) {
+            final CompiledFunction next = iter.next();
+            if (next.typeCompatible(type)) {
+                return next;
+            }
+        }
+        return mostGeneric();
+    }
+
+    boolean needsCallee() {
+        for (final CompiledFunction inv : this) {
+            assert ScriptFunctionData.needsCallee(inv.getInvoker()) == ScriptFunctionData.needsCallee(mostGeneric().getInvoker());
+        }
+        return ScriptFunctionData.needsCallee(mostGeneric().getInvoker());
+    }
+
+    CompiledFunction mostGeneric() {
+        return last();
+    }
+
+    /**
+     * Is the given type even more specific than this entire list? That means
+     * we have an opportunity for more specific versions of the method
+     * through lazy code generation
+     *
+     * @param type type to check against
+     * @return true if the given type is more specific than all invocations available
+     */
+    boolean isLessSpecificThan(final MethodType type) {
+        return best(type).moreGenericThan(type);
+    }
+
+
+}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/Context.java b/nashorn/src/jdk/nashorn/internal/runtime/Context.java
index 1c4a08c..448df14 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/Context.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/Context.java
@@ -27,9 +27,9 @@
 
 import static jdk.nashorn.internal.codegen.CompilerConstants.RUN_SCRIPT;
 import static jdk.nashorn.internal.codegen.CompilerConstants.STRICT_MODE;
+import static jdk.nashorn.internal.lookup.Lookup.MH;
 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
-import static jdk.nashorn.internal.lookup.Lookup.MH;
 
 import java.io.File;
 import java.io.IOException;
@@ -39,10 +39,13 @@
 import java.lang.reflect.Constructor;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.security.AccessControlContext;
 import java.security.AccessController;
 import java.security.CodeSigner;
 import java.security.CodeSource;
+import java.security.Permissions;
 import java.security.PrivilegedAction;
+import java.security.ProtectionDomain;
 import jdk.internal.org.objectweb.asm.ClassReader;
 import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
 import jdk.nashorn.internal.codegen.Compiler;
@@ -77,7 +80,7 @@
 
         /**
          * Return the context for this installer
-         * @return context
+         * @return ScriptEnvironment
          */
         @Override
         public ScriptEnvironment getOwner() {
@@ -123,7 +126,7 @@
             if (callerLoader != myLoader &&
                 !(callerLoader instanceof StructureLoader) &&
                 !(JavaAdapterFactory.isAdapterClass(caller))) {
-                sm.checkPermission(new RuntimePermission("getNashornGlobal"));
+                sm.checkPermission(new RuntimePermission("nashorn.getGlobal"));
             }
         }
 
@@ -137,7 +140,7 @@
     public static void setGlobal(final ScriptObject global) {
         final SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
-            sm.checkPermission(new RuntimePermission("setNashornGlobal"));
+            sm.checkPermission(new RuntimePermission("nashorn.setGlobal"));
         }
 
         if (global != null && !(global instanceof GlobalObject)) {
@@ -154,7 +157,7 @@
     public static Context getContext() {
         final SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
-            sm.checkPermission(new RuntimePermission("getNashornContext"));
+            sm.checkPermission(new RuntimePermission("nashorn.getContext"));
         }
         return getContextTrusted();
     }
@@ -267,7 +270,7 @@
     public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader) {
         final SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
-            sm.checkPermission(new RuntimePermission("createNashornContext"));
+            sm.checkPermission(new RuntimePermission("nashorn.createContext"));
         }
 
         this.env       = new ScriptEnvironment(options, out, err);
@@ -533,7 +536,13 @@
         if (index != -1) {
             final SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
-                sm.checkPackageAccess(fullName.substring(0, index));
+                AccessController.doPrivileged(new PrivilegedAction<Void>() {
+                    @Override
+                    public Void run() {
+                        sm.checkPackageAccess(fullName.substring(0, index));
+                        return null;
+                    }
+                }, createNoPermissionsContext());
             }
         }
 
@@ -599,7 +608,7 @@
     public ScriptObject newGlobal() {
         final SecurityManager sm = System.getSecurityManager();
         if (sm != null) {
-            sm.checkPermission(new RuntimePermission("createNashornGlobal"));
+            sm.checkPermission(new RuntimePermission("nashorn.newGlobal"));
         }
 
         return newGlobalTrusted();
@@ -676,6 +685,10 @@
         return (context != null) ? context : Context.getContextTrusted();
     }
 
+    private static AccessControlContext createNoPermissionsContext() {
+        return new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, new Permissions()) });
+    }
+
     private Object evaluateSource(final Source source, final ScriptObject scope, final ScriptObject thiz) {
         ScriptFunction script = null;
 
@@ -731,6 +744,7 @@
             global = (GlobalObject)Context.getGlobalTrusted();
             script = global.findCachedClass(source);
             if (script != null) {
+                Compiler.LOG.fine("Code cache hit for " + source + " avoiding recompile.");
                 return script;
             }
         }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ECMAException.java b/nashorn/src/jdk/nashorn/internal/runtime/ECMAException.java
index a07fb58..a32e721 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ECMAException.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ECMAException.java
@@ -237,7 +237,7 @@
             return (String)name;
         }
 
-        return (String)name + ": " + (String)msg;
+        return name + ": " + msg;
     }
 
     private static Throwable asThrowable(final Object obj) {
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/FinalScriptFunctionData.java b/nashorn/src/jdk/nashorn/internal/runtime/FinalScriptFunctionData.java
new file mode 100644
index 0000000..ed54b2e
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/runtime/FinalScriptFunctionData.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.nashorn.internal.runtime;
+
+import static jdk.nashorn.internal.lookup.Lookup.MH;
+
+import java.lang.invoke.MethodHandle;
+
+/**
+ * This is a subclass that represents a script function that may not be regenerated.
+ * This is used for example for bound functions and builtins.
+ */
+public final class FinalScriptFunctionData extends ScriptFunctionData {
+
+    /**
+     * Constructor - used for bind
+     *
+     * @param name          name
+     * @param arity         arity
+     * @param list          precompiled code
+     * @param isStrict      strict
+     * @param isBuiltin     builtin
+     * @param isConstructor constructor
+     */
+    FinalScriptFunctionData(final String name, int arity, CompiledFunctions functions, final boolean isStrict, final boolean isBuiltin, final boolean isConstructor) {
+        super(name, arity, isStrict, isBuiltin, isConstructor);
+        code.addAll(functions);
+    }
+
+    /**
+     * Constructor - used from ScriptFunction. This assumes that we have code alraedy for the
+     * method (typically a native method) and possibly specializations.
+     *
+     * @param name           name
+     * @param mh             method handle for generic version of method
+     * @param specs          specializations
+     * @param isStrict       strict
+     * @param isBuiltin      builtin
+     * @param isConstructor  constructor
+     */
+    FinalScriptFunctionData(final String name, final MethodHandle mh, final MethodHandle[] specs, final boolean isStrict, final boolean isBuiltin, final boolean isConstructor) {
+        super(name, arity(mh), isStrict, isBuiltin, isConstructor);
+
+        addInvoker(mh);
+        if (specs != null) {
+            for (final MethodHandle spec : specs) {
+                addInvoker(spec);
+            }
+        }
+    }
+
+    private void addInvoker(final MethodHandle mh) {
+        boolean needsCallee = needsCallee(mh);
+        if (isConstructor(mh)) {
+            //only nasgen constructors: (boolean, self, args) are subject to binding a boolean newObj. isConstructor
+            //is too conservative a check. However, isConstructor(mh) always implies isConstructor param
+            assert isConstructor();
+            code.add(new CompiledFunction(MH.insertArguments(mh, 0, false), composeConstructor(MH.insertArguments(mh, 0, true), needsCallee))); //make sure callee state can be determined when we reach constructor
+        } else {
+            code.add(new CompiledFunction(mh));
+        }
+    }
+
+    private static int arity(final MethodHandle mh) {
+        if (isVarArg(mh)) {
+            return -1;
+        }
+
+        //drop self, callee and boolean constructor flag to get real arity
+        return mh.type().parameterCount() - 1 - (needsCallee(mh) ? 1 : 0) - (isConstructor(mh) ? 1 : 0);
+    }
+
+    private static boolean isConstructor(final MethodHandle mh) {
+        return mh.type().parameterCount() >= 1 && mh.type().parameterType(0) == boolean.class;
+    }
+
+}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/NashornLoader.java b/nashorn/src/jdk/nashorn/internal/runtime/NashornLoader.java
index 80a0b8e..5ce3100 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/NashornLoader.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/NashornLoader.java
@@ -30,6 +30,10 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLClassLoader;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
 import java.security.SecureClassLoader;
 import jdk.nashorn.tools.Shell;
 
@@ -40,6 +44,28 @@
  *
  */
 abstract class NashornLoader extends SecureClassLoader {
+    private static final String OBJECTS_PKG = "jdk.nashorn.internal.objects";
+    private static final String RUNTIME_PKG = "jdk.nashorn.internal.runtime";
+    private static final String RUNTIME_LINKER_PKG = "jdk.nashorn.internal.runtime.linker";
+    private static final String SCRIPTS_PKG = "jdk.nashorn.internal.scripts";
+
+    private static final Permission[] SCRIPT_PERMISSIONS;
+    static {
+        SCRIPT_PERMISSIONS = new Permission[4];
+
+        /*
+         * Generated classes get access to runtime, runtime.linker, objects, scripts packages.
+         * Note that the actual scripts can not access these because Java.type, Packages
+         * prevent these restricted packages. And Java reflection and JSR292 access is prevented
+         * for scripts. In other words, nashorn generated portions of script classes can access
+         * clases in these implementation packages.
+         */
+        SCRIPT_PERMISSIONS[0] = new RuntimePermission("accessClassInPackage." + RUNTIME_PKG);
+        SCRIPT_PERMISSIONS[1] = new RuntimePermission("accessClassInPackage." + RUNTIME_LINKER_PKG);
+        SCRIPT_PERMISSIONS[2] = new RuntimePermission("accessClassInPackage." + OBJECTS_PKG);
+        SCRIPT_PERMISSIONS[3] = new RuntimePermission("accessClassInPackage." + SCRIPTS_PKG);
+    }
+
     private final Context context;
 
     final Context getContext() {
@@ -68,11 +94,30 @@
         if (i != -1) {
             final SecurityManager sm = System.getSecurityManager();
             if (sm != null) {
-                sm.checkPackageAccess(name.substring(0, i));
+                final String pkgName = name.substring(0, i);
+                switch (pkgName) {
+                    case RUNTIME_PKG:
+                    case RUNTIME_LINKER_PKG:
+                    case OBJECTS_PKG:
+                    case SCRIPTS_PKG:
+                        // allow it.
+                        break;
+                    default:
+                        sm.checkPackageAccess(pkgName);
+                }
             }
         }
     }
 
+    @Override
+    protected PermissionCollection getPermissions(CodeSource codesource) {
+        final Permissions permCollection = new Permissions();
+        for (final Permission perm : SCRIPT_PERMISSIONS) {
+            permCollection.add(perm);
+        }
+        return permCollection;
+    }
+
     /**
      * Create a secure URL class loader for the given classpath
      * @param classPath classpath for the loader to search from
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
new file mode 100644
index 0000000..03f0ab5
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/runtime/RecompilableScriptFunctionData.java
@@ -0,0 +1,185 @@
+/*
+ * 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 java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+
+import jdk.nashorn.internal.codegen.Compiler;
+import jdk.nashorn.internal.codegen.CompilerConstants;
+import jdk.nashorn.internal.codegen.FunctionSignature;
+import jdk.nashorn.internal.ir.FunctionNode;
+import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
+import jdk.nashorn.internal.parser.Token;
+import jdk.nashorn.internal.parser.TokenType;
+
+import static jdk.nashorn.internal.lookup.Lookup.MH;
+
+/**
+ * This is a subclass that represents a script function that may be regenerated,
+ * for example with specialization based on call site types, or lazily generated.
+ * The common denominator is that it can get new invokers during its lifespan,
+ * unlike {@link FinalScriptFunctionData}
+ */
+public final class RecompilableScriptFunctionData extends ScriptFunctionData {
+
+    private final FunctionNode functionNode;
+    private final PropertyMap  allocatorMap;
+    private final CodeInstaller<ScriptEnvironment> installer;
+    private final String allocatorClassName;
+
+    /** lazily generated allocator */
+    private MethodHandle allocator;
+
+    private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
+
+    /**
+     * Constructor - public as scripts use it
+     *
+     * @param functionNode       functionNode that represents this function code
+     * @param installer          installer for code regeneration versions of this function
+     * @param allocatorClassName name of our allocator class, will be looked up dynamically if used as a constructor
+     * @param allocatorMap       allocator map to seed instances with, when constructing
+     */
+    public RecompilableScriptFunctionData(final FunctionNode functionNode, final CodeInstaller<ScriptEnvironment> installer, final String allocatorClassName, final PropertyMap allocatorMap) {
+        super(functionNode.isAnonymous() ?
+                "" :
+                functionNode.getIdent().getName(),
+              functionNode.getParameters().size(),
+              functionNode.isStrictMode(),
+              false,
+              true);
+
+        this.functionNode       = functionNode;
+        this.installer          = installer;
+        this.allocatorClassName = allocatorClassName;
+        this.allocatorMap       = allocatorMap;
+    }
+
+    @Override
+    String toSource() {
+        final Source source = functionNode.getSource();
+        final long   token  = tokenFor(functionNode);
+
+        if (source != null && token != 0) {
+            return source.getString(Token.descPosition(token), Token.descLength(token));
+        }
+
+        return "function " + (name == null ? "" : name) + "() { [native code] }";
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
+        final Source source = functionNode.getSource();
+        final long   token  = tokenFor(functionNode);
+
+        if (source != null) {
+            sb.append(source.getName())
+                .append(':')
+                .append(source.getLine(Token.descPosition(token)))
+                .append(' ');
+        }
+
+        return sb.toString() + super.toString();
+    }
+
+    private static long tokenFor(final FunctionNode fn) {
+        final int  position   = Token.descPosition(fn.getFirstToken());
+        final int  length     = Token.descPosition(fn.getLastToken()) - position + Token.descLength(fn.getLastToken());
+
+        return Token.toDesc(TokenType.FUNCTION, position, length);
+    }
+
+    @Override
+    ScriptObject allocate() {
+        try {
+            ensureHasAllocator(); //if allocatorClass name is set to null (e.g. for bound functions) we don't even try
+            return allocator == null ? null : (ScriptObject)allocator.invokeExact(allocatorMap);
+        } catch (final RuntimeException | Error e) {
+            throw e;
+        } catch (final Throwable t) {
+            throw new RuntimeException(t);
+        }
+    }
+
+    private void ensureHasAllocator() throws ClassNotFoundException {
+        if (allocator == null && allocatorClassName != null) {
+            this.allocator = MH.findStatic(LOOKUP, Context.forStructureClass(allocatorClassName), CompilerConstants.ALLOCATE.tag(), MH.type(ScriptObject.class, PropertyMap.class));
+        }
+    }
+
+    @Override
+    protected void ensureCodeGenerated() {
+         if (!code.isEmpty()) {
+             return; // nothing to do, we have code, at least some.
+         }
+
+         // check if function node is lazy, need to compile it.
+         // note that currently function cloning is not working completely, which
+         // means that the compiler will mutate the function node it has been given
+         // once it has been compiled, it cannot be recompiled. This means that
+         // lazy compilation works (not compiled yet) but e.g. specializations won't
+         // until the copy-on-write changes for IR are in, making cloning meaningless.
+         // therefore, currently method specialization is disabled. TODO
+
+         if (functionNode.isLazy()) {
+             Compiler.LOG.info("Trampoline hit: need to do lazy compilation of '" + functionNode.getName() + "'");
+             new Compiler(installer, functionNode).compile().install();
+
+             // we don't need to update any flags - varArgs and needsCallee are instrincic
+             // in the function world we need to get a destination node from the compile instead
+             // and replace it with our function node. TODO
+         }
+
+         // we can't get here unless we have bytecode, either from eager compilation or from
+         // running a lazy compile on the lines above
+
+         assert functionNode.hasState(CompilationState.INSTALLED);
+
+         // code exists - look it up and add it into the automatically sorted invoker list
+         code.add(
+            new CompiledFunction(
+                MH.findStatic(
+                    LOOKUP,
+                    functionNode.getCompileUnit().getCode(),
+                    functionNode.getName(),
+                    new FunctionSignature(functionNode).
+                        getMethodType())));
+    }
+
+    @Override
+    MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
+        final MethodHandle mh = super.getBestInvoker(callSiteType, args);
+        if (code.isLessSpecificThan(callSiteType)) {
+            // opportunity for code specialization - we can regenerate a better version of this method
+        }
+        return mh;
+    }
+
+}
+
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java
index def0313..36a1d2a 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptEnvironment.java
@@ -82,6 +82,9 @@
     /** Show full Nashorn version */
     public final boolean _fullversion;
 
+    /** Should lazy compilation take place */
+    public final boolean _lazy_compilation;
+
     /** Create a new class loaded for each compilation */
     public final boolean _loader_per_compile;
 
@@ -155,6 +158,7 @@
         _early_lvalue_error   = options.getBoolean("early.lvalue.error");
         _empty_statements     = options.getBoolean("empty.statements");
         _fullversion          = options.getBoolean("fullversion");
+        _lazy_compilation     = options.getBoolean("lazy.compilation");
         _loader_per_compile   = options.getBoolean("loader.per.compile");
         _no_syntax_extensions = options.getBoolean("no.syntax.extensions");
         _package              = options.getString("package");
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java
index 2d28f91..512a0b1 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunction.java
@@ -33,11 +33,11 @@
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
+
 import jdk.internal.dynalink.CallSiteDescriptor;
 import jdk.internal.dynalink.linker.GuardedInvocation;
 import jdk.internal.dynalink.linker.LinkRequest;
 import jdk.nashorn.internal.codegen.CompilerConstants.Call;
-import jdk.nashorn.internal.objects.annotations.SpecializedFunction;
 import jdk.nashorn.internal.lookup.MethodHandleFactory;
 import jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor;
 import jdk.nashorn.internal.runtime.linker.NashornGuards;
@@ -48,16 +48,16 @@
 public abstract class ScriptFunction extends ScriptObject {
 
     /** Method handle for prototype getter for this ScriptFunction */
-    public static final MethodHandle G$PROTOTYPE  = findOwnMH("G$prototype",  Object.class, Object.class);
+    public static final MethodHandle G$PROTOTYPE = findOwnMH("G$prototype", Object.class, Object.class);
 
     /** Method handle for prototype setter for this ScriptFunction */
-    public static final MethodHandle S$PROTOTYPE  = findOwnMH("S$prototype",  void.class, Object.class, Object.class);
+    public static final MethodHandle S$PROTOTYPE = findOwnMH("S$prototype", void.class, Object.class, Object.class);
 
     /** Method handle for length getter for this ScriptFunction */
-    public static final MethodHandle G$LENGTH     = findOwnMH("G$length",     int.class, Object.class);
+    public static final MethodHandle G$LENGTH = findOwnMH("G$length", int.class, Object.class);
 
     /** Method handle for name getter for this ScriptFunction */
-    public static final MethodHandle G$NAME       = findOwnMH("G$name",       Object.class, Object.class);
+    public static final MethodHandle G$NAME = findOwnMH("G$name", Object.class, Object.class);
 
     /** Method handle for allocate function for this ScriptFunction */
     static final MethodHandle ALLOCATE = findOwnMH("allocate", Object.class);
@@ -67,7 +67,9 @@
     /** method handle to scope getter for this ScriptFunction */
     public static final Call GET_SCOPE = virtualCallNoLookup(ScriptFunction.class, "getScope", ScriptObject.class);
 
-    private final ScriptFunctionData data;
+    private static final MethodHandle IS_FUNCTION_MH  = findOwnMH("isFunctionMH", boolean.class, Object.class, ScriptFunctionData.class);
+
+    private static final MethodHandle IS_NONSTRICT_FUNCTION = findOwnMH("isNonStrictFunction", boolean.class, Object.class, Object.class, ScriptFunctionData.class);
 
     /** Reference to constructor prototype. */
     protected Object prototype;
@@ -75,6 +77,8 @@
     /** The parent scope. */
     private final ScriptObject scope;
 
+    private final ScriptFunctionData data;
+
     /**
      * Constructor
      *
@@ -97,7 +101,7 @@
             final boolean builtin,
             final boolean isConstructor) {
 
-        this (new ScriptFunctionData(name, methodHandle, specs, strict, builtin, isConstructor), map, scope);
+        this(new FinalScriptFunctionData(name, methodHandle, specs, strict, builtin, isConstructor), map, scope);
     }
 
     /**
@@ -118,8 +122,8 @@
             constructorCount++;
         }
 
-        this.data = data;
-        this.scope  = scope;
+        this.data  = data;
+        this.scope = scope;
     }
 
     @Override
@@ -295,20 +299,20 @@
     /**
      * Return the most appropriate invoke handle if there are specializations
      * @param type most specific method type to look for invocation with
+     * @param callsite args for trampoline invocation
      * @return invoke method handle
      */
-    private final MethodHandle getBestInvoker(final MethodType type) {
-        return data.getBestInvoker(type);
+    private MethodHandle getBestInvoker(final MethodType type, final Object[] args) {
+        return data.getBestInvoker(type, args);
     }
 
     /**
-     * Get the invoke handle - the most generic (and if no specializations are in place, only) invocation
-     * method handle for this ScriptFunction
-     * @see SpecializedFunction
-     * @return invokeHandle
+     * Return the most appropriate invoke handle if there are specializations
+     * @param type most specific method type to look for invocation with
+     * @return invoke method handle
      */
-    public final MethodHandle getInvokeHandle() {
-        return data.getInvoker();
+    public MethodHandle getBestInvoker(final MethodType type) {
+        return getBestInvoker(type, null);
     }
 
     /**
@@ -319,7 +323,7 @@
      * @return bound invoke handle
      */
     public final MethodHandle getBoundInvokeHandle(final ScriptObject self) {
-        return MH.bindTo(bindToCalleeIfNeeded(getInvokeHandle()), self);
+        return MH.bindTo(bindToCalleeIfNeeded(data.getGenericInvoker()), self);
     }
 
     /**
@@ -329,7 +333,8 @@
      * @return the potentially bound method handle
      */
     private MethodHandle bindToCalleeIfNeeded(final MethodHandle methodHandle) {
-        return data.needsCallee() ? MH.bindTo(methodHandle, this) : methodHandle;
+        return ScriptFunctionData.needsCallee(methodHandle) ? MH.bindTo(methodHandle, this) : methodHandle;
+
     }
 
     /**
@@ -340,15 +345,6 @@
         return data.getName();
     }
 
-    /**
-     * Does this script function need to be compiled. This determined by
-     * null checking invokeHandle
-     *
-     * @return true if this needs compilation
-     */
-    public final boolean needsCompilation() {
-        return data.getInvoker() == null;
-    }
 
     /**
      * Get the scope for this function
@@ -359,15 +355,6 @@
     }
 
     /**
-     * Reset the invoker handle. This is used by trampolines for
-     * lazy code generation
-     * @param invoker new invoker
-     */
-    protected void resetInvoker(final MethodHandle invoker) {
-        data.resetInvoker(invoker);
-    }
-
-    /**
      * Prototype getter for this ScriptFunction - follows the naming convention
      * used by Nasgen and the code generator
      *
@@ -464,7 +451,7 @@
     @Override
     protected GuardedInvocation findNewMethod(final CallSiteDescriptor desc) {
         final MethodType type = desc.getMethodType();
-        return new GuardedInvocation(pairArguments(data.getBestConstructor(type), type), null, NashornGuards.getFunctionGuard(this));
+        return new GuardedInvocation(pairArguments(data.getBestConstructor(type.changeParameterType(0, ScriptFunction.class), null), type), null, getFunctionGuard(this));
     }
 
     @SuppressWarnings("unused")
@@ -472,7 +459,7 @@
         if (obj instanceof ScriptObject || !ScriptFunctionData.isPrimitiveThis(obj)) {
             return obj;
         }
-        return ((GlobalObject) Context.getGlobalTrusted()).wrapAsObject(obj);
+        return ((GlobalObject)Context.getGlobalTrusted()).wrapAsObject(obj);
     }
 
     /**
@@ -506,8 +493,7 @@
         MethodHandle guard = null;
 
         if (data.needsCallee()) {
-            final MethodHandle callHandle = getBestInvoker(type);
-
+            final MethodHandle callHandle = getBestInvoker(type, request.getArguments());
             if (NashornCallSiteDescriptor.isScope(desc)) {
                 // Make a handle that drops the passed "this" argument and substitutes either Global or Undefined
                 // (callee, this, args...) => (callee, args...)
@@ -525,13 +511,12 @@
                     if (ScriptFunctionData.isPrimitiveThis(request.getArguments()[1])) {
                         boundHandle = MH.filterArguments(boundHandle, 1, WRAPFILTER);
                     } else {
-                        guard = NashornGuards.getNonStrictFunctionGuard(this);
+                        guard = getNonStrictFunctionGuard(this);
                     }
                 }
             }
         } else {
-            final MethodHandle callHandle = getBestInvoker(type.dropParameterTypes(0, 1));
-
+            final MethodHandle callHandle = getBestInvoker(type.dropParameterTypes(0, 1), request.getArguments());
             if (NashornCallSiteDescriptor.isScope(desc)) {
                 // Make a handle that drops the passed "this" argument and substitutes either Global or Undefined
                 // (this, args...) => (args...)
@@ -545,7 +530,8 @@
         }
 
         boundHandle = pairArguments(boundHandle, type);
-        return new GuardedInvocation(boundHandle, guard == null ? NashornGuards.getFunctionGuard(this) : guard);
+
+        return new GuardedInvocation(boundHandle, guard == null ? getFunctionGuard(this) : guard);
    }
 
     /**
@@ -554,13 +540,50 @@
      * These don't want a callee parameter, so bind that. Name binding is optional.
      */
     MethodHandle getCallMethodHandle(final MethodType type, final String bindName) {
-        return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(getBestInvoker(type)), bindName), type);
+        return pairArguments(bindToNameIfNeeded(bindToCalleeIfNeeded(getBestInvoker(type, null)), bindName), type);
     }
 
     private static MethodHandle bindToNameIfNeeded(final MethodHandle methodHandle, final String bindName) {
         return bindName == null ? methodHandle : MH.insertArguments(methodHandle, 1, bindName);
     }
 
+    /**
+     * Get the guard that checks if a {@link ScriptFunction} is equal to
+     * a known ScriptFunction, using reference comparison
+     *
+     * @param function The ScriptFunction to check against. This will be bound to the guard method handle
+     *
+     * @return method handle for guard
+     */
+    private static MethodHandle getFunctionGuard(final ScriptFunction function) {
+        assert function.data != null;
+        return MH.insertArguments(IS_FUNCTION_MH, 1, function.data);
+    }
+
+    /**
+     * Get a guard that checks if a {@link ScriptFunction} is equal to
+     * a known ScriptFunction using reference comparison, and whether the type of
+     * the second argument (this-object) is not a JavaScript primitive type.
+     *
+     * @param function The ScriptFunction to check against. This will be bound to the guard method handle
+     *
+     * @return method handle for guard
+     */
+    private static MethodHandle getNonStrictFunctionGuard(final ScriptFunction function) {
+        assert function.data != null;
+        return MH.insertArguments(IS_NONSTRICT_FUNCTION, 2, function.data);
+    }
+
+    @SuppressWarnings("unused")
+    private static boolean isFunctionMH(final Object self, final ScriptFunctionData data) {
+        return self instanceof ScriptFunction && ((ScriptFunction)self).data == data;
+    }
+
+    @SuppressWarnings("unused")
+    private static boolean isNonStrictFunction(final Object self, final Object arg, final ScriptFunctionData data) {
+        return self instanceof ScriptFunction && ((ScriptFunction)self).data == data && arg instanceof ScriptObject;
+    }
+
     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
         final Class<?>   own = ScriptFunction.class;
         final MethodType mt  = MH.type(rtype, types);
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
index 8f8193e..f83cfa2 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptFunctionData.java
@@ -32,227 +32,94 @@
 import java.lang.invoke.MethodHandle;
 import java.lang.invoke.MethodHandles;
 import java.lang.invoke.MethodType;
-import jdk.nashorn.internal.ir.FunctionNode;
-import jdk.nashorn.internal.parser.Token;
-import jdk.nashorn.internal.parser.TokenType;
+
+import jdk.nashorn.internal.runtime.linker.JavaAdapterFactory;
 
 /**
  * A container for data needed to instantiate a specific {@link ScriptFunction} at runtime.
  * Instances of this class are created during codegen and stored in script classes'
  * constants array to reduce function instantiation overhead during runtime.
  */
-public final class ScriptFunctionData {
-    private static final MethodHandle BIND_VAR_ARGS = findOwnMH("bindVarArgs", Object[].class, Object[].class, Object[].class);
+public abstract class ScriptFunctionData {
+
+    /** Name of the function or "" for anonynous functions */
+    protected final String name;
+
+    /** All versions of this function that have been generated to code */
+    protected final CompiledFunctions code;
+
+    private int arity;
+
+    private final boolean isStrict;
+
+    private final boolean isBuiltin;
+
+    private final boolean isConstructor;
+
     private static final MethodHandle NEWFILTER     = findOwnMH("newFilter", Object.class, Object.class, Object.class);
-
-    // 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;
-    private static final int IS_VARARGS     = 0b0000_1000;
-    private static final int IS_CONSTRUCTOR = 0b0001_0000;
-
-    /** Name of the function or "" */
-    private final String name;
-    /** Source of this function, or null */
-    private final Source source;
-    /** Map for new instance constructor */
-    private PropertyMap  allocatorMap;
-    /** Start position and length in source */
-    private final long   token;
-    /** Number of expected arguments, either taken from FunctionNode or calculated from method handle signature*/
-    private int          arity;
-    private final int    flags;
-
-    /** Reference to code for this method. */
-    private MethodHandle invoker;
-    /** Reference to code for this method when called to create "new" object. This must always be populated with a
-     * result of calling {@link #composeConstructor(MethodHandle)} on the value of the {@link #invoker} field. */
-    private MethodHandle constructor;
-    /** Constructor to create a new instance. */
-    private MethodHandle allocator;
-    /** Generic invoker to used in {@link ScriptFunction#invoke(Object, Object...)}. */
-    private MethodHandle genericInvoker;
-    /** Specializations - see @SpecializedFunction */
-    private MethodHandle[] invokeSpecializations;
-    /** Specializations - see @SpecializedFunction. Same restrictions as for {@link #constructor} apply; only populate
-     * with method handles returned from {@link #composeConstructor(MethodHandle)}. */
-    private MethodHandle[] constructSpecializations;
-
-    /**
-     * Constructor
-     * @param fn the function node
-     * @param allocatorMap the allocator property map
-     */
-    public ScriptFunctionData(final FunctionNode fn, final PropertyMap allocatorMap) {
-
-        final long firstToken = fn.getFirstToken();
-        final long lastToken  = fn.getLastToken();
-        final int  position   = Token.descPosition(firstToken);
-        final int  length     = Token.descPosition(lastToken) - position + Token.descLength(lastToken);
-
-        this.name         = fn.isAnonymous() ? "" : fn.getIdent().getName();
-        this.source       = fn.getSource();
-        this.allocatorMap = allocatorMap;
-        this.token        = Token.toDesc(TokenType.FUNCTION, position, length);
-        this.arity        = fn.getParameters().size();
-        this.flags        = makeFlags(fn.needsCallee(), fn.isVarArg(), fn.isStrictMode(), false, true);
-    }
+    private static final MethodHandle BIND_VAR_ARGS = findOwnMH("bindVarArgs", Object[].class, Object[].class, Object[].class);
 
     /**
      * Constructor
      *
-     * @param name the function name
-     * @param methodHandle the method handle
-     * @param specs array of specialized method handles
-     * @param strict strict flag
-     * @param builtin builtin flag
-     * @param isConstructor constructor flags
+     * @param name          script function name
+     * @param arity         arity
+     * @param isStrict      is the function strict
+     * @param isBuiltin     is the function built in
+     * @param isConstructor is the function a constructor
      */
-    public ScriptFunctionData(final String name, final MethodHandle methodHandle, final MethodHandle[] specs, final boolean strict, final boolean builtin, final boolean isConstructor) {
-        this(name, null, 0L, methodHandle, specs, strict, builtin, isConstructor);
+    protected ScriptFunctionData(final String name, final int arity, final boolean isStrict, final boolean isBuiltin, final boolean isConstructor) {
+        this.name          = name;
+        this.arity         = arity;
+        this.code          = new CompiledFunctions();
+        this.isStrict      = isStrict;
+        this.isBuiltin     = isBuiltin;
+        this.isConstructor = isConstructor;
     }
 
-    private ScriptFunctionData(final String name, final Source source, final long token, final MethodHandle methodHandle, final MethodHandle[] specs, final boolean strict, final boolean builtin, final boolean isConstructor) {
-        this.name   = name;
-        this.source = source;
-        this.token  = token;
-
-        final boolean isVarArg = isVarArg(methodHandle);
-        final boolean needsCallee = needsCallee(methodHandle);
-
-        this.flags = makeFlags(needsCallee, isVarArg, strict, builtin, isConstructor);
-        int lArity = isVarArg ? -1 : methodHandle.type().parameterCount() - 1; //drop the self param for arity
-
-        if (needsCallee && !isVarArg) {
-            lArity--;
-        }
-
-        if (isConstructor(methodHandle)) {
-            assert isConstructor;
-            if (!isVarArg) {
-                lArity--;    // drop the boolean flag for arity
-            }
-            /*
-             * We insert a boolean argument to tell if the method was invoked as constructor or not if the method
-             * handle's first argument is boolean.
-             */
-            this.invoker     = MH.insertArguments(methodHandle, 0, false);
-            this.constructor = composeConstructor(MH.insertArguments(methodHandle, 0, true));
-
-            if (specs != null) {
-                this.invokeSpecializations    = new MethodHandle[specs.length];
-                this.constructSpecializations = new MethodHandle[specs.length];
-                for (int i = 0; i < specs.length; i++) {
-                    this.invokeSpecializations[i]    = MH.insertArguments(specs[i], 0, false);
-                    this.constructSpecializations[i] = composeConstructor(MH.insertArguments(specs[i], 0, true));
-                }
-            }
-        } else {
-            this.invoker                  = methodHandle;
-            this.constructor              = null; // delay composition of the constructor
-            this.invokeSpecializations    = specs;
-            this.constructSpecializations = null; // delay composition of the constructors
-        }
-        this.arity = lArity;
-    }
-
-    /**
-     * Get the arity of the function.
-     * @return the arity
-     */
-    int getArity() {
+    final int getArity() {
         return arity;
     }
 
     /**
-     * Set the arity of the function.
-     * @param arity the arity
+     * Used from e.g. Native*$Constructors as an explicit call. TODO - make arity immutable and final
+     * @param arity new arity
      */
-    void setArity(int arity) {
+    void setArity(final int arity) {
         this.arity = arity;
     }
 
-    /**
-     * Get the function name.
-     * @return function name
-     */
-    String getName() {
-        return name;
-    }
+    CompiledFunction bind(final CompiledFunction originalInv, final ScriptFunction fn, final Object self, final Object[] args) {
+        final MethodHandle boundInvoker = bindInvokeHandle(originalInv.getInvoker(), fn, self, args);
 
-    /**
-     * Get this function as a String containing its source code. If no source code
-     * exists in this ScriptFunction, its contents will be displayed as {@code [native code]}
-     * @return string representation of this function's source
-     */
-    String toSource() {
-        if (source != null && token != 0) {
-            return source.getString(Token.descPosition(token), Token.descLength(token));
+        if (isConstructor()) {
+            ensureConstructor(originalInv);
+            return new CompiledFunction(boundInvoker, bindConstructHandle(originalInv.getConstructor(), fn, args));
         }
 
-        return "function " + (name == null ? "" : name) + "() { [native code] }";
-    }
-
-    @Override
-    public String toString() {
-        final StringBuilder sb = new StringBuilder();
-
-        sb.append(super.toString())
-                .append(" [ ")
-                .append(invoker)
-                .append(", ")
-                .append((name == null || name.isEmpty()) ? "<anonymous>" : name);
-
-        if (source != null) {
-            sb.append(" @ ")
-                    .append(source.getName())
-                    .append(':')
-                    .append(source.getLine(Token.descPosition(token)));
-        }
-        sb.append(" ]");
-
-        return sb.toString();
+        return new CompiledFunction(boundInvoker);
     }
 
     /**
-     * Returns true if the function needs a callee argument.
-     * @return the needsCallee flag
-     */
-    boolean needsCallee() {
-        return (flags & HAS_CALLEE) != 0;
-    }
-
-    /**
-     * Returns true if this is a strict-mode function.
-     * @return the strict flag
+     * Is this a ScriptFunction generated with strict semantics?
+     * @return true if strict, false otherwise
      */
     public boolean isStrict() {
-        return (flags & IS_STRICT) != 0;
+        return isStrict;
     }
 
-    /**
-     * Returns true if this is a built-in function.
-     * @return the built-in flag
-     */
-    private boolean isBuiltin() {
-        return (flags & IS_BUILTIN) != 0;
+    boolean isBuiltin() {
+        return isBuiltin;
     }
 
-    /**
-     * Returns true if this function can be used as a constructor.
-     * @return the constructor flag
-     */
-    private boolean isConstructor() {
-        return (flags & IS_CONSTRUCTOR) != 0;
+    boolean isConstructor() {
+        return isConstructor;
     }
 
-    /**
-     * Returns true if this is a var-arg function.
-     * @return the var-arg flag
-     */
-    private boolean isVarArg() {
-        return (flags & IS_VARARGS) != 0;
+    boolean needsCallee() {
+        // we don't know if we need a callee or not unless we are generated
+        ensureCodeGenerated();
+        return code.needsCallee();
     }
 
     /**
@@ -261,127 +128,408 @@
      * @return true if this argument must be an object
      */
     boolean needsWrappedThis() {
-        return (flags & (IS_STRICT | IS_BUILTIN)) == 0;
+        return !isStrict && !isBuiltin;
+    }
+
+    String toSource() {
+        return "function " + (name == null ? "" : name) + "() { [native code] }";
+    }
+
+    String getName() {
+        return name;
     }
 
     /**
-     * Get the method handle used to invoke this function.
-     * @return the invoke handle
+     * Get this function as a String containing its source code. If no source code
+     * exists in this ScriptFunction, its contents will be displayed as {@code [native code]}
+     *
+     * @return string representation of this function
      */
-    MethodHandle getInvoker() {
-        return invoker;
-    }
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder();
 
-    MethodHandle getBestInvoker(final MethodType type) {
-        return SpecializedMethodChooser.candidateWithLowestWeight(type, invoker, invokeSpecializations);
+        sb.append("name='").
+                append(name.isEmpty() ? "<anonymous>" : name).
+                append("' ").
+                append(code.size()).
+                append(" invokers=").
+                append(code);
+
+        return sb.toString();
     }
 
     /**
-     * Get the method handle used to invoke this function as a constructor.
-     * @return the constructor handle
+     * Pick the best invoker, i.e. the one version of this method with as narrow and specific
+     * types as possible. If the call site arguments are objects, but boxed primitives we can
+     * also try to get a primitive version of the method and do an unboxing filter, but then
+     * we need to insert a guard that checks the argument is really always a boxed primitive
+     * and not suddenly a "real" object
+     *
+     * @param callSiteType callsite type
+     * @param args         arguments at callsite on first trampoline invocation
+     * @return method handle to best invoker
      */
-    private MethodHandle getConstructor() {
-        if (constructor == null) {
-            constructor = composeConstructor(invoker);
-        }
-
-        return constructor;
+    MethodHandle getBestInvoker(final MethodType callSiteType, final Object[] args) {
+        return getBest(callSiteType).getInvoker();
     }
 
-    MethodHandle getBestConstructor(MethodType descType) {
+    MethodHandle getBestInvoker(final MethodType callSiteType) {
+        return getBestInvoker(callSiteType, null);
+    }
+
+    MethodHandle getBestConstructor(final MethodType callSiteType, final Object[] args) {
         if (!isConstructor()) {
             throw typeError("not.a.constructor", toSource());
         }
-        return SpecializedMethodChooser.candidateWithLowestWeight(descType, getConstructor(), getConstructSpecializations());
+        ensureCodeGenerated();
+
+        final CompiledFunction best = getBest(callSiteType);
+        ensureConstructor(best);
+        return best.getConstructor();
     }
 
-    private MethodHandle composeConstructor(MethodHandle ctor) {
+    MethodHandle getBestConstructor(final MethodType callSiteType) {
+        return getBestConstructor(callSiteType, null);
+    }
+
+    /**
+     * Subclass responsibility. If we can have lazy code generation, this is a hook to ensure that
+     * code exists before performing an operation.
+     */
+    protected void ensureCodeGenerated() {
+        //empty
+    }
+
+    /**
+     * Return a generic Object/Object invoker for this method. It will ensure code
+     * is generated, get the most generic of all versions of this function and adapt it
+     * to Objects.
+     *
+     * TODO this is only public because {@link JavaAdapterFactory} can't supply us with
+     * a MethodType that we can use for lookup due to boostrapping problems. Can be fixed
+     *
+     * @return generic invoker of this script function
+     */
+    public final MethodHandle getGenericInvoker() {
+        ensureCodeGenerated();
+        return composeGenericMethod(code.mostGeneric().getInvoker());
+    }
+
+    private CompiledFunction getBest(final MethodType callSiteType) {
+        ensureCodeGenerated();
+        return code.best(callSiteType);
+    }
+
+    /**
+     * Allocates an object using this function's allocator.
+     * @return the object allocated using this function's allocator, or null if the function doesn't have an allocator.
+     */
+    ScriptObject allocate() {
+        return null;
+    }
+
+    /**
+     * This method is used to create the immutable portion of a bound function.
+     * See {@link ScriptFunction#makeBoundFunction(Object, Object[])}
+     *
+     * @param fn the original function being bound
+     * @param self this reference to bind. Can be null.
+     * @param args additional arguments to bind. Can be null.
+     */
+    ScriptFunctionData makeBoundFunctionData(final ScriptFunction fn, final Object self, final Object[] args) {
+        ensureCodeGenerated();
+
+        final Object[] allArgs = args == null ? ScriptRuntime.EMPTY_ARRAY : args;
+        final int length = args == null ? 0 : args.length;
+
+        CompiledFunctions boundList = new CompiledFunctions();
+        for (final CompiledFunction inv : code) {
+            boundList.add(bind(inv, fn, self, allArgs));
+        }
+        ScriptFunctionData boundData = new FinalScriptFunctionData(name, arity == -1 ? -1 : Math.max(0, arity - length), boundList, isStrict(), isBuiltin(), isConstructor());
+        return boundData;
+    }
+
+    /**
+     * Compose a constructor given a primordial constructor handle
+     *
+     * @param ctor         primordial constructor handle
+     * @param needsCallee  do we need to pass a callee
+     *
+     * @return the composed constructor
+     */
+    protected MethodHandle composeConstructor(final MethodHandle ctor, final boolean needsCallee) {
         // If it was (callee, this, args...), permute it to (this, callee, args...). We're doing this because having
         // "this" in the first argument position is what allows the elegant folded composition of
         // (newFilter x constructor x allocator) further down below in the code. Also, ensure the composite constructor
         // always returns Object.
-        MethodHandle composedCtor = changeReturnTypeToObject(swapCalleeAndThis(ctor));
+        MethodHandle composedCtor = needsCallee ? swapCalleeAndThis(ctor) : ctor;
+
+        composedCtor = changeReturnTypeToObject(composedCtor);
 
         final MethodType ctorType = composedCtor.type();
+
         // Construct a dropping type list for NEWFILTER, but don't include constructor "this" into it, so it's actually
         // captured as "allocation" parameter of NEWFILTER after we fold the constructor into it.
         // (this, [callee, ]args...) => ([callee, ]args...)
         final Class<?>[] ctorArgs = ctorType.dropParameterTypes(0, 1).parameterArray();
+
         // Fold constructor into newFilter that replaces the return value from the constructor with the originally
         // allocated value when the originally allocated value is a primitive.
         // (result, this, [callee, ]args...) x (this, [callee, ]args...) => (this, [callee, ]args...)
         composedCtor = MH.foldArguments(MH.dropArguments(NEWFILTER, 2, ctorArgs), composedCtor);
 
         // allocate() takes a ScriptFunction and returns a newly allocated ScriptObject...
-        if (needsCallee()) {
+        if (needsCallee) {
             // ...we either fold it into the previous composition, if we need both the ScriptFunction callee object and
             // the newly allocated object in the arguments, so (this, callee, args...) x (callee) => (callee, args...),
             // or...
             return MH.foldArguments(composedCtor, ScriptFunction.ALLOCATE);
         }
+
         // ...replace the ScriptFunction argument with the newly allocated object, if it doesn't need the callee
         // (this, args...) filter (callee) => (callee, args...)
         return MH.filterArguments(composedCtor, 0, ScriptFunction.ALLOCATE);
     }
 
     /**
-     * Get an adapted version of the invoker handle that only uses {@code Object} as parameter and return types.
-     * @return the generic invoke handle
+     * If this function's method handles need a callee parameter, swap the order of first two arguments for the passed
+     * method handle. If this function's method handles don't need a callee parameter, returns the original method
+     * handle unchanged.
+     *
+     * @param mh a method handle with order of arguments {@code (callee, this, args...)}
+     *
+     * @return a method handle with order of arguments {@code (this, callee, args...)}
      */
-    private MethodHandle getGenericInvoker() {
-        if (genericInvoker == null) {
-            assert invoker != null : "invoker is null";
-            genericInvoker = makeGenericMethod(invoker);
+    private static MethodHandle swapCalleeAndThis(final MethodHandle mh) {
+        final MethodType type = mh.type();
+        assert type.parameterType(0) == ScriptFunction.class : type;
+        assert type.parameterType(1) == Object.class : type;
+        final MethodType newType = type.changeParameterType(0, Object.class).changeParameterType(1, ScriptFunction.class);
+        final int[] reorder = new int[type.parameterCount()];
+        reorder[0] = 1;
+        assert reorder[1] == 0;
+        for (int i = 2; i < reorder.length; ++i) {
+            reorder[i] = i;
         }
-        return genericInvoker;
+        return MethodHandles.permuteArguments(mh, newType, reorder);
+    }
+
+    /**
+     * Convert this argument for non-strict functions according to ES 10.4.3
+     *
+     * @param thiz the this argument
+     *
+     * @return the converted this object
+     */
+    private Object convertThisObject(final Object thiz) {
+        if (!(thiz instanceof ScriptObject) && needsWrappedThis()) {
+            if (JSType.nullOrUndefined(thiz)) {
+                return Context.getGlobalTrusted();
+            }
+
+            if (isPrimitiveThis(thiz)) {
+                return ((GlobalObject)Context.getGlobalTrusted()).wrapAsObject(thiz);
+            }
+        }
+
+        return thiz;
+    }
+
+    static boolean isPrimitiveThis(final Object obj) {
+        return obj instanceof String || obj instanceof ConsString ||
+               obj instanceof Number || obj instanceof Boolean;
+    }
+
+    /**
+     * Creates an invoker method handle for a bound function.
+     *
+     * @param targetFn the function being bound
+     * @param originalInvoker an original invoker method handle for the function. This can be its generic invoker or
+     * any of its specializations.
+     * @param self the "this" value being bound
+     * @param args additional arguments being bound
+     *
+     * @return a bound invoker method handle that will bind the self value and the specified arguments. The resulting
+     * invoker never needs a callee; if the original invoker needed it, it will be bound to {@code fn}. The resulting
+     * invoker still takes an initial {@code this} parameter, but it is always dropped and the bound {@code self} passed
+     * to the original invoker on invocation.
+     */
+    private MethodHandle bindInvokeHandle(final MethodHandle originalInvoker, final ScriptFunction targetFn, final Object self, final Object[] args) {
+        // Is the target already bound? If it is, we won't bother binding either callee or self as they're already bound
+        // in the target and will be ignored anyway.
+        final boolean isTargetBound = targetFn.isBoundFunction();
+
+        final boolean needsCallee = needsCallee(originalInvoker);
+        assert needsCallee == needsCallee() : "callee contract violation 2";
+        assert !(isTargetBound && needsCallee); // already bound functions don't need a callee
+
+        final Object boundSelf = isTargetBound ? null : convertThisObject(self);
+        final MethodHandle boundInvoker;
+
+        if (isVarArg(originalInvoker)) {
+            // First, bind callee and this without arguments
+            final MethodHandle noArgBoundInvoker;
+
+            if (isTargetBound) {
+                // Don't bind either callee or this
+                noArgBoundInvoker = originalInvoker;
+            } else if (needsCallee) {
+                // Bind callee and this
+                noArgBoundInvoker = MH.insertArguments(originalInvoker, 0, targetFn, boundSelf);
+            } else {
+                // Only bind this
+                noArgBoundInvoker = MH.bindTo(originalInvoker, boundSelf);
+            }
+            // Now bind arguments
+            if (args.length > 0) {
+                boundInvoker = varArgBinder(noArgBoundInvoker, args);
+            } else {
+                boundInvoker = noArgBoundInvoker;
+            }
+        } else {
+            final Object[] boundArgs = new Object[Math.min(originalInvoker.type().parameterCount(), args.length + (isTargetBound ? 0 : (needsCallee  ? 2 : 1)))];
+            int next = 0;
+            if (!isTargetBound) {
+                if (needsCallee) {
+                    boundArgs[next++] = targetFn;
+                }
+                boundArgs[next++] = boundSelf;
+            }
+            // If more bound args were specified than the function can take, we'll just drop those.
+            System.arraycopy(args, 0, boundArgs, next, boundArgs.length - next);
+            // If target is already bound, insert additional bound arguments after "this" argument, at position 1;
+            // "this" will get dropped anyway by the target invoker. We previously asserted that already bound functions
+            // don't take a callee parameter, so we can know that the signature is (this[, args...]) therefore args
+            // start at position 1. If the function is not bound, we start inserting arguments at position 0.
+            boundInvoker = MH.insertArguments(originalInvoker, isTargetBound ? 1 : 0, boundArgs);
+        }
+
+        if (isTargetBound) {
+            return boundInvoker;
+        }
+
+        // If the target is not already bound, add a dropArguments that'll throw away the passed this
+        return MH.dropArguments(boundInvoker, 0, Object.class);
+    }
+
+    /**
+     * Creates a constructor method handle for a bound function using the passed constructor handle.
+     *
+     * @param originalConstructor the constructor handle to bind. It must be a composed constructor.
+     * @param fn the function being bound
+     * @param args arguments being bound
+     *
+     * @return a bound constructor method handle that will bind the specified arguments. The resulting constructor never
+     * needs a callee; if the original constructor needed it, it will be bound to {@code fn}. The resulting constructor
+     * still takes an initial {@code this} parameter and passes it to the underlying original constructor. Finally, if
+     * this script function data object has no constructor handle, null is returned.
+     */
+    private static MethodHandle bindConstructHandle(final MethodHandle originalConstructor, final ScriptFunction fn, final Object[] args) {
+        assert originalConstructor != null;
+
+        // If target function is already bound, don't bother binding the callee.
+        final MethodHandle calleeBoundConstructor = fn.isBoundFunction() ? originalConstructor :
+            MH.dropArguments(MH.bindTo(originalConstructor, fn), 0, ScriptFunction.class);
+
+        if (args.length == 0) {
+            return calleeBoundConstructor;
+        }
+
+        if (isVarArg(calleeBoundConstructor)) {
+            return varArgBinder(calleeBoundConstructor, args);
+        }
+
+        final Object[] boundArgs;
+
+        final int maxArgCount = calleeBoundConstructor.type().parameterCount() - 1;
+        if (args.length <= maxArgCount) {
+            boundArgs = args;
+        } else {
+            boundArgs = new Object[maxArgCount];
+            System.arraycopy(args, 0, boundArgs, 0, maxArgCount);
+        }
+
+        return MH.insertArguments(calleeBoundConstructor, 1, boundArgs);
+    }
+
+    /**
+     * Takes a method handle, and returns a potentially different method handle that can be used in
+     * {@code ScriptFunction#invoke(Object, Object...)} or {code ScriptFunction#construct(Object, Object...)}.
+     * The returned method handle will be sure to return {@code Object}, and will have all its parameters turned into
+     * {@code Object} as well, except for the following ones:
+     * <ul>
+     *   <li>a last parameter of type {@code Object[]} which is used for vararg functions,</li>
+     *   <li>the first argument, which is forced to be {@link ScriptFunction}, in case the function receives itself
+     *   (callee) as an argument.</li>
+     * </ul>
+     *
+     * @param mh the original method handle
+     *
+     * @return the new handle, conforming to the rules above.
+     */
+    protected MethodHandle composeGenericMethod(final MethodHandle mh) {
+        final MethodType type = mh.type();
+        MethodType newType = type.generic();
+        if (isVarArg(mh)) {
+            newType = newType.changeParameterType(type.parameterCount() - 1, Object[].class);
+        }
+        if (needsCallee(mh)) {
+            newType = newType.changeParameterType(0, ScriptFunction.class);
+        }
+        return type.equals(newType) ? mh : mh.asType(newType);
     }
 
     /**
      * Execute this script function.
+     *
      * @param self  Target object.
      * @param arguments  Call arguments.
      * @return ScriptFunction result.
+     *
      * @throws Throwable if there is an exception/error with the invocation or thrown from it
      */
     Object invoke(final ScriptFunction fn, final Object self, final Object... arguments) throws Throwable {
-        final MethodHandle genInvoker = getGenericInvoker();
-        final Object selfObj = convertThisObject(self);
-        final Object[] args = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;
+        final MethodHandle mh = getGenericInvoker();
 
-        if (isVarArg()) {
-            if (needsCallee()) {
-                return genInvoker.invokeExact(fn, selfObj, args);
+        final Object       selfObj    = convertThisObject(self);
+        final Object[]     args       = arguments == null ? ScriptRuntime.EMPTY_ARRAY : arguments;
+
+        if (isVarArg(mh)) {
+            if (needsCallee(mh)) {
+                return mh.invokeExact(fn, selfObj, args);
             }
-            return genInvoker.invokeExact(selfObj, args);
+            return mh.invokeExact(selfObj, args);
         }
 
-        final int paramCount = genInvoker.type().parameterCount();
-        if (needsCallee()) {
+        final int paramCount = mh.type().parameterCount();
+        if (needsCallee(mh)) {
             switch (paramCount) {
             case 2:
-                return genInvoker.invokeExact(fn, selfObj);
+                return mh.invokeExact(fn, selfObj);
             case 3:
-                return genInvoker.invokeExact(fn, selfObj, getArg(args, 0));
+                return mh.invokeExact(fn, selfObj, getArg(args, 0));
             case 4:
-                return genInvoker.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1));
+                return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1));
             case 5:
-                return genInvoker.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
+                return mh.invokeExact(fn, selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
             default:
-                return genInvoker.invokeWithArguments(withArguments(fn, selfObj, paramCount, args));
+                return mh.invokeWithArguments(withArguments(fn, selfObj, paramCount, args));
             }
         }
 
         switch (paramCount) {
         case 1:
-            return genInvoker.invokeExact(selfObj);
+            return mh.invokeExact(selfObj);
         case 2:
-            return genInvoker.invokeExact(selfObj, getArg(args, 0));
+            return mh.invokeExact(selfObj, getArg(args, 0));
         case 3:
-            return genInvoker.invokeExact(selfObj, getArg(args, 0), getArg(args, 1));
+            return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1));
         case 4:
-            return genInvoker.invokeExact(selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
+            return mh.invokeExact(selfObj, getArg(args, 0), getArg(args, 1), getArg(args, 2));
         default:
-            return genInvoker.invokeWithArguments(withArguments(null, selfObj, paramCount, args));
+            return mh.invokeWithArguments(withArguments(null, selfObj, paramCount, args));
         }
     }
 
@@ -389,15 +537,13 @@
         return i < args.length ? args[i] : UNDEFINED;
     }
 
-    private Object[] withArguments(final ScriptFunction fn, final Object self, final int argCount, final Object[] args) {
+    private static Object[] withArguments(final ScriptFunction fn, final Object self, final int argCount, final Object[] args) {
         final Object[] finalArgs = new Object[argCount];
 
         int nextArg = 0;
-        if (needsCallee()) {
-            assert fn != null;
+        if (fn != null) {
+            //needs callee
             finalArgs[nextArg++] = fn;
-        } else {
-            assert fn == null;
         }
         finalArgs[nextArg++] = self;
 
@@ -413,255 +559,14 @@
 
         return finalArgs;
     }
-
-    /**
-     * Get the specialized construct handles for this function.
-     * @return array of specialized construct handles
-     */
-    private MethodHandle[] getConstructSpecializations() {
-        if(constructSpecializations == null && invokeSpecializations != null) {
-            final MethodHandle[] ctors = new MethodHandle[invokeSpecializations.length];
-            for(int i = 0; i < ctors.length; ++i) {
-                ctors[i] = composeConstructor(invokeSpecializations[i]);
-            }
-            constructSpecializations = ctors;
-        }
-        return constructSpecializations;
-    }
-
-    /**
-     * Set the method handles for this function.
-     * @param invoker the invoker handle
-     * @param allocator the allocator handle
-     */
-    public void setMethodHandles(final MethodHandle invoker, final MethodHandle allocator) {
-        // We can't make method handle fields final because they're not available during codegen
-        // and they're set when first called, so we enforce set-once here.
-        if (this.invoker == null) {
-            this.invoker     = invoker;
-            this.constructor = null; // delay constructor composition
-            this.allocator   = allocator;
-        }
-    }
-
-    /**
-     * Used by the trampoline. Must not be any wider than package
-     * private
-     * @param invoker new invoker
-     */
-    void resetInvoker(final MethodHandle invoker) {
-        this.invoker     = invoker;
-        this.constructor = null; //delay constructor composition
-    }
-
-    /**
-     * Allocates an object using this function's allocator.
-     * @return the object allocated using this function's allocator, or null if the function doesn't have an allocator.
-     */
-    ScriptObject allocate() {
-        if (allocator == null) {
-            return null;
-        }
-
-        try {
-            return (ScriptObject)allocator.invokeExact(allocatorMap);
-        } catch (final RuntimeException | Error e) {
-            throw e;
-        } catch (final Throwable t) {
-            throw new RuntimeException(t);
-        }
-    }
-
-    /**
-     * This method is used to create the immutable portion of a bound function.
-     * See {@link ScriptFunction#makeBoundFunction(Object, Object[])}
-     *
-     * @param fn the original function being bound
-     * @param self this reference to bind. Can be null.
-     * @param args additional arguments to bind. Can be null.
-     */
-    ScriptFunctionData makeBoundFunctionData(final ScriptFunction fn, final Object self, final Object[] args) {
-        final Object[] allArgs = args == null ? ScriptRuntime.EMPTY_ARRAY : args;
-
-        final boolean isConstructor = isConstructor();
-        // Note that the new ScriptFunctionData's method handle will not need a callee regardless of whether the
-        // original did.
-        final ScriptFunctionData boundData = new ScriptFunctionData(name, source, token,
-                bindInvokeHandle(invoker, fn, self, allArgs), bindInvokeSpecializations(fn, self, allArgs), isStrict(), isBuiltin(), isConstructor);
-        if(isConstructor) {
-            // Can't just rely on bound invoke as a basis for constructor, as it ignores the passed "this" in favor of the
-            // bound "this"; constructor on the other hand must see the actual "this" received from the allocator.
-
-            // Binding a function will force constructor composition in getConstructor(); not really any way around that
-            // as it's the composed constructor that has to be bound to the function.
-            boundData.constructor = bindConstructHandle(getConstructor(), fn, allArgs);
-            boundData.constructSpecializations = bindConstructorSpecializations(fn, allArgs);
-        }
-        assert boundData.allocator == null;
-        final int thisArity = getArity();
-        if(thisArity != -1) {
-            boundData.setArity(Math.max(0, thisArity - args.length));
-        } else {
-            assert boundData.getArity() == -1;
-        }
-        return boundData;
-    }
-
-    /**
-     * Convert this argument for non-strict functions according to ES 10.4.3
-     *
-     * @param thiz the this argument
-     *
-     * @return the converted this object
-     */
-    Object convertThisObject(final Object thiz) {
-        if (!(thiz instanceof ScriptObject) && needsWrappedThis()) {
-            if (JSType.nullOrUndefined(thiz)) {
-                return Context.getGlobalTrusted();
-            }
-
-            if (isPrimitiveThis(thiz)) {
-                return ((GlobalObject)Context.getGlobalTrusted()).wrapAsObject(thiz);
-            }
-        }
-
-        return thiz;
-    }
-
-    static boolean isPrimitiveThis(Object obj) {
-        return obj instanceof String || obj instanceof ConsString ||
-               obj instanceof Number || obj instanceof Boolean;
-    }
-
-    /**
-     * Creates an invoker method handle for a bound function.
-     * @param targetFn the function being bound
-     * @param originalInvoker an original invoker method handle for the function. This can be its generic invoker or
-     * any of its specializations.
-     * @param self the "this" value being bound
-     * @param args additional arguments being bound
-     * @return a bound invoker method handle that will bind the self value and the specified arguments. The resulting
-     * invoker never needs a callee; if the original invoker needed it, it will be bound to {@code fn}. The resulting
-     * invoker still takes an initial {@code this} parameter, but it is always dropped and the bound {@code self} passed
-     * to the original invoker on invocation.
-     */
-    private MethodHandle bindInvokeHandle(final MethodHandle originalInvoker, final ScriptFunction targetFn, final Object self, final Object[] args) {
-        // Is the target already bound? If it is, we won't bother binding either callee or self as they're already bound
-        // in the target and will be ignored anyway.
-        final boolean isTargetBound = targetFn.isBoundFunction();
-        assert !(isTargetBound && needsCallee()); // already bound functions don't need a callee
-        final Object boundSelf = isTargetBound ? null : convertThisObject(self);
-        final MethodHandle boundInvoker;
-        if(isVarArg(originalInvoker)) {
-            // First, bind callee and this without arguments
-            final MethodHandle noArgBoundInvoker;
-            if(isTargetBound) {
-                // Don't bind either callee or this
-                noArgBoundInvoker = originalInvoker;
-            } else if(needsCallee()) {
-                // Bind callee and this
-                noArgBoundInvoker = MH.insertArguments(originalInvoker, 0, targetFn, boundSelf);
-            } else {
-                // Only bind this
-                noArgBoundInvoker = MH.bindTo(originalInvoker, boundSelf);
-            }
-            // Now bind arguments
-            if(args.length > 0) {
-                boundInvoker = varArgBinder(noArgBoundInvoker, args);
-            } else {
-                boundInvoker = noArgBoundInvoker;
-            }
-        } else {
-            final Object[] boundArgs = new Object[Math.min(originalInvoker.type().parameterCount(),
-                    args.length + (isTargetBound ? 0 : (needsCallee() ? 2 : 1)))];
-            int next = 0;
-            if(!isTargetBound) {
-                if(needsCallee()) {
-                    boundArgs[next++] = targetFn;
-                }
-                boundArgs[next++] = boundSelf;
-            }
-            // If more bound args were specified than the function can take, we'll just drop those.
-            System.arraycopy(args, 0, boundArgs, next, boundArgs.length - next);
-            // If target is already bound, insert additional bound arguments after "this" argument, at position 1;
-            // "this" will get dropped anyway by the target invoker. We previously asserted that already bound functions
-            // don't take a callee parameter, so we can know that the signature is (this[, args...]) therefore args
-            // start at position 1. If the function is not bound, we start inserting arguments at position 0.
-            boundInvoker = MH.insertArguments(originalInvoker, isTargetBound ? 1 : 0, boundArgs);
-        }
-        if(isTargetBound) {
-            return boundInvoker;
-        }
-        // If the target is not already bound, add a dropArguments that'll throw away the passed this
-        return MH.dropArguments(boundInvoker, 0, Object.class);
-    }
-
-    private MethodHandle[] bindInvokeSpecializations(final ScriptFunction fn, final Object self, final Object[] args) {
-        if(invokeSpecializations == null) {
-            return null;
-        }
-        final MethodHandle[] boundSpecializations = new MethodHandle[invokeSpecializations.length];
-        for(int i = 0; i < invokeSpecializations.length; ++i) {
-            boundSpecializations[i] = bindInvokeHandle(invokeSpecializations[i], fn, self, args);
-        }
-        return boundSpecializations;
-    }
-
-    /**
-     * Creates a constructor method handle for a bound function using the passed constructor handle.
-     * @param originalConstructor the constructor handle to bind. It must be a composed constructor.
-     * @param fn the function being bound
-     * @param args arguments being bound
-     * @return a bound constructor method handle that will bind the specified arguments. The resulting constructor never
-     * needs a callee; if the original constructor needed it, it will be bound to {@code fn}. The resulting constructor
-     * still takes an initial {@code this} parameter and passes it to the underlying original constructor. Finally, if
-     * this script function data object has no constructor handle, null is returned.
-     */
-    private static MethodHandle bindConstructHandle(final MethodHandle originalConstructor, final ScriptFunction fn, final Object[] args) {
-        if(originalConstructor == null) {
-            return null;
-        }
-
-        // If target function is already bound, don't bother binding the callee.
-        final MethodHandle calleeBoundConstructor = fn.isBoundFunction() ? originalConstructor :
-            MH.dropArguments(MH.bindTo(originalConstructor, fn), 0, ScriptFunction.class);
-        if(args.length == 0) {
-            return calleeBoundConstructor;
-        }
-
-        if(isVarArg(calleeBoundConstructor)) {
-            return varArgBinder(calleeBoundConstructor, args);
-        }
-
-        final Object[] boundArgs;
-        final int maxArgCount = calleeBoundConstructor.type().parameterCount() - 1;
-        if (args.length <= maxArgCount) {
-            boundArgs = args;
-        } else {
-            boundArgs = new Object[maxArgCount];
-            System.arraycopy(args, 0, boundArgs, 0, maxArgCount);
-        }
-        return MH.insertArguments(calleeBoundConstructor, 1, boundArgs);
-    }
-
-    private MethodHandle[] bindConstructorSpecializations(final ScriptFunction fn, final Object[] args) {
-        final MethodHandle[] ctorSpecs = getConstructSpecializations();
-        if(ctorSpecs == null) {
-            return null;
-        }
-        final MethodHandle[] boundSpecializations = new MethodHandle[ctorSpecs.length];
-        for(int i = 0; i < ctorSpecs.length; ++i) {
-            boundSpecializations[i] = bindConstructHandle(ctorSpecs[i], fn, args);
-        }
-        return boundSpecializations;
-    }
-
     /**
      * Takes a variable-arity method and binds a variable number of arguments in it. The returned method will filter the
      * vararg array and pass a different array that prepends the bound arguments in front of the arguments passed on
      * invocation
+     *
      * @param mh the handle
      * @param args the bound arguments
+     *
      * @return the bound method handle
      */
     private static MethodHandle varArgBinder(final MethodHandle mh, final Object[] args) {
@@ -671,98 +576,9 @@
     }
 
     /**
-     * Convert boolean flags to int.
-     * @param needsCallee needs-callee flag
-     * @param isVarArg var-arg flag
-     * @param isStrict strict flag
-     * @param isBuiltin builtin flag
-     * @return int flags
-     */
-    private static int makeFlags(final boolean needsCallee, final boolean isVarArg, final boolean isStrict, final boolean isBuiltin, final boolean isConstructor) {
-        int flags = 0;
-        if (needsCallee) {
-            flags |= HAS_CALLEE;
-        }
-        if (isVarArg) {
-            flags |= IS_VARARGS;
-        }
-        if (isStrict) {
-            flags |= IS_STRICT;
-        }
-        if (isBuiltin) {
-            flags |= IS_BUILTIN;
-        }
-        if (isConstructor) {
-            flags |= IS_CONSTRUCTOR;
-        }
-
-        return flags;
-    }
-
-    /**
-     * Test if a methodHandle refers to a constructor.
-     * @param methodHandle MethodHandle to test.
-     * @return True if method is a constructor.
-     */
-    private static boolean isConstructor(final MethodHandle methodHandle) {
-        return methodHandle.type().parameterCount() >= 1 && methodHandle.type().parameterType(0) == boolean.class;
-    }
-
-    /**
-     * 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 > 1 && type.parameterType(1) == ScriptFunction.class;
-        }
-        return type.parameterType(0) == ScriptFunction.class;
-    }
-
-    private static boolean isVarArg(MethodHandle methodHandle) {
-        final MethodType type = methodHandle.type();
-        return type.parameterType(type.parameterCount() - 1).isArray();
-    }
-
-    /**
-     * Takes a method handle, and returns a potentially different method handle that can be used in
-     * {@link ScriptFunction#invoke(Object, Object...)} or {@link ScriptFunction#construct(Object, Object...)}.
-     * The returned method handle will be sure to return {@code Object}, and will have all its parameters turned into
-     * {@code Object} as well, except for the following ones:
-     * <ul>
-     *   <li>a last parameter of type {@code Object[]} which is used for vararg functions,</li>
-     *   <li>the first argument, which is forced to be {@link ScriptFunction}, in case the function receives itself
-     *   (callee) as an argument.</li>
-     * </ul>
-     *
-     * @param handle the original method handle
-     * @return the new handle, conforming to the rules above.
-     */
-    private MethodHandle makeGenericMethod(final MethodHandle handle) {
-        final MethodType type = handle.type();
-        MethodType newType = type.generic();
-        if (isVarArg()) {
-            newType = newType.changeParameterType(type.parameterCount() - 1, Object[].class);
-        }
-        if (needsCallee()) {
-            newType = newType.changeParameterType(0, ScriptFunction.class);
-        }
-        return type.equals(newType) ? handle : handle.asType(newType);
-    }
-
-    /**
      * Adapts the method handle so its return type is {@code Object}. If the handle's return type is already
      * {@code Object}, the handle is returned unchanged.
+     *
      * @param mh the handle to adapt
      * @return the adapted handle
      */
@@ -770,45 +586,67 @@
         return MH.asType(mh, mh.type().changeReturnType(Object.class));
     }
 
+    private void ensureConstructor(final CompiledFunction inv) {
+        if (!inv.hasConstructor()) {
+            inv.setConstructor(composeConstructor(inv.getInvoker(), needsCallee(inv.getInvoker())));
+        }
+    }
 
     /**
-     * If this function's method handles need a callee parameter, swap the order of first two arguments for the passed
-     * method handle. If this function's method handles don't need a callee parameter, returns the original method
-     * handle unchanged.
-     * @param mh a method handle with order of arguments {@code (callee, this, args...)}
-     * @return a method handle with order of arguments {@code (this, callee, args...)}
+     * 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 mh the examined method handle
+     *
+     * @return true if the method handle expects a callee, false otherwise
      */
-    private MethodHandle swapCalleeAndThis(final MethodHandle mh) {
-        if (!needsCallee()) {
-            return mh;
+    protected static boolean needsCallee(final MethodHandle mh) {
+        final MethodType type   = mh.type();
+        final int        length = type.parameterCount();
+
+        if (length == 0) {
+            return false;
         }
+
+        if (type.parameterType(0) == boolean.class) {
+            return length > 1 && type.parameterType(1) == ScriptFunction.class;
+        }
+
+        return type.parameterType(0) == ScriptFunction.class;
+    }
+
+    /**
+     * Check if a javascript function methodhandle is a vararg handle
+     *
+     * @param mh method handle to check
+     *
+     * @return true if vararg
+     */
+    protected static boolean isVarArg(final MethodHandle mh) {
         final MethodType type = mh.type();
-        assert type.parameterType(0) == ScriptFunction.class;
-        assert type.parameterType(1) == Object.class;
-        final MethodType newType = type.changeParameterType(0, Object.class).changeParameterType(1, ScriptFunction.class);
-        final int[] reorder = new int[type.parameterCount()];
-        reorder[0] = 1;
-        assert reorder[1] == 0;
-        for (int i = 2; i < reorder.length; ++i) {
-            reorder[i] = i;
-        }
-        return MethodHandles.permuteArguments(mh, newType, reorder);
+        return type.parameterType(type.parameterCount() - 1).isArray();
     }
 
     @SuppressWarnings("unused")
     private static Object[] bindVarArgs(final Object[] array1, final Object[] array2) {
-        if(array2 == null) {
+        if (array2 == null) {
             // Must clone it, as we can't allow the receiving method to alter the array
             return array1.clone();
         }
+
         final int l2 = array2.length;
-        if(l2 == 0) {
+        if (l2 == 0) {
             return array1.clone();
         }
+
         final int l1 = array1.length;
         final Object[] concat = new Object[l1 + l2];
         System.arraycopy(array1, 0, concat, 0, l1);
         System.arraycopy(array2, 0, concat, l1, l2);
+
         return concat;
     }
 
@@ -820,5 +658,4 @@
     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
         return MH.findStatic(MethodHandles.lookup(), ScriptFunctionData.class, name, MH.type(rtype, types));
     }
-
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/ScriptLoader.java b/nashorn/src/jdk/nashorn/internal/runtime/ScriptLoader.java
index d632166..370faf3 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/ScriptLoader.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/ScriptLoader.java
@@ -26,6 +26,7 @@
 package jdk.nashorn.internal.runtime;
 
 import java.security.CodeSource;
+import java.security.ProtectionDomain;
 
 /**
  * Responsible for loading script generated classes.
@@ -57,6 +58,9 @@
      * @return Installed class.
      */
     synchronized Class<?> installClass(final String name, final byte[] data, final CodeSource cs) {
+        if (cs == null) {
+            return defineClass(name, data, 0, data.length, new ProtectionDomain(null, getPermissions(null)));
+        }
         return defineClass(name, data, 0, data.length, cs);
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/SpecializedMethodChooser.java b/nashorn/src/jdk/nashorn/internal/runtime/SpecializedMethodChooser.java
deleted file mode 100644
index 2853cdf..0000000
--- a/nashorn/src/jdk/nashorn/internal/runtime/SpecializedMethodChooser.java
+++ /dev/null
@@ -1,99 +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.runtime;
-
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodType;
-import jdk.nashorn.internal.codegen.types.Type;
-import jdk.nashorn.internal.runtime.options.Options;
-
-class SpecializedMethodChooser {
-    /** Should specialized function and specialized constructors for the builtin be used if available? */
-    private static final boolean DISABLE_SPECIALIZATION = Options.getBooleanProperty("nashorn.scriptfunction.specialization.disable");
-
-    static MethodHandle candidateWithLowestWeight(final MethodType descType, final MethodHandle initialCandidate, final MethodHandle[] specs) {
-        if (DISABLE_SPECIALIZATION || specs == null) {
-            return initialCandidate;
-        }
-
-        int          minimumWeight = Integer.MAX_VALUE;
-        MethodHandle candidate     = initialCandidate;
-
-        for (final MethodHandle spec : specs) {
-            final MethodType specType = spec.type();
-
-            if (!typeCompatible(descType, specType)) {
-                continue;
-            }
-
-            //return type is ok. we want a wider or equal one for our callsite.
-            final int specWeight = weigh(specType);
-            if (specWeight < minimumWeight) {
-                candidate = spec;
-                minimumWeight = specWeight;
-            }
-        }
-
-        return candidate;
-    }
-
-    private static boolean typeCompatible(final MethodType desc, final MethodType spec) {
-        //spec must fit in desc
-        final Class<?>[] dparray = desc.parameterArray();
-        final Class<?>[] sparray = spec.parameterArray();
-
-        if (dparray.length != sparray.length) {
-            return false;
-        }
-
-        for (int i = 0; i < dparray.length; i++) {
-            final Type dp = Type.typeFor(dparray[i]);
-            final Type sp = Type.typeFor(sparray[i]);
-
-            if (dp.isBoolean()) {
-                return false; //don't specialize on booleans, we have the "true" vs int 1 ambiguity in resolution
-            }
-
-            //specialization arguments must be at least as wide as dp, if not wider
-            if (Type.widest(dp, sp) != sp) {
-                //e.g. specialization takes double and callsite says "object". reject.
-                //but if specialization says double and callsite says "int" or "long" or "double", that's fine
-                return false;
-            }
-        }
-
-        return true; // anything goes for return type, take the convenient one and it will be upcasted thru dynalink magic.
-    }
-
-    private static int weigh(final MethodType t) {
-        int weight = Type.typeFor(t.returnType()).getWeight();
-        for (final Class<?> paramType : t.parameterArray()) {
-            final int pweight = Type.typeFor(paramType).getWeight();
-            weight += pweight;
-        }
-        return weight;
-    }
-}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java b/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java
index da64655..35786b0 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/StructureLoader.java
@@ -38,6 +38,7 @@
 import java.security.CodeSource;
 import java.security.PrivilegedActionException;
 import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
 import jdk.nashorn.internal.codegen.ObjectClassGenerator;
 
 /**
@@ -129,6 +130,6 @@
         }
 
         final byte[] code = new ObjectClassGenerator(context).generate(descriptor);
-        return defineClass(name, code, 0, code.length);
+        return defineClass(name, code, 0, code.length, new ProtectionDomain(null, getPermissions(null)));
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java
index 3c5e2d8..6b55656 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/Bootstrap.java
@@ -57,7 +57,7 @@
     static {
         final DynamicLinkerFactory factory = new DynamicLinkerFactory();
         factory.setPrioritizedLinkers(new NashornLinker(), new NashornPrimitiveLinker(), new NashornStaticClassLinker(),
-                new JSObjectLinker());
+                new JSObjectLinker(), new ReflectionCheckLinker());
         factory.setFallbackLinkers(new BeansLinker(), new NashornBottomLinker());
         factory.setSyncOnRelink(true);
         final int relinkThreshold = Options.getIntProperty("nashorn.unstable.relink.threshold", -1);
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java
index a9e4bd0..7b0d6d7 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaAdapterFactory.java
@@ -54,6 +54,7 @@
 import java.security.CodeSource;
 import java.security.Permissions;
 import java.security.PrivilegedAction;
+import java.security.PrivilegedExceptionAction;
 import java.security.ProtectionDomain;
 import java.security.SecureClassLoader;
 import java.security.SecureRandom;
@@ -410,9 +411,14 @@
      */
     public static MethodHandle getConstructor(final Class<?> sourceType, final Class<?> targetType) throws Exception {
         final StaticClass adapterClass = getAdapterClassFor(new Class<?>[] { targetType });
-        return MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new LinkRequestImpl(NashornCallSiteDescriptor.get(
-                "dyn:new", MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false,
-                adapterClass, null)).getInvocation(), adapterClass);
+        return AccessController.doPrivileged(new PrivilegedExceptionAction<MethodHandle>() {
+            @Override
+            public MethodHandle run() throws Exception {
+                return  MH.bindTo(Bootstrap.getLinkerServices().getGuardedInvocation(new LinkRequestImpl(NashornCallSiteDescriptor.get(
+                    "dyn:new", MethodType.methodType(targetType, StaticClass.class, sourceType), 0), false,
+                    adapterClass, null)).getInvocation(), adapterClass);
+            }
+        });
     }
 
     /**
@@ -456,9 +462,26 @@
     private static ClassLoader createClassLoader(final ClassLoader parentLoader, final String className,
             final byte[] classBytes, final String privilegedActionClassName) {
         return new AdapterLoader(parentLoader) {
+            private final ClassLoader myLoader = getClass().getClassLoader();
             private final ProtectionDomain myProtectionDomain = getClass().getProtectionDomain();
 
             @Override
+            public Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
+                try {
+                    return super.loadClass(name, resolve);
+                } catch (final SecurityException se) {
+                    // we may be implementing an interface or extending a class that was
+                    // loaded by a loader that prevents package.access. If so, it'd throw
+                    // SecurityException for nashorn's classes!. For adapter's to work, we
+                    // should be able to refer to nashorn classes.
+                    if (name.startsWith("jdk.nashorn.internal.")) {
+                        return myLoader.loadClass(name);
+                    }
+                    throw se;
+                }
+            }
+
+            @Override
             protected Class<?> findClass(final String name) throws ClassNotFoundException {
                 if(name.equals(className)) {
                     final byte[] bytes = classBytes;
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java
index 3d8363e..834898a 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/JavaArgumentConverters.java
@@ -39,7 +39,7 @@
 import jdk.nashorn.internal.runtime.ScriptObject;
 
 /**
- * Utility class shared by {@link NashornLinker} and {@code NashornPrimitiveLinker} for converting JS values to Java
+ * Utility class shared by {@code NashornLinker} and {@code NashornPrimitiveLinker} for converting JS values to Java
  * types.
  */
 public class JavaArgumentConverters {
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java
index ed15248..a8e7918 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/NashornGuards.java
@@ -40,8 +40,6 @@
     private static final MethodHandle IS_SCRIPTOBJECT          = findOwnMH("isScriptObject", boolean.class, Object.class);
     private static final MethodHandle IS_SCRIPTFUNCTION        = findOwnMH("isScriptFunction", boolean.class, Object.class);
     private static final MethodHandle IS_MAP                   = findOwnMH("isMap", boolean.class, Object.class, PropertyMap.class);
-    private static final MethodHandle IS_FUNCTION_MH           = findOwnMH("isFunctionMH", boolean.class, Object.class, MethodHandle.class);
-    private static final MethodHandle IS_NONSTRICT_FUNCTION    = findOwnMH("isNonStrictFunction", boolean.class, Object.class, Object.class, MethodHandle.class);
     private static final MethodHandle IS_INSTANCEOF_2          = findOwnMH("isInstanceOf2", boolean.class, Object.class, Class.class, Class.class);
 
     // don't create me!
@@ -87,33 +85,6 @@
         return MH.insertArguments(IS_INSTANCEOF_2, 1, class1, class2);
     }
 
-    /**
-     * Get the guard that checks if a {@link ScriptFunction} is equal to
-     * a known ScriptFunction, using reference comparison
-     *
-     * @param function The ScriptFunction to check against. This will be bound to the guard method handle
-     *
-     * @return method handle for guard
-     */
-    public static MethodHandle getFunctionGuard(final ScriptFunction function) {
-        assert function.getInvokeHandle() != null;
-        return MH.insertArguments(IS_FUNCTION_MH, 1, function.getInvokeHandle());
-    }
-
-    /**
-     * Get a guard that checks if a {@link ScriptFunction} is equal to
-     * a known ScriptFunction using reference comparison, and whether the type of
-     * the second argument (this-object) is not a JavaScript primitive type.
-     *
-     * @param function The ScriptFunction to check against. This will be bound to the guard method handle
-     *
-     * @return method handle for guard
-     */
-    public static MethodHandle getNonStrictFunctionGuard(final ScriptFunction function) {
-        assert function.getInvokeHandle() != null;
-        return MH.insertArguments(IS_NONSTRICT_FUNCTION, 2, function.getInvokeHandle());
-    }
-
     @SuppressWarnings("unused")
     private static boolean isScriptObject(final Object self) {
         return self instanceof ScriptObject;
@@ -130,16 +101,6 @@
     }
 
     @SuppressWarnings("unused")
-    private static boolean isFunctionMH(final Object self, final MethodHandle mh) {
-        return self instanceof ScriptFunction && ((ScriptFunction)self).getInvokeHandle() == mh;
-    }
-
-    @SuppressWarnings("unused")
-    private static boolean isNonStrictFunction(final Object self, final Object arg, final MethodHandle mh) {
-        return self instanceof ScriptFunction && ((ScriptFunction)self).getInvokeHandle() == mh && arg instanceof ScriptObject;
-    }
-
-    @SuppressWarnings("unused")
     private static boolean isInstanceOf2(final Object self, final Class<?> class1, final Class<?> class2) {
         return class1.isInstance(self) || class2.isInstance(self);
     }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/PrimitiveLookup.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/PrimitiveLookup.java
index 968dba9..eaba64f 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/linker/PrimitiveLookup.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/PrimitiveLookup.java
@@ -109,7 +109,6 @@
             }
             return new GuardedInvocation(method, guard, link.getSwitchPoint());
         }
-        assert desc.getNameTokenCount() <= 2; // Named operations would hit the return null after findProperty
         return null;
     }
 }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/linker/ReflectionCheckLinker.java b/nashorn/src/jdk/nashorn/internal/runtime/linker/ReflectionCheckLinker.java
new file mode 100644
index 0000000..eb8837a
--- /dev/null
+++ b/nashorn/src/jdk/nashorn/internal/runtime/linker/ReflectionCheckLinker.java
@@ -0,0 +1,60 @@
+/*
+ * 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.linker;
+
+import jdk.internal.dynalink.linker.GuardedInvocation;
+import jdk.internal.dynalink.linker.LinkRequest;
+import jdk.internal.dynalink.linker.LinkerServices;
+import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
+
+/**
+ * Check java reflection permission for java reflective and java.lang.invoke access from scripts
+ */
+final class ReflectionCheckLinker implements TypeBasedGuardingDynamicLinker{
+    @Override
+    public boolean canLinkType(final Class<?> type) {
+        return canLinkTypeStatic(type);
+    }
+
+    private static boolean canLinkTypeStatic(final Class<?> type) {
+        if (type == Class.class || ClassLoader.class.isAssignableFrom(type)) {
+            return true;
+        }
+        final String name = type.getName();
+        return name.startsWith("java.lang.reflect.") || name.startsWith("java.lang.invoke.");
+    }
+
+    @Override
+    public GuardedInvocation getGuardedInvocation(final LinkRequest origRequest, final LinkerServices linkerServices)
+            throws Exception {
+        final SecurityManager sm = System.getSecurityManager();
+        if (sm != null) {
+            sm.checkPermission(new RuntimePermission("nashorn.JavaReflection"));
+        }
+        // let the next linker deal with actual linking
+        return null;
+    }
+}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/options/OptionTemplate.java b/nashorn/src/jdk/nashorn/internal/runtime/options/OptionTemplate.java
index 82b6edb..16ff04e 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/options/OptionTemplate.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/options/OptionTemplate.java
@@ -278,7 +278,7 @@
                     this.valueNextArg = Boolean.parseBoolean(arg);
                     break;
                 default:
-                    throw new IllegalArgumentException();
+                    throw new IllegalArgumentException(keyToken);
                 }
             }
 
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/options/Options.java b/nashorn/src/jdk/nashorn/internal/runtime/options/Options.java
index 0f30b1a..3e09fa5 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/options/Options.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/options/Options.java
@@ -66,6 +66,9 @@
     /** The options map of enabled options */
     private final TreeMap<String, Option<?>> options;
 
+    /** System property that can be used for command line option propagation */
+    private static final String NASHORN_ARGS_PROPERTY = "nashorn.args";
+
     /**
      * Constructor
      *
@@ -386,6 +389,14 @@
         final LinkedList<String> argList = new LinkedList<>();
         Collections.addAll(argList, args);
 
+    final String extra = getStringProperty(NASHORN_ARGS_PROPERTY, null);
+    if (extra != null) {
+        final StringTokenizer st = new StringTokenizer(extra);
+        while (st.hasMoreTokens()) {
+        argList.add(st.nextToken());
+        }
+    }
+
         while (!argList.isEmpty()) {
             final String arg = argList.remove(0);
 
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/DefaultRegExp.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/DefaultRegExp.java
index 7c1dd8b..ecbc8bc 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/DefaultRegExp.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/DefaultRegExp.java
@@ -95,14 +95,14 @@
             return null; // never matches or similar, e.g. a[]
         }
 
-        RegExpMatcher matcher = this.matcher;
+        RegExpMatcher currentMatcher = this.matcher;
 
-        if (matcher == null || matcher.getInput() != str) {
-            matcher = new DefaultMatcher(str);
-            this.matcher = matcher;
+        if (currentMatcher == null || matcher.getInput() != str) {
+            currentMatcher = new DefaultMatcher(str);
+            this.matcher  = currentMatcher;
         }
 
-        return matcher;
+        return currentMatcher;
     }
 
     class DefaultMatcher implements RegExpMatcher {
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/JoniRegExp.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/JoniRegExp.java
index f8c35bf..719f6c3 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/JoniRegExp.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/JoniRegExp.java
@@ -97,14 +97,14 @@
             return null;
         }
 
-        RegExpMatcher matcher = this.matcher;
+        RegExpMatcher currentMatcher = this.matcher;
 
-        if (matcher == null || input != matcher.getInput()) {
-            matcher = new JoniMatcher(input);
-            this.matcher = matcher;
+        if (currentMatcher == null || input != currentMatcher.getInput()) {
+            currentMatcher = new JoniMatcher(input);
+            this.matcher   = currentMatcher;
         }
 
-        return matcher;
+        return currentMatcher;
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExp.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExp.java
index a4274f6..ff694b9 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExp.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExp.java
@@ -156,7 +156,7 @@
      *
      * @param key the message key
      * @param str string argument
-     * @throws jdk.nashorn.internal.runtime.ParserException
+     * @throws jdk.nashorn.internal.runtime.ParserException unconditionally
      */
     protected static void throwParserException(final String key, final String str) throws ParserException {
         throw new ParserException(ECMAErrors.getMessage("parser.error.regex." + key, str));
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java
index 367cc85..6ff66f2 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpFactory.java
@@ -25,7 +25,6 @@
 
 package jdk.nashorn.internal.runtime.regexp;
 
-import jdk.nashorn.internal.parser.Lexer;
 import jdk.nashorn.internal.runtime.ParserException;
 import jdk.nashorn.internal.runtime.options.Options;
 
@@ -35,7 +34,6 @@
  */
 public class RegExpFactory {
 
-
     private final static RegExpFactory instance;
 
     private final static String JDK  = "jdk";
@@ -60,7 +58,8 @@
      * Creates a Regular expression from the given {@code pattern} and {@code flags} strings.
      *
      * @param pattern RegExp pattern string
-     * @param flags RegExp flags string
+     * @param flags   RegExp flags string
+     * @return new RegExp
      * @throws ParserException if flags is invalid or pattern string has syntax error.
      */
     protected RegExp compile(final String pattern, final String flags) throws ParserException {
@@ -71,8 +70,8 @@
      * Compile a regexp with the given {@code source} and {@code flags}.
      *
      * @param pattern RegExp pattern string
-     * @param flags  flag string
-     *
+     * @param flags   flag string
+     * @return new RegExp
      * @throws ParserException if invalid source or flags
      */
     public static RegExp create(final String pattern, final String flags) {
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpResult.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpResult.java
index ff838b6..cd81be3 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpResult.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpResult.java
@@ -80,11 +80,11 @@
 
     /**
      * Get the group with the given index or the empty string if group index is not valid.
-     * @param index the group index
+     * @param groupIndex the group index
      * @return the group or ""
      */
-    public Object getGroup(int index) {
-        return index >= 0 && index < groups.length ? groups[index] : "";
+    public Object getGroup(final int groupIndex) {
+        return groupIndex >= 0 && groupIndex < groups.length ? groups[groupIndex] : "";
     }
 
     /**
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java
index b579865..e8c60c4 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/RegExpScanner.java
@@ -182,8 +182,6 @@
      * @return Committed token
      */
     private boolean commit(final int n) {
-        final int startIn = position;
-
         switch (n) {
         case 1:
             sb.append(ch0);
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java
index 8aaabe2..43cc518 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/ByteCodeMachine.java
@@ -1459,4 +1459,4 @@
     private int finish() {
         return bestLen;
     }
-}
\ No newline at end of file
+}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/encoding/AsciiTables.java b/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/encoding/AsciiTables.java
index 77eba2d..3dec4ba 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/encoding/AsciiTables.java
+++ b/nashorn/src/jdk/nashorn/internal/runtime/regexp/joni/encoding/AsciiTables.java
@@ -154,4 +154,4 @@
             {0x59, 0x79},
             {0x5a, 0x7a}
     };
-}
\ No newline at end of file
+}
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties b/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties
index 4547e44..43a7ff6 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Messages.properties
@@ -42,6 +42,8 @@
 parser.error.expected.comma=Expected comma but found {0}
 parser.error.expected=Expected {0} but found {1}
 parser.error.invalid.return=Invalid return statement
+parser.error.no.func.decl.here=Function declarations can only occur at program or function body level. You should use a function expression here instead.
+parser.error.no.func.decl.here.warn=Function declarations should only occur at program or function body level. Function declaration in nested block was converted to a function expression.
 parser.error.property.redefinition=Property "{0}" already defined
 parser.error.unexpected.token=Unexpected token: {0}
 parser.error.many.vars.in.for.in.loop=Only one variable allowed in for..in loop
@@ -57,7 +59,7 @@
 parser.error.strict.cant.delete.ident=cannot delete identifier "{0}" in strict mode
 parser.error.strict.param.redefinition=strict mode function cannot have duplicate parameter name "{0}"
 parser.error.strict.no.octal=cannot use octal value in strict mode
-parser.error.strict.no.func.here=In strict mode, functions can only be declared at top-level or immediately within a function
+parser.error.strict.no.func.decl.here=In strict mode, function declarations can only occur at program or function body level. You should use a function expression here instead.
 type.error.strict.getter.setter.poison=In strict mode, "caller", "callee", and "arguments" properties can not be accessed on functions or the arguments object
 
 # not the expected type in a given context
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties b/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties
index 534eb17..e63f7a3 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/Options.properties
@@ -165,6 +165,12 @@
     desc="Generate local variable table in .class files." \
 }
 
+nashorn.option.lazy.compilation = {                                                                      \
+    name="--lazy-compilation",                                                                           \
+    is_undocumented=true,                                                                                \
+    desc="EXPERIMENTAL: Use lazy code generation strategies - do not compile the entire script at once." \
+}
+
 nashorn.option.loader.per.compile = {              \
     name="--loader-per-compile",                   \
     is_undocumented=true,                          \
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js b/nashorn/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js
index 15c67f9..0b967d6 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/mozilla_compat.js
@@ -1,21 +1,21 @@
 /*
  * 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.
@@ -34,7 +34,7 @@
         if (arguments.length < 2) {
             throw new TypeError("JavaAdapter requires atleast two arguments");
         }
-            
+
         var types = Array.prototype.slice.call(arguments, 0, arguments.length - 1);
         var NewType = Java.extend.apply(Java, types);
         return new NewType(arguments[arguments.length - 1]);
@@ -56,10 +56,10 @@
                     return type;
                 } catch (e) {}
             }
-            
+
             return oldNoSuchProperty? oldNoSuchProperty(name) : undefined;
         }
-        
+
         var prefix = "[JavaPackage ";
         return function() {
             for (var i in arguments) {
@@ -343,7 +343,9 @@
     configurable: true, enumerable: false, writable: true,
     value: function(clazz) {
         if (Java.isType(clazz)) {
-            this[clazz.class.getSimpleName()] = clazz;
+            var className = Java.typeName(clazz);
+            var simpleName = className.substring(className.lastIndexOf('.') + 1);
+            this[simpleName] = clazz;
         } else {
             throw new TypeError(clazz + " is not a Java class");
         }
diff --git a/nashorn/src/jdk/nashorn/internal/runtime/resources/parser.js b/nashorn/src/jdk/nashorn/internal/runtime/resources/parser.js
index b89f7e1..8671d36 100644
--- a/nashorn/src/jdk/nashorn/internal/runtime/resources/parser.js
+++ b/nashorn/src/jdk/nashorn/internal/runtime/resources/parser.js
@@ -1,21 +1,21 @@
 /*
  * 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.
@@ -47,7 +47,7 @@
             code = arguments[0];
     }
 
-    var jsonStr = Packages.jdk.nashorn.internal.runtime.ScriptRuntime.parse(code, name, location);
+    var jsonStr = Packages.jdk.nashorn.api.scripting.ScriptUtils.parse(code, name, location);
     return JSON.parse(jsonStr,
         function (prop, value) {
             if (typeof(value) == 'string' && prop == "value") {
diff --git a/nashorn/test/script/basic/JDK-8006755.js b/nashorn/test/script/basic/JDK-8006755.js
index 3012a4b..0924347 100644
--- a/nashorn/test/script/basic/JDK-8006755.js
+++ b/nashorn/test/script/basic/JDK-8006755.js
@@ -31,7 +31,7 @@
 var scope = { x: "hello" };
 
 with (scope) {
-    function main() {
+    var main = function() {
         if (x != "hello") {
             fail("x != 'hello'");
         }
diff --git a/nashorn/test/script/basic/JDK-8008448.js b/nashorn/test/script/basic/JDK-8008448.js
index d5ffbd4..b30e341 100644
--- a/nashorn/test/script/basic/JDK-8008448.js
+++ b/nashorn/test/script/basic/JDK-8008448.js
@@ -32,7 +32,7 @@
 
 var File = Java.type("java.io.File");
 var FilenameFilter = Java.type("java.io.FilenameFilter");
-var Source = Java.type("jdk.nashorn.internal.runtime.Source")
+var SourceHelper = Java.type("jdk.nashorn.test.models.SourceHelper")
 
 // Filter out non .js files
 var files = new File(__DIR__).listFiles(new FilenameFilter() {
@@ -44,5 +44,5 @@
 
 // parse each file to make sure it does not result in exception
 for each (var f in files) {
-    parse(new Source(f.toString(), f).getString());
+    parse(SourceHelper.readFully(f));
 }
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java b/nashorn/test/script/basic/JDK-8009868.js
similarity index 76%
copy from nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
copy to nashorn/test/script/basic/JDK-8009868.js
index b20257c..ffbc8c2 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
+++ b/nashorn/test/script/basic/JDK-8009868.js
@@ -1,30 +1,35 @@
 /*
  * 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.
- *
+ * 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.
  */
 
-package jdk.nashorn.internal.test.models;
 
-public final class FinalClass {
-    //empty
+/**
+ * JDK-8009868: For loop with "true" as condition results in AssertionError in codegen
+ *
+ * @test
+ * @run
+ */
+
+// This used to crash with AssertionError in codegen
+for(; true;) {
+    break;
 }
diff --git a/nashorn/test/script/basic/JDK-8010199.js b/nashorn/test/script/basic/JDK-8010199.js
new file mode 100644
index 0000000..ddd3ba0
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8010199.js
@@ -0,0 +1,51 @@
+/*
+ * 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-8010199: javax.script.Invocable implementation for nashorn does not return null when matching functions are missing
+ *
+ * @test
+ * @run
+ */
+
+var m = new javax.script.ScriptEngineManager();
+var e = m.getEngineByName("nashorn");
+
+var iface = e.getInterface(java.lang.Runnable.class);
+
+if (iface != null) {
+    fail("Expected interface object to be null");
+}
+
+e.eval("var runcalled = false; function run() { runcalled = true }");
+
+iface = e.getInterface(java.lang.Runnable.class);
+if (iface == null) {
+    fail("Expected interface object to be non-null");
+}
+
+iface.run();
+
+if (e.get("runcalled") != true) {
+    fail("runcalled is not true");
+}
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java b/nashorn/test/script/basic/JDK-8010709.js
similarity index 69%
copy from nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java
copy to nashorn/test/script/basic/JDK-8010709.js
index dc04a0d..d2ec87a 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java
+++ b/nashorn/test/script/basic/JDK-8010709.js
@@ -4,9 +4,7 @@
  *
  * 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.
+ * 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
@@ -23,14 +21,26 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+/**
+ * JDK-8010709  org on the top level doesn't resolve 
+ *
+ * @test
+ * @run
+ */
 
-public class DessertToppingFloorWaxDriver {
-    public void decorateDessert(DessertTopping dt) {
-        dt.pourOnDessert();
+function check(pkgName) {
+    if (typeof this[pkgName] != 'object') {
+        fail(pkgName + " not defined");
     }
 
-    public void waxFloor(FloorWax fw) {
-        fw.shineUpTheFloor();
+    if (String(this[pkgName]) != '[JavaPackage ' + pkgName + ']') {
+        fail(pkgName + " is not a JavaPackage");
     }
 }
+
+check("com");
+check("edu");
+check("java");
+check("javafx");
+check("javax");
+check("org");
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/ConstructorWithArgument.java b/nashorn/test/script/basic/JDK-8010720.js
similarity index 65%
copy from nashorn/test/src/jdk/nashorn/internal/test/models/ConstructorWithArgument.java
copy to nashorn/test/script/basic/JDK-8010720.js
index ba4a216..1dddf22 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/ConstructorWithArgument.java
+++ b/nashorn/test/script/basic/JDK-8010720.js
@@ -1,40 +1,49 @@
 /*
  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
+ * 
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
+ * 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.
  */
 
-package jdk.nashorn.internal.test.models;
+/**
+ * JDK-8010720:  Linkage problem with java.lang.String.length()
+ *
+ * @test
+ * @run
+ */
 
-public abstract class ConstructorWithArgument {
-    private final String token;
+var s = new java.lang.String("nashorn");
 
-    protected ConstructorWithArgument(String token) {
-        this.token = token;
-    }
-
-    public String getToken() {
-        return token;
-    }
-
-    protected abstract void doSomething();
+if (s.length() != 7) {
+    fail("s.length() does not return expected value");
 }
+
+if (s.length != 7) {
+    fail("s.length does not return expected value");
+}
+
+
+if ('hello'.length() != 5) {
+    fail("'hello'.length() does not return expected value");
+}
+
+if ('hello'.length != 5) {
+    fail("'hello'.length does not return expected value");
+}
+
diff --git a/nashorn/test/script/basic/JDK-8017010.js b/nashorn/test/script/basic/JDK-8017010.js
new file mode 100644
index 0000000..aa6e61a
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8017010.js
@@ -0,0 +1,68 @@
+/*
+ * 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-8010710 - slot/scope problem with temporary expressions
+ * as array index in self modifying assigns
+ *
+ * @test
+ * @run 
+ */
+function zero() {
+    return 0;
+}
+
+//try complex self modifying assignment and force slots to temporary value index operators
+var a = [1, 2, 3, 4, 5];
+var b = [a, a];
+print(b[zero() + 1][2 + a[0]] += 10);
+
+//repro for NASHORN-258 that never made it
+function AddRoundKey() {        
+    var r=0;  
+    state[r][1] &= 17;    
+}
+
+var srcFiles = [];
+for(i=0;i<100;i++) {
+    srcFiles.push('dummy');
+}
+var added = '';
+
+//this broke the javafx build system. verify it works
+function bouncingBall() {
+    for (j=0; j<100; j++) {
+	added += srcFiles[j];
+    }
+}
+bouncingBall();
+print(added);
+
+//this is how they should have done it for speed, that works always, verify this too
+function bouncingBall2() {
+    for (var k=0; k<100; k++) {
+	added += srcFiles[k];
+    }
+}
+bouncingBall2();
+print(added);
diff --git a/nashorn/test/script/basic/JDK-8017010.js.EXPECTED b/nashorn/test/script/basic/JDK-8017010.js.EXPECTED
new file mode 100644
index 0000000..296c81e
--- /dev/null
+++ b/nashorn/test/script/basic/JDK-8017010.js.EXPECTED
@@ -0,0 +1,3 @@
+14
+dummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummy
+dummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummydummy
diff --git a/nashorn/test/script/basic/NASHORN-258.js b/nashorn/test/script/basic/NASHORN-258.js
index 0f4f674..6d71266 100644
--- a/nashorn/test/script/basic/NASHORN-258.js
+++ b/nashorn/test/script/basic/NASHORN-258.js
@@ -29,6 +29,16 @@
  */
 
 function test3(a) {
+    for (i = 0; i < a.length ; i++) {
+	for (j = 0; j < a[i].length ; j++) {
+	    for (k = 0; k < a[i][j].length ; k++) {
+		a[i][j][k] *= 8;
+	    }
+	}
+    }
+}
+
+function test3local(a) {
     for (var i = 0; i < a.length ; i++) {
 	for (var j = 0; j < a[i].length ; j++) {
 	    for (var k = 0; k < a[i][j].length ; k++) {
@@ -45,6 +55,8 @@
 test3(array);
 print(array);
 
+test3local(array);
+print(array);
 
 function outer() {
     
diff --git a/nashorn/test/script/basic/NASHORN-258.js.EXPECTED b/nashorn/test/script/basic/NASHORN-258.js.EXPECTED
index 8c95c91..5986c47 100644
--- a/nashorn/test/script/basic/NASHORN-258.js.EXPECTED
+++ b/nashorn/test/script/basic/NASHORN-258.js.EXPECTED
@@ -1,2 +1,3 @@
 8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8,8
+64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64,64
 1,1,8,1,1,8,1,1,8,1,1,8,1,1,8,1,1,8,1,1,8,1,1,8,1,1,8
diff --git a/nashorn/test/script/basic/NASHORN-401.js b/nashorn/test/script/basic/NASHORN-401.js
index b6058a9..6a663e1 100644
--- a/nashorn/test/script/basic/NASHORN-401.js
+++ b/nashorn/test/script/basic/NASHORN-401.js
@@ -28,7 +28,7 @@
  * @run
  */
 
-var t = new Packages.jdk.nashorn.internal.runtime.Nashorn401TestSubject();
+var t = new Packages.jdk.nashorn.test.models.Nashorn401TestSubject();
 
 print(t.method2(10));
 print(t.method2(10.2));
diff --git a/nashorn/test/script/basic/NASHORN-837.js b/nashorn/test/script/basic/NASHORN-837.js
index 0632fb3..ef9ec64 100644
--- a/nashorn/test/script/basic/NASHORN-837.js
+++ b/nashorn/test/script/basic/NASHORN-837.js
@@ -28,23 +28,13 @@
  * @run
  */
 
-var failed = false;
-
 try {
-    try {
-	throw new TypeError('error');
-    } catch (iox) {
-	function f() {
-	    print(iox.message);
-	}
+    throw new TypeError('error');
+} catch (iox) {
+    var f = function() {
+        if(iox.message != 'error') {
+            print("Failure! iox did not throw correct exception");
+        }
     }
-    f();
-} catch (e) {
-    failed = (e instanceof ReferenceError);
-    //iox not defined should be thrown
 }
-
-if (!failed) {
-    print("Failure! iox did not throw correct exception");
-}
-
+f();
diff --git a/nashorn/test/script/basic/compile-octane.js.EXPECTED b/nashorn/test/script/basic/compile-octane.js.EXPECTED
index dab419e..39f866c 100644
--- a/nashorn/test/script/basic/compile-octane.js.EXPECTED
+++ b/nashorn/test/script/basic/compile-octane.js.EXPECTED
@@ -16,6 +16,9 @@
 Compiling... gbemu.js
 Compiled OK: gbemu.js
 
+Compiling... mandreel.js
+Compiled OK: mandreel.js
+
 Compiling... navier-stokes.js
 Compiled OK: navier-stokes.js
 
diff --git a/nashorn/test/script/basic/consstring.js b/nashorn/test/script/basic/consstring.js
index b8dc51d..0cc8b1f 100644
--- a/nashorn/test/script/basic/consstring.js
+++ b/nashorn/test/script/basic/consstring.js
@@ -37,4 +37,4 @@
 list.add((str + "3").toString());            // toString() called on primitive string
 list.add(new String(str + "4").toString());  // toString() called on String object
 
-Packages.jdk.nashorn.internal.test.models.StringArgs.checkString(list);
+Packages.jdk.nashorn.test.models.StringArgs.checkString(list);
diff --git a/nashorn/test/script/basic/fileline.js b/nashorn/test/script/basic/fileline.js
index 3323a12..ccf879b 100644
--- a/nashorn/test/script/basic/fileline.js
+++ b/nashorn/test/script/basic/fileline.js
@@ -41,8 +41,8 @@
 load(__DIR__ + "loadedfile.js");
 
 // Add check for base part of a URL. We can't test __DIR__ inside
-// a script that is downloaded from a URL. check for Source.baseURL
+// a script that is downloaded from a URL. check for SourceHelper.baseURL
 // which is exposed as __DIR__ for URL case.
 
 var url = new java.net.URL("http://www.acme.com:8080/foo/bar.js");
-print(Packages.jdk.nashorn.internal.runtime.Source.baseURL(url));
+print(Packages.jdk.nashorn.test.models.SourceHelper.baseURL(url));
diff --git a/nashorn/test/script/basic/javainnerclasses.js b/nashorn/test/script/basic/javainnerclasses.js
index df1e74d..c84571d 100644
--- a/nashorn/test/script/basic/javainnerclasses.js
+++ b/nashorn/test/script/basic/javainnerclasses.js
@@ -29,25 +29,25 @@
  */
  
 // Do it with Java.type()
-var outer = new (Java.type("jdk.nashorn.internal.test.models.OuterClass"))("apple")
+var outer = new (Java.type("jdk.nashorn.test.models.OuterClass"))("apple")
 print(outer)
-var innerStatic = new (Java.type("jdk.nashorn.internal.test.models.OuterClass$InnerStaticClass"))("orange")
+var innerStatic = new (Java.type("jdk.nashorn.test.models.OuterClass$InnerStaticClass"))("orange")
 print(innerStatic)
-var innerNonStatic = new (Java.type("jdk.nashorn.internal.test.models.OuterClass$InnerNonStaticClass"))(outer, "pear")
+var innerNonStatic = new (Java.type("jdk.nashorn.test.models.OuterClass$InnerNonStaticClass"))(outer, "pear")
 print(innerNonStatic)
 
 // Now do it with Packages and explicit $ names
-var outer = new Packages.jdk.nashorn.internal.test.models.OuterClass("red")
+var outer = new Packages.jdk.nashorn.test.models.OuterClass("red")
 print(outer)
-var innerStatic = new Packages.jdk.nashorn.internal.test.models.OuterClass$InnerStaticClass("green")
+var innerStatic = new Packages.jdk.nashorn.test.models.OuterClass$InnerStaticClass("green")
 print(innerStatic)
-var innerNonStatic = new Packages.jdk.nashorn.internal.test.models.OuterClass$InnerNonStaticClass(outer, "blue")
+var innerNonStatic = new Packages.jdk.nashorn.test.models.OuterClass$InnerNonStaticClass(outer, "blue")
 print(innerNonStatic)
 
 // Now do it with Packages and nested properties
-var outer = new Packages.jdk.nashorn.internal.test.models.OuterClass("sweet")
+var outer = new Packages.jdk.nashorn.test.models.OuterClass("sweet")
 print(outer)
-var innerStatic = new Packages.jdk.nashorn.internal.test.models.OuterClass.InnerStaticClass("sour")
+var innerStatic = new Packages.jdk.nashorn.test.models.OuterClass.InnerStaticClass("sour")
 print(innerStatic)
-var innerNonStatic = new Packages.jdk.nashorn.internal.test.models.OuterClass.InnerNonStaticClass(outer, "bitter")
+var innerNonStatic = new Packages.jdk.nashorn.test.models.OuterClass.InnerNonStaticClass(outer, "bitter")
 print(innerNonStatic)
diff --git a/nashorn/test/script/basic/list.js b/nashorn/test/script/basic/list.js
index 12e4071..72ae0be 100644
--- a/nashorn/test/script/basic/list.js
+++ b/nashorn/test/script/basic/list.js
@@ -28,7 +28,7 @@
  * @run
  */
 var l = new java.util.ArrayList();
-print("l.class.name=" + l.class.name) // Has "class" property like any POJO
+print("l.class.name=" + Java.typeName(l.class)) // Has "class" property like any POJO
 
 l.add("foo")
 l.add("bar")
diff --git a/nashorn/test/script/basic/map.js b/nashorn/test/script/basic/map.js
index 2ab27ab..c024f6e 100644
--- a/nashorn/test/script/basic/map.js
+++ b/nashorn/test/script/basic/map.js
@@ -28,7 +28,7 @@
  * @run
  */
 var m = new (Java.type("java.util.LinkedHashMap"));
-print("m.class.name=" + m.class.name) // Has "class" property like any POJO
+print("m.class.name=" + Java.typeName(m.class)) // Has "class" property like any POJO
 
 var empty_key = "empty"
 
diff --git a/nashorn/test/script/basic/run-octane.js b/nashorn/test/script/basic/run-octane.js
index 42bf77f..315451c 100644
--- a/nashorn/test/script/basic/run-octane.js
+++ b/nashorn/test/script/basic/run-octane.js
@@ -31,7 +31,8 @@
     "crypto.js", 
     "deltablue.js", 
     "earley-boyer.js", 
-    "gbemu.js",	     
+    "gbemu.js",
+    "mandreel.js",
     "navier-stokes.js", 
     "pdfjs.js",
     "raytrace.js",
@@ -49,6 +50,12 @@
     { name: "gbemu.js" },
 ];
 
+
+//TODO mandreel can be compiled as a test, but not run multiple times unless modified to not have global state
+var compileOnly = {
+    "mandreel.js" : true
+};
+
 var dir = (typeof(__DIR__) == 'undefined') ? "test/script/basic/" : __DIR__;
 
 // TODO: why is this path hard coded when it's defined in project properties?
@@ -63,6 +70,10 @@
     return str.indexOf(suffix, str.length - suffix.length) !== -1;
 }
 
+function should_compile_only(name) {
+    return (typeof compile_only !== 'undefined') || compileOnly[name] === true;
+}
+
 function run_one_benchmark(arg, iters) {
 
     var file_name;
@@ -77,14 +88,18 @@
     }
     file_name = file[file.length - 1];
 
-    if (typeof compile_only !== 'undefined') {
+    var compile_and_return = should_compile_only(file_name);
+    if (compile_and_return) {
+	if (typeof compile_only === 'undefined') { //for a run, skip compile onlies, don't even compile them
+	    return;
+	}
 	print("Compiling... " + file_name);
     }
 
     load(path + 'base.js');
     load(arg);
     
-    if (typeof compile_only !== 'undefined') {
+    if (compile_and_return) {
 	print("Compiled OK: " + file_name);
 	print("");
 	return;
@@ -164,7 +179,7 @@
 
 function run_suite(tests, iters) {
     for (var idx = 0; idx < tests.length; idx++) {
-	run_one_benchmark(tests[idx], iters, false);
+	run_one_benchmark(tests[idx], iters);
     }
 }
 
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java b/nashorn/test/script/basic/runsunspider-eager.js
similarity index 76%
copy from nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
copy to nashorn/test/script/basic/runsunspider-eager.js
index b20257c..db358d2 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
+++ b/nashorn/test/script/basic/runsunspider-eager.js
@@ -1,30 +1,33 @@
 /*
  * 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.
- *
+ * 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.
  */
 
-package jdk.nashorn.internal.test.models;
+/**
+ * runsunspider : runs the sunspider tests and checks for compliance
+ *
+ * @test
+ * @option -timezone=PST
+ * @runif external.sunspider
+ */
 
-public final class FinalClass {
-    //empty
-}
+load(__DIR__ + "runsunspider.js");
+
diff --git a/nashorn/test/script/basic/runsunspider.js.EXPECTED b/nashorn/test/script/basic/runsunspider-eager.js.EXPECTED
similarity index 100%
rename from nashorn/test/script/basic/runsunspider.js.EXPECTED
rename to nashorn/test/script/basic/runsunspider-eager.js.EXPECTED
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java b/nashorn/test/script/basic/runsunspider-lazy.js
similarity index 76%
copy from nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
copy to nashorn/test/script/basic/runsunspider-lazy.js
index b20257c..6e24c0c 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
+++ b/nashorn/test/script/basic/runsunspider-lazy.js
@@ -1,30 +1,34 @@
 /*
  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
+ * 
  * This code is free software; you can redistribute it and/or modify it
  * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.  Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
+ * 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.
  */
 
-package jdk.nashorn.internal.test.models;
+/**
+ * runsunspider : runs the sunspider tests and checks for compliance
+ *
+ * @test
+ * @option -timezone=PST
+ * @option --lazy-compilation
+ * @runif external.sunspider
+ */
 
-public final class FinalClass {
-    //empty
-}
+load(__DIR__ + "runsunspider.js");
+
diff --git a/nashorn/test/script/basic/runsunspider.js.EXPECTED b/nashorn/test/script/basic/runsunspider-lazy.js.EXPECTED
similarity index 100%
copy from nashorn/test/script/basic/runsunspider.js.EXPECTED
copy to nashorn/test/script/basic/runsunspider-lazy.js.EXPECTED
diff --git a/nashorn/test/script/basic/runsunspider.js b/nashorn/test/script/basic/runsunspider.js
index 7b0d732..7f78797 100644
--- a/nashorn/test/script/basic/runsunspider.js
+++ b/nashorn/test/script/basic/runsunspider.js
@@ -24,39 +24,11 @@
 /**
  * runsunspider : runs the sunspider tests and checks for compliance
  *
- * @test
- * @option -timezone=PST
- * @runif external.sunspider
- */
-
-/*
- * Copyright (c) 2010-2011, 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.
+ * @subtest
  */
 
 /**
  * This is not a test, but a test "framework" for running sunspider tests.
- *
  */
 
 function assertEq(a, b) {
diff --git a/nashorn/test/script/basic/stdin.js b/nashorn/test/script/basic/stdin.js
index ba546d5..a3a9332 100644
--- a/nashorn/test/script/basic/stdin.js
+++ b/nashorn/test/script/basic/stdin.js
@@ -28,8 +28,8 @@
  * @run
  */
 
-print(java.lang.System.in.class.name);
+print(Java.typeName(java.lang.System.in.class));
 var prop = "in";
-print(java.lang.System[prop].class.name);
-print(java.lang.System["in"].class.name);
+print(Java.typeName(java.lang.System[prop].class));
+print(Java.typeName(java.lang.System["in"].class));
 
diff --git a/nashorn/test/script/currently-failing/JDK-8006529.js b/nashorn/test/script/currently-failing/JDK-8006529.js
index da08d2b..ca21f0b 100644
--- a/nashorn/test/script/currently-failing/JDK-8006529.js
+++ b/nashorn/test/script/currently-failing/JDK-8006529.js
@@ -39,12 +39,13 @@
  * and FunctionNode because of package-access check and so reflective calls.
  */
 
-var Parser         = Java.type("jdk.nashorn.internal.parser.Parser")
-var Compiler       = Java.type("jdk.nashorn.internal.codegen.Compiler")
-var Context        = Java.type("jdk.nashorn.internal.runtime.Context")
+var Parser            = Java.type("jdk.nashorn.internal.parser.Parser")
+var Compiler          = Java.type("jdk.nashorn.internal.codegen.Compiler")
+var Context           = Java.type("jdk.nashorn.internal.runtime.Context")
 var ScriptEnvironment = Java.type("jdk.nashorn.internal.runtime.ScriptEnvironment")
-var Source         = Java.type("jdk.nashorn.internal.runtime.Source")
-var FunctionNode   = Java.type("jdk.nashorn.internal.ir.FunctionNode")
+var Source            = Java.type("jdk.nashorn.internal.runtime.Source")
+var FunctionNode      = Java.type("jdk.nashorn.internal.ir.FunctionNode")
+var ThrowErrorManager = Java.type("jdk.nashorn.internal.runtime.Context$ThrowErrorManager");
 
 // Compiler class methods and fields
 var parseMethod = Parser.class.getMethod("parse");
@@ -90,7 +91,7 @@
 // representing it.
 function compile(source) {
     var source   = new Source("<no name>", source);
-    var parser   = new Parser(Context.getContext().getEnv(), source, null);
+    var parser   = new Parser(Context.getContext().getEnv(), source, new ThrowErrorManager());
     var func     = parseMethod.invoke(parser);
     var compiler = new Compiler(Context.getContext().getEnv(), func);
 
diff --git a/nashorn/test/script/currently-failing/clone_ir.js b/nashorn/test/script/currently-failing/clone_ir.js
new file mode 100644
index 0000000..3c1eccf
--- /dev/null
+++ b/nashorn/test/script/currently-failing/clone_ir.js
@@ -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.
+ * 
+ * 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.
+ */
+
+/**
+ * clone_ir : Check that functionNode.clone copies all nodes and that they
+ * are not the same references
+ *
+ * @test
+ * @run
+ */
+
+var js1 = "var tuple = { func : function f(x) { if (x) { print('true'); { print('block_under-true'); } } else { print('false'); } } }";
+
+var Parser            = Java.type("jdk.nashorn.internal.parser.Parser");
+var ASTWriter         = Java.type("jdk.nashorn.internal.ir.debug.ASTWriter");
+var Context           = Java.type("jdk.nashorn.internal.runtime.Context");
+var ScriptEnvironment = Java.type("jdk.nashorn.internal.runtime.ScriptEnvironment");
+var Source            = Java.type("jdk.nashorn.internal.runtime.Source");
+var FunctionNode      = Java.type("jdk.nashorn.internal.ir.FunctionNode");
+var ThrowErrorManager = Java.type("jdk.nashorn.internal.runtime.Context$ThrowErrorManager");
+var System            = Java.type("java.lang.System");
+
+var toArrayMethod = ASTWriter.class.getMethod("toArray");
+var parseMethod  = Parser.class.getMethod("parse");    
+
+function toString(obj) {
+    var output = "{ ";
+    for (property in obj) {
+	output += property + ': ' + obj[property]+'; ';
+    }
+    return output + '}'
+}
+
+function flatten(func) {
+    var writer   = new ASTWriter(func);
+    var funcList = toArrayMethod.invoke(writer);
+    
+    var res = [];
+    for each (x in funcList) {
+	    res.push({ name: x.getClass().getName(), id: System.identityHashCode(x) });
+	}
+    return res;
+}
+
+function check(contents) {
+    return check_src(new Source("<no name>", contents));
+}
+
+function check_src(src) {
+    var parser  = new Parser(Context.getContext().getEnv(), src, new ThrowErrorManager());
+
+    var func = parseMethod.invoke(parser);
+    print(func);
+    var func2 = func.clone();
+
+    var f1 = flatten(func);
+    var f2 = flatten(func2);
+
+    print(f1.map(toString));
+    print(f2.map(toString));
+
+    if (f1.length != f2.length) {
+	print("length difference between original and clone " + f1.length + " != " + f2.length);
+	return false;
+    }
+
+    for (var i = 0; i < f1.length; i++) {
+	if (f1[i].name !== f2[i].name) {
+	    print("name conflict at " + i + " " + f1[i].name + " != " + f2[i].name);
+	    return false;
+	} else if (f1[i].id === f2[i].id) {
+	    print("id problem at " + i + " " + toString(f1[i]) + " was not deep copied to " + toString(f2[i]) + " became " + f1[i].id + " != " + f2[i].id);
+	    return false;
+	}
+    }
+    
+    return true;
+}
+
+print(check(js1));
diff --git a/nashorn/test/script/sandbox/javaextend.js b/nashorn/test/script/sandbox/javaextend.js
index 318a679..33cc6b0 100644
--- a/nashorn/test/script/sandbox/javaextend.js
+++ b/nashorn/test/script/sandbox/javaextend.js
@@ -27,7 +27,7 @@
  */
 
 function model(n) {
-  return Java.type("jdk.nashorn.internal.test.models." + n)
+  return Java.type("jdk.nashorn.test.models." + n)
 }
 
 // Can't extend a final class  
diff --git a/nashorn/test/script/sandbox/javaextend.js.EXPECTED b/nashorn/test/script/sandbox/javaextend.js.EXPECTED
index 2a5ad63..69c7818 100644
--- a/nashorn/test/script/sandbox/javaextend.js.EXPECTED
+++ b/nashorn/test/script/sandbox/javaextend.js.EXPECTED
@@ -1,6 +1,6 @@
-TypeError: Can not extend final class jdk.nashorn.internal.test.models.FinalClass.
-TypeError: Can not extend class jdk.nashorn.internal.test.models.NoAccessibleConstructorClass as it has no public or protected constructors.
-TypeError: Can not extend/implement non-public class/interface jdk.nashorn.internal.test.models.NonPublicClass.
+TypeError: Can not extend final class jdk.nashorn.test.models.FinalClass.
+TypeError: Can not extend class jdk.nashorn.test.models.NoAccessibleConstructorClass as it has no public or protected constructors.
+TypeError: Can not extend/implement non-public class/interface jdk.nashorn.test.models.NonPublicClass.
 TypeError: Can not extend multiple classes java.lang.Number and java.lang.Thread. At most one of the specified types can be a class, the rest must all be interfaces.
 abcdabcd
 run-object
diff --git a/nashorn/test/script/sandbox/reflection.js b/nashorn/test/script/sandbox/reflection.js
index 7364879..e892c646 100644
--- a/nashorn/test/script/sandbox/reflection.js
+++ b/nashorn/test/script/sandbox/reflection.js
@@ -30,9 +30,7 @@
  */
 
 function check(e) {
-    if (e instanceof java.lang.SecurityException) {
-        print(e);
-    } else {
+    if (! (e instanceof java.lang.SecurityException)) {
         fail("expected SecurityException, got " + e);
     }
 }
diff --git a/nashorn/test/script/sandbox/reflection.js.EXPECTED b/nashorn/test/script/sandbox/reflection.js.EXPECTED
deleted file mode 100644
index 202333e..0000000
--- a/nashorn/test/script/sandbox/reflection.js.EXPECTED
+++ /dev/null
@@ -1 +0,0 @@
-java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "accessDeclaredMembers")
diff --git a/nashorn/test/script/sandbox/unsafe.js b/nashorn/test/script/sandbox/unsafe.js
index b273cd0..6b3f43a 100644
--- a/nashorn/test/script/sandbox/unsafe.js
+++ b/nashorn/test/script/sandbox/unsafe.js
@@ -30,9 +30,7 @@
  */
 
 function check(e) {
-   if (e instanceof java.lang.SecurityException) {
-       print(e);
-   } else {
+   if (! (e instanceof java.lang.SecurityException)) {
        fail("expected SecurityException, got " + e);
    }
 }
diff --git a/nashorn/test/script/sandbox/unsafe.js.EXPECTED b/nashorn/test/script/sandbox/unsafe.js.EXPECTED
deleted file mode 100644
index 3f1cbec..0000000
--- a/nashorn/test/script/sandbox/unsafe.js.EXPECTED
+++ /dev/null
@@ -1,4 +0,0 @@
-java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "accessClassInPackage.sun.misc")
-java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "accessClassInPackage.sun.misc")
-java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "accessClassInPackage.sun")
-java.security.AccessControlException: access denied ("java.lang.RuntimePermission" "getClassLoader")
diff --git a/nashorn/test/script/trusted/urlreader.js b/nashorn/test/script/trusted/urlreader.js
index a5e06ee..ca03730 100644
--- a/nashorn/test/script/trusted/urlreader.js
+++ b/nashorn/test/script/trusted/urlreader.js
@@ -9,7 +9,7 @@
 var URL = Java.type("java.net.URL");
 var File = Java.type("java.io.File");
 var JString = Java.type("java.lang.String");
-var Source = Java.type("jdk.nashorn.internal.runtime.Source");
+var SourceHelper = Java.type("jdk.nashorn.test.models.SourceHelper");
 
 var url = new File(__FILE__).toURI().toURL();
 var reader = new URLReader(url);
@@ -19,9 +19,9 @@
 
 // check URL read
 // read URL content by directly reading from URL
-var str = new Source(url.toString(), url).getString();
+var str = SourceHelper.readFully(url);
 // read URL content via URLReader
-var content = new JString(Source.readFully(reader));
+var content = new JString(SourceHelper.readFully(reader));
 
 // assert that the content is same
 Assert.assertEquals(str, content);
diff --git a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java
index f4fd114..48277aa 100644
--- a/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java
+++ b/nashorn/test/src/jdk/nashorn/api/scripting/ScriptEngineTest.java
@@ -47,7 +47,6 @@
 import javax.script.ScriptEngineManager;
 import javax.script.ScriptException;
 import javax.script.SimpleScriptContext;
-import jdk.nashorn.internal.runtime.Version;
 import netscape.javascript.JSObject;
 import org.testng.Assert;
 import org.testng.annotations.Test;
@@ -129,7 +128,6 @@
         assertEquals(fac.getParameter(ScriptEngine.NAME), "javascript");
         assertEquals(fac.getLanguageVersion(), "ECMA - 262 Edition 5.1");
         assertEquals(fac.getEngineName(), "Oracle Nashorn");
-        assertEquals(fac.getEngineVersion(), Version.version());
         assertEquals(fac.getOutputStatement("context"), "print(context)");
         assertEquals(fac.getProgram("print('hello')", "print('world')"), "print('hello');print('world');");
         assertEquals(fac.getParameter(ScriptEngine.NAME), "javascript");
@@ -285,6 +283,68 @@
         }
     }
 
+    public interface Foo {
+        public void bar();
+    }
+
+    public interface Foo2 extends Foo {
+        public void bar2();
+    }
+
+    @Test
+    public void getInterfaceMissingTest() {
+        final ScriptEngineManager manager = new ScriptEngineManager();
+        final ScriptEngine engine = manager.getEngineByName("nashorn");
+
+        // don't define any function.
+        try {
+            engine.eval("");
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+
+        Runnable runnable = ((Invocable)engine).getInterface(Runnable.class);
+        if (runnable != null) {
+            fail("runnable is not null!");
+        }
+
+        // now define "run"
+        try {
+            engine.eval("function run() { print('this is run function'); }");
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+        runnable = ((Invocable)engine).getInterface(Runnable.class);
+        // should not return null now!
+        runnable.run();
+
+        // define only one method of "Foo2"
+        try {
+            engine.eval("function bar() { print('bar function'); }");
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+
+        Foo2 foo2 = ((Invocable)engine).getInterface(Foo2.class);
+        if (foo2 != null) {
+            throw new RuntimeException("foo2 is not null!");
+        }
+
+        // now define other method of "Foo2"
+        try {
+            engine.eval("function bar2() { print('bar2 function'); }");
+        } catch (final Exception exp) {
+            exp.printStackTrace();
+            fail(exp.getMessage());
+        }
+        foo2 = ((Invocable)engine).getInterface(Foo2.class);
+        foo2.bar();
+        foo2.bar2();
+    }
+
     @Test
     public void accessGlobalTest() {
         final ScriptEngineManager m = new ScriptEngineManager();
@@ -313,27 +373,6 @@
         }
     }
 
-    public static void alert(final Object msg) {
-        System.out.println(msg);
-    }
-
-    @Test
-    public void exposeMethodTest() {
-        final ScriptEngineManager m = new ScriptEngineManager();
-        final ScriptEngine e = m.getEngineByName("nashorn");
-
-        try {
-            final Method alert = ScriptEngineTest.class.getMethod("alert", Object.class);
-            // expose a Method object as global var.
-            e.put("alert", alert);
-            // call the global var.
-            e.eval("alert.invoke(null, 'alert! alert!!')");
-        } catch (final NoSuchMethodException | SecurityException | ScriptException exp) {
-            exp.printStackTrace();
-            fail(exp.getMessage());
-        }
-    }
-
     @Test
     public void putGlobalFunctionTest() {
         final ScriptEngineManager m = new ScriptEngineManager();
@@ -593,13 +632,6 @@
     }
 
     @Test
-    public void versionTest() {
-        final ScriptEngineManager m = new ScriptEngineManager();
-        final ScriptEngine e = m.getEngineByName("nashorn");
-        assertEquals(e.getFactory().getEngineVersion(), Version.version());
-    }
-
-    @Test
     public void noEnumerablePropertiesTest() {
         final ScriptEngineManager m = new ScriptEngineManager();
         final ScriptEngine e = m.getEngineByName("nashorn");
@@ -874,26 +906,4 @@
             fail(se.getMessage());
         }
     }
-
-    @Test
-    public void factoryOptionsTest() {
-        final ScriptEngineManager sm = new ScriptEngineManager();
-        for (ScriptEngineFactory fac : sm.getEngineFactories()) {
-            if (fac instanceof NashornScriptEngineFactory) {
-                final NashornScriptEngineFactory nfac = (NashornScriptEngineFactory)fac;
-                // specify --no-syntax-extensions flag
-                final String[] options = new String[] { "--no-syntax-extensions" };
-                final ScriptEngine e = nfac.getScriptEngine(options);
-                try {
-                    // try nashorn specific extension
-                    e.eval("var f = funtion(x) 2*x;");
-                    fail("should have thrown exception!");
-                } catch (final ScriptException se) {
-                }
-                return;
-            }
-        }
-
-        fail("Cannot find nashorn factory!");
-    }
 }
diff --git a/nashorn/test/src/jdk/nashorn/internal/codegen/CompilerTest.java b/nashorn/test/src/jdk/nashorn/internal/codegen/CompilerTest.java
index adf361f..3cbe1ed 100644
--- a/nashorn/test/src/jdk/nashorn/internal/codegen/CompilerTest.java
+++ b/nashorn/test/src/jdk/nashorn/internal/codegen/CompilerTest.java
@@ -44,6 +44,7 @@
     private static final boolean VERBOSE  = Boolean.valueOf(System.getProperty("compilertest.verbose"));
     private static final boolean TEST262  = Boolean.valueOf(System.getProperty("compilertest.test262"));
     private static final String TEST_BASIC_DIR  = System.getProperty("test.basic.dir");
+    private static final String TEST_NODE_DIR  = System.getProperty("test.node.dir");
     private static final String TEST262_SUITE_DIR = System.getProperty("test262.suite.dir");
 
     interface TestFilter {
@@ -81,21 +82,22 @@
     @Test
     public void compileAllTests() {
         if (TEST262) {
-            compileTestSet(TEST262_SUITE_DIR, new TestFilter() {
+            compileTestSet(new File(TEST262_SUITE_DIR), new TestFilter() {
                 @Override
                 public boolean exclude(final File file, final String content) {
                     return content.indexOf("@negative") != -1;
                 }
             });
         }
-        compileTestSet(TEST_BASIC_DIR, null);
+        compileTestSet(new File(TEST_BASIC_DIR), null);
+        compileTestSet(new File(TEST_NODE_DIR, "node"), null);
+        compileTestSet(new File(TEST_NODE_DIR, "src"), null);
     }
 
-    private void compileTestSet(final String testSet, final TestFilter filter) {
+    private void compileTestSet(final File testSetDir, final TestFilter filter) {
         passed = 0;
         failed = 0;
         skipped = 0;
-        final File testSetDir = new File(testSet);
         if (! testSetDir.isDirectory()) {
             log("WARNING: " + testSetDir + " not found or not a directory");
             return;
@@ -103,7 +105,7 @@
         log(testSetDir.getAbsolutePath());
         compileJSDirectory(testSetDir, filter);
 
-        log(testSet + " compile done!");
+        log(testSetDir + " compile done!");
         log("compile ok: " + passed);
         log("compile failed: " + failed);
         log("compile skipped: " + skipped);
diff --git a/nashorn/test/src/jdk/nashorn/internal/runtime/TrustedScriptEngineTest.java b/nashorn/test/src/jdk/nashorn/internal/runtime/TrustedScriptEngineTest.java
index 0f14756..0f740a4 100644
--- a/nashorn/test/src/jdk/nashorn/internal/runtime/TrustedScriptEngineTest.java
+++ b/nashorn/test/src/jdk/nashorn/internal/runtime/TrustedScriptEngineTest.java
@@ -25,7 +25,7 @@
 
 package jdk.nashorn.internal.runtime;
 
-
+import static org.testng.Assert.assertEquals;
 import static org.testng.Assert.assertTrue;
 import static org.testng.Assert.fail;
 
@@ -40,6 +40,13 @@
  * Tests for trusted client usage of nashorn script engine factory extension API
  */
 public class TrustedScriptEngineTest {
+    @Test
+    public void versionTest() {
+        final ScriptEngineManager m = new ScriptEngineManager();
+        final ScriptEngine e = m.getEngineByName("nashorn");
+        assertEquals(e.getFactory().getEngineVersion(), Version.version());
+    }
+
     private static class MyClassLoader extends ClassLoader {
         // to check if script engine uses the specified class loader
         private final boolean[] reached = new boolean[1];
@@ -116,4 +123,26 @@
 
         fail("Cannot find nashorn factory!");
     }
+
+    @Test
+    public void factoryOptionsTest() {
+        final ScriptEngineManager sm = new ScriptEngineManager();
+        for (ScriptEngineFactory fac : sm.getEngineFactories()) {
+            if (fac instanceof NashornScriptEngineFactory) {
+                final NashornScriptEngineFactory nfac = (NashornScriptEngineFactory)fac;
+                // specify --no-syntax-extensions flag
+                final String[] options = new String[] { "--no-syntax-extensions" };
+                final ScriptEngine e = nfac.getScriptEngine(options);
+                try {
+                    // try nashorn specific extension
+                    e.eval("var f = funtion(x) 2*x;");
+                    fail("should have thrown exception!");
+                } catch (final ScriptException se) {
+                }
+                return;
+            }
+        }
+
+        fail("Cannot find nashorn factory!");
+    }
 }
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/ConstructorWithArgument.java b/nashorn/test/src/jdk/nashorn/test/models/ConstructorWithArgument.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/ConstructorWithArgument.java
rename to nashorn/test/src/jdk/nashorn/test/models/ConstructorWithArgument.java
index ba4a216..9a20157 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/ConstructorWithArgument.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/ConstructorWithArgument.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public abstract class ConstructorWithArgument {
     private final String token;
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/DessertTopping.java b/nashorn/test/src/jdk/nashorn/test/models/DessertTopping.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/DessertTopping.java
rename to nashorn/test/src/jdk/nashorn/test/models/DessertTopping.java
index 8427c83..591e032 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/DessertTopping.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/DessertTopping.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public interface DessertTopping {
     public String pourOnDessert();
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java b/nashorn/test/src/jdk/nashorn/test/models/DessertToppingFloorWaxDriver.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java
rename to nashorn/test/src/jdk/nashorn/test/models/DessertToppingFloorWaxDriver.java
index dc04a0d..856029a 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/DessertToppingFloorWaxDriver.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/DessertToppingFloorWaxDriver.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public class DessertToppingFloorWaxDriver {
     public void decorateDessert(DessertTopping dt) {
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java b/nashorn/test/src/jdk/nashorn/test/models/FinalClass.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
rename to nashorn/test/src/jdk/nashorn/test/models/FinalClass.java
index b20257c..8a3e843 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/FinalClass.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/FinalClass.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public final class FinalClass {
     //empty
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/FloorWax.java b/nashorn/test/src/jdk/nashorn/test/models/FloorWax.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/FloorWax.java
rename to nashorn/test/src/jdk/nashorn/test/models/FloorWax.java
index c094ccf..44ac96e 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/FloorWax.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/FloorWax.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public interface FloorWax {
     public String shineUpTheFloor();
diff --git a/nashorn/test/src/jdk/nashorn/internal/runtime/Nashorn401TestSubject.java b/nashorn/test/src/jdk/nashorn/test/models/Nashorn401TestSubject.java
similarity index 97%
rename from nashorn/test/src/jdk/nashorn/internal/runtime/Nashorn401TestSubject.java
rename to nashorn/test/src/jdk/nashorn/test/models/Nashorn401TestSubject.java
index ed6475d..2e7d9c6 100644
--- a/nashorn/test/src/jdk/nashorn/internal/runtime/Nashorn401TestSubject.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/Nashorn401TestSubject.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.runtime;
+package jdk.nashorn.test.models;
 
 public class Nashorn401TestSubject {
     public String method2(int arg) {
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/NoAccessibleConstructorClass.java b/nashorn/test/src/jdk/nashorn/test/models/NoAccessibleConstructorClass.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/NoAccessibleConstructorClass.java
rename to nashorn/test/src/jdk/nashorn/test/models/NoAccessibleConstructorClass.java
index c79edfb..f0ddb1a 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/NoAccessibleConstructorClass.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/NoAccessibleConstructorClass.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public class NoAccessibleConstructorClass {
     NoAccessibleConstructorClass() { }
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/NonPublicClass.java b/nashorn/test/src/jdk/nashorn/test/models/NonPublicClass.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/NonPublicClass.java
rename to nashorn/test/src/jdk/nashorn/test/models/NonPublicClass.java
index 046cdf2..d0a6185 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/NonPublicClass.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/NonPublicClass.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 class NonPublicClass {
     public NonPublicClass() { }
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/OuterClass.java b/nashorn/test/src/jdk/nashorn/test/models/OuterClass.java
similarity index 97%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/OuterClass.java
rename to nashorn/test/src/jdk/nashorn/test/models/OuterClass.java
index cb9484b..5db86f2 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/OuterClass.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/OuterClass.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public class OuterClass {
     private final String value;
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/OverloadedSam.java b/nashorn/test/src/jdk/nashorn/test/models/OverloadedSam.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/OverloadedSam.java
rename to nashorn/test/src/jdk/nashorn/test/models/OverloadedSam.java
index c63da54..05736bc 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/OverloadedSam.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/OverloadedSam.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public interface OverloadedSam {
     public void sam(String s);
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/OverrideObject.java b/nashorn/test/src/jdk/nashorn/test/models/OverrideObject.java
similarity index 97%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/OverrideObject.java
rename to nashorn/test/src/jdk/nashorn/test/models/OverrideObject.java
index 40cf7f9..5312ffb 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/OverrideObject.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/OverrideObject.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public class OverrideObject {
     @Override
diff --git a/nashorn/test/src/jdk/nashorn/internal/runtime/Nashorn401TestSubject.java b/nashorn/test/src/jdk/nashorn/test/models/SourceHelper.java
similarity index 61%
copy from nashorn/test/src/jdk/nashorn/internal/runtime/Nashorn401TestSubject.java
copy to nashorn/test/src/jdk/nashorn/test/models/SourceHelper.java
index ed6475d..46b1e48 100644
--- a/nashorn/test/src/jdk/nashorn/internal/runtime/Nashorn401TestSubject.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/SourceHelper.java
@@ -23,35 +23,33 @@
  * questions.
  */
 
-package jdk.nashorn.internal.runtime;
+package jdk.nashorn.test.models;
 
-public class Nashorn401TestSubject {
-    public String method2(int arg) {
-        return "int method 2";
+import java.io.File;
+import java.io.IOException;
+import java.io.Reader;
+import java.net.URL;
+import jdk.nashorn.internal.runtime.Source;
+
+/**
+ * Helper class to facilitate script access of nashorn Source class.
+ */
+public final class SourceHelper {
+    private SourceHelper() {}
+
+    public static String baseURL(final URL url) {
+        return Source.baseURL(url);
     }
 
-    public String method2(double arg) {
-        return "double method 2";
+    public static String readFully(final File file) throws IOException {
+        return new String(Source.readFully(file));
     }
 
-    public String method2(String arg) {
-        return "string method 2";
+    public static String readFully(final URL url) throws IOException {
+        return new Source(url.toString(), url).getString();
     }
 
-    public String method3(double arg) {
-        return "double method 3: " + arg;
+    public static String readFully(final Reader reader) throws IOException {
+        return new String(Source.readFully(reader));
     }
-
-    public String method3(int arg) {
-        return "int method 3: " + arg;
-    }
-
-    public String method4(Double arg) {
-        return "double method 4: " + arg;
-    }
-
-    public String method4(int arg) {
-        return "int method 4: " + arg;
-    }
-
 }
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/StringArgs.java b/nashorn/test/src/jdk/nashorn/test/models/StringArgs.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/StringArgs.java
rename to nashorn/test/src/jdk/nashorn/test/models/StringArgs.java
index 8ecdbfd..1fdcd5d 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/StringArgs.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/StringArgs.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 import java.util.List;
 
diff --git a/nashorn/test/src/jdk/nashorn/internal/test/models/Toothpaste.java b/nashorn/test/src/jdk/nashorn/test/models/Toothpaste.java
similarity index 96%
rename from nashorn/test/src/jdk/nashorn/internal/test/models/Toothpaste.java
rename to nashorn/test/src/jdk/nashorn/test/models/Toothpaste.java
index 0946eb3..7eae95f 100644
--- a/nashorn/test/src/jdk/nashorn/internal/test/models/Toothpaste.java
+++ b/nashorn/test/src/jdk/nashorn/test/models/Toothpaste.java
@@ -23,7 +23,7 @@
  * questions.
  */
 
-package jdk.nashorn.internal.test.models;
+package jdk.nashorn.test.models;
 
 public abstract class Toothpaste {
     public void applyToBrush() {