Mercurial > people > rkennke > jdk9-shenandoah-final > nashorn
changeset 818:26a5fdb90de2
8035820: Optimistic recompilation
Reviewed-by: hannesw, jlaskey, sundar
Contributed-by: attila.szegedi@oracle.com, marcus.lagergren@oracle.com
line wrap: on
line diff
--- a/.hgignore Tue Feb 25 18:56:10 2014 +0530 +++ b/.hgignore Wed Feb 26 13:17:57 2014 +0100 @@ -13,6 +13,8 @@ *.clazz *.log *.orig +*.rej +*~ genfiles.properties hotspot.log .DS_Store*
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/rundiff.sh Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,25 @@ +#!/bin/sh + +# do two runs of a script, one optimistic and one pessimistic, expect identical outputs +# if not, display and error message and a diff + +which opendiff >/dev/null +RES=$? +if [ $RES = 0 ]; then + DIFFTOOL=opendiff +else + DIFFTOOL=diff +fi + +OPTIMISTIC=out_optimistic +PESSIMISTIC=out_pessimistic +$JAVA_HOME/bin/java -ea -jar ../dist/nashorn.jar ${@} >$PESSIMISTIC +$JAVA_HOME/bin/java -ea -Dnashorn.optimistic -jar ../dist/nashorn.jar ${@} >$OPTIMISTIC + +if ! diff -q $PESSIMISTIC $OPTIMISTIC >/dev/null ; then + echo "Failure! Results are different" + echo "" + $DIFFTOOL $PESSIMISTIC $OPTIMISTIC +else + echo "OK - Results are identical" +fi
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/runnormal.sh Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,3 @@ +#!/bin/sh +FILENAME="./pessimistic_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" +$JAVA_HOME/bin/java -ea -Xms2G -Xmx2G -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$FILENAME,stackdepth=128 -Djava.ext.dirs=dist jdk.nashorn.tools.Shell ${@}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/runnormaldual.sh Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,3 @@ +#!/bin/sh +FILENAME="./optimistic_dual_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" +$JAVA_HOME/bin/java -ea -Dnashorn.fields.dual -Xms2G -Xmx2G -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$FILENAME,stackdepth=128 -XX:TypeProfileLevel=222 -XX:+UnlockExperimentalVMOptions -XX:+UseTypeSpeculation -XX:+UseMathExactIntrinsics ${@}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/runopt.sh Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,3 @@ +#!/bin/sh +FILENAME="./optimistic_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" +$JAVA_HOME/bin/java -ea -Dnashorn.optimistic -Dnashorn.fastrewrite -Xms2G -Xmx2G -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$FILENAME,stackdepth=128 -XX:TypeProfileLevel=222 -XX:+UnlockExperimentalVMOptions -XX:+UseTypeSpeculation -XX:-UseMathExactIntrinsics -Xbootclasspath/p:dist/nashorn.jar jdk.nashorn.tools.Shell ${@}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/runoptdual.sh Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,3 @@ +#!/bin/sh +FILENAME="./optimistic_dual_$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" +$JAVA_HOME/bin/java -ea -Dnashorn.fields.dual -Dnashorn.optimistic -Xms2G -Xmx2G -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$FILENAME,stackdepth=128 -XX:TypeProfileLevel=222 -XX:+UnlockExperimentalVMOptions -XX:+UseTypeSpeculation -XX:-UseMathExactIntrinsics ${@}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/bin/runoptdualcatch.sh Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,25 @@ +#!/bin/sh + +#FLAGS="-Djava.lang.invoke.MethodHandle.COMPILE_THRESHOLD=3 -Djava.lang.invoke.MethodHandle.DUMP_CLASS_FILES=true -Djava.lang.invoke.MethodHandle.TRACE_METHOD_LINKAGE=true -Djava.lang.invoke.MethodHandle.TRACE_INTERPRETER=true" + +FILENAME="./optimistic_dual_catch$(date|sed "s/ /_/g"|sed "s/:/_/g").jfr" + +$JAVA_HOME/bin/java \ +-ea \ +-esa \ +$FLAGS \ +-Dnashorn.fastrewrite \ +-Dnashorn.optimistic \ +-Xbootclasspath/p:/Users/marcus/src/tip/dist/nashorn.jar \ +-Xms2G -Xmx2G \ +-XX:+UnlockCommercialFeatures \ +-XX:+FlightRecorder \ +-XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath=$FILENAME,stackdepth=1024 \ +-XX:TypeProfileLevel=222 \ +-XX:+UnlockExperimentalVMOptions \ +-XX:+UseTypeSpeculation \ +-XX:+UseMathExactIntrinsics \ +-XX:+UnlockDiagnosticVMOptions \ +-cp $CLASSPATH:../build/test/classes/ \ +jdk.nashorn.tools.Shell ${@} +
--- a/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MethodGenerator.java Tue Feb 25 18:56:10 2014 +0530 +++ b/buildtools/nasgen/src/jdk/nashorn/internal/tools/nasgen/MethodGenerator.java Wed Feb 26 13:17:57 2014 +0100 @@ -413,7 +413,8 @@ super.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", - "(Ljava/lang/String;)V", false); + "(Ljava/lang/String;)V", + false); } // print the object on the top of the stack @@ -426,6 +427,7 @@ super.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", - "(Ljava/lang/Object;)V", false); + "(Ljava/lang/Object;)V", + false); } }
--- a/make/build.xml Tue Feb 25 18:56:10 2014 +0530 +++ b/make/build.xml Wed Feb 26 13:17:57 2014 +0100 @@ -365,18 +365,6 @@ </testng> </target> - <target name="test-basicparallel" depends="jar, check-testng, check-external-tests, compile-test, generate-policy-file"> - <!-- use just build.test.classes.dir to avoid referring to TestNG --> - <java classname="${parallel.test.runner}" dir="${basedir}" classpath="${build.test.classes.dir}" failonerror="true" fork="true"> - <jvmarg line="${ext.class.path}"/> - <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs}"/> - <syspropertyset> - <propertyref prefix="test-sys-prop."/> - <mapper type="glob" from="test-sys-prop.*" to="*"/> - </syspropertyset> - </java> - </target> - <target name="check-jemmy.jfx.testng" unless="jemmy.jfx.testng.available"> <echo message="WARNING: Jemmy or JavaFX or TestNG not available, will not run tests. Please copy testng.jar, JemmyCore.jar, JemmyFX.jar, JemmyAWTInput.jar under test${file.separator}lib directory. And make sure you have jfxrt.jar in ${java.home}${file.separator}lib${file.separator}ext dir."/> </target> @@ -467,6 +455,28 @@ </java> </target> +<!-- classpath="${build.test.classes.dir}"--> + + <target name="testparallel" depends="test-parallel"/> + + <target name="test-parallel" depends="jar, check-testng, check-external-tests, compile-test, generate-policy-file" if="testng.available"> + <!-- use just build.test.classes.dir to avoid referring to TestNG --> + <java classname="${parallel.test.runner}" dir="${basedir}" + failonerror="true" + fork="true"> + <jvmarg line="${ext.class.path}"/> + <jvmarg line="${run.test.jvmargs} -Xmx${run.test.xmx} ${run.test.jvmsecurityargs}"/> + <classpath> + <pathelement path="${run.test.classpath}"/> + <pathelement path="${build.test.classes.dir}"/> + </classpath> + <syspropertyset> + <propertyref prefix="test-sys-prop."/> + <mapper type="glob" from="test-sys-prop.*" to="*"/> + </syspropertyset> + </java> + </target> + <target name="all" depends="test, docs" description="Build, test and generate docs for nashorn"/>
--- a/make/nbproject/ide-targets.xml Tue Feb 25 18:56:10 2014 +0530 +++ b/make/nbproject/ide-targets.xml Wed Feb 26 13:17:57 2014 +0100 @@ -31,9 +31,10 @@ <classpath path="${run.test.classpath}"/> </nbjpdastart> <java classname="jdk.nashorn.tools.Shell" classpath="${run.test.classpath}" dir="samples" fork="true"> + <jvmarg line="-Dnashorn.optimistic"/> <jvmarg line="${ext.class.path}"/> <jvmarg line="${run.test.jvmargs}"/> - <arg value="test.js"/> + <arg value="../make/str.js"/> <jvmarg value="-Xdebug"/> <jvmarg value="-Xrunjdwp:transport=dt_socket,address=${jpda.address}"/> </java>
--- a/make/project.properties Tue Feb 25 18:56:10 2014 +0530 +++ b/make/project.properties Wed Feb 26 13:17:57 2014 +0100 @@ -175,7 +175,7 @@ mandreel.js # test root for sunspider -sunspider-test-sys-prop.test.js.roots=${test.external.dir}/sunspider/tests/sunspider-1.0/ +sunspider-test-sys-prop.test.js.roots=${test.external.dir}/sunspider/tests/sunspider-1.0.2/ # framework root for sunspider sunspider-test-sys-prop.test.js.framework=${test.basic.dir}/runsunspider.js @@ -258,20 +258,29 @@ src.dir=src test.src.dir=test/src -# -Xmx is used for all tests, -Xms only for octane benchmark run.test.xmx=3G run.test.xms=2G +#uncomment to enable flight recording - crank up stack trace for lambda forms +#jfr.args=-XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:FlightRecorderOptions=defaultrecording=true,disk=true,dumponexit=true,dumponexitpath="test_suite.jfr",stackdepth=1024 \ +jfr.args= + run.test.user.language=tr run.test.user.country=TR -run.test.jvmargs.common=-server -XX:+TieredCompilation -Dfile.encoding=UTF-8 -Duser.language=${run.test.user.language} -Duser.country=${run.test.user.country} -XX:+HeapDumpOnOutOfMemoryError +run.test.jvmargs.common=\ + -server \ + -Dfile.encoding=UTF-8 \ + -Duser.language=${run.test.user.language} \ + -Duser.country=${run.test.user.country} \ + ${jfr.args} \ + -XX:+HeapDumpOnOutOfMemoryError #-XX:-UseCompressedKlassPointers -XX:+PrintHeapAtGC -XX:ClassMetaspaceSize=300M # -XX:+PrintCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMethods # turn on assertions for tests -run.test.jvmargs.main=${run.test.jvmargs.common} -ea +run.test.jvmargs.main=${run.test.jvmargs.common} -ea -Dnashorn.optimistic -Dnashorn.lazy #-XX:-UseCompressedKlassPointers -XX:+PrintHeapAtGC -XX:ClassMetaspaceSize=300M run.test.jvmargs.octane.main=${run.test.jvmargs.common}
--- a/src/jdk/internal/dynalink/DynamicLinker.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/DynamicLinker.java Wed Feb 26 13:17:57 2014 +0100 @@ -140,7 +140,6 @@ * @author Attila Szegedi */ public class DynamicLinker { - private static final String CLASS_NAME = DynamicLinker.class.getName(); private static final String RELINK_METHOD_NAME = "relink"; @@ -148,6 +147,7 @@ private static final String INITIAL_LINK_METHOD_NAME = "linkCallSite"; private final LinkerServices linkerServices; + private final GuardedInvocationFilter prelinkFilter; private final int runtimeContextArgCount; private final boolean syncOnRelink; private final int unstableRelinkThreshold; @@ -156,18 +156,20 @@ * Creates a new dynamic linker. * * @param linkerServices the linkerServices used by the linker, created by the factory. + * @param prelinkFilter see {@link DynamicLinkerFactory#setPrelinkFilter(GuardedInvocationFilter)} * @param runtimeContextArgCount see {@link DynamicLinkerFactory#setRuntimeContextArgCount(int)} */ - DynamicLinker(LinkerServices linkerServices, int runtimeContextArgCount, boolean syncOnRelink, - int unstableRelinkThreshold) { + DynamicLinker(LinkerServices linkerServices, GuardedInvocationFilter prelinkFilter, int runtimeContextArgCount, + boolean syncOnRelink, int unstableRelinkThreshold) { if(runtimeContextArgCount < 0) { throw new IllegalArgumentException("runtimeContextArgCount < 0"); } if(unstableRelinkThreshold < 0) { throw new IllegalArgumentException("unstableRelinkThreshold < 0"); } + this.linkerServices = linkerServices; + this.prelinkFilter = prelinkFilter; this.runtimeContextArgCount = runtimeContextArgCount; - this.linkerServices = linkerServices; this.syncOnRelink = syncOnRelink; this.unstableRelinkThreshold = unstableRelinkThreshold; } @@ -224,11 +226,10 @@ final boolean unstableDetectionEnabled = unstableRelinkThreshold > 0; final boolean callSiteUnstable = unstableDetectionEnabled && relinkCount >= unstableRelinkThreshold; final LinkRequest linkRequest = - runtimeContextArgCount == 0 ? new LinkRequestImpl(callSiteDescriptor, callSiteUnstable, arguments) - : new RuntimeContextLinkRequestImpl(callSiteDescriptor, callSiteUnstable, arguments, - runtimeContextArgCount); + runtimeContextArgCount == 0 ? + new LinkRequestImpl(callSiteDescriptor, callSite, callSiteUnstable, arguments) : + new RuntimeContextLinkRequestImpl(callSiteDescriptor, callSite, callSiteUnstable, arguments, runtimeContextArgCount); - // Find a suitable method handle with a guard GuardedInvocation guardedInvocation = linkerServices.getGuardedInvocation(linkRequest); // None found - throw an exception @@ -248,6 +249,11 @@ } } + // Make sure we filter the invocation before linking it into the call site. This is typically used to match the + // return type of the invocation to the call site. + guardedInvocation = prelinkFilter.filter(guardedInvocation, linkRequest, linkerServices); + guardedInvocation.getClass(); // null pointer check + int newRelinkCount = relinkCount; // Note that the short-circuited "&&" evaluation below ensures we'll increment the relinkCount until // threshold + 1 but not beyond that. Threshold + 1 is treated as a special value to signal that resetAndRelink
--- a/src/jdk/internal/dynalink/DynamicLinkerFactory.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/DynamicLinkerFactory.java Wed Feb 26 13:17:57 2014 +0100 @@ -102,14 +102,15 @@ import jdk.internal.dynalink.support.ClassLoaderGetterContextProvider; import jdk.internal.dynalink.support.CompositeGuardingDynamicLinker; import jdk.internal.dynalink.support.CompositeTypeBasedGuardingDynamicLinker; +import jdk.internal.dynalink.support.DefaultPrelinkFilter; import jdk.internal.dynalink.support.LinkerServicesImpl; import jdk.internal.dynalink.support.TypeConverterFactory; /** * A factory class for creating {@link DynamicLinker}s. The most usual dynamic linker is a linker that is a composition * of all {@link GuardingDynamicLinker}s known and pre-created by the caller as well as any - * {@link AutoDiscovery automatically discovered} guarding linkers and the standard fallback {@link BeansLinker}. See - * {@link DynamicLinker} documentation for tips on how to use this class. + * {@link AutoDiscovery automatically discovered} guarding linkers and the standard fallback {@link BeansLinker} and a + * {@link DefaultPrelinkFilter}. See {@link DynamicLinker} documentation for tips on how to use this class. * * @author Attila Szegedi */ @@ -128,6 +129,7 @@ private int runtimeContextArgCount = 0; private boolean syncOnRelink = false; private int unstableRelinkThreshold = DEFAULT_UNSTABLE_RELINK_THRESHOLD; + private GuardedInvocationFilter prelinkFilter; /** * Sets the class loader for automatic discovery of available linkers. If not set explicitly, then the thread @@ -246,7 +248,19 @@ } /** - * Creates a new dynamic linker consisting of all the prioritized, autodiscovered, and fallback linkers. + * Set the pre-link filter. This is a {@link GuardedInvocationFilter} that will get the final chance to modify the + * guarded invocation after it has been created by a component linker and before the dynamic linker links it into + * the call site. It is normally used to adapt the return value type of the invocation to the type of the call site. + * When not set explicitly, {@link DefaultPrelinkFilter} will be used. + * @param prelinkFilter the pre-link filter for the dynamic linker. + */ + public void setPrelinkFilter(GuardedInvocationFilter prelinkFilter) { + this.prelinkFilter = prelinkFilter; + } + + /** + * Creates a new dynamic linker consisting of all the prioritized, autodiscovered, and fallback linkers as well as + * the pre-link filter. * * @return the new dynamic Linker */ @@ -306,8 +320,12 @@ } } + if(prelinkFilter == null) { + prelinkFilter = new DefaultPrelinkFilter(); + } + return new DynamicLinker(new LinkerServicesImpl(new TypeConverterFactory(typeConverters), composite), - runtimeContextArgCount, syncOnRelink, unstableRelinkThreshold); + prelinkFilter, runtimeContextArgCount, syncOnRelink, unstableRelinkThreshold); } private static ClassLoader getThreadContextClassLoader() {
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/internal/dynalink/GuardedInvocationFilter.java Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,105 @@ +/* + * 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. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file, and Oracle licenses the original version of this file under the BSD + * license: + */ +/* + Copyright 2009-2013 Attila Szegedi + + Licensed under both the Apache License, Version 2.0 (the "Apache License") + and the BSD License (the "BSD License"), with licensee being free to + choose either of the two at their discretion. + + You may not use this file except in compliance with either the Apache + License or the BSD License. + + If you choose to use this file in compliance with the Apache License, the + following notice applies to you: + + You may obtain a copy of the Apache License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. + + If you choose to use this file in compliance with the BSD License, the + following notice applies to you: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package jdk.internal.dynalink; + +import jdk.internal.dynalink.linker.GuardedInvocation; +import jdk.internal.dynalink.linker.LinkRequest; +import jdk.internal.dynalink.linker.LinkerServices; + +/** + * Interface for objects that are used to transform one guarded invocation into another one. Typical usage is for + * implementing {@link DynamicLinkerFactory#setPrelinkFilter(GuardedInvocationFilter) pre-link filters}. + */ +public interface GuardedInvocationFilter { + /** + * Given a guarded invocation, return a potentially different guarded invocation. + * @param inv the original guarded invocation. Null is never passed. + * @param linkRequest the link request for which the invocation was generated (usually by some linker). + * @param linkerServices the linker services that can be used during creation of a new invocation. + * @return either the passed guarded invocation or a different one, with the difference usually determined based on + * information in the link request and the differing invocation created with the assistance of the linker services. + * Whether or not {@code null} is an accepted return value is dependent on the user of the filter. + */ + public GuardedInvocation filter(GuardedInvocation inv, LinkRequest linkRequest, LinkerServices linkerServices); +}
--- a/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/beans/AbstractJavaLinker.java Wed Feb 26 13:17:57 2014 +0100 @@ -97,7 +97,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; - import jdk.internal.dynalink.CallSiteDescriptor; import jdk.internal.dynalink.beans.GuardedInvocationComponent.ValidationType; import jdk.internal.dynalink.linker.GuardedInvocation; @@ -107,6 +106,7 @@ import jdk.internal.dynalink.support.CallSiteDescriptorFactory; import jdk.internal.dynalink.support.Guards; import jdk.internal.dynalink.support.Lookup; +import jdk.internal.dynalink.support.TypeUtilities; /** * A base class for both {@link StaticClassLinker} and {@link BeanLinker}. Deals with common aspects of property @@ -459,12 +459,16 @@ private GuardedInvocationComponent getPropertySetter(CallSiteDescriptor callSiteDescriptor, LinkerServices linkerServices, List<String> operations) throws Exception { - final MethodType type = callSiteDescriptor.getMethodType(); switch(callSiteDescriptor.getNameTokenCount()) { case 2: { // Must have three arguments: target object, property name, and property value. assertParameterCount(callSiteDescriptor, 3); + // We want setters that conform to "Object(O, V)". Note, we aren't doing "R(O, V)" as it might not be + // valid for us to convert return values proactively. Also, since we don't know what setters will be + // invoked, we'll conservatively presume Object return type. + final MethodType type = callSiteDescriptor.getMethodType().changeReturnType(Object.class); + // What's below is basically: // foldArguments(guardWithTest(isNotNull, invoke, null|nextComponent.invocation), // get_setter_handle(type, linkerServices)) @@ -473,8 +477,8 @@ // component's invocation. // Call site type is "ret_type(object_type,property_name_type,property_value_type)", which we'll - // abbreviate to R(O, N, V) going forward. - // We want setters that conform to "R(O, V)" + // abbreviate to R(O, N, V) going forward, although we don't really use R here (see above about using + // Object return type). final MethodType setterType = type.dropParameterTypes(1, 2); // Bind property setter handle to the expected setter type and linker services. Type is // MethodHandle(Object, String, Object) @@ -495,11 +499,11 @@ final MethodHandle fallbackFolded; if(nextComponent == null) { - // Object(MethodHandle)->R(MethodHandle, O, N, V); returns constant null + // Object(MethodHandle)->Object(MethodHandle, O, N, V); returns constant null fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_METHOD_HANDLE, 1, type.parameterList()).asType(type.insertParameterTypes(0, MethodHandle.class)); } else { - // R(O, N, V)->R(MethodHandle, O, N, V); adapts the next component's invocation to drop the + // Object(O, N, V)->Object(MethodHandle, O, N, V); adapts the next component's invocation to drop the // extra argument resulting from fold fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(), 0, MethodHandle.class); @@ -545,9 +549,12 @@ private GuardedInvocationComponent getPropertyGetter(CallSiteDescriptor callSiteDescriptor, LinkerServices linkerServices, List<String> ops) throws Exception { - final MethodType type = callSiteDescriptor.getMethodType(); switch(callSiteDescriptor.getNameTokenCount()) { case 2: { + // Since we can't know what kind of a getter we'll get back on different invocations, we'll just + // conservatively presume Object. Note we can't just coerce to a narrower call site type as the linking + // runtime might not allow coercing at that call site. + final MethodType type = callSiteDescriptor.getMethodType().changeReturnType(Object.class); // Must have exactly two arguments: receiver and name assertParameterCount(callSiteDescriptor, 2); @@ -563,11 +570,11 @@ GET_ANNOTATED_METHOD, 1, callSiteDescriptor.getLookup()); final MethodHandle callSiteBoundInvoker = MethodHandles.filterArguments(GETTER_INVOKER, 0, callSiteBoundMethodGetter); - // Object(AnnotatedDynamicMethod, Object)->R(AnnotatedDynamicMethod, T0) + // Object(AnnotatedDynamicMethod, Object)->Object(AnnotatedDynamicMethod, T0) final MethodHandle invokeHandleTyped = linkerServices.asType(callSiteBoundInvoker, MethodType.methodType(type.returnType(), AnnotatedDynamicMethod.class, type.parameterType(0))); // Since it's in the target of a fold, drop the unnecessary second argument - // R(AnnotatedDynamicMethod, T0)->R(AnnotatedDynamicMethod, T0, T1) + // Object(AnnotatedDynamicMethod, T0)->Object(AnnotatedDynamicMethod, T0, T1) final MethodHandle invokeHandleFolded = MethodHandles.dropArguments(invokeHandleTyped, 2, type.parameterType(1)); final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor, @@ -575,17 +582,19 @@ final MethodHandle fallbackFolded; if(nextComponent == null) { - // Object(AnnotatedDynamicMethod)->R(AnnotatedDynamicMethod, T0, T1); returns constant null + // Object(AnnotatedDynamicMethod)->Object(AnnotatedDynamicMethod, T0, T1); returns constant null fallbackFolded = MethodHandles.dropArguments(CONSTANT_NULL_DROP_ANNOTATED_METHOD, 1, type.parameterList()).asType(type.insertParameterTypes(0, AnnotatedDynamicMethod.class)); } else { - // R(T0, T1)->R(AnnotatedDynamicMethod, T0, T1); adapts the next component's invocation to drop the - // extra argument resulting from fold - fallbackFolded = MethodHandles.dropArguments(nextComponent.getGuardedInvocation().getInvocation(), - 0, AnnotatedDynamicMethod.class); + // Object(T0, T1)->Object(AnnotatedDynamicMethod, T0, T1); adapts the next component's invocation to + // drop the extra argument resulting from fold and to change its return type to Object. + final MethodHandle nextInvocation = nextComponent.getGuardedInvocation().getInvocation(); + final MethodType nextType = nextInvocation.type(); + fallbackFolded = MethodHandles.dropArguments(nextInvocation.asType( + nextType.changeReturnType(Object.class)), 0, AnnotatedDynamicMethod.class); } - // fold(R(AnnotatedDynamicMethod, T0, T1), AnnotatedDynamicMethod(T0, T1)) + // fold(Object(AnnotatedDynamicMethod, T0, T1), AnnotatedDynamicMethod(T0, T1)) final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest( IS_ANNOTATED_METHOD_NOT_NULL, invokeHandleFolded, fallbackFolded), typedGetter); if(nextComponent == null) { @@ -612,8 +621,8 @@ // value is null. final ValidationType validationType = annGetter.validationType; // TODO: we aren't using the type that declares the most generic getter here! - return new GuardedInvocationComponent(linkerServices.asType(getter, type), getGuard(validationType, - type), clazz, validationType); + return new GuardedInvocationComponent(getter, getGuard(validationType, + callSiteDescriptor.getMethodType()), clazz, validationType); } default: { // Can't do anything with more than 3 name components @@ -642,21 +651,25 @@ } } - private static final MethodHandle IS_DYNAMIC_METHOD_NOT_NULL = Guards.asType(Guards.isNotNull(), - MethodType.methodType(boolean.class, DynamicMethod.class)); - private static final MethodHandle DYNAMIC_METHOD_IDENTITY = MethodHandles.identity(DynamicMethod.class); + private static final MethodHandle IS_DYNAMIC_METHOD = Guards.isInstance(DynamicMethod.class, + MethodType.methodType(boolean.class, Object.class)); + private static final MethodHandle OBJECT_IDENTITY = MethodHandles.identity(Object.class); private GuardedInvocationComponent getMethodGetter(CallSiteDescriptor callSiteDescriptor, LinkerServices linkerServices, List<String> ops) throws Exception { - final MethodType type = callSiteDescriptor.getMethodType(); + // The created method handle will always return a DynamicMethod (or null), but since we don't want that type to + // be visible outside of this linker, declare it to return Object. + final MethodType type = callSiteDescriptor.getMethodType().changeReturnType(Object.class); switch(callSiteDescriptor.getNameTokenCount()) { case 2: { // Must have exactly two arguments: receiver and name assertParameterCount(callSiteDescriptor, 2); final GuardedInvocationComponent nextComponent = getGuardedInvocationComponent(callSiteDescriptor, linkerServices, ops); - if(nextComponent == null) { - // No next component operation; just return a component for this operation. + if(nextComponent == null || !TypeUtilities.areAssignable(DynamicMethod.class, + nextComponent.getGuardedInvocation().getInvocation().type().returnType())) { + // No next component operation, or it can never produce a dynamic method; just return a component + // for this operation. return getClassGuardedInvocationComponent(linkerServices.asType(getDynamicMethod, type), type); } @@ -665,21 +678,20 @@ // bunch of method signature adjustments. Basically, execute method getter; if it returns a non-null // DynamicMethod, use identity to return it, otherwise delegate to nextComponent's invocation. - final MethodHandle typedGetter = linkerServices.asType(getDynamicMethod, type.changeReturnType( - DynamicMethod.class)); + final MethodHandle typedGetter = linkerServices.asType(getDynamicMethod, type); // Since it is part of the foldArgument() target, it will have extra args that we need to drop. final MethodHandle returnMethodHandle = linkerServices.asType(MethodHandles.dropArguments( - DYNAMIC_METHOD_IDENTITY, 1, type.parameterList()), type.insertParameterTypes(0, - DynamicMethod.class)); + OBJECT_IDENTITY, 1, type.parameterList()), type.insertParameterTypes(0, Object.class)); final MethodHandle nextComponentInvocation = nextComponent.getGuardedInvocation().getInvocation(); - // The assumption is that getGuardedInvocationComponent() already asType()'d it correctly - assert nextComponentInvocation.type().equals(type); + // The assumption is that getGuardedInvocationComponent() already asType()'d it correctly modulo the + // return type. + assert nextComponentInvocation.type().changeReturnType(type.returnType()).equals(type); // Since it is part of the foldArgument() target, we have to drop an extra arg it receives. final MethodHandle nextCombinedInvocation = MethodHandles.dropArguments(nextComponentInvocation, 0, - DynamicMethod.class); + Object.class); // Assemble it all into a fold(guard(isNotNull, identity, nextInvocation), get) final MethodHandle compositeGetter = MethodHandles.foldArguments(MethodHandles.guardWithTest( - IS_DYNAMIC_METHOD_NOT_NULL, returnMethodHandle, nextCombinedInvocation), typedGetter); + IS_DYNAMIC_METHOD, returnMethodHandle, nextCombinedInvocation), typedGetter); return nextComponent.compose(compositeGetter, getClassGuard(type), clazz, ValidationType.EXACT_CLASS); } @@ -695,7 +707,7 @@ // No delegation to the next component of the composite operation; if we have a method with that name, // we'll always return it at this point. return getClassGuardedInvocationComponent(linkerServices.asType(MethodHandles.dropArguments( - MethodHandles.constant(DynamicMethod.class, method), 0, type.parameterType(0)), type), type); + MethodHandles.constant(Object.class, method), 0, type.parameterType(0)), type), type); } default: { // Can't do anything with more than 3 name components @@ -704,6 +716,30 @@ } } + static class MethodPair { + final MethodHandle method1; + final MethodHandle method2; + + MethodPair(final MethodHandle method1, final MethodHandle method2) { + this.method1 = method1; + this.method2 = method2; + } + + MethodHandle guardWithTest(final MethodHandle test) { + return MethodHandles.guardWithTest(test, method1, method2); + } + } + + static MethodPair matchReturnTypes(MethodHandle m1, MethodHandle m2) { + final MethodType type1 = m1.type(); + final MethodType type2 = m2.type(); + final Class<?> commonRetType = TypeUtilities.getCommonLosslessConversionType(type1.returnType(), + type2.returnType()); + return new MethodPair( + m1.asType(type1.changeReturnType(commonRetType)), + m2.asType(type2.changeReturnType(commonRetType))); + } + private static void assertParameterCount(CallSiteDescriptor descriptor, int paramCount) { if(descriptor.getMethodType().parameterCount() != paramCount) { throw new BootstrapMethodError(descriptor.getName() + " must have exactly " + paramCount + " parameters."); @@ -739,11 +775,14 @@ } private static MethodHandle GET_DYNAMIC_METHOD = MethodHandles.dropArguments(privateLookup.findOwnSpecial( - "getDynamicMethod", DynamicMethod.class, Object.class), 1, Object.class); + "getDynamicMethod", Object.class, Object.class), 1, Object.class); private final MethodHandle getDynamicMethod = GET_DYNAMIC_METHOD.bindTo(this); @SuppressWarnings("unused") - private DynamicMethod getDynamicMethod(Object name) { + // This method is marked to return Object instead of DynamicMethod as it's used as a linking component and we don't + // want to make the DynamicMethod type observable externally (e.g. as the return type of a MethodHandle returned for + // "dyn:getMethod" linking). + private Object getDynamicMethod(Object name) { return getDynamicMethod(String.valueOf(name), methods); }
--- a/src/jdk/internal/dynalink/beans/BeanLinker.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/beans/BeanLinker.java Wed Feb 26 13:17:57 2014 +0100 @@ -235,8 +235,9 @@ } else { checkGuard = convertArgToInt(RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor); } - return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard), - binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(), + final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation), + nextComponent.getGuardedInvocation().getInvocation()); + return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(), gic.getValidatorClass(), gic.getValidationType()); } @@ -306,7 +307,7 @@ } /*private*/ MethodHandle bind(MethodHandle handle) { - return bindToFixedKey(linkerServices.asType(handle, methodType)); + return bindToFixedKey(linkerServices.asTypeLosslessReturn(handle, methodType)); } /*private*/ MethodHandle bindTest(MethodHandle handle) { @@ -438,8 +439,9 @@ final MethodHandle checkGuard = convertArgToInt(invocation == SET_LIST_ELEMENT ? RANGE_CHECK_LIST : RANGE_CHECK_ARRAY, linkerServices, callSiteDescriptor); - return nextComponent.compose(MethodHandles.guardWithTest(binder.bindTest(checkGuard), - binder.bind(invocation), nextComponent.getGuardedInvocation().getInvocation()), gi.getGuard(), + final MethodPair matchedInvocations = matchReturnTypes(binder.bind(invocation), + nextComponent.getGuardedInvocation().getInvocation()); + return nextComponent.compose(matchedInvocations.guardWithTest(binder.bindTest(checkGuard)), gi.getGuard(), gic.getValidatorClass(), gic.getValidationType()); }
--- a/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/beans/OverloadedDynamicMethod.java Wed Feb 26 13:17:57 2014 +0100 @@ -148,7 +148,6 @@ } } - @SuppressWarnings("fallthrough") @Override public MethodHandle getInvocation(final CallSiteDescriptor callSiteDescriptor, final LinkerServices linkerServices) { final MethodType callSiteType = callSiteDescriptor.getMethodType(); @@ -207,7 +206,7 @@ case 1: { // Very lucky, we ended up with a single candidate method handle based on the call site signature; we // can link it very simply by delegating to the SingleDynamicMethod. - invokables.iterator().next().getInvocation(callSiteDescriptor, linkerServices); + return invokables.iterator().next().getInvocation(callSiteDescriptor, linkerServices); } default: { // We have more than one candidate. We have no choice but to link to a method that resolves overloads on
--- a/src/jdk/internal/dynalink/beans/OverloadedMethod.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/beans/OverloadedMethod.java Wed Feb 26 13:17:57 2014 +0100 @@ -93,6 +93,7 @@ import java.util.concurrent.ConcurrentHashMap; import jdk.internal.dynalink.linker.LinkerServices; import jdk.internal.dynalink.support.Lookup; +import jdk.internal.dynalink.support.TypeUtilities; /** * Represents a subset of overloaded methods for a certain method name on a certain class. It can be either a fixarg or @@ -114,13 +115,15 @@ OverloadedMethod(List<MethodHandle> methodHandles, OverloadedDynamicMethod parent, MethodType callSiteType, LinkerServices linkerServices) { this.parent = parent; - this.callSiteType = callSiteType; + final Class<?> commonRetType = getCommonReturnType(methodHandles); + this.callSiteType = callSiteType.changeReturnType(commonRetType); this.linkerServices = linkerServices; fixArgMethods = new ArrayList<>(methodHandles.size()); varArgMethods = new ArrayList<>(methodHandles.size()); final int argNum = callSiteType.parameterCount(); for(MethodHandle mh: methodHandles) { + mh = mh.asType(mh.type().changeReturnType(commonRetType)); if(mh.isVarargsCollector()) { final MethodHandle asFixed = mh.asFixedArity(); if(argNum == asFixed.type().parameterCount()) { @@ -137,7 +140,7 @@ final MethodHandle bound = SELECT_METHOD.bindTo(this); final MethodHandle collecting = SingleDynamicMethod.collectArguments(bound, argNum).asType( callSiteType.changeReturnType(MethodHandle.class)); - invoker = MethodHandles.foldArguments(MethodHandles.exactInvoker(callSiteType), collecting); + invoker = MethodHandles.foldArguments(MethodHandles.exactInvoker(this.callSiteType), collecting); } MethodHandle getInvoker() { @@ -262,4 +265,13 @@ b.append(classes[l - 1].getComponentType().getCanonicalName()).append("..."); } } + + private static Class<?> getCommonReturnType(List<MethodHandle> methodHandles) { + final Iterator<MethodHandle> it = methodHandles.iterator(); + Class<?> retType = it.next().type().returnType(); + while(it.hasNext()) { + retType = TypeUtilities.getCommonLosslessConversionType(retType, it.next().type().returnType()); + } + return retType; + } }
--- a/src/jdk/internal/dynalink/beans/SingleDynamicMethod.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/beans/SingleDynamicMethod.java Wed Feb 26 13:17:57 2014 +0100 @@ -156,7 +156,9 @@ /** * Given a method handle and a call site type, adapts the method handle to the call site type. Performs type * conversions as needed using the specified linker services, and in case that the method handle is a vararg - * collector, matches it to the arity of the call site. + * collector, matches it to the arity of the call site. The type of the return value is only changed if it can be + * converted using a conversion that loses neither precision nor magnitude, see + * {@link LinkerServices#asTypeLosslessReturn(MethodHandle, MethodType)}. * @param target the method handle to adapt * @param callSiteType the type of the call site * @param linkerServices the linker services used for type conversions @@ -286,7 +288,7 @@ private static MethodHandle createConvertingInvocation(final MethodHandle sizedMethod, final LinkerServices linkerServices, final MethodType callSiteType) { - return linkerServices.asType(sizedMethod, callSiteType); + return linkerServices.asTypeLosslessReturn(sizedMethod, callSiteType); } private static boolean typeMatchesDescription(String paramTypes, MethodType type) {
--- a/src/jdk/internal/dynalink/linker/GuardedInvocation.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/linker/GuardedInvocation.java Wed Feb 26 13:17:57 2014 +0100 @@ -90,7 +90,9 @@ import java.lang.invoke.WrongMethodTypeException; import java.util.List; import jdk.internal.dynalink.CallSiteDescriptor; +import jdk.internal.dynalink.support.CatchExceptionCombinator; import jdk.internal.dynalink.support.Guards; +import jdk.nashorn.internal.runtime.options.Options; /** * Represents a conditionally valid method handle. It is an immutable triple of an invocation method handle, a guard @@ -102,11 +104,24 @@ * @author Attila Szegedi */ public class GuardedInvocation { + private static final boolean USE_FAST_REWRITE = Options.getBooleanProperty("nashorn.fastrewrite"); + private final MethodHandle invocation; private final MethodHandle guard; + private final Class<? extends Throwable> exception; private final SwitchPoint switchPoint; /** + * Creates a new guarded invocation. This invocation is unconditional as it has no invalidations. + * + * @param invocation the method handle representing the invocation. Must not be null. + * @throws NullPointerException if invocation is null. + */ + public GuardedInvocation(MethodHandle invocation) { + this(invocation, null, null, null); + } + + /** * Creates a new guarded invocation. * * @param invocation the method handle representing the invocation. Must not be null. @@ -116,7 +131,18 @@ * @throws NullPointerException if invocation is null. */ public GuardedInvocation(MethodHandle invocation, MethodHandle guard) { - this(invocation, guard, null); + this(invocation, guard, null, null); + } + + /** + * Creates a new guarded invocation. + * + * @param invocation the method handle representing the invocation. Must not be null. + * @param switchPoint the optional switch point that can be used to invalidate this linkage. + * @throws NullPointerException if invocation is null. + */ + public GuardedInvocation(MethodHandle invocation, SwitchPoint switchPoint) { + this(invocation, null, switchPoint, null); } /** @@ -130,25 +156,29 @@ * @throws NullPointerException if invocation is null. */ public GuardedInvocation(MethodHandle invocation, MethodHandle guard, SwitchPoint switchPoint) { - invocation.getClass(); // NPE check - this.invocation = invocation; - this.guard = guard; - this.switchPoint = switchPoint; + this(invocation, guard, switchPoint, null); } /** * Creates a new guarded invocation. * * @param invocation the method handle representing the invocation. Must not be null. - * @param switchPoint the optional switch point that can be used to invalidate this linkage. * @param guard the method handle representing the guard. Must have the same method type as the invocation, except * it must return boolean. For some useful guards, check out the {@link Guards} class. It can be null. If both it * and the switch point are null, this represents an unconditional invocation, which is legal but unusual. + * @param switchPoint the optional switch point that can be used to invalidate this linkage. + * @param exception the optional exception type that is expected to be thrown by the invocation and that also + * invalidates the linkage. * @throws NullPointerException if invocation is null. */ - public GuardedInvocation(MethodHandle invocation, SwitchPoint switchPoint, MethodHandle guard) { - this(invocation, guard, switchPoint); + public GuardedInvocation(MethodHandle invocation, MethodHandle guard, SwitchPoint switchPoint, Class<? extends Throwable> exception) { + invocation.getClass(); // NPE check + this.invocation = invocation; + this.guard = guard; + this.switchPoint = switchPoint; + this.exception = exception; } + /** * Returns the invocation method handle. * @@ -177,6 +207,15 @@ } /** + * Returns the exception type that if thrown should be used to invalidate the linkage. + * + * @return the exception type that if thrown should be used to invalidate the linkage. Can be null. + */ + public Class<? extends Throwable> getException() { + return exception; + } + + /** * Returns true if and only if this guarded invocation has a switchpoint, and that switchpoint has been invalidated. * @return true if and only if this guarded invocation has a switchpoint, and that switchpoint has been invalidated. */ @@ -206,7 +245,7 @@ * @return a new guarded invocation with the replaced methods and the same switch point as this invocation. */ public GuardedInvocation replaceMethods(MethodHandle newInvocation, MethodHandle newGuard) { - return new GuardedInvocation(newInvocation, newGuard, switchPoint); + return new GuardedInvocation(newInvocation, newGuard, switchPoint, exception); } private GuardedInvocation replaceMethodsOrThis(MethodHandle newInvocation, MethodHandle newGuard) { @@ -241,6 +280,20 @@ } /** + * Changes the type of the invocation, as if {@link LinkerServices#asTypeLosslessReturn(MethodHandle, MethodType)} was + * applied to its invocation and {@link LinkerServices#asType(MethodHandle, MethodType)} applied to its guard, if it + * has one (with return type changed to boolean, and parameter count potentially truncated for the guard). If the + * invocation doesn't change its type, returns this object. + * @param linkerServices the linker services to use for the conversion + * @param newType the new type of the invocation. + * @return a guarded invocation with the new type applied to it. + */ + public GuardedInvocation asTypeSafeReturn(LinkerServices linkerServices, MethodType newType) { + return replaceMethodsOrThis(linkerServices.asTypeLosslessReturn(invocation, newType), guard == null ? null : + Guards.asType(linkerServices, guard, newType)); + } + + /** * Changes the type of the invocation, as if {@link MethodHandle#asType(MethodType)} was applied to its invocation * and its guard, if it has one (with return type changed to boolean for guard). If the invocation already is of the * required type, returns this object. @@ -303,9 +356,17 @@ public MethodHandle compose(MethodHandle switchpointFallback, MethodHandle guardFallback) { final MethodHandle guarded = guard == null ? invocation : MethodHandles.guardWithTest(guard, invocation, guardFallback); - return switchPoint == null ? guarded : switchPoint.guardWithTest(guarded, switchpointFallback); + final MethodHandle catchGuarded = exception == null ? guarded : catchException(guarded, exception, + MethodHandles.dropArguments(guardFallback, 0, exception)); + return switchPoint == null ? catchGuarded : switchPoint.guardWithTest(catchGuarded, switchpointFallback); } + private static MethodHandle catchException(final MethodHandle target, final Class<? extends Throwable> exType, final MethodHandle handler) { + if(USE_FAST_REWRITE) { + return CatchExceptionCombinator.catchException(target, exType, handler); + } + return MethodHandles.catchException(target, exType, handler); + } private static void assertType(MethodHandle mh, MethodType type) { if(!mh.type().equals(type)) { throw new WrongMethodTypeException("Expected type: " + type + " actual type: " + mh.type());
--- a/src/jdk/internal/dynalink/linker/GuardingDynamicLinker.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/linker/GuardingDynamicLinker.java Wed Feb 26 13:17:57 2014 +0100 @@ -101,10 +101,16 @@ * @return a guarded invocation with a method handle suitable for the arguments, as well as a guard condition that * if fails should trigger relinking. Must return null if it can't resolve the invocation. If the returned * invocation is unconditional (which is actually quite rare), the guard in the return value can be null. The - * invocation can also have a switch point for asynchronous invalidation of the linkage. If the linker does not - * recognize any native language runtime contexts in arguments, or does recognize its own, but receives a call site - * descriptor without its recognized context in the arguments, it should invoke - * {@link LinkRequest#withoutRuntimeContext()} and link for that. + * invocation can also have a switch point for asynchronous invalidation of the linkage, as well as a + * {@link Throwable} subclass that describes an expected exception condition that also triggers relinking (often it + * is faster to rely on an infrequent but expected {@link ClassCastException} than on an always evaluated + * {@code instanceof} guard). If the linker does not recognize any native language runtime contexts in arguments, or + * does recognize its own, but receives a call site descriptor without its recognized context in the arguments, it + * should invoke {@link LinkRequest#withoutRuntimeContext()} and link for that. While the linker must produce an + * invocation with parameter types matching those in the call site descriptor of the link request, it should not try + * to match the return type expected at the call site except when it can do it with only the conversions that lose + * neither precision nor magnitude, see {@link LinkerServices#asTypeLosslessReturn(java.lang.invoke.MethodHandle, + * java.lang.invoke.MethodType)}. * @throws Exception if the operation fails for whatever reason */ public GuardedInvocation getGuardedInvocation(LinkRequest linkRequest, LinkerServices linkerServices)
--- a/src/jdk/internal/dynalink/linker/LinkRequest.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/linker/LinkRequest.java Wed Feb 26 13:17:57 2014 +0100 @@ -101,6 +101,17 @@ public CallSiteDescriptor getCallSiteDescriptor(); /** + * Returns the call site token for the call site being linked. This token is an opaque object that is guaranteed to + * have different identity for different call sites, and is also guaranteed to not become weakly reachable before + * the call site does and to become weakly reachable some time after the call site does. This makes it ideal as a + * candidate for a key in a weak hash map in which a linker might want to keep per-call site linking state (usually + * profiling information). + * + * @return the call site token for the call site being linked. + */ + public Object getCallSiteToken(); + + /** * Returns the arguments for the invocation being linked. The returned array is a clone; modifications to it won't * affect the arguments in this request. *
--- a/src/jdk/internal/dynalink/linker/LinkerServices.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/linker/LinkerServices.java Wed Feb 26 13:17:57 2014 +0100 @@ -87,7 +87,9 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import jdk.internal.dynalink.DynamicLinker; +import jdk.internal.dynalink.DynamicLinkerFactory; import jdk.internal.dynalink.linker.ConversionComparator.Comparison; +import jdk.internal.dynalink.support.TypeUtilities; /** * Interface for services provided to {@link GuardingDynamicLinker} instances by the {@link DynamicLinker} that owns @@ -103,18 +105,34 @@ * parameters. It will apply {@link MethodHandle#asType(MethodType)} for all primitive-to-primitive, * wrapper-to-primitive, primitive-to-wrapper conversions as well as for all upcasts. For all other conversions, * it'll insert {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with composite filters - * provided by {@link GuardingTypeConverterFactory} implementations. It doesn't use language-specific conversions on - * the return type. + * provided by {@link GuardingTypeConverterFactory} implementations. * * @param handle target method handle * @param fromType the types of source arguments - * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)} and - * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with - * {@link GuardingTypeConverterFactory} produced type converters as filters. + * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)}, + * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)}, and + * {@link MethodHandles#filterReturnValue(MethodHandle, MethodHandle)} with + * {@link GuardingTypeConverterFactory}-produced type converters as filters. */ public MethodHandle asType(MethodHandle handle, MethodType fromType); /** + * Similar to {@link #asType(MethodHandle, MethodType)} except it only converts the return type of the method handle + * when it can be done using a conversion that loses neither precision nor magnitude, otherwise it leaves it + * unchanged. The idea is that other conversions should not be performed by individual linkers, but instead the + * {@link DynamicLinkerFactory#setPrelinkFilter(jdk.internal.dynalink.GuardedInvocationFilter) pre-link filter of + * the dynamic linker} should implement the strategy of dealing with potentially lossy return type conversions in a + * manner specific to the language runtime. + * + * @param handle target method handle + * @param fromType the types of source arguments + * @return a method handle that is a suitable combination of {@link MethodHandle#asType(MethodType)}, and + * {@link MethodHandles#filterArguments(MethodHandle, int, MethodHandle...)} with + * {@link GuardingTypeConverterFactory}-produced type converters as filters. + */ + public MethodHandle asTypeLosslessReturn(MethodHandle handle, MethodType fromType); + + /** * Given a source and target type, returns a method handle that converts between them. Never returns null; in worst * case it will return an identity conversion (that might fail for some values at runtime). You rarely need to use * this method directly; you should mostly rely on {@link #asType(MethodHandle, MethodType)} instead. You really @@ -161,4 +179,23 @@ * conversion. */ public Comparison compareConversion(Class<?> sourceType, Class<?> targetType1, Class<?> targetType2); + + /** + * If we could just use Java 8 constructs, then {@code asTypeSafeReturn} would be a method with default + * implementation. Since we can't do that, we extract common default implementations into this static class. + */ + public static class Implementation { + /** + * Default implementation for {@link LinkerServices#asTypeLosslessReturn(MethodHandle, MethodType)}. + * @param linkerServices the linker services that delegates to this implementation + * @param handle the passed handle + * @param fromType the passed type + * @return the converted method handle, as per the {@code asTypeSafeReturn} semantics. + */ + public static MethodHandle asTypeLosslessReturn(LinkerServices linkerServices, MethodHandle handle, MethodType fromType) { + final Class<?> handleReturnType = handle.type().returnType(); + return linkerServices.asType(handle, TypeUtilities.isConvertibleWithoutLoss(handleReturnType, fromType.returnType()) ? + fromType : fromType.changeReturnType(handleReturnType)); + } + } }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/internal/dynalink/support/CatchExceptionCombinator.java Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,180 @@ +package jdk.internal.dynalink.support; + +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_STATIC; +import static jdk.internal.org.objectweb.asm.Opcodes.ACC_SUPER; +import static jdk.internal.org.objectweb.asm.Opcodes.V1_7; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Function; +import jdk.internal.org.objectweb.asm.ClassWriter; +import jdk.internal.org.objectweb.asm.Label; +import jdk.internal.org.objectweb.asm.Type; +import jdk.internal.org.objectweb.asm.commons.InstructionAdapter; +import jdk.nashorn.internal.runtime.RewriteException; +import sun.misc.Unsafe; + +/** + * Generates method handles that combine an invocation and a handler for a {@link RewriteException}. Always immediately + * generates compiled bytecode. + */ +public class CatchExceptionCombinator { + static { + System.err.println("*** Running with fast catch combinator handler ***"); + } + private static final Type METHOD_HANDLE_TYPE = Type.getType(MethodHandle.class); + private static final String METHOD_HANDLE_TYPE_NAME = METHOD_HANDLE_TYPE.getInternalName(); + private static final String OBJECT_TYPE_NAME = Type.getInternalName(Object.class); + + private static final String HANDLER_TYPE_NAME = "java.lang.invoke.CatchExceptionCombinator$MH"; + private static final String INVOKE_METHOD_NAME = "invoke"; + + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static final ConcurrentMap<CombinatorParameters, ClassTemplate> handlerClassBytes = new ConcurrentHashMap<>(); + + private static final class CombinatorParameters { + final MethodType targetType; + final Class<? extends Throwable> exType; + final MethodType handlerType; + + CombinatorParameters(final MethodType targetType, final Class<? extends Throwable> exType, MethodType handlerType) { + this.targetType = targetType; + this.exType = exType; + this.handlerType = handlerType; + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof CombinatorParameters) { + final CombinatorParameters p = (CombinatorParameters)obj; + return targetType.equals(p.targetType) && exType.equals(p.exType) && handlerType.equals(p.handlerType); + } + return false; + } + + @Override + public int hashCode() { + return targetType.hashCode() ^ exType.hashCode() ^ handlerType.hashCode(); + } + } + + /** + * Catch exception - create combinator + * @param target target + * @param exType type to check for + * @param handler catch handler + * @return target wrapped in catch handler + */ + public static MethodHandle catchException(final MethodHandle target, final Class<? extends Throwable> exType, final MethodHandle handler) { + final MethodType targetType = target.type(); + final MethodType handlerType = handler.type(); + + final ClassTemplate classTemplate = handlerClassBytes.computeIfAbsent( + new CombinatorParameters(targetType, exType, handlerType), new Function<CombinatorParameters, ClassTemplate>() { + @Override + public ClassTemplate apply(final CombinatorParameters parameters) { + return generateClassTemplate(parameters); + } + }); + return classTemplate.instantiate(target, handler, targetType); + } + + private static final class ClassTemplate { + final byte[] bytes; + final int target_cp_index; + final int handler_cp_index; + final int cp_size; + + ClassTemplate(final byte[] bytes, final int target_cp_index, final int handler_cp_index, final int cp_size) { + this.bytes = bytes; + this.target_cp_index = target_cp_index; + this.handler_cp_index = handler_cp_index; + this.cp_size = cp_size; + } + + MethodHandle instantiate(final MethodHandle target, final MethodHandle handler, final MethodType type) { + final Object[] cpPatch = new Object[cp_size]; + cpPatch[target_cp_index] = target; + cpPatch[handler_cp_index] = handler; + final Class<?> handlerClass = UNSAFE.defineAnonymousClass(CatchExceptionCombinator.class, bytes, cpPatch); + try { + return MethodHandles.lookup().findStatic(handlerClass, INVOKE_METHOD_NAME, type); + } catch (NoSuchMethodException | IllegalAccessException e) { + throw new AssertionError(e); + } + } + } + + private static ClassTemplate generateClassTemplate(final CombinatorParameters combinatorParameters) { + final ClassWriter w = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + w.visit(V1_7, ACC_PUBLIC | ACC_SUPER, HANDLER_TYPE_NAME, null, OBJECT_TYPE_NAME, null); + + final MethodType targetType = combinatorParameters.targetType; + final Class<? extends Throwable> exType = combinatorParameters.exType; + final String methodDescriptor = targetType.toMethodDescriptorString(); + final Class<?> returnType = targetType.returnType(); + final MethodType handlerType = combinatorParameters.handlerType; + + // NOTE: must use strings as placeholders in the constant pool, even if we'll be replacing them with method handles. + final String targetPlaceholder = "T_PLACEHOLDER"; + final String handlerPlaceholder = "H_PLACEHOLDER"; + final int target_cp_index = w.newConst(targetPlaceholder); + final int handler_cp_index = w.newConst(handlerPlaceholder); + + final InstructionAdapter mv = new InstructionAdapter(w.visitMethod(ACC_PUBLIC | ACC_STATIC, INVOKE_METHOD_NAME, methodDescriptor, null, null)); + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Hidden;", true); + mv.visitAnnotation("Ljava/lang/invoke/LambdaForm$Compiled;", true); + mv.visitAnnotation("Ljava/lang/invoke/ForceInline;", true); + + mv.visitCode(); + + final Label _try = new Label(); + final Label _end_try= new Label(); + + mv.visitLabel(_try); + // Invoke + mv.aconst(targetPlaceholder); + mv.checkcast(METHOD_HANDLE_TYPE); + final Class<?>[] paramTypes = targetType.parameterArray(); + for(int i = 0, slot = 0; i < paramTypes.length; ++i) { + final Type paramType = Type.getType(paramTypes[i]); + mv.load(slot, paramType); + slot += paramType.getSize(); + } + generateInvokeBasic(mv, methodDescriptor); + final Type asmReturnType = Type.getType(returnType); + mv.areturn(asmReturnType); + + mv.visitTryCatchBlock(_try, _end_try, _end_try, Type.getInternalName(exType)); + mv.visitLabel(_end_try); + // Handle exception + mv.aconst(handlerPlaceholder); + mv.checkcast(METHOD_HANDLE_TYPE); + mv.swap(); + final Class<?>[] handlerParamTypes = handlerType.parameterArray(); + for(int i = 1, slot = 0; i < handlerParamTypes.length; ++i) { + final Type paramType = Type.getType(handlerParamTypes[i]); + mv.load(slot, paramType); + slot += paramType.getSize(); + } + generateInvokeBasic(mv, handlerType.toMethodDescriptorString()); + mv.areturn(asmReturnType); + + mv.visitMaxs(0, 0); + mv.visitEnd(); + + w.visitEnd(); + final byte[] bytes = w.toByteArray(); + final int cp_size = (((bytes[8] & 0xFF) << 8) | (bytes[9] & 0xFF)); + return new ClassTemplate(bytes, target_cp_index, handler_cp_index, cp_size); + } + + private static void generateInvokeBasic(final InstructionAdapter mv, final String methodDesc) { + mv.invokevirtual(METHOD_HANDLE_TYPE_NAME, "invokeBasic", methodDesc, false); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/internal/dynalink/support/DefaultPrelinkFilter.java Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,99 @@ +/* + * 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. + */ + +/* + * This file is available under and governed by the GNU General Public + * License version 2 only, as published by the Free Software Foundation. + * However, the following notice accompanied the original version of this + * file, and Oracle licenses the original version of this file under the BSD + * license: + */ +/* + Copyright 2009-2013 Attila Szegedi + + Licensed under both the Apache License, Version 2.0 (the "Apache License") + and the BSD License (the "BSD License"), with licensee being free to + choose either of the two at their discretion. + + You may not use this file except in compliance with either the Apache + License or the BSD License. + + If you choose to use this file in compliance with the Apache License, the + following notice applies to you: + + You may obtain a copy of the Apache License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied. See the License for the specific language governing + permissions and limitations under the License. + + If you choose to use this file in compliance with the BSD License, the + following notice applies to you: + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of the copyright holder nor the names of + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDER + BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +package jdk.internal.dynalink.support; + +import jdk.internal.dynalink.GuardedInvocationFilter; +import jdk.internal.dynalink.linker.GuardedInvocation; +import jdk.internal.dynalink.linker.LinkRequest; +import jdk.internal.dynalink.linker.LinkerServices; + +/** + * Default filter for guarded invocation pre link filtering + */ +public class DefaultPrelinkFilter implements GuardedInvocationFilter { + @Override + public GuardedInvocation filter(GuardedInvocation inv, LinkRequest request, LinkerServices linkerServices) { + return inv.asType(linkerServices, request.getCallSiteDescriptor().getMethodType()); + } +}
--- a/src/jdk/internal/dynalink/support/LinkRequestImpl.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/support/LinkRequestImpl.java Wed Feb 26 13:17:57 2014 +0100 @@ -95,6 +95,7 @@ public class LinkRequestImpl implements LinkRequest { private final CallSiteDescriptor callSiteDescriptor; + private final Object callSiteToken; private final Object[] arguments; private final boolean callSiteUnstable; @@ -102,11 +103,13 @@ * Creates a new link request. * * @param callSiteDescriptor the descriptor for the call site being linked + * @param callSiteToken the opaque token for the call site being linked. * @param callSiteUnstable true if the call site being linked is considered unstable * @param arguments the arguments for the invocation */ - public LinkRequestImpl(CallSiteDescriptor callSiteDescriptor, boolean callSiteUnstable, Object... arguments) { + public LinkRequestImpl(CallSiteDescriptor callSiteDescriptor, Object callSiteToken, boolean callSiteUnstable, Object... arguments) { this.callSiteDescriptor = callSiteDescriptor; + this.callSiteToken = callSiteToken; this.callSiteUnstable = callSiteUnstable; this.arguments = arguments; } @@ -127,6 +130,11 @@ } @Override + public Object getCallSiteToken() { + return callSiteToken; + } + + @Override public boolean isCallSiteUnstable() { return callSiteUnstable; } @@ -138,6 +146,6 @@ @Override public LinkRequest replaceArguments(CallSiteDescriptor newCallSiteDescriptor, Object[] newArguments) { - return new LinkRequestImpl(newCallSiteDescriptor, callSiteUnstable, newArguments); + return new LinkRequestImpl(newCallSiteDescriptor, callSiteToken, callSiteUnstable, newArguments); } }
--- a/src/jdk/internal/dynalink/support/LinkerServicesImpl.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/support/LinkerServicesImpl.java Wed Feb 26 13:17:57 2014 +0100 @@ -127,6 +127,11 @@ } @Override + public MethodHandle asTypeLosslessReturn(MethodHandle handle, MethodType fromType) { + return Implementation.asTypeLosslessReturn(this, handle, fromType); + } + + @Override public MethodHandle getTypeConverter(Class<?> sourceType, Class<?> targetType) { return typeConverterFactory.getTypeConverter(sourceType, targetType); }
--- a/src/jdk/internal/dynalink/support/RuntimeContextLinkRequestImpl.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/support/RuntimeContextLinkRequestImpl.java Wed Feb 26 13:17:57 2014 +0100 @@ -101,15 +101,16 @@ * Creates a new link request. * * @param callSiteDescriptor the descriptor for the call site being linked + * @param callSiteToken the opaque token for the call site being linked. * @param arguments the arguments for the invocation * @param callSiteUnstable true if the call site being linked is considered unstable * @param runtimeContextArgCount the number of the leading arguments on the stack that represent the language * runtime specific context arguments. * @throws IllegalArgumentException if runtimeContextArgCount is less than 1. */ - public RuntimeContextLinkRequestImpl(CallSiteDescriptor callSiteDescriptor, boolean callSiteUnstable, - Object[] arguments, int runtimeContextArgCount) { - super(callSiteDescriptor, callSiteUnstable, arguments); + public RuntimeContextLinkRequestImpl(CallSiteDescriptor callSiteDescriptor, Object callSiteToken, + boolean callSiteUnstable, Object[] arguments, int runtimeContextArgCount) { + super(callSiteDescriptor, callSiteToken, callSiteUnstable, arguments); if(runtimeContextArgCount < 1) { throw new IllegalArgumentException("runtimeContextArgCount < 1"); } @@ -121,14 +122,14 @@ if(contextStrippedRequest == null) { contextStrippedRequest = new LinkRequestImpl(CallSiteDescriptorFactory.dropParameterTypes(getCallSiteDescriptor(), 1, - runtimeContextArgCount + 1), isCallSiteUnstable(), getTruncatedArguments()); + runtimeContextArgCount + 1), getCallSiteToken(), isCallSiteUnstable(), getTruncatedArguments()); } return contextStrippedRequest; } @Override public LinkRequest replaceArguments(CallSiteDescriptor callSiteDescriptor, Object[] arguments) { - return new RuntimeContextLinkRequestImpl(callSiteDescriptor, isCallSiteUnstable(), arguments, + return new RuntimeContextLinkRequestImpl(callSiteDescriptor, getCallSiteToken(), isCallSiteUnstable(), arguments, runtimeContextArgCount); }
--- a/src/jdk/internal/dynalink/support/TypeUtilities.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/internal/dynalink/support/TypeUtilities.java Wed Feb 26 13:17:57 2014 +0100 @@ -106,38 +106,49 @@ } /** - * Given two types represented by c1 and c2, returns a type that is their most specific common superclass or - * superinterface. + * Given two types represented by c1 and c2, returns a type that is their most specific common supertype for + * purposes of lossless conversions. * * @param c1 one type * @param c2 another type - * @return their most common superclass or superinterface. If they have several unrelated superinterfaces as their - * most specific common type, or the types themselves are completely unrelated interfaces, {@link java.lang.Object} - * is returned. + * @return their most common superclass or superinterface for purposes of lossless conversions. If they have several + * unrelated superinterfaces as their most specific common type, or the types themselves are completely + * unrelated interfaces, {@link java.lang.Object} is returned. */ - public static Class<?> getMostSpecificCommonType(Class<?> c1, Class<?> c2) { + public static Class<?> getCommonLosslessConversionType(Class<?> c1, Class<?> c2) { if(c1 == c2) { return c1; + } else if(isConvertibleWithoutLoss(c2, c1)) { + return c1; + } else if(isConvertibleWithoutLoss(c1, c2)) { + return c2; + } + if(c1 == void.class) { + return c2; + } else if(c2 == void.class) { + return c1; } - Class<?> c3 = c2; - if(c3.isPrimitive()) { - if(c3 == Byte.TYPE) - c3 = Byte.class; - else if(c3 == Short.TYPE) - c3 = Short.class; - else if(c3 == Character.TYPE) - c3 = Character.class; - else if(c3 == Integer.TYPE) - c3 = Integer.class; - else if(c3 == Float.TYPE) - c3 = Float.class; - else if(c3 == Long.TYPE) - c3 = Long.class; - else if(c3 == Double.TYPE) - c3 = Double.class; + if(c1.isPrimitive() && c2.isPrimitive()) { + if((c1 == byte.class && c2 == char.class) || (c1 == char.class && c2 == byte.class)) { + // byte + char = int + return int.class; + } else if((c1 == short.class && c2 == char.class) || (c1 == char.class && c2 == short.class)) { + // short + char = int + return int.class; + } else if((c1 == int.class && c2 == float.class) || (c1 == float.class && c2 == int.class)) { + // int + float = double + return double.class; + } } - Set<Class<?>> a1 = getAssignables(c1, c3); - Set<Class<?>> a2 = getAssignables(c3, c1); + // For all other cases. This will handle long + (float|double) = Number case as well as boolean + anything = Object case too. + return getMostSpecificCommonTypeUnequalNonprimitives(c1, c2); + } + + private static Class<?> getMostSpecificCommonTypeUnequalNonprimitives(Class<?> c1, Class<?> c2) { + final Class<?> npc1 = c1.isPrimitive() ? getWrapperType(c1) : c1; + final Class<?> npc2 = c2.isPrimitive() ? getWrapperType(c2) : c2; + Set<Class<?>> a1 = getAssignables(npc1, npc2); + Set<Class<?>> a2 = getAssignables(npc2, npc1); a1.retainAll(a2); if(a1.isEmpty()) { // Can happen when at least one of the arguments is an interface, @@ -168,7 +179,7 @@ max.add(clazz); } if(max.size() > 1) { - return OBJECT_CLASS; + return Object.class; } return max.get(0); } @@ -232,25 +243,60 @@ * {@link #isSubtype(Class, Class)}) as well as boxing conversion (JLS 5.1.7) optionally followed by widening * reference conversion and unboxing conversion (JLS 5.1.8) optionally followed by widening primitive conversion. * - * @param callSiteType the parameter type at the call site - * @param methodType the parameter type in the method declaration - * @return true if callSiteType is method invocation convertible to the methodType. + * @param sourceType the type being converted from (call site type for parameter types, method type for return types) + * @param targetType the parameter type being converted to (method type for parameter types, call site type for return types) + * @return true if source type is method invocation convertible to target type. */ - public static boolean isMethodInvocationConvertible(Class<?> callSiteType, Class<?> methodType) { - if(methodType.isAssignableFrom(callSiteType)) { + public static boolean isMethodInvocationConvertible(Class<?> sourceType, Class<?> targetType) { + if(targetType.isAssignableFrom(sourceType)) { return true; } - if(callSiteType.isPrimitive()) { - if(methodType.isPrimitive()) { - return isProperPrimitiveSubtype(callSiteType, methodType); + if(sourceType.isPrimitive()) { + if(targetType.isPrimitive()) { + return isProperPrimitiveSubtype(sourceType, targetType); } // Boxing + widening reference conversion - return methodType.isAssignableFrom(WRAPPER_TYPES.get(callSiteType)); + assert WRAPPER_TYPES.get(sourceType) != null : sourceType.getName(); + return targetType.isAssignableFrom(WRAPPER_TYPES.get(sourceType)); + } + if(targetType.isPrimitive()) { + final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(sourceType); + return unboxedCallSiteType != null + && (unboxedCallSiteType == targetType || isProperPrimitiveSubtype(unboxedCallSiteType, targetType)); } - if(methodType.isPrimitive()) { - final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(callSiteType); + return false; + } + + /** + * Determines whether a type can be converted to another without losing any + * precision. + * + * @param sourceType the source type + * @param targetType the target type + * @return true if lossess conversion is possible + */ + public static boolean isConvertibleWithoutLoss(Class<?> sourceType, Class<?> targetType) { + if(targetType.isAssignableFrom(sourceType)) { + return true; + } + if(sourceType.isPrimitive()) { + if(sourceType == void.class) { + return true; // Void can be losslessly represented by any type + } + if(targetType.isPrimitive()) { + return isProperPrimitiveLosslessSubtype(sourceType, targetType); + } + // Boxing + widening reference conversion + assert WRAPPER_TYPES.get(sourceType) != null : sourceType.getName(); + return targetType.isAssignableFrom(WRAPPER_TYPES.get(sourceType)); + } + if(targetType.isPrimitive()) { + if(targetType == void.class) { + return false; // Void can't represent anything losslessly + } + final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(sourceType); return unboxedCallSiteType != null - && (unboxedCallSiteType == methodType || isProperPrimitiveSubtype(unboxedCallSiteType, methodType)); + && (unboxedCallSiteType == targetType || isProperPrimitiveLosslessSubtype(unboxedCallSiteType, targetType)); } return false; } @@ -266,7 +312,7 @@ */ public static boolean isPotentiallyConvertible(Class<?> callSiteType, Class<?> methodType) { // Widening or narrowing reference conversion - if(methodType.isAssignableFrom(callSiteType) || callSiteType.isAssignableFrom(methodType)) { + if(areAssignable(callSiteType, methodType)) { return true; } if(callSiteType.isPrimitive()) { @@ -287,6 +333,16 @@ } /** + * Returns true if either of the types is assignable from the other. + * @param c1 one of the types + * @param c2 another one of the types + * @return true if either c1 is assignable from c2 or c2 is assignable from c1. + */ + public static boolean areAssignable(Class<?> c1, Class<?> c2) { + return c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1); + } + + /** * Determines whether one type is a subtype of another type, as per JLS 4.10 "Subtyping". Note: this is not strict * or proper subtype, therefore true is also returned for identical types; to be completely precise, it allows * identity conversion (JLS 5.1.1), widening primitive conversion (JLS 5.1.2) and widening reference conversion (JLS @@ -353,6 +409,37 @@ return false; } + /** + * Similar to {@link #isProperPrimitiveSubtype(Class, Class)}, except it disallows conversions from int and long to + * float, and from long to double, as those can lose precision. It also disallows conversion from and to char and + * anything else (similar to boolean) as char is not meant to be an arithmetic type. + * @param subType the supposed subtype + * @param superType the supposed supertype + * @return true if subType is a proper (not identical to) primitive subtype of the superType that can be represented + * by the supertype without no precision loss. + */ + private static boolean isProperPrimitiveLosslessSubtype(Class<?> subType, Class<?> superType) { + if(superType == boolean.class || subType == boolean.class) { + return false; + } + if(superType == char.class || subType == char.class) { + return false; + } + if(subType == byte.class) { + return true; + } + if(subType == short.class) { + return superType != byte.class; + } + if(subType == int.class) { + return superType == long.class || superType == double.class; + } + if(subType == float.class) { + return superType == double.class; + } + return false; + } + private static final Map<Class<?>, Class<?>> WRAPPER_TO_PRIMITIVE_TYPES = createWrapperToPrimitiveTypes(); private static Map<Class<?>, Class<?>> createWrapperToPrimitiveTypes() {
--- a/src/jdk/nashorn/api/scripting/JSObject.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/api/scripting/JSObject.java Wed Feb 26 13:17:57 2014 +0100 @@ -26,7 +26,6 @@ package jdk.nashorn.api.scripting; import java.util.Collection; -import java.util.Collections; import java.util.Set; /**
--- a/src/jdk/nashorn/api/scripting/NashornException.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/api/scripting/NashornException.java Wed Feb 26 13:17:57 2014 +0100 @@ -182,7 +182,7 @@ if (ECMAErrors.isScriptFrame(st)) { final String className = "<" + st.getFileName() + ">"; String methodName = st.getMethodName(); - if (methodName.equals(CompilerConstants.RUN_SCRIPT.symbolName())) { + if (methodName.equals(CompilerConstants.PROGRAM.symbolName())) { methodName = "<program>"; } @@ -224,10 +224,22 @@ return buf.toString(); } + /** + * Get the thrown object. Subclass responsibility + * @return thrown object + */ protected Object getThrown() { return null; } + /** + * Initialization function for ECMA errors. Stores the error + * in the ecmaError field of this class. It is only initialized + * once, and then reused + * + * @param global the global + * @return initialized exception + */ protected NashornException initEcmaError(final ScriptObject global) { if (ecmaError != null) { return this; // initialized already!
--- a/src/jdk/nashorn/api/scripting/NashornScriptEngine.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/api/scripting/NashornScriptEngine.java Wed Feb 26 13:17:57 2014 +0100 @@ -61,6 +61,7 @@ import jdk.nashorn.internal.runtime.ErrorManager; import jdk.nashorn.internal.runtime.GlobalObject; import jdk.nashorn.internal.runtime.Property; +import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; @@ -463,7 +464,7 @@ private void setContextVariables(final ScriptObject ctxtGlobal, final ScriptContext ctxt) { // set "context" global variable via contextProperty - because this // property is non-writable - contextProperty.setObjectValue(ctxtGlobal, ctxtGlobal, ctxt, false); + contextProperty.setValue(ctxtGlobal, ctxtGlobal, ctxt, false); Object args = ScriptObjectMirror.unwrap(ctxt.getAttribute("arguments"), ctxtGlobal); if (args == null || args == UNDEFINED) { args = ScriptRuntime.EMPTY_ARRAY; @@ -598,6 +599,15 @@ }; } + /** + * Check if the global script environment tells us to do optimistic + * compilation + * @return true if optimistic compilation enabled + */ + public static boolean isOptimistic() { + return ScriptEnvironment.globalOptimistic(); + } + private ScriptFunction compileImpl(final Source source, final ScriptContext ctxt) throws ScriptException { return compileImpl(source, getNashornGlobalFrom(ctxt)); }
--- a/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/api/scripting/ScriptObjectMirror.java Wed Feb 26 13:17:57 2014 +0100 @@ -169,6 +169,12 @@ }); } + /** + * Call member function + * @param functionName function name + * @param args arguments + * @return return value of function + */ public Object callMember(final String functionName, final Object... args) { functionName.getClass(); // null check final ScriptObject oldGlobal = Context.getGlobal();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/IntDeque.java Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,87 @@ +/* + * 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; + +/** + * Small helper class for fast int deques + */ +public class IntDeque { + private int[] deque = new int[16]; + private int nextFree = 0; + + /** + * Push an int value + * @param value value + */ + public void push(final int value) { + if (nextFree == deque.length) { + final int[] newDeque = new int[nextFree * 2]; + System.arraycopy(deque, 0, newDeque, 0, nextFree); + deque = newDeque; + } + deque[nextFree++] = value; + } + + /** + * Pop an int value + * @return value + */ + public int pop() { + return deque[--nextFree]; + } + + /** + * Peek + * @return top value + */ + public int peek() { + return deque[nextFree - 1]; + } + + /** + * Get the value of the top element and increment it. + * @return top value + */ + public int getAndIncrement() { + return deque[nextFree - 1]++; + } + + /** + * Decrement the value of the top element and return it. + * @return decremented top value + */ + public int decrementAndGet() { + return --deque[nextFree - 1]; + } + + /** + * Check if deque is empty + * @return true if empty + */ + public boolean isEmpty() { + return nextFree == 0; + } +}
--- a/src/jdk/nashorn/internal/codegen/Attr.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/Attr.java Wed Feb 26 13:17:57 2014 +0100 @@ -43,6 +43,7 @@ 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_PROGRAM_LEVEL; 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; @@ -63,10 +64,13 @@ import jdk.nashorn.internal.ir.CaseNode; import jdk.nashorn.internal.ir.CatchNode; import jdk.nashorn.internal.ir.Expression; +import jdk.nashorn.internal.ir.ExpressionStatement; import jdk.nashorn.internal.ir.ForNode; +import jdk.nashorn.internal.ir.FunctionCall; 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.LexicalContextNode; @@ -74,9 +78,12 @@ import jdk.nashorn.internal.ir.LiteralNode.ArrayLiteralNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ObjectNode; +import jdk.nashorn.internal.ir.Optimistic; +import jdk.nashorn.internal.ir.OptimisticLexicalContext; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; import jdk.nashorn.internal.ir.RuntimeNode.Request; +import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.ir.Statement; import jdk.nashorn.internal.ir.SwitchNode; import jdk.nashorn.internal.ir.Symbol; @@ -85,6 +92,7 @@ import jdk.nashorn.internal.ir.TryNode; import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.VarNode; +import jdk.nashorn.internal.ir.WhileNode; import jdk.nashorn.internal.ir.WithNode; import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; import jdk.nashorn.internal.ir.visitor.NodeVisitor; @@ -111,7 +119,9 @@ * computed. */ -final class Attr extends NodeOperatorVisitor<LexicalContext> { +final class Attr extends NodeOperatorVisitor<OptimisticLexicalContext> { + + private final CompilationEnvironment env; /** * Local definitions in current block (to discriminate from function @@ -127,7 +137,8 @@ */ private final Deque<Set<String>> localUses; - private final Deque<Type> returnTypes; + private final Set<Long> optimistic = new HashSet<>(); + private final Set<Long> neverOptimistic = new HashSet<>(); private int catchNestingLevel; @@ -139,12 +150,12 @@ /** * Constructor. */ - Attr(final TemporarySymbols temporarySymbols) { - super(new LexicalContext()); + Attr(final CompilationEnvironment env, final TemporarySymbols temporarySymbols) { + super(new OptimisticLexicalContext(env.useOptimisticTypes())); + this.env = env; this.temporarySymbols = temporarySymbols; - this.localDefs = new ArrayDeque<>(); - this.localUses = new ArrayDeque<>(); - this.returnTypes = new ArrayDeque<>(); + this.localDefs = new ArrayDeque<>(); + this.localUses = new ArrayDeque<>(); } @Override @@ -159,10 +170,7 @@ @Override public Node leaveAccessNode(final AccessNode accessNode) { - //While Object type is assigned here, Access Specialization in FinalizeTypes may narrow this, that - //is why we can't set the access node base to be an object here, that will ruin access specialization - //for example for a.x | 17. - return end(ensureSymbol(Type.OBJECT, accessNode)); + return end(ensureSymbolTypeOverride(accessNode, Type.OBJECT)); } private void initFunctionWideVariables(final FunctionNode functionNode, final Block body) { @@ -235,6 +243,11 @@ @Override public boolean enterVarNode(final VarNode varNode) { + final Expression init = varNode.getInit(); + if (init != null) { + tagOptimistic(init); + } + final String name = varNode.getName().getName(); //if this is used before the var node, the var node symbol needs to be tagged as can be undefined if (uses.contains(name)) { @@ -264,7 +277,7 @@ if (varNode.isStatement()) { final IdentNode ident = varNode.getName(); final Symbol symbol = defineSymbol(body, ident.getName(), IS_VAR); - if (canBeUndefined.contains(ident.getName())) { + if (canBeUndefined.contains(ident.getName()) && varNode.getInit() == null) { symbol.setType(Type.OBJECT); symbol.setCanBeUndefined(); } @@ -299,6 +312,9 @@ if (!(anonymous || body.getExistingSymbol(name) != null)) { assert !anonymous && name != null; newType(defineSymbol(body, name, IS_VAR | IS_FUNCTION_SELF), Type.OBJECT); + if(functionNode.allVarsInScope()) { // basically, has deep eval + lc.setFlag(body, Block.USES_SELF_SYMBOL); + } } } @@ -326,14 +342,24 @@ return end(block); } + private boolean useOptimisticTypes() { + return env.useOptimisticTypes() && !lc.isInSplitNode(); + } + @Override public boolean enterCallNode(final CallNode callNode) { - return start(callNode); + for (final Expression arg : callNode.getArgs()) { + tagOptimistic(arg); + } + return true; } @Override public Node leaveCallNode(final CallNode callNode) { - return end(ensureSymbol(callNode.getType(), callNode)); + for (final Expression arg : callNode.getArgs()) { + inferParameter(arg, arg.getType()); + } + return end(ensureSymbolTypeOverride(callNode, Type.OBJECT)); } @Override @@ -346,8 +372,13 @@ // define block-local exception variable final String exname = exception.getName(); - final Symbol def = defineSymbol(block, exname, IS_VAR | IS_LET | IS_ALWAYS_DEFINED); - newType(def, Type.OBJECT); //we can catch anything, not just ecma exceptions + // If the name of the exception starts with ":e", this is a synthetic catch block, likely a catch-all. Its + // symbol is naturally internal, and should be treated as such. + final boolean isInternal = exname.startsWith(EXCEPTION_PREFIX.symbolName()); + final Symbol def = defineSymbol(block, exname, IS_VAR | IS_LET | IS_ALWAYS_DEFINED | (isInternal ? IS_INTERNAL : 0)); + // Normally, we can catch anything, not just ECMAExceptions, hence Type.OBJECT. However, for catches with + // internal symbol, we can be sure the caught type is a Throwable. + newType(def, isInternal ? Type.typeFor(EXCEPTION_PREFIX.type()) : Type.OBJECT); addLocalDef(exname); @@ -382,6 +413,10 @@ flags |= IS_SCOPE; } + if (lc.getCurrentFunction().isProgram()) { + flags |= IS_PROGRAM_LEVEL; + } + final FunctionNode function = lc.getFunction(block); if (symbol != null) { // Symbol was already defined. Check if it needs to be redefined. @@ -433,14 +468,19 @@ } @Override + public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { + final Expression expr = expressionStatement.getExpression(); + if (!expr.isSelfModifying()) { //self modifying ops like i++ need the optimistic type for their own operation, not just the return value, as there is no difference. gah. + tagNeverOptimistic(expr); + } + return true; + } + + @Override public boolean enterFunctionNode(final FunctionNode functionNode) { start(functionNode, false); - if (functionNode.isLazy()) { - return false; - } - - //an outermost function in our lexical context that is not a program (runScript) + //an outermost function in our lexical context that is not a program //is possible - it is a function being compiled lazily if (functionNode.isDeclared()) { final Iterator<Block> blocks = lc.getBlocks(); @@ -449,7 +489,6 @@ } } - returnTypes.push(functionNode.getReturnType()); pushLocalsFunction(); return true; @@ -471,7 +510,7 @@ final boolean anonymous = functionNode.isAnonymous(); final String name = anonymous ? null : functionNode.getIdent().getName(); if (anonymous || body.getExistingSymbol(name) != null) { - newFunctionNode = (FunctionNode)ensureSymbol(FunctionNode.FUNCTION_TYPE, newFunctionNode); + newFunctionNode = (FunctionNode)ensureSymbol(newFunctionNode, FunctionNode.FUNCTION_TYPE); } else { assert name != null; final Symbol self = body.getExistingSymbol(name); @@ -480,11 +519,6 @@ } } - //unknown parameters are promoted to object type. - if (newFunctionNode.hasLazyChildren()) { - //the final body has already been assigned as we have left the function node block body by now - objectifySymbols(body); - } newFunctionNode = finalizeParameters(newFunctionNode); newFunctionNode = finalizeTypes(newFunctionNode); for (final Symbol symbol : newFunctionNode.getDeclaredSymbols()) { @@ -496,7 +530,7 @@ List<VarNode> syntheticInitializers = null; - if (body.getFlag(Block.NEEDS_SELF_SYMBOL)) { + if (newFunctionNode.usesSelfSymbol()) { syntheticInitializers = new ArrayList<>(2); LOG.info("Accepting self symbol init for ", newFunctionNode.getName()); // "var fn = :callee" @@ -520,19 +554,12 @@ newFunctionNode = newFunctionNode.setBody(lc, newFunctionNode.getBody().setStatements(lc, newStatements)); } - if (returnTypes.peek().isUnknown()) { - LOG.info("Unknown return type promoted to object"); - newFunctionNode = newFunctionNode.setReturnType(lc, Type.OBJECT); - } - final Type returnType = returnTypes.pop(); - newFunctionNode = newFunctionNode.setReturnType(lc, returnType.isUnknown() ? Type.OBJECT : returnType); - newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR); + final int optimisticFlag = lc.hasOptimisticAssumptions() ? FunctionNode.IS_OPTIMISTIC : 0; + newFunctionNode = newFunctionNode.setState(lc, CompilationState.ATTR).setFlag(lc, optimisticFlag); popLocals(); - end(newFunctionNode, false); - - return newFunctionNode; + return end(newFunctionNode, false); } /** @@ -580,9 +607,9 @@ final FunctionNode functionNode = lc.getDefiningFunction(symbol); assert functionNode != null; assert lc.getFunctionBody(functionNode).getExistingSymbol(CALLEE.symbolName()) != null; - lc.setFlag(functionNode.getBody(), Block.NEEDS_SELF_SYMBOL); + lc.setFlag(functionNode.getBody(), Block.USES_SELF_SYMBOL); newType(symbol, FunctionNode.FUNCTION_TYPE); - } else if (!identNode.isInitializedHere()) { + } else if (!(identNode.isInitializedHere() || symbol.isAlwaysDefined())) { /* * See NASHORN-448, JDK-8016235 * @@ -617,8 +644,12 @@ symbol.increaseUseCount(); } addLocalUse(identNode.getName()); + IdentNode node = (IdentNode)identNode.setSymbol(lc, symbol); + if (isTaggedOptimistic(identNode) && symbol.isScope()) { + node = ensureSymbolTypeOverride(node, symbol.getSymbolType()); + } - return end(identNode.setSymbol(lc, symbol)); + return end(node); } private boolean inCatch() { @@ -636,16 +667,21 @@ } } - private boolean symbolNeedsToBeScope(Symbol symbol) { + private boolean symbolNeedsToBeScope(final Symbol symbol) { if (symbol.isThis() || symbol.isInternal()) { return false; } + + if (lc.getCurrentFunction().allVarsInScope()) { + return true; + } + boolean previousWasBlock = false; for (final Iterator<LexicalContextNode> it = lc.getAllNodes(); it.hasNext();) { final LexicalContextNode node = it.next(); - if (node instanceof FunctionNode) { - // We reached the function boundary without seeing a definition for the symbol - it needs to be in - // scope. + if (node instanceof FunctionNode || node instanceof SplitNode) { + // We reached the function boundary or a splitting boundary without seeing a definition for the symbol. + // It needs to be in scope. return true; } else if (node instanceof WithNode) { if (previousWasBlock) { @@ -729,7 +765,8 @@ @Override public Node leaveIndexNode(final IndexNode indexNode) { - return end(ensureSymbol(Type.OBJECT, indexNode)); + // return end(ensureSymbolOptimistic(Type.OBJECT, indexNode)); + return end(ensureSymbolTypeOverride(indexNode, Type.OBJECT)); } @SuppressWarnings("rawtypes") @@ -751,31 +788,7 @@ @Override public Node leaveObjectNode(final ObjectNode objectNode) { - return end(ensureSymbol(Type.OBJECT, objectNode)); - } - - @Override - public Node leaveReturnNode(final ReturnNode returnNode) { - final Expression expr = returnNode.getExpression(); - final Type returnType; - - if (expr != null) { - //we can't do parameter specialization if we return something that hasn't been typed yet - final Symbol symbol = expr.getSymbol(); - if (expr.getType().isUnknown() && symbol.isParam()) { - symbol.setType(Type.OBJECT); - } - - returnType = widestReturnType(returnTypes.pop(), symbol.getSymbolType()); - } else { - returnType = Type.OBJECT; //undefined - } - LOG.info("Returntype is now ", returnType); - returnTypes.push(returnType); - - end(returnNode); - - return returnNode; + return end(ensureSymbol(objectNode, Type.OBJECT)); } @Override @@ -819,9 +832,7 @@ switchNode.setTag(newInternal(lc.getCurrentFunction().uniqueName(SWITCH_TAG_PREFIX.symbolName()), type)); - end(switchNode); - - return switchNode.setCases(lc, newCases); + return end(switchNode.setCases(lc, newCases)); } @Override @@ -848,7 +859,10 @@ assert symbol != null; // NASHORN-467 - use before definition of vars - conservative - if (isLocalUse(ident.getName())) { + //function each(iterator, context) { + // for (var i = 0, length = this.length >>> 0; i < length; i++) { if (i in this) iterator.call(context, this[i], i, this); } + // + if (isLocalUse(ident.getName()) && varNode.getInit() == null) { newType(symbol, Type.OBJECT); symbol.setCanBeUndefined(); } @@ -894,29 +908,55 @@ } @Override + public boolean enterNOT(UnaryNode unaryNode) { + tagNeverOptimistic(unaryNode.getExpression()); + return true; + } + + public boolean enterUnaryArithmetic(final UnaryNode unaryNode) { + tagOptimistic(unaryNode.getExpression()); + return true; + } + + private UnaryNode leaveUnaryArithmetic(final UnaryNode unaryNode) { + return end(coerce(unaryNode, unaryNode.getMostPessimisticType(), unaryNode.getExpression().getType())); + } + + @Override + public boolean enterADD(final UnaryNode unaryNode) { + return enterUnaryArithmetic(unaryNode); + } + + @Override public Node leaveADD(final UnaryNode unaryNode) { - return end(ensureSymbol(arithType(), unaryNode)); + return leaveUnaryArithmetic(unaryNode); } @Override public Node leaveBIT_NOT(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.INT, unaryNode)); + return end(coerce(unaryNode, Type.INT)); + } + + @Override + public boolean enterDECINC(final UnaryNode unaryNode) { + return enterUnaryArithmetic(unaryNode); } @Override public Node leaveDECINC(final UnaryNode unaryNode) { // @see assignOffset - final Type type = arithType(); - newType(unaryNode.rhs().getSymbol(), type); - return end(ensureSymbol(type, unaryNode)); + final Type pessimisticType = unaryNode.getMostPessimisticType(); + final UnaryNode newUnaryNode = ensureSymbolTypeOverride(unaryNode, pessimisticType, unaryNode.getExpression().getType()); + newType(newUnaryNode.getExpression().getSymbol(), newUnaryNode.getType()); + return end(newUnaryNode); } @Override public Node leaveDELETE(final UnaryNode unaryNode) { - final FunctionNode currentFunctionNode = lc.getCurrentFunction(); - final boolean strictMode = currentFunctionNode.isStrict(); - final Expression rhs = unaryNode.rhs(); - final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this); + final FunctionNode currentFunctionNode = lc.getCurrentFunction(); + final boolean strictMode = currentFunctionNode.isStrict(); + final Expression rhs = unaryNode.getExpression(); + final Expression strictFlagNode = (Expression)LiteralNode.newInstance(unaryNode, strictMode).accept(this); Request request = Request.DELETE; final List<Expression> args = new ArrayList<>(); @@ -925,7 +965,10 @@ // If this is a declared variable or a function parameter, delete always fails (except for globals). final String name = ((IdentNode)rhs).getName(); - final boolean failDelete = strictMode || rhs.getSymbol().isParam() || (rhs.getSymbol().isVar() && !isProgramLevelSymbol(name)); + final boolean isParam = rhs.getSymbol().isParam(); + final boolean isVar = rhs.getSymbol().isVar(); + final boolean isNonProgramVar = isVar && !rhs.getSymbol().isProgramLevel(); + final boolean failDelete = strictMode || isParam || isNonProgramVar; if (failDelete && rhs.getSymbol().isThis()) { return LiteralNode.newInstance(unaryNode, true).accept(this); @@ -968,29 +1011,14 @@ return leaveRuntimeNode(runtimeNode); } - /** - * Is the symbol denoted by the specified name in the current lexical context defined in the program level - * @param name the name of the symbol - * @return true if the symbol denoted by the specified name in the current lexical context defined in the program level. - */ - private boolean isProgramLevelSymbol(final String name) { - for(final Iterator<Block> it = lc.getBlocks(); it.hasNext();) { - final Block next = it.next(); - if(next.getExistingSymbol(name) != null) { - return next == lc.getFunctionBody(lc.getOutermostFunction()); - } - } - throw new AssertionError("Couldn't find symbol " + name + " in the context"); - } - @Override public Node leaveNEW(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.OBJECT, unaryNode.setRHS(((CallNode)unaryNode.rhs()).setIsNew()))); + return end(coerce(unaryNode.setExpression(((CallNode)unaryNode.getExpression()).setIsNew()), Type.OBJECT)); } @Override public Node leaveNOT(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.BOOLEAN, unaryNode)); + return end(coerce(unaryNode, Type.BOOLEAN)); } private IdentNode compilerConstant(CompilerConstants cc) { @@ -1010,7 +1038,7 @@ @Override public Node leaveTYPEOF(final UnaryNode unaryNode) { - final Expression rhs = unaryNode.rhs(); + final Expression rhs = unaryNode.getExpression(); List<Expression> args = new ArrayList<>(); if (rhs instanceof IdentNode && !rhs.getSymbol().isParam() && !rhs.getSymbol().isVar()) { @@ -1033,17 +1061,29 @@ @Override public Node leaveRuntimeNode(final RuntimeNode runtimeNode) { - return end(ensureSymbol(runtimeNode.getRequest().getReturnType(), runtimeNode)); + return end(ensureSymbol(runtimeNode, runtimeNode.getRequest().getReturnType())); + } + + @Override + public boolean enterSUB(final UnaryNode unaryNode) { + return enterUnaryArithmetic(unaryNode); } @Override public Node leaveSUB(final UnaryNode unaryNode) { - return end(ensureSymbol(arithType(), unaryNode)); + return leaveUnaryArithmetic(unaryNode); } @Override public Node leaveVOID(final UnaryNode unaryNode) { - return end(ensureSymbol(Type.OBJECT, unaryNode)); + return end(ensureSymbol(unaryNode, Type.OBJECT)); + } + + @Override + public boolean enterADD(final BinaryNode binaryNode) { + tagOptimistic(binaryNode.lhs()); + tagOptimistic(binaryNode.rhs()); + return true; } /** @@ -1055,18 +1095,22 @@ final Expression lhs = binaryNode.lhs(); final Expression rhs = binaryNode.rhs(); - ensureTypeNotUnknown(lhs); - ensureTypeNotUnknown(rhs); - //even if we are adding two known types, this can overflow. i.e. - //int and number -> number. - //int and int are also number though. - //something and object is object - return end(ensureSymbol(Type.widest(arithType(), Type.widest(lhs.getType(), rhs.getType())), binaryNode)); + //an add is at least as wide as the current arithmetic type, possibly wider in the case of objects + //which will be corrected in the post pass if unknown at this stage + + Type argumentsType = Type.widest(lhs.getType(), rhs.getType()); + if(argumentsType.getTypeClass() == String.class) { + assert binaryNode.isTokenType(TokenType.ADD); + argumentsType = Type.OBJECT; + } + final Type pessimisticType = Type.widest(Type.NUMBER, argumentsType); + + return end(ensureSymbolTypeOverride(binaryNode, pessimisticType, argumentsType)); } @Override public Node leaveAND(final BinaryNode binaryNode) { - return end(ensureSymbol(Type.OBJECT, binaryNode)); + return end(ensureSymbol(binaryNode, Type.OBJECT)); } /** @@ -1075,11 +1119,18 @@ */ private boolean enterAssignmentNode(final BinaryNode binaryNode) { start(binaryNode); + final Expression lhs = binaryNode.lhs(); + if (lhs instanceof IdentNode) { + if (CompilerConstants.isCompilerConstant(((IdentNode)lhs).getName())) { + tagNeverOptimistic(binaryNode.rhs()); + } + } + //tagOptimistic(binaryNode.lhs()); + tagOptimistic(binaryNode.rhs()); return true; } - /** * This assign helper is called after an assignment, when all children of * the assign has been processed. It fixes the types and recursively makes @@ -1096,6 +1147,7 @@ final Block block = lc.getCurrentBlock(); final IdentNode ident = (IdentNode)lhs; final String name = ident.getName(); + final Symbol symbol = findSymbol(block, name); if (symbol == null) { @@ -1114,7 +1166,7 @@ } newType(lhs.getSymbol(), type); - return end(ensureSymbol(type, binaryNode)); + return end(ensureSymbol(binaryNode, type)); } private boolean isLocal(FunctionNode function, Symbol symbol) { @@ -1140,11 +1192,11 @@ @Override public Node leaveASSIGN_ADD(final BinaryNode binaryNode) { - final Expression lhs = binaryNode.lhs(); - final Expression rhs = binaryNode.rhs(); + final Expression lhs = binaryNode.lhs(); + final Expression rhs = binaryNode.rhs(); + final Type widest = Type.widest(lhs.getType(), rhs.getType()); + //Type.NUMBER if we can't prove that the add doesn't overflow. todo - final Type widest = Type.widest(lhs.getType(), rhs.getType()); - //Type.NUMBER if we can't prove that the add doesn't overflow. todo return leaveSelfModifyingAssignmentNode(binaryNode, widest.isNumeric() ? Type.NUMBER : Type.OBJECT); } @@ -1249,22 +1301,49 @@ } @Override + public boolean enterBIT_AND(final BinaryNode binaryNode) { + return enterBitwiseOperator(binaryNode); + } + + @Override public Node leaveBIT_AND(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); + return leaveBitwiseOperator(binaryNode); + } + + @Override + public boolean enterBIT_OR(final BinaryNode binaryNode) { + return enterBitwiseOperator(binaryNode); } @Override public Node leaveBIT_OR(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); + return leaveBitwiseOperator(binaryNode); + } + + @Override + public boolean enterBIT_XOR(final BinaryNode binaryNode) { + return enterBitwiseOperator(binaryNode); } @Override public Node leaveBIT_XOR(final BinaryNode binaryNode) { + return leaveBitwiseOperator(binaryNode); + } + + public boolean enterBitwiseOperator(final BinaryNode binaryNode) { + start(binaryNode); + tagOptimistic(binaryNode.lhs()); + tagOptimistic(binaryNode.rhs()); + return true; + } + + private Node leaveBitwiseOperator(final BinaryNode binaryNode) { return end(coerce(binaryNode, Type.INT)); } @Override public Node leaveCOMMARIGHT(final BinaryNode binaryNode) { +// return end(ensureSymbol(binaryNode, binaryNode.rhs().getType())); return leaveComma(binaryNode, binaryNode.rhs()); } @@ -1274,8 +1353,11 @@ } private Node leaveComma(final BinaryNode commaNode, final Expression effectiveExpr) { - ensureTypeNotUnknown(effectiveExpr); - return end(ensureSymbol(effectiveExpr.getType(), commaNode)); + Type type = effectiveExpr.getType(); + if (type.isUnknown()) { //TODO more optimistic + type = Type.OBJECT; + } + return end(ensureSymbol(commaNode, type)); } @Override @@ -1284,34 +1366,32 @@ } private Node leaveCmp(final BinaryNode binaryNode) { - ensureTypeNotUnknown(binaryNode.lhs()); - ensureTypeNotUnknown(binaryNode.rhs()); - Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); - ensureSymbol(widest, binaryNode.lhs()); - ensureSymbol(widest, binaryNode.rhs()); - return end(ensureSymbol(Type.BOOLEAN, binaryNode)); + //infect untyped comp with opportunistic type from other + final Expression lhs = binaryNode.lhs(); + final Expression rhs = binaryNode.rhs(); + final Type type = Type.narrowest(lhs.getType(), rhs.getType(), Type.INT); + inferParameter(lhs, type); + inferParameter(rhs, type); + Type widest = Type.widest(lhs.getType(), rhs.getType()); + ensureSymbol(lhs, widest); + ensureSymbol(rhs, widest); + return end(ensureSymbol(binaryNode, Type.BOOLEAN)); } - 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); - return ensureSymbol(destType, binaryNode); - } - - private Node coerce(final BinaryNode binaryNode, final Type type) { - return coerce(binaryNode, type, type); + private boolean enterBinaryArithmetic(final BinaryNode binaryNode) { + tagOptimistic(binaryNode.lhs()); + tagOptimistic(binaryNode.rhs()); + return true; } //leave a binary node and inherit the widest type of lhs , rhs private Node leaveBinaryArithmetic(final BinaryNode binaryNode) { - assert !Compiler.shouldUseIntegerArithmetic(); - return end(coerce(binaryNode, Type.NUMBER)); + return end(coerce(binaryNode, binaryNode.getMostPessimisticType(), Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()))); + } + + @Override + public boolean enterEQ(BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); } @Override @@ -1320,16 +1400,31 @@ } @Override + public boolean enterEQ_STRICT(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveEQ_STRICT(final BinaryNode binaryNode) { return leaveCmp(binaryNode); } @Override + public boolean enterGE(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveGE(final BinaryNode binaryNode) { return leaveCmp(binaryNode); } @Override + public boolean enterGT(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveGT(final BinaryNode binaryNode) { return leaveCmp(binaryNode); } @@ -1354,11 +1449,21 @@ } @Override + public boolean enterLE(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveLE(final BinaryNode binaryNode) { return leaveCmp(binaryNode); } @Override + public boolean enterLT(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveLT(final BinaryNode binaryNode) { return leaveCmp(binaryNode); } @@ -1369,33 +1474,63 @@ } @Override + public boolean enterMUL(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveMUL(final BinaryNode binaryNode) { return leaveBinaryArithmetic(binaryNode); } @Override + public boolean enterNE(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveNE(final BinaryNode binaryNode) { return leaveCmp(binaryNode); } @Override + public boolean enterNE_STRICT(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveNE_STRICT(final BinaryNode binaryNode) { return leaveCmp(binaryNode); } @Override public Node leaveOR(final BinaryNode binaryNode) { - return end(ensureSymbol(Type.OBJECT, binaryNode)); + return end(ensureSymbol(binaryNode, Type.OBJECT)); + } + + @Override + public boolean enterSAR(final BinaryNode binaryNode) { + return enterBitwiseOperator(binaryNode); } @Override public Node leaveSAR(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); + return leaveBitwiseOperator(binaryNode); + } + + @Override + public boolean enterSHL(final BinaryNode binaryNode) { + return enterBitwiseOperator(binaryNode); } @Override public Node leaveSHL(final BinaryNode binaryNode) { - return end(coerce(binaryNode, Type.INT)); + return leaveBitwiseOperator(binaryNode); + } + + @Override + public boolean enterSHR(final BinaryNode binaryNode) { + return enterBitwiseOperator(binaryNode); } @Override @@ -1404,11 +1539,22 @@ } @Override + public boolean enterSUB(final BinaryNode binaryNode) { + return enterBinaryArithmetic(binaryNode); + } + + @Override public Node leaveSUB(final BinaryNode binaryNode) { return leaveBinaryArithmetic(binaryNode); } @Override + public boolean enterForNode(ForNode forNode) { + tagNeverOptimistic(forNode.getTest()); + return true; + } + + @Override public Node leaveForNode(final ForNode forNode) { if (forNode.isForIn()) { forNode.setIterator(newInternal(lc.getCurrentFunction().uniqueName(ITERATOR_PREFIX.symbolName()), Type.typeFor(ITERATOR_PREFIX.type()))); //NASHORN-73 @@ -1420,54 +1566,59 @@ newType(forNode.getInit().getSymbol(), Type.OBJECT); } - end(forNode); + return end(forNode); + } - return forNode; + @Override + public boolean enterTernaryNode(TernaryNode ternaryNode) { + tagNeverOptimistic(ternaryNode.getTest()); + return true; } @Override public Node leaveTernaryNode(final TernaryNode ternaryNode) { - final Expression trueExpr = ternaryNode.getTrueExpression(); - final Expression falseExpr = ternaryNode.getFalseExpression(); - - ensureTypeNotUnknown(trueExpr); - ensureTypeNotUnknown(falseExpr); - - final Type type = widestReturnType(trueExpr.getType(), falseExpr.getType()); - return end(ensureSymbol(type, ternaryNode)); + final Type trueType = ternaryNode.getTrueExpression().getType(); + final Type falseType = ternaryNode.getFalseExpression().getType(); + final Type type; + if (trueType.isUnknown() || falseType.isUnknown()) { + type = Type.UNKNOWN; + } else { + type = widestReturnType(trueType, falseType); + } + return end(ensureSymbol(ternaryNode, type)); } /** * When doing widening for return types of a function or a ternary operator, it is not valid to widen a boolean to - * anything other than Object. Also, widening a numeric type to an object type must widen to Object proper and not - * any more specific subclass (e.g. widest of int/long/double and String is Object). + * anything other than object. Note that this wouldn't be necessary if {@code Type.widest} did not allow + * boolean-to-number widening. Eventually, we should address it there, but it affects too many other parts of the + * system and is sometimes legitimate (e.g. whenever a boolean value would undergo ToNumber conversion anyway). * @param t1 type 1 * @param t2 type 2 - * @return wider of t1 and t2, except if one is boolean and the other is neither boolean nor unknown, or if one is - * numeric and the other is neither numeric nor unknown in which case {@code Type.OBJECT} is returned. + * @return wider of t1 and t2, except if one is boolean and the other is neither boolean nor unknown, in which case + * {@code Type.OBJECT} is returned. */ private static Type widestReturnType(final Type t1, final Type t2) { if (t1.isUnknown()) { return t2; } else if (t2.isUnknown()) { return t1; - } else if (t1.isBoolean() != t2.isBoolean() || t1.isNumeric() != t2.isNumeric()) { + } else if(t1.isBoolean() != t2.isBoolean() || t1.isNumeric() != t2.isNumeric()) { return Type.OBJECT; } return Type.widest(t1, t2); } private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags) { - final Class<?> type = cc.type(); // Must not call this method for constants with no explicit types; use the one with (..., Type) signature instead. - assert type != null; - initCompileConstant(cc, block, flags, Type.typeFor(type)); + assert cc.type() != null; + initCompileConstant(cc, block, flags, Type.typeFor(cc.type())); } private void initCompileConstant(final CompilerConstants cc, final Block block, final int flags, final Type type) { - final Symbol symbol = defineSymbol(block, cc.symbolName(), flags); - symbol.setTypeOverride(type); - symbol.setNeedsSlot(true); + defineSymbol(block, cc.symbolName(), flags). + setTypeOverride(type). + setNeedsSlot(true); } /** @@ -1477,22 +1628,29 @@ * @param functionNode the function node */ private void initParameters(final FunctionNode functionNode, final Block body) { + final boolean isOptimistic = env.useOptimisticTypes(); int pos = 0; for (final IdentNode param : functionNode.getParameters()) { addLocalDef(param.getName()); - final Type callSiteParamType = functionNode.getHints().getParameterType(pos); - int flags = IS_PARAM; - if (callSiteParamType != null) { - LOG.info("Param ", param, " has a callsite type ", callSiteParamType, ". Using that."); - flags |= Symbol.IS_SPECIALIZED_PARAM; - } - - final Symbol paramSymbol = defineSymbol(body, param.getName(), flags); + final Symbol paramSymbol = defineSymbol(body, param.getName(), IS_PARAM); assert paramSymbol != null; - newType(paramSymbol, callSiteParamType == null ? Type.UNKNOWN : callSiteParamType); - + final Type callSiteParamType = env.getParamType(functionNode, pos); + if (callSiteParamType != null) { + LOG.info("Callsite type override for parameter " + pos + " " + paramSymbol + " => " + callSiteParamType); + newType(paramSymbol, callSiteParamType); + } else { + // When we're using optimistic compilation, we'll generate specialized versions of the functions anyway + // based on their input type, so if we're doing a compilation without parameter types explicitly + // specified in the compilation environment, just pre-initialize them all to Object. Note that this is + // not merely an optimization; it has correctness implications as Type.UNKNOWN is narrower than all + // other types, which when combined with optimistic typing can cause invalid coercions to be introduced + // in the generated code. E.g. "var b = { x: 0 }; (function (i) { this.x += i }).apply(b, [1.1])" would + // erroneously allow coercion of "i" to int when "this.x" is an optimistic-int and "i" starts out + // with Type.UNKNOWN. + newType(paramSymbol, isOptimistic ? Type.OBJECT : Type.UNKNOWN); + } LOG.info("Initialized param ", pos, "=", paramSymbol); pos++; } @@ -1508,49 +1666,47 @@ private FunctionNode finalizeParameters(final FunctionNode functionNode) { final List<IdentNode> newParams = new ArrayList<>(); final boolean isVarArg = functionNode.isVarArg(); - final int nparams = functionNode.getParameters().size(); + final boolean pessimistic = !useOptimisticTypes(); - int specialize = 0; - int pos = 0; for (final IdentNode param : functionNode.getParameters()) { final Symbol paramSymbol = functionNode.getBody().getExistingSymbol(param.getName()); assert paramSymbol != null; - assert paramSymbol.isParam(); + assert paramSymbol.isParam() : paramSymbol + " " + paramSymbol.getFlags(); newParams.add((IdentNode)param.setSymbol(lc, paramSymbol)); assert paramSymbol != null; - Type type = functionNode.getHints().getParameterType(pos); - if (type == null) { - type = Type.OBJECT; + Type type = paramSymbol.getSymbolType(); + + // all param types are initialized to unknown + // first we check if we do have a type (inferred during generation) + // and it's not an object. In that case we make an optimistic + // assumption + if (!type.isUnknown() && !type.isObject()) { + //optimistically inferred + lc.logOptimisticAssumption(paramSymbol, type); } - // 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: - // we also need more than half of the parameters to be specializable - // for the heuristic to be worth it, and we need more than one use of - // the parameter to consider it, i.e. function(x) { call(x); } doens't count - if (paramSymbol.getUseCount() > 1 && !paramSymbol.getSymbolType().isObject()) { - LOG.finest("Parameter ", param, " could profit from specialization to ", paramSymbol.getSymbolType()); - specialize++; + //known runtime types are hardcoded already in initParameters so avoid any + //overly optimistic assumptions, e.g. a double parameter known from + //RecompilableScriptFunctionData is with us all the way + if (type.isUnknown()) { + newType(paramSymbol, Type.OBJECT); } - newType(paramSymbol, Type.widest(type, paramSymbol.getSymbolType())); + // if we are pessimistic, we are always an object + if (pessimistic) { + newType(paramSymbol, Type.OBJECT); + } // parameters should not be slots for a function that uses variable arity signature if (isVarArg) { paramSymbol.setNeedsSlot(false); + newType(paramSymbol, Type.OBJECT); } - - pos++; } FunctionNode newFunctionNode = functionNode; - if (nparams == 0 || (specialize * 2) < nparams) { - newFunctionNode = newFunctionNode.clearSnapshot(lc); - } - return newFunctionNode.setParameters(lc, newParams); } @@ -1571,45 +1727,6 @@ } } - private static void ensureTypeNotUnknown(final Expression node) { - - final Symbol symbol = node.getSymbol(); - - LOG.info("Ensure type not unknown for: ", symbol); - - /* - * Note that not just unknowns, but params need to be blown - * up to objects, because we can have something like - * - * function f(a) { - * var b = ~a; //b and a are inferred to be int - * return b; - * } - * - * In this case, it would be correct to say that "if you have - * an int at the callsite, just pass it". - * - * However - * - * function f(a) { - * var b = ~a; //b and a are inferred to be int - * return b == 17; //b is still inferred to be int. - * } - * - * can be called with f("17") and if we assume that b is an - * int and don't blow it up to an object in the comparison, we - * are screwed. I hate JavaScript. - * - * This check has to be done for any operation that might take - * objects as parameters, for example +, but not *, which is known - * to coerce types into doubles - */ - if (node.getType().isUnknown() || (symbol.isParam() && !symbol.isSpecializedParam())) { - newType(symbol, Type.OBJECT); - symbol.setCanBeUndefined(); - } - } - private static Symbol pseudoSymbol(final String name) { return new Symbol(name, 0, Type.OBJECT); } @@ -1618,15 +1735,6 @@ return newInternal(lc.getCurrentFunction().uniqueName(EXCEPTION_PREFIX.symbolName()), Type.typeFor(EXCEPTION_PREFIX.type())); } - /** - * Return the type that arithmetic ops should use. Until we have implemented better type - * analysis (range based) or overflow checks that are fast enough for int arithmetic, - * this is the number type - * @return the arithetic type - */ - private static Type arithType() { - return Compiler.shouldUseIntegerArithmetic() ? Type.INT : Type.NUMBER; - } /** * If types have changed, we can have failed to update vars. For example @@ -1638,9 +1746,15 @@ */ private FunctionNode finalizeTypes(final FunctionNode functionNode) { final Set<Node> changed = new HashSet<>(); + final Deque<Type> returnTypes = new ArrayDeque<>(); + FunctionNode currentFunctionNode = functionNode; + int fixedPointIterations = 0; do { + fixedPointIterations++; + assert fixedPointIterations < 0x100 : "too many fixed point iterations for " + functionNode.getName() + " -> most likely infinite loop"; changed.clear(); + final FunctionNode newFunctionNode = (FunctionNode)currentFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { private Expression widen(final Expression node, final Type to) { @@ -1656,7 +1770,9 @@ } newType(symbol, to); final Expression newNode = node.setSymbol(lc, symbol); - changed.add(newNode); + if (node != newNode) { + changed.add(newNode); + } return newNode; } return node; @@ -1664,7 +1780,31 @@ @Override public boolean enterFunctionNode(final FunctionNode node) { - return !node.isLazy(); + returnTypes.push(Type.UNKNOWN); + return true; + } + + @Override + public Node leaveFunctionNode(final FunctionNode node) { + Type returnType = returnTypes.pop(); + if (returnType.isUnknown()) { + returnType = Type.OBJECT; + } + return node.setReturnType(lc, returnType); + } + + @Override + public Node leaveReturnNode(final ReturnNode returnNode) { + Type returnType = returnTypes.pop(); + if (returnNode.hasExpression()) { + returnType = widestReturnType(returnType, returnNode.getExpression().getType()); //getSymbol().getSymbolType()); + } else { + returnType = Type.OBJECT; //undefined + } + + returnTypes.push(returnType); + + return returnNode; } // @@ -1683,10 +1823,16 @@ @SuppressWarnings("fallthrough") @Override public Node leaveBinaryNode(final BinaryNode binaryNode) { - final Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); + Type widest = Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()); BinaryNode newBinaryNode = binaryNode; if (isAdd(binaryNode)) { + if(widest.getTypeClass() == String.class) { + // Erase "String" to "Object" as we have trouble with optimistically typed operands that + // would be typed "String" in the code generator as they are always loaded using the type + // of the operation. + widest = Type.OBJECT; + } newBinaryNode = (BinaryNode)widen(newBinaryNode, widest); if (newBinaryNode.getType().isObject() && !isAddString(newBinaryNode)) { return new RuntimeNode(newBinaryNode, Request.ADD); @@ -1728,6 +1874,11 @@ } + @Override + public Node leaveTernaryNode(TernaryNode ternaryNode) { + return widen(ternaryNode, Type.widest(ternaryNode.getTrueExpression().getType(), ternaryNode.getFalseExpression().getType())); + } + private boolean isAdd(final Node node) { return node.isTokenType(TokenType.ADD); } @@ -1773,24 +1924,145 @@ return leaveSelfModifyingAssignmentNode(binaryNode, binaryNode.getWidestOperationType()); } - private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode, final Type destType) { + private Node leaveSelfModifyingAssignmentNode(final BinaryNode binaryNode, final Type pessimisticType) { //e.g. for -=, Number, no wider, destType (binaryNode.getWidestOperationType()) is the coerce type final Expression lhs = binaryNode.lhs(); - - newType(lhs.getSymbol(), destType); //may not narrow if dest is already wider than destType - - return end(ensureSymbol(destType, binaryNode)); + final BinaryNode newBinaryNode = ensureSymbolTypeOverride(binaryNode, pessimisticType, Type.widest(lhs.getType(), binaryNode.rhs().getType())); + newType(lhs.getSymbol(), newBinaryNode.getType()); //may not narrow if dest is already wider than destType + return end(newBinaryNode); } - private Expression ensureSymbol(final Type type, final Expression expr) { + private Expression ensureSymbol(final Expression expr, final Type type) { LOG.info("New TEMPORARY added to ", lc.getCurrentFunction().getName(), " type=", type); return temporarySymbols.ensureSymbol(lc, type, expr); } + @Override + public boolean enterReturnNode(ReturnNode returnNode) { + tagOptimistic(returnNode.getExpression()); + return true; + } + + @Override + public boolean enterIfNode(IfNode ifNode) { + tagNeverOptimistic(ifNode.getTest()); + return true; + } + + @Override + public boolean enterWhileNode(WhileNode whileNode) { + tagNeverOptimistic(whileNode.getTest()); + return true; + } + + /** + * Used to signal that children should be optimistic. Otherwise every identnode + * in the entire program would basically start out as being guessed as an int + * and warmup would take an ENORMOUS time. This is also used while we get all + * the logic up and running, as we currently can't afford to debug every potential + * situtation that has to do with unwarranted optimism. We currently only tag + * type overrides, all other nodes are nops in this function + * + * @param expr an expression that is to be tagged as optimistic. + */ + private long tag(final Optimistic expr) { + return ((long)lc.getCurrentFunction().getId() << 32) | expr.getProgramPoint(); + } + + /** + * This is used to guarantee that there are no optimistic setters, something that + * doesn't make sense in our current model, where only optimistic getters can exist. + * If we set something, we use the callSiteType. We might want to use dual fields + * though and incorporate this later for the option of putting something wider than + * is currently in the field causing an UnwarrantedOptimismException. + * + * @param expr expression to be tagged as never optimistic + */ + private void tagNeverOptimistic(final Expression expr) { + if (expr instanceof Optimistic) { + LOG.info("Tagging TypeOverride node '" + expr + "' never optimistic"); + neverOptimistic.add(tag((Optimistic)expr)); + } + } + + private void tagOptimistic(final Expression expr) { + if (expr instanceof Optimistic) { + LOG.info("Tagging TypeOverride node '" + expr + "' as optimistic"); + optimistic.add(tag((Optimistic)expr)); + } + } + + private boolean isTaggedNeverOptimistic(final Optimistic expr) { + return neverOptimistic.contains(tag(expr)); + } + + private boolean isTaggedOptimistic(final Optimistic expr) { + return optimistic.contains(tag(expr)); + } + + private Type getOptimisticType(Optimistic expr) { + return useOptimisticTypes() ? env.getOptimisticType(expr) : expr.getMostPessimisticType(); + } + + /** + * This is the base function for typing a TypeOverride as optimistic. For any expression that + * can also be a type override (call, ident node (scope load), access node, index node) we use + * the override type to communicate optimism. + * + * @param pessimisticType conservative always guaranteed to work for this operation + * @param to node to set type for + */ + private <T extends Expression & Optimistic> T ensureSymbolTypeOverride(final T node, final Type pessimisticType) { + return ensureSymbolTypeOverride(node, pessimisticType, null); + } + + @SuppressWarnings("unchecked") + private <T extends Expression & Optimistic> T ensureSymbolTypeOverride(final T node, final Type pessimisticType, final Type argumentsType) { + // check what the most optimistic type for this node should be + // if we are running with optimistic types, this starts out as e.g. int, and based on previous + // failed assumptions it can be wider, for example double if we have failed this assumption + // in a previous run + Type optimisticType = getOptimisticType(node); + + if (argumentsType != null) { + optimisticType = Type.widest(optimisticType, argumentsType); + } + + // the symbol of the expression is the pessimistic one, i.e. IndexNodes are always Object for consistency + // with the type system. + T expr = (T)ensureSymbol(node, pessimisticType); + + if (optimisticType.isObject()) { + return expr; + } + + if (isTaggedNeverOptimistic(expr)) { + return expr; + } + + if(!(node instanceof FunctionCall && ((FunctionCall)node).isFunction())) { + // in the case that we have an optimistic type, set the type override (setType is inherited from TypeOverride) + // but maintain the symbol type set above. Also flag the function as optimistic. Don't do this for any + // expressions that are used as the callee of a function call. + if (optimisticType.narrowerThan(pessimisticType)) { + expr = (T)expr.setType(temporarySymbols, optimisticType); + expr = (T)Node.setIsOptimistic(expr, true); + if (optimisticType.isPrimitive()) { + final Symbol symbol = expr.getSymbol(); + if (symbol.isShared()) { + expr = (T)expr.setSymbol(lc, symbol.createUnshared(symbol.getName())); + } + } + LOG.fine(expr, " turned optimistic with type=", optimisticType); + assert ((Optimistic)expr).isOptimistic(); + } + } + return expr; + } + + private Symbol newInternal(final String name, final Type type) { - final Symbol iter = defineSymbol(lc.getCurrentBlock(), name, IS_VAR | IS_INTERNAL); - iter.setType(type); // NASHORN-73 - return iter; + return defineSymbol(lc.getCurrentBlock(), name, IS_VAR | IS_INTERNAL).setType(type); //NASHORN-73 } private static void newType(final Symbol symbol, final Type type) { @@ -1845,34 +2117,46 @@ localUses.peek().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 body body for the function node we are leaving - */ - private static void objectifySymbols(final Block body) { - body.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - private void toObject(final Block block) { - for (final Symbol symbol : block.getSymbols()) { - if (!symbol.isTemp()) { - newType(symbol, Type.OBJECT); - } - } + private void inferParameter(final Expression node, final Type type) { + final Symbol symbol = node.getSymbol(); + if (useOptimisticTypes() && symbol.isParam()) { + final Type symbolType = symbol.getSymbolType(); + if(symbolType.isBoolean() && !(type.isBoolean() || type == Type.OBJECT)) { + // boolean parameters can only legally be widened to Object + return; + } + if (symbolType != type) { + LOG.info("Infer parameter type " + symbol + " ==> " + type + " " + lc.getCurrentFunction().getSource().getName() + " " + lc.getCurrentFunction().getName()); } + symbol.setType(type); //will be overwritten by object later if pessimistic anyway + lc.logOptimisticAssumption(symbol, type); + } + } - @Override - public boolean enterBlock(final Block block) { - toObject(block); - return true; + private BinaryNode coerce(final BinaryNode binaryNode, final Type pessimisticType) { + return coerce(binaryNode, pessimisticType, null); + } + + private BinaryNode coerce(final BinaryNode binaryNode, final Type pessimisticType, final Type argumentsType) { + BinaryNode newNode = ensureSymbolTypeOverride(binaryNode, pessimisticType, argumentsType); + inferParameter(binaryNode.lhs(), newNode.getType()); + inferParameter(binaryNode.rhs(), newNode.getType()); + return newNode; + } + + private UnaryNode coerce(final UnaryNode unaryNode, final Type pessimisticType) { + return coerce(unaryNode, pessimisticType, null); + } + + private UnaryNode coerce(final UnaryNode unaryNode, final Type pessimisticType, final Type argumentType) { + UnaryNode newNode = ensureSymbolTypeOverride(unaryNode, pessimisticType, argumentType); + if (newNode.isOptimistic()) { + if (unaryNode.getExpression() instanceof Optimistic) { + newNode = newNode.setExpression(Node.setIsOptimistic(unaryNode.getExpression(), true)); } - - @Override - public boolean enterFunctionNode(final FunctionNode node) { - return false; - } - }); + } + inferParameter(unaryNode.getExpression(), newNode.getType()); + return newNode; } private static String name(final Node node) {
--- a/src/jdk/nashorn/internal/codegen/BranchOptimizer.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/BranchOptimizer.java Wed Feb 26 13:17:57 2014 +0100 @@ -57,7 +57,7 @@ } private void branchOptimizer(final UnaryNode unaryNode, final Label label, final boolean state) { - final Expression rhs = unaryNode.rhs(); + final Expression rhs = unaryNode.getExpression(); switch (unaryNode.tokenType()) { case NOT:
--- a/src/jdk/nashorn/internal/codegen/ClassEmitter.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/ClassEmitter.java Wed Feb 26 13:17:57 2014 +0100 @@ -54,19 +54,23 @@ import java.io.ByteArrayOutputStream; import java.io.PrintWriter; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Arrays; import java.util.EnumSet; import java.util.HashSet; import java.util.Set; - -import jdk.internal.org.objectweb.asm.ClassReader; import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.org.objectweb.asm.util.TraceClassVisitor; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.SplitNode; +import jdk.nashorn.internal.ir.debug.NashornClassReader; +import jdk.nashorn.internal.ir.debug.NashornTextifier; +import jdk.nashorn.internal.runtime.Context; import jdk.nashorn.internal.runtime.PropertyMap; +import jdk.nashorn.internal.runtime.RewriteException; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.Source; @@ -106,6 +110,8 @@ * @see Compiler */ public class ClassEmitter implements Emitter { + /** Default flags for class generation - public class */ + private static final EnumSet<Flag> DEFAULT_METHOD_FLAGS = EnumSet.of(Flag.PUBLIC); /** Sanity check flag - have we started on a class? */ private boolean classStarted; @@ -125,9 +131,6 @@ /** The script environment */ protected final ScriptEnvironment env; - /** Default flags for class generation - oublic class */ - private static final EnumSet<Flag> DEFAULT_METHOD_FLAGS = EnumSet.of(Flag.PUBLIC); - /** Compile unit class name. */ private String unitClassName; @@ -376,9 +379,19 @@ static String disassemble(final byte[] bytecode) { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (final PrintWriter pw = new PrintWriter(baos)) { - new ClassReader(bytecode).accept(new TraceClassVisitor(pw), 0); + final NashornClassReader cr = new NashornClassReader(bytecode); + final Context ctx = AccessController.doPrivileged(new PrivilegedAction<Context>() { + @Override + public Context run() { + return Context.getContext(); + } + }); + TraceClassVisitor tcv = new TraceClassVisitor(null, new NashornTextifier(ctx.getEnv(), cr), pw); + cr.accept(tcv, 0); } - return new String(baos.toByteArray()); + + final String str = new String(baos.toByteArray()); + return str; } /** @@ -475,17 +488,40 @@ * @return method emitter to use for weaving this method */ MethodEmitter method(final FunctionNode functionNode) { + final FunctionSignature signature = new FunctionSignature(functionNode); final MethodVisitor mv = cw.visitMethod( ACC_PUBLIC | ACC_STATIC | (functionNode.isVarArg() ? ACC_VARARGS : 0), functionNode.getName(), - new FunctionSignature(functionNode).toString(), + signature.toString(), null, null); - return new MethodEmitter(this, mv, functionNode); + final MethodEmitter method = new MethodEmitter(this, mv, functionNode); + method.setParameterTypes(signature.getParamTypes()); + return method; } /** + * Add a new method to the class, representing a rest-of version of the function node + * + * @param functionNode the function node to generate a method for + * @return method emitter to use for weaving this method + */ + MethodEmitter restOfMethod(final FunctionNode functionNode) { + final MethodVisitor mv = cw.visitMethod( + ACC_PUBLIC | ACC_STATIC, + functionNode.getName(), + Type.getMethodDescriptor(functionNode.getReturnType().getTypeClass(), RewriteException.class), + null, + null); + + final MethodEmitter method = new MethodEmitter(this, mv, functionNode); + method.setParameterTypes(new FunctionSignature(functionNode).getParamTypes()); + return method; + } + + + /** * Start generating the <clinit> method in the class * * @return method emitter to use for weaving <clinit>
--- a/src/jdk/nashorn/internal/codegen/CodeGenerator.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/CodeGenerator.java Wed Feb 26 13:17:57 2014 +0100 @@ -29,6 +29,7 @@ import static jdk.nashorn.internal.codegen.ClassEmitter.Flag.STATIC; import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; +import static jdk.nashorn.internal.codegen.CompilerConstants.CREATE_PROGRAM_FUNCTION; 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.QUICK_PREFIX; @@ -45,20 +46,34 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup; +import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY; +import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getClassName; +import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getPaddedFieldCount; import static jdk.nashorn.internal.ir.Symbol.IS_INTERNAL; import static jdk.nashorn.internal.ir.Symbol.IS_TEMP; +import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; +import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_FAST_SCOPE; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC; +import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_SCOPE; import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_STRICT; import java.io.PrintWriter; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Deque; import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Map; +import java.util.RandomAccess; import java.util.Set; import java.util.TreeMap; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; @@ -94,6 +109,7 @@ import jdk.nashorn.internal.ir.LoopNode; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.ObjectNode; +import jdk.nashorn.internal.ir.Optimistic; import jdk.nashorn.internal.ir.PropertyNode; import jdk.nashorn.internal.ir.ReturnNode; import jdk.nashorn.internal.ir.RuntimeNode; @@ -120,17 +136,20 @@ import jdk.nashorn.internal.runtime.DebugLogger; import jdk.nashorn.internal.runtime.ECMAException; import jdk.nashorn.internal.runtime.JSType; -import jdk.nashorn.internal.runtime.Property; +import jdk.nashorn.internal.runtime.OptimisticReturnFilters; import jdk.nashorn.internal.runtime.PropertyMap; import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; +import jdk.nashorn.internal.runtime.RewriteException; import jdk.nashorn.internal.runtime.Scope; import jdk.nashorn.internal.runtime.ScriptFunction; import jdk.nashorn.internal.runtime.ScriptObject; import jdk.nashorn.internal.runtime.ScriptRuntime; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.Undefined; +import jdk.nashorn.internal.runtime.UnwarrantedOptimismException; import jdk.nashorn.internal.runtime.arrays.ArrayData; import jdk.nashorn.internal.runtime.linker.LinkerCallSite; +import jdk.nashorn.internal.runtime.options.Options; /** * This is the lowest tier of the code generator. It takes lowered ASTs emitted @@ -153,9 +172,24 @@ */ final class CodeGenerator extends NodeOperatorVisitor<CodeGeneratorLexicalContext> { + private static final Type SCOPE_TYPE = Type.typeFor(ScriptObject.class); + private static final String GLOBAL_OBJECT = Type.getInternalName(Global.class); - private static final String SCRIPTFUNCTION_IMPL_OBJECT = Type.getInternalName(ScriptFunctionImpl.class); + private static final String SCRIPTFUNCTION_IMPL_NAME = Type.getInternalName(ScriptFunctionImpl.class); + private static final Type SCRIPTFUNCTION_IMPL_TYPE = Type.typeFor(ScriptFunction.class); + + private static final Call INIT_REWRITE_EXCEPTION = CompilerConstants.specialCallNoLookup(RewriteException.class, + "<init>", void.class, UnwarrantedOptimismException.class, Object[].class); + private static final Call INIT_REWRITE_EXCEPTION_REST_OF = CompilerConstants.specialCallNoLookup(RewriteException.class, + "<init>", void.class, UnwarrantedOptimismException.class, Object[].class, int[].class); + + private static final Call ENSURE_INT = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class, + "ensureInt", int.class, Object.class, int.class); + private static final Call ENSURE_LONG = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class, + "ensureLong", long.class, Object.class, int.class); + private static final Call ENSURE_NUMBER = CompilerConstants.staticCallNoLookup(OptimisticReturnFilters.class, + "ensureNumber", double.class, Object.class, int.class); /** Constant data & installation. The only reason the compiler keeps this is because it is assigned * by reflection in class installation */ @@ -183,10 +217,18 @@ private static final DebugLogger LOG = new DebugLogger("codegen", "nashorn.codegen.debug"); /** From what size should we use spill instead of fields for JavaScript objects? */ - private static final int OBJECT_SPILL_THRESHOLD = 300; + private static final int OBJECT_SPILL_THRESHOLD = Options.getIntProperty("nashorn.spill.threshold", 256); private final Set<String> emittedMethods = new HashSet<>(); + // Function Id -> ContinuationInfo. Used by compilation of rest-of function only. + private final Map<Integer, ContinuationInfo> fnIdToContinuationInfo = new HashMap<>(); + + // Function Id -> (Function Id -> Function Data)). Used by compilation of most-optimistic function only. + private final Map<Integer, Map<Integer, RecompilableScriptFunctionData>> fnIdToNestedFunctions = new HashMap<>(); + + private final Deque<Label> scopeEntryLabels = new ArrayDeque<>(); + /** * Constructor. * @@ -209,6 +251,26 @@ } /** + * For an optimistic call site, we need to tag the callsite optimistic and + * encode the program point of the callsite into it + * + * @param node node that can be optimistic + * @return + */ + private int getCallSiteFlagsOptimistic(final Optimistic node) { + int flags = getCallSiteFlags(); + if (node.isOptimistic()) { + flags |= CALLSITE_OPTIMISTIC; + flags |= node.getProgramPoint() << CALLSITE_PROGRAM_POINT_SHIFT; //encode program point in high bits + } + return flags; + } + + private static boolean isOptimistic(final int flags) { + return (flags & CALLSITE_OPTIMISTIC) != 0; + } + + /** * Load an identity node * * @param identNode an identity node to load @@ -222,33 +284,80 @@ return method.load(symbol).convert(type); } - final String name = symbol.getName(); - final Source source = lc.getCurrentFunction().getSource(); - - if (CompilerConstants.__FILE__.name().equals(name)) { - return method.load(source.getName()); - } else if (CompilerConstants.__DIR__.name().equals(name)) { - return method.load(source.getBase()); - } else if (CompilerConstants.__LINE__.name().equals(name)) { - return method.load(source.getLine(identNode.position())).convert(Type.OBJECT); + // If this is either __FILE__, __DIR__, or __LINE__ then load the property initially as Object as we'd convert + // it anyway for replaceLocationPropertyPlaceholder. + final boolean isCompileTimePropertyName = identNode.isCompileTimePropertyName(); + + assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; + final int flags = CALLSITE_SCOPE | getCallSiteFlagsOptimistic(identNode); + 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 && !isOptimisticOrRestOf()) { + method.loadCompilerConstant(SCOPE); + loadSharedScopeVar(type, symbol, flags); + } else { + loadFastScopeVar(identNode, type, flags, isCompileTimePropertyName); + } } else { - assert identNode.getSymbol().isScope() : identNode + " is not in scope!"; - - final int flags = CALLSITE_SCOPE | getCallSiteFlags(); - method.loadCompilerConstant(SCOPE); - - 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(type, symbol, flags); + new OptimisticOperation() { + @Override + void loadStack() { + method.loadCompilerConstant(SCOPE); + } + @Override + void consumeStack() { + dynamicGet(method, identNode, isCompileTimePropertyName ? Type.OBJECT : type, identNode.getName(), flags, identNode.isFunction()); + if(isCompileTimePropertyName) { + replaceCompileTimeProperty(identNode, type); + } } - return loadFastScopeVar(type, symbol, flags, identNode.isFunction()); - } - return method.dynamicGet(type, identNode.getName(), flags, identNode.isFunction()); + }.emit(identNode, type); + } + + return method; + } + + private void replaceCompileTimeProperty(final IdentNode identNode, final Type type) { + final String name = identNode.getSymbol().getName(); + if (CompilerConstants.__FILE__.name().equals(name)) { + replaceCompileTimeProperty(identNode, type, getCurrentSource().getName()); + } else if (CompilerConstants.__DIR__.name().equals(name)) { + replaceCompileTimeProperty(identNode, type, getCurrentSource().getBase()); + } else if (CompilerConstants.__LINE__.name().equals(name)) { + replaceCompileTimeProperty(identNode, type, getCurrentSource().getLine(identNode.position())); } } /** + * When an ident with name __FILE__, __DIR__, or __LINE__ is loaded, we'll try to look it up as any other + * identifier. However, if it gets all the way up to the Global object, it will send back a special value that + * represents a placeholder for these compile-time location properties. This method will generate code that loads + * the value of the compile-time location property and then invokes a method in Global that will replace the + * placeholder with the value. Effectively, if the symbol for these properties is defined anywhere in the lexical + * scope, they take precedence, but if they aren't, then they resolve to the compile-time location property. + * @param identNode the ident node + * @param type the desired return type for the ident node + * @param propertyValue the actual value of the property + */ + private void replaceCompileTimeProperty(final IdentNode identNode, final Type type, final Object propertyValue) { + assert method.peekType().isObject(); + if(propertyValue instanceof String) { + method.load((String)propertyValue); + } else if(propertyValue instanceof Integer) { + method.load(((Integer)propertyValue).intValue()); + method.convert(Type.OBJECT); + } else { + throw new AssertionError(); + } + globalReplaceLocationPropertyPlaceholder(); + convertOptimisticReturnValue(identNode, type); + } + + private boolean isOptimisticOrRestOf() { + return useOptimisticTypes() || compiler.getCompilationEnvironment().isCompileRestOf(); + } + + /** * Check if this symbol can be accessed directly with a putfield or getfield or dynamic load * * @param symbol symbol to check for fast scope @@ -301,13 +410,25 @@ private MethodEmitter loadSharedScopeVar(final Type valueType, final Symbol symbol, final int flags) { method.load(isFastScope(symbol) ? getScopeProtoDepth(lc.getCurrentBlock(), symbol) : -1); - final SharedScopeCall scopeCall = lc.getScopeGet(unit, valueType, symbol, flags | CALLSITE_FAST_SCOPE); + final SharedScopeCall scopeCall = lc.getScopeGet(unit, symbol, valueType, flags | CALLSITE_FAST_SCOPE); return scopeCall.generateInvoke(method); } - private MethodEmitter loadFastScopeVar(final Type valueType, final Symbol symbol, final int flags, final boolean isMethod) { - loadFastScopeProto(symbol, false); - return method.dynamicGet(valueType, symbol.getName(), flags | CALLSITE_FAST_SCOPE, isMethod); + private MethodEmitter loadFastScopeVar(final IdentNode identNode, final Type type, final int flags, final boolean isCompileTimePropertyName) { + return new OptimisticOperation() { + @Override + void loadStack() { + method.loadCompilerConstant(SCOPE); + loadFastScopeProto(identNode.getSymbol(), false); + } + @Override + void consumeStack() { + dynamicGet(method, identNode, isCompileTimePropertyName ? Type.OBJECT : type, identNode.getSymbol().getName(), flags | CALLSITE_FAST_SCOPE, identNode.isFunction()); + if(isCompileTimePropertyName) { + replaceCompileTimeProperty(identNode, type); + } + } + }.emit(identNode, type); } private MethodEmitter storeFastScopeVar(final Symbol symbol, final int flags) { @@ -333,7 +454,7 @@ private void loadFastScopeProto(final Symbol symbol, final boolean swap) { final int depth = getScopeProtoDepth(lc.getCurrentBlock(), symbol); - assert depth != -1; + assert depth != -1 : "Couldn't find scope depth for symbol " + symbol.getName(); if (depth > 0) { if (swap) { method.swap(); @@ -356,12 +477,12 @@ * @return the method emitter used */ MethodEmitter load(final Expression node) { - return load(node, node.hasType() ? node.getType() : null, false); + return load(node, node.hasType() ? node.getType() : null); } // Test whether conversion from source to target involves a call of ES 9.1 ToPrimitive // with possible side effects from calling an object's toString or valueOf methods. - private boolean noToPrimitiveConversion(final Type source, final Type target) { + private static boolean noToPrimitiveConversion(final Type source, final Type target) { // Object to boolean conversion does not cause ToPrimitive call return source.isJSPrimitive() || !target.isJSPrimitive() || target.isBoolean(); } @@ -421,7 +542,7 @@ */ final CodeGenerator codegen = this; - node.accept(new NodeVisitor<LexicalContext>(lc) { + node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { @Override public boolean enterIdentNode(final IdentNode identNode) { loadIdent(identNode, type); @@ -430,21 +551,39 @@ @Override public boolean enterAccessNode(final AccessNode accessNode) { - if (!baseAlreadyOnStack) { - load(accessNode.getBase(), Type.OBJECT); - } - assert method.peekType().isObject(); - method.dynamicGet(type, accessNode.getProperty().getName(), getCallSiteFlags(), accessNode.isFunction()); + new OptimisticOperation() { + @Override + void loadStack() { + if (!baseAlreadyOnStack) { + load(accessNode.getBase(), Type.OBJECT); + } + assert method.peekType().isObject(); + } + @Override + void consumeStack() { + final int flags = getCallSiteFlagsOptimistic(accessNode); + dynamicGet(method, accessNode, type, accessNode.getProperty().getName(), flags, accessNode.isFunction()); + } + }.emit(accessNode, baseAlreadyOnStack ? 1 : 0); return false; } @Override public boolean enterIndexNode(final IndexNode indexNode) { - if (!baseAlreadyOnStack) { - load(indexNode.getBase(), Type.OBJECT); - load(indexNode.getIndex()); - } - method.dynamicGetIndex(type, getCallSiteFlags(), indexNode.isFunction()); + new OptimisticOperation() { + @Override + void loadStack() { + if (!baseAlreadyOnStack) { + load(indexNode.getBase(), Type.OBJECT); + load(indexNode.getIndex()); + } + } + @Override + void consumeStack() { + final int flags = getCallSiteFlagsOptimistic(indexNode); + dynamicGetIndex(method, indexNode, type, flags, indexNode.isFunction()); + } + }.emit(indexNode, baseAlreadyOnStack ? 2 : 0); return false; } @@ -505,6 +644,7 @@ private void initSymbols(final Iterable<Symbol> symbols) { final LinkedList<Symbol> numbers = new LinkedList<>(); final LinkedList<Symbol> objects = new LinkedList<>(); + final boolean useOptimistic = useOptimisticTypes(); for (final Symbol symbol : symbols) { /* @@ -514,14 +654,21 @@ * Otherwise we must, unless we perform control/escape analysis, * assign them undefined. */ - final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis() || !symbol.canBeUndefined(); - - if (symbol.hasSlot() && !isInternal) { - assert symbol.getSymbolType().isNumber() || symbol.getSymbolType().isObject() : "no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + lc.getCurrentFunction(); - if (symbol.getSymbolType().isNumber()) { - numbers.add(symbol); - } else if (symbol.getSymbolType().isObject()) { - objects.add(symbol); + final boolean isInternal = symbol.isParam() || symbol.isInternal() || symbol.isThis(); + + if (symbol.hasSlot()) { + final Type type = symbol.getSymbolType(); + if(symbol.canBeUndefined() && !isInternal) { + if (type.isNumber()) { + numbers.add(symbol); + } else if (type.isObject()) { + objects.add(symbol); + } else { + throw new AssertionError("no potentially undefined narrower local vars than doubles are allowed: " + symbol + " in " + lc.getCurrentFunction()); + } + } else if(useOptimistic && !symbol.isAlwaysDefined()) { + method.loadForcedInitializer(type); + method.store(symbol); } } } @@ -570,46 +717,82 @@ return true; } + private boolean useOptimisticTypes() { + return !lc.inSplitNode() && compiler.getCompilationEnvironment().useOptimisticTypes(); + } + @Override public Node leaveBlock(final Block block) { - method.label(block.getBreakLabel()); + + popBlockScope(block); + lc.releaseBlockSlots(useOptimisticTypes()); + symbolInfo(block); - - if (block.needsScope() && !block.isTerminal()) { - popBlockScope(block); - } return block; } private void popBlockScope(final Block block) { - final Label exitLabel = new Label("block_exit"); - final Label recoveryLabel = new Label("block_catch"); - final Label skipLabel = new Label("skip_catch"); + if(!block.needsScope() || lc.isFunctionBody()) { + method.label(block.getBreakLabel()); + return; + } + + final Label entry = scopeEntryLabels.pop(); + final Label afterCatchLabel; + final Label recoveryLabel = new Label("block_popscope_catch"); /* pop scope a la try-finally */ - method.loadCompilerConstant(SCOPE); - method.invoke(ScriptObject.GET_PROTO); - method.storeCompilerConstant(SCOPE); - method._goto(skipLabel); - method.label(exitLabel); + if(block.isTerminal()) { + // Block is terminal; there's no normal-flow path for popping the scope. Label current position as the end + // of the try block, and mark after-catch to be the block's break label. + final Label endTryLabel = new Label("block_popscope_end_try"); + method._try(entry, endTryLabel, recoveryLabel); + method.label(endTryLabel); + afterCatchLabel = block.getBreakLabel(); + } else { + // Block is non-terminal; Label current position as the block's break label (as it'll need to execute the + // scope popping when it gets here) and as the end of the try block. Mark after-catch with a new label. + final Label endTryLabel = block.getBreakLabel(); + method._try(entry, endTryLabel, recoveryLabel); + method.label(endTryLabel); + popScope(); + afterCatchLabel = new Label("block_after_catch"); + method._goto(afterCatchLabel); + } method._catch(recoveryLabel); + popScope(); + method.athrow(); + method.label(afterCatchLabel); + } + + private void popScope() { + popScopes(1); + } + + private void popScopesUntil(final LexicalContextNode until) { + popScopes(lc.getScopeNestingLevelTo(until)); + } + + private void popScopes(final int count) { + if(count == 0) { + return; + } + assert count > 0; // together with count == 0 check, asserts nonnegative count + assert method.hasScope(); method.loadCompilerConstant(SCOPE); - method.invoke(ScriptObject.GET_PROTO); + for(int i = 0; i < count; ++i) { + method.invoke(ScriptObject.GET_PROTO); + } method.storeCompilerConstant(SCOPE); - method.athrow(); - method.label(skipLabel); - method._try(block.getEntryLabel(), exitLabel, recoveryLabel, Throwable.class); } @Override public boolean enterBreakNode(final BreakNode breakNode) { - lineNumber(breakNode); + enterStatement(breakNode); final BreakableNode breakFrom = lc.getBreakable(breakNode.getLabel()); - for (int i = 0; i < lc.getScopeNestingLevelTo(breakFrom); i++) { - closeWith(); - } + popScopesUntil(breakFrom); method.splitAwareGoto(lc, breakFrom.getBreakLabel()); return false; @@ -668,68 +851,100 @@ private MethodEmitter sharedScopeCall(final IdentNode identNode, final int flags) { final Symbol symbol = identNode.getSymbol(); - int scopeCallFlags = flags; - method.loadCompilerConstant(SCOPE); - if (isFastScope(symbol)) { - method.load(getScopeProtoDepth(currentBlock, symbol)); - scopeCallFlags |= CALLSITE_FAST_SCOPE; - } else { - method.load(-1); // Bypass fast-scope code in shared callsite - } - loadArgs(args); - final Type[] paramTypes = method.getTypesFromStack(args.size()); - final SharedScopeCall scopeCall = codegenLexicalContext.getScopeCall(unit, symbol, identNode.getType(), callNodeType, paramTypes, scopeCallFlags); - return scopeCall.generateInvoke(method); + final boolean isFastScope = isFastScope(symbol); + final int scopeCallFlags = flags | (isFastScope ? CALLSITE_FAST_SCOPE : 0); + new OptimisticOperation() { + @Override + void loadStack() { + method.loadCompilerConstant(SCOPE); + if (isFastScope) { + method.load(getScopeProtoDepth(currentBlock, symbol)); + } else { + method.load(-1); // Bypass fast-scope code in shared callsite + } + loadArgs(args); + } + @Override + void consumeStack() { + final Type[] paramTypes = method.getTypesFromStack(args.size()); + final SharedScopeCall scopeCall = codegenLexicalContext.getScopeCall(unit, symbol, identNode.getType(), callNodeType, paramTypes, scopeCallFlags); + scopeCall.generateInvoke(method); + } + }.emit(callNode); + return method; } private void scopeCall(final IdentNode node, final int flags) { - load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3 - // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. - method.loadNull(); //the 'this' - method.dynamicCall(callNodeType, 2 + loadArgs(args), flags); + new OptimisticOperation() { + int argsCount; + @Override + void loadStack() { + load(node, Type.OBJECT); // foo() makes no sense if foo == 3 + // ScriptFunction will see CALLSITE_SCOPE and will bind scope accordingly. + method.loadNull(); //the 'this' + argsCount = loadArgs(args); + } + @Override + void consumeStack() { + dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags); + } + }.emit(callNode); } private void evalCall(final IdentNode node, final int flags) { - load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3 - - final Label not_eval = new Label("not_eval"); + final Label invoke_direct_eval = new Label("invoke_direct_eval"); + final Label is_not_eval = new Label("is_not_eval"); final Label eval_done = new Label("eval_done"); - // check if this is the real built-in eval - method.dup(); - globalIsEval(); - - method.ifeq(not_eval); - // We don't need ScriptFunction object for 'eval' - method.pop(); - - method.loadCompilerConstant(SCOPE); // Load up self (scope). - - final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); - // load evaluated code - load(evalArgs.getCode(), Type.OBJECT); - // load second and subsequent args for side-effect - final List<Expression> args = callNode.getArgs(); - final int numArgs = args.size(); - for (int i = 1; i < numArgs; i++) { - load(args.get(i)).pop(); - } - // special/extra 'eval' arguments - load(evalArgs.getThis()); - method.load(evalArgs.getLocation()); - method.load(evalArgs.getStrictMode()); - method.convert(Type.OBJECT); - - // direct call to Global.directEval - globalDirectEval(); - method.convert(callNodeType); - method._goto(eval_done); - - method.label(not_eval); - // This is some scope 'eval' or global eval replaced by user - // but not the built-in ECMAScript 'eval' function call - method.loadNull(); - method.dynamicCall(callNodeType, 2 + loadArgs(args), flags); + new OptimisticOperation() { + int argsCount; + @Override + void loadStack() { + load(node, Type.OBJECT); // Type.OBJECT as foo() makes no sense if foo == 3 + method.dup(); + globalIsEval(); + method.ifeq(is_not_eval); + + // We don't need ScriptFunction object for 'eval' + method.pop(); + // Load up self (scope). + method.loadCompilerConstant(SCOPE); + final CallNode.EvalArgs evalArgs = callNode.getEvalArgs(); + // load evaluated code + load(evalArgs.getCode(), Type.OBJECT); + // load second and subsequent args for side-effect + final List<Expression> callArgs = callNode.getArgs(); + final int numArgs = callArgs.size(); + for (int i = 1; i < numArgs; i++) { + load(callArgs.get(i)).pop(); + } + // special/extra 'eval' arguments + load(evalArgs.getThis()); + method.load(evalArgs.getLocation()); + method.load(evalArgs.getStrictMode()); + method.convert(Type.OBJECT); + method._goto(invoke_direct_eval); + + method.label(is_not_eval); + // This is some scope 'eval' or global eval replaced by user + // but not the built-in ECMAScript 'eval' function call + method.loadNull(); + argsCount = loadArgs(callArgs); + } + + @Override + void consumeStack() { + // Ordinary call + dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags); + method._goto(eval_done); + + method.label(invoke_direct_eval); + // direct call to Global.directEval + globalDirectEval(); + convertOptimisticReturnValue(callNode, callNodeType); + method.convert(callNodeType); + } + }.emit(callNode); method.label(eval_done); } @@ -739,7 +954,7 @@ final Symbol symbol = node.getSymbol(); if (symbol.isScope()) { - final int flags = getCallSiteFlags() | CALLSITE_SCOPE; + final int flags = getCallSiteFlagsOptimistic(callNode) | CALLSITE_SCOPE; final int useCount = symbol.getUseCount(); // Threshold for generating shared scope callsite is lower for fast scope symbols because we know @@ -749,7 +964,8 @@ evalCall(node, flags); } else if (useCount <= SharedScopeCall.FAST_SCOPE_CALL_THRESHOLD || (!isFastScope(symbol) && useCount <= SharedScopeCall.SLOW_SCOPE_CALL_THRESHOLD) - || CodeGenerator.this.lc.inDynamicScope()) { + || CodeGenerator.this.lc.inDynamicScope() + || isOptimisticOrRestOf()) { scopeCall(node, flags); } else { sharedScopeCall(node, flags); @@ -764,62 +980,104 @@ @Override public boolean enterAccessNode(final AccessNode node) { - load(node.getBase(), Type.OBJECT); - method.dup(); - method.dynamicGet(node.getType(), node.getProperty().getName(), getCallSiteFlags(), true); - method.swap(); - method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags()); + new OptimisticOperation() { + int argCount; + @Override + void loadStack() { + load(node.getBase(), Type.OBJECT); + method.dup(); + // NOTE: not using a nested OptimisticOperation on this dynamicGet, as we expect to get back + // a callable object. Nobody in their right mind would optimistically type this call site. + assert !node.isOptimistic(); + method.dynamicGet(node.getType(), node.getProperty().getName(), getCallSiteFlags(), true); + method.swap(); + argCount = loadArgs(args); + } + @Override + void consumeStack() { + final int flags = getCallSiteFlagsOptimistic(callNode); + dynamicCall(method, callNode, callNodeType, 2 + argCount, flags); + } + }.emit(callNode); return false; } @Override public boolean enterFunctionNode(final FunctionNode origCallee) { - // NOTE: visiting the callee will leave a constructed ScriptFunction object on the stack if - // callee.needsCallee() == true - final FunctionNode callee = (FunctionNode)origCallee.accept(CodeGenerator.this); - - final boolean isVarArg = callee.isVarArg(); - final int argCount = isVarArg ? -1 : callee.getParameters().size(); - - final String signature = new FunctionSignature(true, callee.needsCallee(), callee.getReturnType(), isVarArg ? null : callee.getParameters()).toString(); - - if (callee.isStrict()) { // self is undefined - method.loadUndefined(Type.OBJECT); - } else { // get global from scope (which is the self) - globalInstance(); - } - loadArgs(args, signature, isVarArg, argCount); - assert callee.getCompileUnit() != null : "no compile unit for " + callee.getName() + " " + Debug.id(callee) + " " + callNode; - method.invokestatic(callee.getCompileUnit().getUnitClassName(), callee.getName(), signature); - assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType(); + new OptimisticOperation() { + FunctionNode callee; + int argsCount; + @Override + void loadStack() { + callee = (FunctionNode)origCallee.accept(CodeGenerator.this); + if (callee.isStrict()) { // "this" is undefined + method.loadUndefined(Type.OBJECT); + } else { // get global from scope (which is the self) + globalInstance(); + } + argsCount = loadArgs(args); + } + + @Override + void consumeStack() { + final int flags = getCallSiteFlagsOptimistic(callNode); + //assert callNodeType.equals(callee.getReturnType()) : callNodeType + " != " + callee.getReturnType(); + dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags); + //assert method.peekType().equals(callee.getReturnType()) : method.peekType() + " != " + callee.getReturnType(); + } + }.emit(callNode); method.convert(callNodeType); return false; } @Override public boolean enterIndexNode(final IndexNode node) { - load(node.getBase(), Type.OBJECT); - method.dup(); - final Type indexType = node.getIndex().getType(); - if (indexType.isObject() || indexType.isBoolean()) { - load(node.getIndex(), Type.OBJECT); //TODO - } else { - load(node.getIndex()); - } - method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); - method.swap(); - method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags()); - + new OptimisticOperation() { + int argsCount; + @Override + void loadStack() { + load(node.getBase(), Type.OBJECT); + method.dup(); + final Type indexType = node.getIndex().getType(); + if (indexType.isObject() || indexType.isBoolean()) { + load(node.getIndex(), Type.OBJECT); //TODO + } else { + load(node.getIndex()); + } + // NOTE: not using a nested OptimisticOperation on this dynamicGetIndex, as we expect to get + // back a callable object. Nobody in their right mind would optimistically type this call site. + assert !node.isOptimistic(); + method.dynamicGetIndex(node.getType(), getCallSiteFlags(), true); + method.swap(); + argsCount = loadArgs(args); + } + @Override + void consumeStack() { + final int flags = getCallSiteFlagsOptimistic(callNode); + dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags); + } + }.emit(callNode); return false; } @Override protected boolean enterDefault(final Node node) { - // Load up function. - load(function, Type.OBJECT); //TODO, e.g. booleans can be used as functions - method.loadNull(); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE - method.dynamicCall(callNodeType, 2 + loadArgs(args), getCallSiteFlags() | CALLSITE_SCOPE); + new OptimisticOperation() { + int argsCount; + @Override + void loadStack() { + // Load up function. + load(function, Type.OBJECT); //TODO, e.g. booleans can be used as functions + method.loadNull(); // ScriptFunction will figure out the correct this when it sees CALLSITE_SCOPE + argsCount = loadArgs(args); + } + @Override + void consumeStack() { + final int flags = getCallSiteFlagsOptimistic(callNode) | CALLSITE_SCOPE; + dynamicCall(method, callNode, callNodeType, 2 + argsCount, flags); + } + }.emit(callNode); return false; } @@ -830,14 +1088,113 @@ return false; } + private void convertOptimisticReturnValue(final Optimistic expr, final Type desiredType) { + if (expr.isOptimistic()) { + final Type optimisticType = getOptimisticCoercedType(desiredType, (Expression)expr); + if(!optimisticType.isObject()) { + method.load(expr.getProgramPoint()); + if(optimisticType.isInteger()) { + method.invoke(ENSURE_INT); + } else if(optimisticType.isLong()) { + method.invoke(ENSURE_LONG); + } else if(optimisticType.isNumber()) { + method.invoke(ENSURE_NUMBER); + } else { + throw new AssertionError(optimisticType); + } + } + } + method.convert(desiredType); + } + + /** + * Emits the correct dynamic getter code. Normally just delegates to method emitter, except when the target + * expression is optimistic, and the desired type is narrower than the optimistic type. In that case, it'll emit a + * dynamic getter with its original optimistic type, and explicitly insert a narrowing conversion. This way we can + * preserve the optimism of the values even if they're subsequently immediately coerced into a narrower type. This + * is beneficial because in this case we can still presume that since the original getter was optimistic, the + * conversion has no side effects. + * @param method the method emitter + * @param expr the expression that is being loaded through the getter + * @param desiredType the desired type for the loaded expression (coercible from its original type) + * @param name the name of the property being get + * @param flags call site flags + * @param isMethod whether we're preferrably retrieving a function + * @return the passed in method emitter + */ + private static MethodEmitter dynamicGet(MethodEmitter method, Expression expr, Type desiredType, final String name, final int flags, boolean isMethod) { + final int finalFlags = maybeRemoveOptimisticFlags(desiredType, flags); + if(isOptimistic(finalFlags)) { + return method.dynamicGet(getOptimisticCoercedType(desiredType, expr), name, finalFlags, isMethod).convert(desiredType); + } + return method.dynamicGet(desiredType, name, finalFlags, isMethod); + } + + private static MethodEmitter dynamicGetIndex(MethodEmitter method, Expression expr, Type desiredType, int flags, boolean isMethod) { + final int finalFlags = maybeRemoveOptimisticFlags(desiredType, flags); + if(isOptimistic(finalFlags)) { + return method.dynamicGetIndex(getOptimisticCoercedType(desiredType, expr), finalFlags, isMethod).convert(desiredType); + } + return method.dynamicGetIndex(desiredType, finalFlags, isMethod); + } + + private static MethodEmitter dynamicCall(MethodEmitter method, Expression expr, Type desiredType, int argCount, int flags) { + final int finalFlags = maybeRemoveOptimisticFlags(desiredType, flags); + if(isOptimistic(finalFlags)) { + return method.dynamicCall(getOptimisticCoercedType(desiredType, expr), argCount, finalFlags).convert(desiredType); + } + return method.dynamicCall(desiredType, argCount, finalFlags); + } + + /** + * Given an optimistic expression and a desired coercing type, returns the type that should be used as the return + * type of the dynamic invocation that is emitted as the code for the expression load. If the coercing type is + * either boolean or narrower than the expression's optimistic type, then the optimistic type is returned, otherwise + * the coercing type. Note that if you use this method to determine the return type of the code for the expression, + * you will need to add an explicit {@link MethodEmitter#convert(Type)} after it to make sure that any further + * coercing is done into the final type in case the returned type here was the optimistic type. Effectively, this + * method allows for moving the coercion into the optimistic type when it won't adversely affect the optimistic + * evaluation semantics, and for preserving the optimistic type and doing a separate coercion when it would affect + * it. + * @param coercingType the type into which the expression will ultimately be coerced + * @param optimisticExpr the optimistic expression that will be coerced after evaluation. + * @return + */ + private static Type getOptimisticCoercedType(final Type coercingType, final Expression optimisticExpr) { + assert optimisticExpr instanceof Optimistic && ((Optimistic)optimisticExpr).isOptimistic(); + final Type optimisticType = optimisticExpr.getType(); + if(coercingType.isBoolean() || coercingType.narrowerThan(optimisticType)) { + return optimisticType; + } + return coercingType; + } + + /** + * If given an object type, ensures that the flags have their optimism removed (object return valued expressions are + * never optimistic). + * @param type the return value type + * @param flags original flags + * @return either the original flags, or flags with optimism stripped, if the return value type is object + */ + private static int maybeRemoveOptimisticFlags(Type type, int flags) { + return type.isObject() ? nonOptimisticFlags(flags) : flags; + } + + /** + * Returns the flags with optimistic flag and program point removed. + * @param flags the flags that need optimism stripped from them. + * @return flags without optimism + */ + static int nonOptimisticFlags(int flags) { + return flags & ~(CALLSITE_OPTIMISTIC | (-1 << CALLSITE_PROGRAM_POINT_SHIFT)); + } + @Override public boolean enterContinueNode(final ContinueNode continueNode) { - lineNumber(continueNode); + enterStatement(continueNode); final LoopNode continueTo = lc.getContinueTo(continueNode.getLabel()); - for (int i = 0; i < lc.getScopeNestingLevelTo(continueTo); i++) { - closeWith(); - } + popScopesUntil(continueTo); method.splitAwareGoto(lc, continueTo.getContinueLabel()); return false; @@ -845,23 +1202,25 @@ @Override public boolean enterEmptyNode(final EmptyNode emptyNode) { - lineNumber(emptyNode); + enterStatement(emptyNode); return false; } @Override public boolean enterExpressionStatement(final ExpressionStatement expressionStatement) { - lineNumber(expressionStatement); - - expressionStatement.getExpression().accept(this); + enterStatement(expressionStatement); + + final Expression expr = expressionStatement.getExpression(); + assert expr.isTokenType(TokenType.DISCARD); + expr.accept(this); return false; } @Override public boolean enterBlockStatement(final BlockStatement blockStatement) { - lineNumber(blockStatement); + enterStatement(blockStatement); blockStatement.getBlock().accept(this); @@ -870,7 +1229,7 @@ @Override public boolean enterForNode(final ForNode forNode) { - lineNumber(forNode); + enterStatement(forNode); if (forNode.isForIn()) { enterForIn(forNode); @@ -899,6 +1258,8 @@ body.accept(this); method.label(forNode.getContinueLabel()); + lineNumber(forNode); + if (!body.isTerminal() && modify != null) { load(modify); } @@ -931,8 +1292,9 @@ new Store<Expression>(init) { @Override protected void storeNonDiscard() { - return; + //empty } + @Override protected void evaluate() { method.load(iter); @@ -961,7 +1323,7 @@ final FunctionNode function = lc.getCurrentFunction(); if (isFunctionBody) { - if(method.hasScope()) { + if (method.hasScope()) { if (function.needsParentScope()) { method.loadCompilerConstant(CALLEE); method.invoke(ScriptFunction.GET_SCOPE); @@ -974,6 +1336,15 @@ if (function.needsArguments()) { initArguments(function); } + final Symbol returnSymbol = block.getExistingSymbol(RETURN.symbolName()); + if(returnSymbol.hasSlot() && useOptimisticTypes() && + // NOTE: a program that has no declared functions will assign ":return = UNDEFINED" first thing as it + // starts to run, so we don't have to force initialize :return (see Lower.enterBlock()). + !(function.isProgram() && !function.hasDeclaredFunctions())) + { + method.loadForcedInitializer(returnSymbol.getSymbolType()); + method.store(returnSymbol); + } } /* @@ -988,61 +1359,79 @@ // TODO for LET we can do better: if *block* does not contain any eval/with, we don't need its vars in scope. - final List<String> nameList = new ArrayList<>(); - final List<Symbol> locals = new ArrayList<>(); - - // Initalize symbols and values - final List<Symbol> newSymbols = new ArrayList<>(); - final List<Symbol> values = new ArrayList<>(); - + final List<Symbol> localsToInitialize = new ArrayList<>(); final boolean hasArguments = function.needsArguments(); + final List<MapTuple<Symbol>> tuples = new ArrayList<>(); for (final Symbol symbol : block.getSymbols()) { - - if (symbol.isInternal() || symbol.isThis() || symbol.isTemp()) { + if (symbol.isInternal() && !symbol.isThis()) { + if (symbol.hasSlot()) { + localsToInitialize.add(symbol); + } + continue; + } + + if (symbol.isThis() || symbol.isTemp()) { continue; } if (symbol.isVar()) { + assert !varsInScope || symbol.isScope(); if (varsInScope || symbol.isScope()) { - nameList.add(symbol.getName()); - newSymbols.add(symbol); - values.add(null); assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName(); assert !symbol.hasSlot() : "slot for " + symbol + " should have been removed in Lower already" + function.getName(); + tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol) { + //this tuple will not be put fielded, as it has no value, just a symbol + @Override + public boolean isPrimitive() { + return symbol.getSymbolType().isPrimitive(); + } + }); } else { assert symbol.hasSlot() : symbol + " should have a slot only, no scope"; - locals.add(symbol); + localsToInitialize.add(symbol); } } else if (symbol.isParam() && (varsInScope || hasArguments || symbol.isScope())) { - nameList.add(symbol.getName()); - newSymbols.add(symbol); - values.add(hasArguments ? null : symbol); assert symbol.isScope() : "scope for " + symbol + " should have been set in Lower already " + function.getName() + " varsInScope="+varsInScope+" hasArguments="+hasArguments+" symbol.isScope()=" + symbol.isScope(); assert !(hasArguments && symbol.hasSlot()) : "slot for " + symbol + " should have been removed in Lower already " + function.getName(); + tuples.add(new MapTuple<Symbol>(symbol.getName(), symbol, hasArguments ? null : symbol) { + //this symbol will be put fielded, we can't initialize it as undefined with a known type + @Override + public Class<?> getValueType() { + return (OBJECT_FIELDS_ONLY || value == null || value.getSymbolType().isBoolean()) ? Object.class : value.getSymbolType().getTypeClass(); + //return OBJECT_FIELDS_ONLY ? Object.class : symbol.getSymbolType().getTypeClass(); + } + }); } } // we may have locals that need to be initialized - initSymbols(locals); + initSymbols(localsToInitialize); /* * Create a new object based on the symbols and values, generate * bootstrap code for object */ - new FieldObjectCreator<Symbol>(this, nameList, newSymbols, values, true, hasArguments) { + new FieldObjectCreator<Symbol>(this, tuples, true, hasArguments) { @Override protected void loadValue(final Symbol value) { method.load(value); } }.makeObject(method); - - // runScript(): merge scope into global + // program function: merge scope into global if (isFunctionBody && function.isProgram()) { method.invoke(ScriptRuntime.MERGE_SCOPE); } method.storeCompilerConstant(SCOPE); + if(!isFunctionBody) { + // Function body doesn't need a try/catch to restore scope, as it'd be a dead store anyway. Allowing it + // actually causes issues with UnwarrantedOptimismException handlers as ASM will sort this handler to + // the top of the exception handler table, so it'll be triggered instead of the UOE handlers. + final Label scopeEntryLabel = new Label(""); + scopeEntryLabels.push(scopeEntryLabel); + method.label(scopeEntryLabel); + } } else { // Since we don't have a scope, parameters didn't get assigned array indices by the FieldObjectCreator, so // we need to assign them separately here. @@ -1075,15 +1464,33 @@ method.storeCompilerConstant(ARGUMENTS); } + /** + * Should this code generator skip generating code for inner functions? If lazy compilation is on, or we're + * doing an on-demand ("just-in-time") compilation, then we aren't generating code for inner functions. + */ + private boolean compileOutermostOnly() { + return RecompilableScriptFunctionData.LAZY_COMPILATION || compiler.getCompilationEnvironment().isOnDemandCompilation(); + } + @Override public boolean enterFunctionNode(final FunctionNode functionNode) { - if (functionNode.isLazy()) { - // Must do it now; can't postpone it until leaveFunctionNode() - newFunctionObject(functionNode, functionNode); + final int fnId = functionNode.getId(); + Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.get(fnId); + if (nestedFunctions == null) { + nestedFunctions = new HashMap<>(); + fnIdToNestedFunctions.put(fnId, nestedFunctions); + } + + // Nested functions are not visited when we either recompile or lazily compile, only the outermost function is. + if (compileOutermostOnly() && (lc.getOutermostFunction() != functionNode)) { + // In case we are not generating code for the function, we must create or retrieve the function object and + // load it on the stack here. + newFunctionObject(functionNode, false); return false; } final String fnName = functionNode.getName(); + // NOTE: we only emit the method for a function with the given name once. We can have multiple functions with // the same name as a result of inlining finally blocks. However, in the future -- with type specialization, // notably -- we might need to check for both name *and* signature. Of course, even that might not be @@ -1092,18 +1499,31 @@ // to decide to either generate a unique method for each inlined copy of the function, maybe figure out its // exact type closure and deduplicate based on that, or just decide that functions in finally blocks aren't // worth it, and generate one method with most generic type closure. - if(!emittedMethods.contains(fnName)) { + if (!emittedMethods.contains(fnName)) { LOG.info("=== BEGIN ", fnName); assert functionNode.getCompileUnit() != null : "no compile unit for " + fnName + " " + Debug.id(functionNode); unit = lc.pushCompileUnit(functionNode.getCompileUnit()); assert lc.hasCompileUnits(); - method = lc.pushMethodEmitter(unit.getClassEmitter().method(functionNode)); + final CompilationEnvironment compEnv = compiler.getCompilationEnvironment(); + final boolean isRestOf = compEnv.isCompileRestOf(); + final ClassEmitter classEmitter = unit.getClassEmitter(); + method = lc.pushMethodEmitter(isRestOf ? classEmitter.restOfMethod(functionNode) : classEmitter.method(functionNode)); + if(useOptimisticTypes()) { + lc.pushUnwarrantedOptimismHandlers(); + } + // new method - reset last line number lastLineNumber = -1; // Mark end for variable tables. method.begin(); + + if (isRestOf) { + final ContinuationInfo ci = new ContinuationInfo(); + fnIdToContinuationInfo.put(fnId, ci); + method._goto(ci.handlerLabel); + } } return true; @@ -1112,15 +1532,24 @@ @Override public Node leaveFunctionNode(final FunctionNode functionNode) { try { - if(emittedMethods.add(functionNode.getName())) { + final boolean markOptimistic; + if (emittedMethods.add(functionNode.getName())) { + markOptimistic = generateUnwarrantedOptimismExceptionHandlers(); + generateContinuationHandler(); method.end(); // wrap up this method unit = lc.popCompileUnit(functionNode.getCompileUnit()); method = lc.popMethodEmitter(method); LOG.info("=== END ", functionNode.getName()); + } else { + markOptimistic = false; } - final FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.EMITTED); - newFunctionObject(newFunctionNode, functionNode); + FunctionNode newFunctionNode = functionNode.setState(lc, CompilationState.EMITTED); + if(markOptimistic) { + newFunctionNode = newFunctionNode.setFlag(lc, FunctionNode.IS_OPTIMISTIC); + } + + newFunctionObject(newFunctionNode, true); return newFunctionNode; } catch (final Throwable t) { Context.printStackTrace(t); @@ -1137,7 +1566,7 @@ @Override public boolean enterIfNode(final IfNode ifNode) { - lineNumber(ifNode); + enterStatement(ifNode); final Expression test = ifNode.getTest(); final Block pass = ifNode.getPass(); @@ -1178,6 +1607,10 @@ return false; } + private void enterStatement(final Statement statement) { + lineNumber(statement); + } + private void lineNumber(final Statement statement) { lineNumber(statement.getLineNumber()); } @@ -1212,6 +1645,7 @@ final Type elementType = arrayType.getElementType(); if (units != null) { + lc.enterSplitNode(); final MethodEmitter savedMethod = method; final FunctionNode currentFunction = lc.getCurrentFunction(); @@ -1251,6 +1685,7 @@ unit = lc.popCompileUnit(unit); } + lc.exitSplitNode(); return method; } @@ -1314,28 +1749,32 @@ * @param object object to load */ void loadConstant(final Object object) { - final String unitClassName = unit.getUnitClassName(); - final ClassEmitter classEmitter = unit.getClassEmitter(); + loadConstant(object, unit, method); + } + + private void loadConstant(final Object object, final CompileUnit compileUnit, final MethodEmitter methodEmitter) { + final String unitClassName = compileUnit.getUnitClassName(); + final ClassEmitter classEmitter = compileUnit.getClassEmitter(); final int index = compiler.getConstantData().add(object); final Class<?> cls = object.getClass(); if (cls == PropertyMap.class) { - method.load(index); - method.invokestatic(unitClassName, GET_MAP.symbolName(), methodDescriptor(PropertyMap.class, int.class)); + methodEmitter.load(index); + methodEmitter.invokestatic(unitClassName, GET_MAP.symbolName(), methodDescriptor(PropertyMap.class, int.class)); classEmitter.needGetConstantMethod(PropertyMap.class); } else if (cls.isArray()) { - method.load(index); + methodEmitter.load(index); final String methodName = ClassEmitter.getArrayMethodName(cls); - method.invokestatic(unitClassName, methodName, methodDescriptor(cls, int.class)); + methodEmitter.invokestatic(unitClassName, methodName, methodDescriptor(cls, int.class)); classEmitter.needGetConstantMethod(cls); } else { - method.loadConstants().load(index).arrayload(); + methodEmitter.loadConstants().load(index).arrayload(); if (object instanceof ArrayData) { // avoid cast to non-public ArrayData subclass - method.checkcast(ArrayData.class); - method.invoke(virtualCallNoLookup(ArrayData.class, "copy", ArrayData.class)); + methodEmitter.checkcast(ArrayData.class); + methodEmitter.invoke(virtualCallNoLookup(ArrayData.class, "copy", ArrayData.class)); } else if (cls != Object.class) { - method.checkcast(cls); + methodEmitter.checkcast(cls); } } } @@ -1382,7 +1821,7 @@ loadArray(arrayLiteral, atype); globalAllocateArray(atype); } else { - assert false : "Unknown literal for " + node.getClass() + " " + value.getClass() + " " + value; + throw new UnsupportedOperationException("Unknown literal for " + node.getClass() + " " + value.getClass() + " " + value); } return method; @@ -1437,61 +1876,58 @@ public boolean enterObjectNode(final ObjectNode objectNode) { final List<PropertyNode> elements = objectNode.getElements(); - final List<String> keys = new ArrayList<>(); - final List<Symbol> symbols = new ArrayList<>(); - final List<Expression> values = new ArrayList<>(); - - boolean hasGettersSetters = false; + final List<MapTuple<Expression>> tuples = new ArrayList<>(); + final List<PropertyNode> gettersSetters = new ArrayList<>(); Expression protoNode = null; - for (PropertyNode propertyNode: elements) { - final Expression value = propertyNode.getValue(); - final String key = propertyNode.getKeyName(); - final Symbol symbol = value == null ? null : propertyNode.getKey().getSymbol(); + boolean restOfProperty = false; + final CompilationEnvironment env = compiler.getCompilationEnvironment(); + final int ccp = env.getCurrentContinuationEntryPoint(); + + for (final PropertyNode propertyNode : elements) { + final Expression value = propertyNode.getValue(); + final String key = propertyNode.getKeyName(); + final Symbol symbol = value == null ? null : propertyNode.getKey().getSymbol(); if (value == null) { - hasGettersSetters = true; + gettersSetters.add(propertyNode); } else if (key.equals(ScriptObject.PROTO_PROPERTY_NAME)) { protoNode = value; continue; } - keys.add(key); - symbols.add(symbol); - values.add(value); + restOfProperty |= + value != null && + isValid(ccp) && + value instanceof Optimistic && + ((Optimistic)value).getProgramPoint() == ccp; + + //for literals, a value of null means object type, i.e. the value null or getter setter function + //(I think) + tuples.add(new MapTuple<Expression>(key, symbol, value) { + @Override + public Class<?> getValueType() { + return (OBJECT_FIELDS_ONLY || value == null || value.getType().isBoolean()) ? Object.class : value.getType().getTypeClass(); + } + }); } + final ObjectCreator<?> oc; if (elements.size() > OBJECT_SPILL_THRESHOLD) { - new SpillObjectCreator(this, keys, symbols, values).makeObject(method); + oc = new SpillObjectCreator(this, tuples); } else { - new FieldObjectCreator<Expression>(this, keys, symbols, values) { + oc = new FieldObjectCreator<Expression>(this, tuples) { @Override protected void loadValue(final Expression node) { load(node); - } - - /** - * Ensure that the properties start out as object types so that - * we can do putfield initializations instead of dynamicSetIndex - * which would be the case to determine initial property type - * otherwise. - * - * Use case, it's very expensive to do a million var x = {a:obj, b:obj} - * just to have to invalidate them immediately on initialization - * - * see NASHORN-594 - */ - @Override - protected MapCreator newMapCreator(final Class<?> fieldObjectClass) { - return new MapCreator(fieldObjectClass, keys, symbols) { - @Override - protected int getPropertyFlags(final Symbol symbol, final boolean hasArguments) { - return super.getPropertyFlags(symbol, hasArguments) | Property.IS_ALWAYS_OBJECT; - } - }; - } - - }.makeObject(method); + }}; + } + oc.makeObject(method); + //if this is a rest of method and our continuation point was found as one of the values + //in the properties above, we need to reset the map to oc.getMap() in the continuation + //handler + if (restOfProperty) { + getContinuationInfo().objectLiteralMap = oc.getMap(); } method.dup(); @@ -1503,31 +1939,26 @@ method.invoke(ScriptObject.SET_PROTO); } - if (hasGettersSetters) { - for (final PropertyNode propertyNode : elements) { - final FunctionNode getter = propertyNode.getGetter(); - final FunctionNode setter = propertyNode.getSetter(); - - if (getter == null && setter == null) { - continue; - } - - method.dup().loadKey(propertyNode.getKey()); - - if (getter == null) { - method.loadNull(); - } else { - getter.accept(this); - } - - if (setter == null) { - method.loadNull(); - } else { - setter.accept(this); - } - - method.invoke(ScriptObject.SET_USER_ACCESSORS); + for (final PropertyNode propertyNode : gettersSetters) { + final FunctionNode getter = propertyNode.getGetter(); + final FunctionNode setter = propertyNode.getSetter(); + + assert getter != null || setter != null; + + method.dup().loadKey(propertyNode.getKey()); + if (getter == null) { + method.loadNull(); + } else { + getter.accept(this); } + + if (setter == null) { + method.loadNull(); + } else { + setter.accept(this); + } + + method.invoke(ScriptObject.SET_USER_ACCESSORS); } method.store(objectNode.getSymbol()); @@ -1536,7 +1967,7 @@ @Override public boolean enterReturnNode(final ReturnNode returnNode) { - lineNumber(returnNode); + enterStatement(returnNode); method.registerReturn(); @@ -1558,7 +1989,7 @@ return node instanceof LiteralNode<?> && ((LiteralNode<?>) node).isNull(); } - private boolean nullCheck(final RuntimeNode runtimeNode, final List<Expression> args, final String signature) { + private boolean nullCheck(final RuntimeNode runtimeNode, final List<Expression> args) { final Request request = runtimeNode.getRequest(); if (!Request.isEQ(request) && !Request.isNE(request)) { @@ -1576,56 +2007,71 @@ rhs = tmp; } + if (!isNullLiteral(rhs)) { + return false; + } + + if (!lhs.getType().isObject()) { + return false; + } + // this is a null literal check, so if there is implicit coercion // involved like {D}x=null, we will fail - this is very rare - if (isNullLiteral(rhs) && lhs.getType().isObject()) { - final Label trueLabel = new Label("trueLabel"); - final Label falseLabel = new Label("falseLabel"); - final Label endLabel = new Label("end"); - - load(lhs); - method.dup(); - if (Request.isEQ(request)) { - method.ifnull(trueLabel); - } else if (Request.isNE(request)) { - method.ifnonnull(trueLabel); - } else { - assert false : "Invalid request " + request; - } - - method.label(falseLabel); - load(rhs); - method.invokestatic(CompilerConstants.className(ScriptRuntime.class), request.toString(), signature); - method._goto(endLabel); - - method.label(trueLabel); - // if NE (not strict) this can be "undefined != null" which is supposed to be false - if (request == Request.NE) { + final Label trueLabel = new Label("trueLabel"); + final Label falseLabel = new Label("falseLabel"); + final Label endLabel = new Label("end"); + + load(lhs); //lhs + final Label popLabel; + if (!Request.isStrict(request)) { + method.dup(); //lhs lhs + popLabel = new Label("pop"); + } else { + popLabel = null; + } + + if (Request.isEQ(request)) { + method.ifnull(!Request.isStrict(request) ? popLabel : trueLabel); + if (!Request.isStrict(request)) { method.loadUndefined(Type.OBJECT); - final Label isUndefined = new Label("isUndefined"); - final Label afterUndefinedCheck = new Label("afterUndefinedCheck"); - method.if_acmpeq(isUndefined); - // not undefined - method.load(true); - method._goto(afterUndefinedCheck); - method.label(isUndefined); - method.load(false); - method.label(afterUndefinedCheck); - } else { + method.if_acmpeq(trueLabel); + } + method.label(falseLabel); + method.load(false); + method._goto(endLabel); + if (!Request.isStrict(request)) { + method.label(popLabel); method.pop(); - method.load(true); } + method.label(trueLabel); + method.load(true); method.label(endLabel); - method.convert(runtimeNode.getType()); - method.store(runtimeNode.getSymbol()); - - return true; + } else if (Request.isNE(request)) { + method.ifnull(!Request.isStrict(request) ? popLabel : falseLabel); + if (!Request.isStrict(request)) { + method.loadUndefined(Type.OBJECT); + method.if_acmpeq(falseLabel); + } + method.label(trueLabel); + method.load(true); + method._goto(endLabel); + if (!Request.isStrict(request)) { + method.label(popLabel); + method.pop(); + } + method.label(falseLabel); + method.load(false); + method.label(endLabel); } - return false; + assert runtimeNode.getType().isBoolean(); + method.convert(runtimeNode.getType()); + method.store(runtimeNode.getSymbol()); + + return true; } - private boolean specializationCheck(final RuntimeNode.Request request, final Expression node, final List<Expression> args) { + private boolean specializationCheck(final RuntimeNode.Request request, final RuntimeNode node, final List<Expression> args) { if (!request.canSpecialize()) { return false; } @@ -1633,32 +2079,41 @@ assert args.size() == 2; final Type returnType = node.getType(); - load(args.get(0)); - load(args.get(1)); - - Request finalRequest = request; - - //if the request is a comparison, i.e. one that can be reversed - //it keeps its semantic, but make sure that the object comes in - //last - final Request reverse = Request.reverse(request); - if (method.peekType().isObject() && reverse != null) { //rhs is object - if (!method.peekType(1).isObject()) { //lhs is not object - method.swap(); //prefer object as lhs - finalRequest = reverse; + new OptimisticOperation() { + private Request finalRequest = request; + + @Override + void loadStack() { + load(args.get(0)); + load(args.get(1)); + + + //if the request is a comparison, i.e. one that can be reversed + //it keeps its semantic, but make sure that the object comes in + //last + final Request reverse = Request.reverse(request); + if (method.peekType().isObject() && reverse != null) { //rhs is object + if (!method.peekType(1).isObject()) { //lhs is not object + method.swap(); //prefer object as lhs + finalRequest = reverse; + } + } } - } - - method.dynamicRuntimeCall( - new SpecializedRuntimeNode( - finalRequest, - new Type[] { - method.peekType(1), - method.peekType() - }, - returnType).getInitialName(), - returnType, - finalRequest); + @Override + void consumeStack() { + method.dynamicRuntimeCall( + new SpecializedRuntimeNode( + finalRequest, + new Type[] { + method.peekType(1), + method.peekType() + }, + returnType).getInitialName(), + returnType, + finalRequest); + + } + }.emit(node); method.convert(node.getType()); method.store(node.getSymbol()); @@ -1681,8 +2136,6 @@ final List<Expression> args = runtimeNode.getArgs(); if (runtimeNode.isPrimitive() && !runtimeNode.isFinal() && isReducible(runtimeNode.getRequest())) { final Expression lhs = args.get(0); - assert args.size() > 1 : runtimeNode + " must have two args"; - final Expression rhs = args.get(1); final Type type = runtimeNode.getType(); final Symbol symbol = runtimeNode.getSymbol(); @@ -1690,23 +2143,33 @@ switch (runtimeNode.getRequest()) { case EQ: case EQ_STRICT: - return enterCmp(lhs, rhs, Condition.EQ, type, symbol); + return enterCmp(lhs, args.get(1), Condition.EQ, type, symbol); case NE: case NE_STRICT: - return enterCmp(lhs, rhs, Condition.NE, type, symbol); + return enterCmp(lhs, args.get(1), Condition.NE, type, symbol); case LE: - return enterCmp(lhs, rhs, Condition.LE, type, symbol); + return enterCmp(lhs, args.get(1), Condition.LE, type, symbol); case LT: - return enterCmp(lhs, rhs, Condition.LT, type, symbol); + return enterCmp(lhs, args.get(1), Condition.LT, type, symbol); case GE: - return enterCmp(lhs, rhs, Condition.GE, type, symbol); + return enterCmp(lhs, args.get(1), Condition.GE, type, symbol); case GT: - return enterCmp(lhs, rhs, Condition.GT, type, symbol); + return enterCmp(lhs, args.get(1), Condition.GT, type, symbol); case ADD: - Type widest = Type.widest(lhs.getType(), rhs.getType()); - load(lhs, widest); - load(rhs, widest); - method.add(); + final Expression rhs = args.get(1); + final Type widest = Type.widest(lhs.getType(), rhs.getType()); + new OptimisticOperation() { + @Override + void loadStack() { + load(lhs, widest); + load(rhs, widest); + } + + @Override + void consumeStack() { + method.add(runtimeNode.getProgramPoint()); + } + }.emit(runtimeNode); method.convert(type); method.store(symbol); return false; @@ -1717,7 +2180,7 @@ } } - if (nullCheck(runtimeNode, args, new FunctionSignature(false, false, runtimeNode.getType(), args).toString())) { + if (nullCheck(runtimeNode, args)) { return false; } @@ -1725,18 +2188,26 @@ return false; } - for (final Expression arg : args) { - load(arg, Type.OBJECT); - } - - method.invokestatic( - CompilerConstants.className(ScriptRuntime.class), - runtimeNode.getRequest().toString(), - new FunctionSignature( - false, - false, - runtimeNode.getType(), - args.size()).toString()); + new OptimisticOperation() { + @Override + void loadStack() { + for (final Expression arg : args) { + load(arg, Type.OBJECT); + } + } + @Override + void consumeStack() { + method.invokestatic( + CompilerConstants.className(ScriptRuntime.class), + runtimeNode.getRequest().toString(), + new FunctionSignature( + false, + false, + runtimeNode.getType(), + args.size()).toString()); + } + }.emit(runtimeNode); + method.convert(runtimeNode.getType()); method.store(runtimeNode.getSymbol()); @@ -1754,7 +2225,7 @@ final Class<?> rtype = fn.getReturnType().getTypeClass(); final boolean needsArguments = fn.needsArguments(); final Class<?>[] ptypes = needsArguments ? - new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class, Object.class} : + new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class, ScriptObject.class} : new Class<?>[] {ScriptFunction.class, Object.class, ScriptObject.class}; final MethodEmitter caller = method; @@ -1798,7 +2269,7 @@ private void fixScopeSlot(final FunctionNode functionNode) { // TODO hack to move the scope to the expected slot (needed because split methods reuse the same slots as the root method) if (functionNode.compilerConstant(SCOPE).getSlot() != SCOPE.slot()) { - method.load(Type.typeFor(ScriptObject.class), SCOPE.slot()); + method.load(SCOPE_TYPE, SCOPE.slot()); method.storeCompilerConstant(SCOPE); } } @@ -1821,7 +2292,7 @@ } catch (final Throwable t) { Context.printStackTrace(t); - final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + lc.getCurrentFunction().getSource().getName()); + final VerifyError e = new VerifyError("Code generation bug in \"" + splitNode.getName() + "\": likely stack misaligned: " + t + " " + getCurrentSource().getName()); e.initCause(t); throw e; } @@ -1889,7 +2360,7 @@ @Override public boolean enterSwitchNode(final SwitchNode switchNode) { - lineNumber(switchNode); + enterStatement(switchNode); final Expression expression = switchNode.getExpression(); final Symbol tag = switchNode.getTag(); @@ -1964,10 +2435,9 @@ } // If reasonable size and not too sparse (80%), use table otherwise use lookup. - if (range > 0 && range < 4096 && range < (size * 5 / 4)) { + if (range > 0 && range < 4096 && range <= (size * 5 / 4)) { final Label[] table = new Label[range]; Arrays.fill(table, defaultLabel); - for (int i = 0; i < size; i++) { final int value = values[i]; table[value - lo] = labels[i]; @@ -2014,7 +2484,7 @@ @Override public boolean enterThrowNode(final ThrowNode throwNode) { - lineNumber(throwNode); + enterStatement(throwNode); if (throwNode.isSyntheticRethrow()) { //do not wrap whatever this is in an ecma exception, just rethrow it @@ -2023,13 +2493,17 @@ return false; } - final Source source = lc.getCurrentFunction().getSource(); - + final Source source = getCurrentSource(); final Expression expression = throwNode.getExpression(); final int position = throwNode.position(); final int line = throwNode.getLineNumber(); final int column = source.getColumn(position); + // NOTE: we first evaluate the expression, and only after it was evaluated do we create the new ECMAException + // object and then somewhat cumbersomely move it beneath the evaluated expression on the stack. The reason for + // this is that if expression is optimistic (or contains an optimistic subexpression), we'd potentially access + // the not-yet-<init>ialized object on the stack from the UnwarrantedOptimismException handler, and bytecode + // verifier forbids that. load(expression, Type.OBJECT); method.load(source.getName()); @@ -2042,9 +2516,13 @@ return false; } + private Source getCurrentSource() { + return lc.getCurrentFunction().getSource(); + } + @Override public boolean enterTryNode(final TryNode tryNode) { - lineNumber(tryNode); + enterStatement(tryNode); final Block body = tryNode.getBody(); final List<Block> catchBlocks = tryNode.getCatchBlocks(); @@ -2053,7 +2531,6 @@ final Label recovery = new Label("catch"); final Label exit = tryNode.getExit(); final Label skip = new Label("skip"); - method.label(entry); body.accept(this); @@ -2062,12 +2539,14 @@ method._goto(skip); } + method._try(entry, exit, recovery, Throwable.class); method.label(exit); method._catch(recovery); method.store(symbol); - for (int i = 0; i < catchBlocks.size(); i++) { + final int catchBlockCount = catchBlocks.size(); + for (int i = 0; i < catchBlockCount; i++) { final Block catchBlock = catchBlocks.get(i); //TODO this is very ugly - try not to call enter/leave methods directly @@ -2084,7 +2563,7 @@ new Store<IdentNode>(exception) { @Override protected void storeNonDiscard() { - return; + //empty } @Override @@ -2106,38 +2585,41 @@ } }.store(); - final Label next; - - if (exceptionCondition != null) { - next = new Label("next"); - load(exceptionCondition, Type.BOOLEAN).ifeq(next); - } else { - next = null; + final boolean isConditionalCatch = exceptionCondition != null; + if (isConditionalCatch) { + load(exceptionCondition, Type.BOOLEAN); + // If catch body doesn't terminate the flow, then when we reach its break label, we could've come in + // through either true or false branch, so we'll need a copy of the boolean evaluation on the stack to + // know which path we took. On the other hand, if it does terminate the flow, then we won't have the + // boolean on the top of the stack at the jump join point, so we must not push it on the stack. + if(!catchBody.hasTerminalFlags()) { + method.dup(); + } + method.ifeq(catchBlock.getBreakLabel()); } catchBody.accept(this); - if (i + 1 != catchBlocks.size() && !catchBody.hasTerminalFlags()) { - method._goto(skip); - } - - if (next != null) { - if (i + 1 == catchBlocks.size()) { - // no next catch block - rethrow if condition failed - method._goto(skip); - method.label(next); - method.load(symbol).athrow(); - } else { - method.label(next); - } - } - leaveBlock(catchBlock); lc.pop(catchBlock); + + if(isConditionalCatch) { + if(!catchBody.hasTerminalFlags()) { + // If it was executed, skip. Note the dup() above that left us this value on stack. On the other + // hand, if the catch body terminates the flow, we can reach here only if it was not executed, so + // IFEQ is implied. + method.ifne(skip); + } + if(i + 1 == catchBlockCount) { + // No next catch block - rethrow if condition failed + method.load(symbol).athrow(); + } + } else { + assert i + 1 == catchBlockCount; + } } method.label(skip); - method._try(entry, exit, recovery, Throwable.class); // Finally body is always inlined elsewhere so it doesn't need to be emitted @@ -2153,7 +2635,7 @@ return false; } - lineNumber(varNode); + enterStatement(varNode); final IdentNode identNode = varNode.getName(); final Symbol identSymbol = identNode.getSymbol(); @@ -2199,7 +2681,7 @@ body.accept(this); if (!whileNode.isTerminal()) { method.label(continueLabel); - lineNumber(whileNode); + enterStatement(whileNode); new BranchOptimizer(this, method).execute(test, loopLabel, true); method.label(breakLabel); } @@ -2207,14 +2689,6 @@ return false; } - private void closeWith() { - if (method.hasScope()) { - method.loadCompilerConstant(SCOPE); - method.invoke(ScriptRuntime.CLOSE_WITH); - method.storeCompilerConstant(SCOPE); - } - } - @Override public boolean enterWithNode(final WithNode withNode) { final Expression expression = withNode.getExpression(); @@ -2226,28 +2700,26 @@ // for its side effect and visit the body, and not bother opening and closing a WithObject. final boolean hasScope = method.hasScope(); - final Label tryLabel; if (hasScope) { - tryLabel = new Label("with_try"); - method.label(tryLabel); method.loadCompilerConstant(SCOPE); - } else { - tryLabel = null; } load(expression, Type.OBJECT); + final Label tryLabel; if (hasScope) { // Construct a WithObject if we have a scope method.invoke(ScriptRuntime.OPEN_WITH); method.storeCompilerConstant(SCOPE); + tryLabel = new Label("with_try"); + method.label(tryLabel); } else { // We just loaded the expression for its side effect and to check // for null or undefined value. globalCheckObjectCoercible(); + tryLabel = null; } - // Always process body body.accept(this); @@ -2258,26 +2730,26 @@ final Label exitLabel = new Label("with_exit"); if (!body.isTerminal()) { - closeWith(); + popScope(); method._goto(exitLabel); } + method._try(tryLabel, endLabel, catchLabel); method.label(endLabel); method._catch(catchLabel); - closeWith(); + popScope(); method.athrow(); method.label(exitLabel); - method._try(tryLabel, endLabel, catchLabel); } return false; } @Override public boolean enterADD(final UnaryNode unaryNode) { - load(unaryNode.rhs(), unaryNode.getType()); + load(unaryNode.getExpression(), unaryNode.getType()); assert unaryNode.getType().isNumeric(); method.store(unaryNode.getSymbol()); return false; @@ -2285,13 +2757,13 @@ @Override public boolean enterBIT_NOT(final UnaryNode unaryNode) { - load(unaryNode.rhs(), Type.INT).load(-1).xor().store(unaryNode.getSymbol()); + load(unaryNode.getExpression(), Type.INT).load(-1).xor().store(unaryNode.getSymbol()); return false; } @Override public boolean enterDECINC(final UnaryNode unaryNode) { - final Expression rhs = unaryNode.rhs(); + final Expression rhs = unaryNode.getExpression(); final Type type = unaryNode.getType(); final TokenType tokenType = unaryNode.tokenType(); final boolean isPostfix = tokenType == TokenType.DECPOSTFIX || tokenType == TokenType.INCPOSTFIX; @@ -2301,18 +2773,26 @@ new SelfModifyingStore<UnaryNode>(unaryNode, rhs) { + private void loadRhs() { + load(rhs, type, true); + } + @Override protected void evaluate() { - load(rhs, type, true); - if (!isPostfix) { - if (type.isInteger()) { - method.load(isIncrement ? 1 : -1); - } else if (type.isLong()) { - method.load(isIncrement ? 1L : -1L); - } else { - method.load(isIncrement ? 1.0 : -1.0); - } - method.add(); + if(isPostfix) { + loadRhs(); + } else { + new OptimisticOperation() { + @Override + void loadStack() { + loadRhs(); + loadMinusOne(); + } + @Override + void consumeStack() { + doDecInc(); + } + }.emit(unaryNode, getOptimisticIgnoreCountForSelfModifyingExpression(rhs)); } } @@ -2320,24 +2800,44 @@ protected void storeNonDiscard() { super.storeNonDiscard(); if (isPostfix) { - if (type.isInteger()) { - method.load(isIncrement ? 1 : -1); - } else if (type.isLong()) { - method.load(isIncrement ? 1L : 1L); - } else { - method.load(isIncrement ? 1.0 : -1.0); - } - method.add(); + new OptimisticOperation() { + @Override + void loadStack() { + loadMinusOne(); + } + @Override + void consumeStack() { + doDecInc(); + } + }.emit(unaryNode, 1); // 1 for non-incremented result on the top of the stack pushed in evaluate() } } + + private void loadMinusOne() { + if (type.isInteger()) { + method.load(isIncrement ? 1 : -1); + } else if (type.isLong()) { + method.load(isIncrement ? 1L : -1L); + } else { + method.load(isIncrement ? 1.0 : -1.0); + } + } + + private void doDecInc() { + method.add(unaryNode.getProgramPoint()); + } }.store(); return false; } + private static int getOptimisticIgnoreCountForSelfModifyingExpression(final Expression target) { + return target instanceof AccessNode ? 1 : target instanceof IndexNode ? 2 : 0; + } + @Override public boolean enterDISCARD(final UnaryNode unaryNode) { - final Expression rhs = unaryNode.rhs(); + final Expression rhs = unaryNode.getExpression(); lc.pushDiscard(rhs); load(rhs); @@ -2353,7 +2853,7 @@ @Override public boolean enterNEW(final UnaryNode unaryNode) { - final CallNode callNode = (CallNode)unaryNode.rhs(); + final CallNode callNode = (CallNode)unaryNode.getExpression(); final List<Expression> args = callNode.getArgs(); // Load function reference. @@ -2367,7 +2867,7 @@ @Override public boolean enterNOT(final UnaryNode unaryNode) { - final Expression rhs = unaryNode.rhs(); + final Expression rhs = unaryNode.getExpression(); load(rhs, Type.BOOLEAN); @@ -2388,22 +2888,41 @@ @Override public boolean enterSUB(final UnaryNode unaryNode) { assert unaryNode.getType().isNumeric(); - load(unaryNode.rhs(), unaryNode.getType()).neg().store(unaryNode.getSymbol()); + new OptimisticOperation() { + @Override + void loadStack() { + load(unaryNode.getExpression(), unaryNode.getType()); + } + @Override + void consumeStack() { + method.neg(unaryNode.getProgramPoint()); + } + }.emit(unaryNode); + method.store(unaryNode.getSymbol()); + return false; } @Override public boolean enterVOID(final UnaryNode unaryNode) { - load(unaryNode.rhs()).pop(); + load(unaryNode.getExpression()).pop(); method.loadUndefined(Type.OBJECT); return false; } - private void enterNumericAdd(final Expression lhs, final Expression rhs, final Type type, final Symbol symbol) { - loadBinaryOperands(lhs, rhs, type); - method.add(); //if the symbol is optimistic, it always needs to be written, not on the stack? - method.store(symbol); + private void enterNumericAdd(final BinaryNode binaryNode, final Expression lhs, final Expression rhs, final Type type) { + new OptimisticOperation() { + @Override + void loadStack() { + loadBinaryOperands(lhs, rhs, type); + } + @Override + void consumeStack() { + method.add(binaryNode.getProgramPoint()); //if the symbol is optimistic, it always needs to be written, not on the stack? + } + }.emit(binaryNode); + method.store(binaryNode.getSymbol()); } @Override @@ -2413,10 +2932,10 @@ final Type type = binaryNode.getType(); if (type.isNumeric()) { - enterNumericAdd(lhs, rhs, type, binaryNode.getSymbol()); + enterNumericAdd(binaryNode, lhs, rhs, type); } else { loadBinaryOperands(binaryNode); - method.add(); + method.add(INVALID_PROGRAM_POINT); method.store(binaryNode.getSymbol()); } @@ -2508,8 +3027,17 @@ @Override protected void evaluate() { - loadBinaryOperands(assignNode.lhs(), assignNode.rhs(), opType, true); - op(); + final Expression lhs = assignNode.lhs(); + new OptimisticOperation() { + @Override + void loadStack() { + loadBinaryOperands(lhs, assignNode.rhs(), opType, true); + } + @Override + void consumeStack() { + op(); + } + }.emit(assignNode, getOptimisticIgnoreCountForSelfModifyingExpression(lhs)); method.convert(assignNode.getType()); } } @@ -2537,7 +3065,7 @@ Type.OBJECT, Request.ADD); } else { - method.add(); + method.add(binaryNode.getProgramPoint()); } } @@ -2591,7 +3119,7 @@ new AssignOp(binaryNode) { @Override protected void op() { - method.div(); + method.div(binaryNode.getProgramPoint()); } }.store(); @@ -2615,7 +3143,7 @@ new AssignOp(binaryNode) { @Override protected void op() { - method.mul(); + method.mul(binaryNode.getProgramPoint()); } }.store(); @@ -2664,7 +3192,7 @@ new AssignOp(binaryNode) { @Override protected void op() { - method.sub(); + method.sub(binaryNode.getProgramPoint()); } }.store(); @@ -2679,8 +3207,16 @@ protected abstract void op(); protected void evaluate(final BinaryNode node) { - loadBinaryOperands(node); - op(); + new OptimisticOperation() { + @Override + void loadStack() { + loadBinaryOperands(node); + } + @Override + void consumeStack() { + op(); + } + }.emit(node); method.store(node.getSymbol()); } } @@ -2725,6 +3261,7 @@ final Expression lhs = binaryNode.lhs(); final Expression rhs = binaryNode.rhs(); + assert lhs.isTokenType(TokenType.DISCARD); load(lhs); load(rhs); method.store(binaryNode.getSymbol()); @@ -2747,7 +3284,7 @@ new BinaryArith() { @Override protected void op() { - method.div(); + method.div(binaryNode.getProgramPoint()); } }.evaluate(binaryNode); @@ -2830,7 +3367,7 @@ new BinaryArith() { @Override protected void op() { - method.mul(); + method.mul(binaryNode.getProgramPoint()); } }.evaluate(binaryNode); @@ -2900,7 +3437,7 @@ new BinaryArith() { @Override protected void op() { - method.sub(); + method.sub(binaryNode.getProgramPoint()); } }.evaluate(binaryNode); @@ -2942,7 +3479,7 @@ /** * Generate all shared scope calls generated during codegen. */ - protected void generateScopeCalls() { + void generateScopeCalls() { for (final SharedScopeCall scopeAccess : lc.getScopeCalls()) { scopeAccess.generateScopeCall(); } @@ -3055,6 +3592,7 @@ if (targetSymbol.isScope()) { method.load(scopeSymbol); depth++; + assert depth == 1; } return false; } @@ -3066,6 +3604,7 @@ load(base, Type.OBJECT); depth += Type.OBJECT.getSlots(); + assert depth == 1; if (isSelfModifying()) { method.dup(); @@ -3167,10 +3706,11 @@ final Symbol symbol = node.getSymbol(); assert symbol != null; if (symbol.isScope()) { + final int flags = CALLSITE_SCOPE | getCallSiteFlags(); if (isFastScope(symbol)) { - storeFastScopeVar(symbol, CALLSITE_SCOPE | getCallSiteFlags()); + storeFastScopeVar(symbol, flags); } else { - method.dynamicSet(node.getName(), CALLSITE_SCOPE | getCallSiteFlags()); + method.dynamicSet(node.getName(), flags); } } else { method.convert(node.getType()); @@ -3210,35 +3750,64 @@ } } - private void newFunctionObject(final FunctionNode functionNode, final FunctionNode originalFunctionNode) { + private void newFunctionObject(final FunctionNode functionNode, final boolean addInitializer) { assert lc.peek() == functionNode; - // We don't emit a ScriptFunction on stack for: - // 1. the outermost compiled function (as there's no code being generated in its outer context that'd need it - // as a callee), and - // 2. for functions that are immediately called upon definition and they don't need a callee, e.g. (function(){})(). - // Such immediately-called functions are invoked using INVOKESTATIC (see enterFunctionNode() of the embedded - // visitor of enterCallNode() for details), and if they don't need a callee, they don't have it on their - // static method's parameter list. - if (lc.getOutermostFunction() == functionNode || - (!functionNode.needsCallee()) && lc.isFunctionDefinedInCurrentCall(originalFunctionNode)) { + + final int fnId = functionNode.getId(); + final CompilationEnvironment env = compiler.getCompilationEnvironment(); + RecompilableScriptFunctionData data = env.getScriptFunctionData(fnId); + // data != null => compileOutermostOnly() + assert data == null || compileOutermostOnly() : functionNode.getName() + " isRecompile=" + env.isOnDemandCompilation() + " data=" + data; + if(data == null) { + final Map<Integer, RecompilableScriptFunctionData> nestedFunctions = fnIdToNestedFunctions.get(fnId); + assert nestedFunctions != null; + // Generate the object class and property map in case this function is ever used as constructor + final int fieldCount = getPaddedFieldCount(functionNode.countThisProperties()); + final String allocatorClassName = Compiler.binaryName(getClassName(fieldCount)); + final PropertyMap allocatorMap = PropertyMap.newMap(null, 0, fieldCount, 0); + + data = new RecompilableScriptFunctionData(functionNode, compiler.getCodeInstaller(), allocatorClassName, allocatorMap, nestedFunctions, compiler.getSourceURL()); + + final FunctionNode parentFn = lc.getParentFunction(functionNode); + if(parentFn == null) { + if(functionNode.isProgram()) { + // Emit the "public static ScriptFunction createScriptFunction(ScriptObject scope)" method + final CompileUnit fnUnit = functionNode.getCompileUnit(); + final MethodEmitter createFunction = fnUnit.getClassEmitter().method( + EnumSet.of(Flag.PUBLIC, Flag.STATIC), CREATE_PROGRAM_FUNCTION.symbolName(), + ScriptFunction.class, ScriptObject.class); + createFunction.begin(); + createFunction._new(SCRIPTFUNCTION_IMPL_NAME, SCRIPTFUNCTION_IMPL_TYPE).dup(); + loadConstant(data, fnUnit, createFunction); + createFunction.load(SCOPE_TYPE, 0); + createFunction.invoke(constructorNoLookup(SCRIPTFUNCTION_IMPL_NAME, RecompilableScriptFunctionData.class, ScriptObject.class)); + createFunction._return(); + createFunction.end(); + } + } else { + fnIdToNestedFunctions.get(parentFn.getId()).put(fnId, data); + } + } + + if(addInitializer && !env.isOnDemandCompilation()) { + functionNode.getCompileUnit().addFunctionInitializer(data, functionNode); + } + + // We don't emit a ScriptFunction on stack for the outermost compiled function (as there's no code being + // generated in its outer context that'd need it as a callee). + if (lc.getOutermostFunction() == functionNode) { return; } - // Generate the object class and property map in case this function is ever used as constructor - final String className = SCRIPTFUNCTION_IMPL_OBJECT; - final int fieldCount = ObjectClassGenerator.getPaddedFieldCount(functionNode.countThisProperties()); - final String allocatorClassName = Compiler.binaryName(ObjectClassGenerator.getClassName(fieldCount)); - final PropertyMap allocatorMap = PropertyMap.newMap(null, 0, fieldCount, 0); - - method._new(className).dup(); - loadConstant(new RecompilableScriptFunctionData(functionNode, compiler.getCodeInstaller(), allocatorClassName, allocatorMap)); - - if (functionNode.isLazy() || functionNode.needsParentScope()) { + method._new(SCRIPTFUNCTION_IMPL_NAME, SCRIPTFUNCTION_IMPL_TYPE).dup(); + loadConstant(data); + + if (functionNode.needsParentScope()) { method.loadCompilerConstant(SCOPE); } else { method.loadNull(); } - method.invoke(constructorNoLookup(className, RecompilableScriptFunctionData.class, ScriptObject.class)); + method.invoke(constructorNoLookup(SCRIPTFUNCTION_IMPL_NAME, RecompilableScriptFunctionData.class, ScriptObject.class)); } // calls on Global class. @@ -3271,6 +3840,10 @@ return method.invokestatic(GLOBAL_OBJECT, "isEval", methodDescriptor(boolean.class, Object.class)); } + private MethodEmitter globalReplaceLocationPropertyPlaceholder() { + return method.invokestatic(GLOBAL_OBJECT, "replaceLocationPropertyPlaceholder", methodDescriptor(Object.class, Object.class, Object.class)); + } + private MethodEmitter globalCheckObjectCoercible() { return method.invokestatic(GLOBAL_OBJECT, "checkObjectCoercible", methodDescriptor(void.class, Object.class)); } @@ -3279,4 +3852,593 @@ return method.invokestatic(GLOBAL_OBJECT, "directEval", methodDescriptor(Object.class, Object.class, Object.class, Object.class, Object.class, Object.class)); } + + private abstract class OptimisticOperation { + MethodEmitter emit(final Optimistic optimistic) { + return emit(optimistic, 0); + } + + MethodEmitter emit(final Optimistic optimistic, final Type desiredType) { + return emit(optimistic, desiredType, 0); + } + + MethodEmitter emit(final Optimistic optimistic, final Type desiredType, final int ignoredArgCount) { + return emit(optimistic.isOptimistic() && !desiredType.isObject(), optimistic.getProgramPoint(), ignoredArgCount); + } + + MethodEmitter emit(final Optimistic optimistic, final int ignoredArgCount) { + return emit(optimistic.isOptimistic(), optimistic.getProgramPoint(), ignoredArgCount); + } + + MethodEmitter emit(final boolean isOptimistic, final int programPoint, final int ignoredArgCount) { + final CompilationEnvironment env = compiler.getCompilationEnvironment(); + final boolean reallyOptimistic = isOptimistic && useOptimisticTypes(); + final boolean optimisticOrContinuation = reallyOptimistic || env.isContinuationEntryPoint(programPoint); + final boolean currentContinuationEntryPoint = env.isCurrentContinuationEntryPoint(programPoint); + final int stackSizeOnEntry = method.getStackSize() - ignoredArgCount; + + // First store the values on the stack opportunistically into local variables. Doing it before loadStack() + // allows us to not have to pop/load any arguments that are pushed onto it by loadStack() in the second + // storeStack(). + storeStack(ignoredArgCount, optimisticOrContinuation); + + // Now, load the stack + loadStack(); + + // Now store the values on the stack ultimately into local variables . In vast majority of cases, this is + // (aside from creating the local types map) a no-op, as the first opportunistic stack store will already + // store all variables. However, there can be operations in the loadStack() that invalidate some of the + // stack stores, e.g. in "x[i] = x[++i]", "++i" will invalidate the already stored value for "i". In such + // unfortunate cases this second storeStack() will restore the invariant that everything on the stack is + // stored into a local variable, although at the cost of doing a store/load on the loaded arguments as well. + final int liveLocalsCount = storeStack(method.getStackSize() - stackSizeOnEntry, optimisticOrContinuation); + assert optimisticOrContinuation == (liveLocalsCount != -1); + assert !optimisticOrContinuation || everyTypeIsKnown(method.getLocalVariableTypes(), liveLocalsCount); + + final Label beginTry; + final Label catchLabel; + final Label afterConsumeStack = reallyOptimistic || currentContinuationEntryPoint ? new Label("") : null; + if(reallyOptimistic) { + beginTry = new Label(""); + catchLabel = new Label(""); + method.label(beginTry); + } else { + beginTry = catchLabel = null; + } + + consumeStack(); + + if(reallyOptimistic) { + method._try(beginTry, afterConsumeStack, catchLabel, UnwarrantedOptimismException.class); + } + + if(reallyOptimistic || currentContinuationEntryPoint) { + method.label(afterConsumeStack); + + final int[] localLoads = method.getLocalLoadsOnStack(0, stackSizeOnEntry); + assert everyStackValueIsLocalLoad(localLoads) : Arrays.toString(localLoads) + ", " + stackSizeOnEntry + ", " + ignoredArgCount; + final List<Type> localTypesList = method.getLocalVariableTypes(); + final int usedLocals = getUsedSlotsWithLiveTemporaries(localTypesList, localLoads); + final Type[] localTypes = localTypesList.subList(0, usedLocals).toArray(new Type[usedLocals]); + assert everyLocalLoadIsValid(localLoads, usedLocals) : Arrays.toString(localLoads) + " ~ " + Arrays.toString(localTypes); + + if(reallyOptimistic) { + addUnwarrantedOptimismHandlerLabel(localTypes, catchLabel); + } + if(currentContinuationEntryPoint) { + final ContinuationInfo ci = getContinuationInfo(); + assert ci.targetLabel == null; // No duplicate program points + ci.targetLabel = afterConsumeStack; + ci.localVariableTypes = localTypes; + + ci.stackStoreSpec = localLoads; + + ci.stackTypes = Arrays.copyOf(method.getTypesFromStack(method.getStackSize()), stackSizeOnEntry); + assert ci.stackStoreSpec.length == ci.stackTypes.length; + ci.returnValueType = method.peekType(); + } + } + return method; + } + + /** + * Stores the current contents of the stack into local variables so they are not lost before invoking something that + * can result in an {@code UnwarantedOptimizationException}. + * @param ignoreArgCount the number of topmost arguments on stack to ignore when deciding on the shape of the catch + * block. Those are used in the situations when we could not place the call to {@code storeStack} early enough + * (before emitting code for pushing the arguments that the optimistic call will pop). This is admittedly a + * deficiency in the design of the code generator when it deals with self-assignments and we should probably look + * into fixing it. + * @return types of the significant local variables after the stack was stored (types for local variables used + * for temporary storage of ignored arguments are not returned). + * @param optimisticOrContinuation if false, this method should not execute + * a label for a catch block for the {@code UnwarantedOptimizationException}, suitable for capturing the + * currently live local variables, tailored to their types. + */ + private int storeStack(final int ignoreArgCount, final boolean optimisticOrContinuation) { + if(!optimisticOrContinuation) { + return -1; // NOTE: correct value to return is lc.getUsedSlotCount(), but it wouldn't be used anyway + } + + final int stackSize = method.getStackSize(); + final Type[] stackTypes = method.getTypesFromStack(stackSize); + final int[] localLoadsOnStack = method.getLocalLoadsOnStack(0, stackSize); + final int usedSlots = getUsedSlotsWithLiveTemporaries(method.getLocalVariableTypes(), localLoadsOnStack); + + final int firstIgnored = stackSize - ignoreArgCount; + // Find the first value on the stack (from the bottom) that is not a load from a local variable. + int firstNonLoad = 0; + while(firstNonLoad < firstIgnored && localLoadsOnStack[firstNonLoad] != Label.Stack.NON_LOAD) { + firstNonLoad++; + } + + // Only do the store/load if first non-load is not an ignored argument. Otherwise, do nothing and return + // the number of used slots as the number of live local variables. + if(firstNonLoad >= firstIgnored) { + return usedSlots; + } + + // Find the number of new temporary local variables that we need; it's the number of values on the stack that + // are not direct loads of existing local variables. + int tempSlotsNeeded = 0; + for(int i = firstNonLoad; i < stackSize; ++i) { + if(localLoadsOnStack[i] == Label.Stack.NON_LOAD) { + tempSlotsNeeded += stackTypes[i].getSlots(); + } + } + + // Ensure all values on the stack that weren't directly loaded from a local variable are stored in a local + // variable. We're starting from highest local variable index, so that in case ignoreArgCount > 0 the ignored + // ones end up at the end of the local variable table. + int lastTempSlot = usedSlots + tempSlotsNeeded; + int ignoreSlotCount = 0; + for(int i = stackSize; i -- > firstNonLoad;) { + final int loadSlot = localLoadsOnStack[i]; + if(loadSlot == Label.Stack.NON_LOAD) { + final Type type = stackTypes[i]; + final int slots = type.getSlots(); + lastTempSlot -= slots; + if(i >= firstIgnored) { + ignoreSlotCount += slots; + } + method.store(type, lastTempSlot); + } else { + method.pop(); + } + } + assert lastTempSlot == usedSlots; // used all temporary locals + + final List<Type> localTypesList = method.getLocalVariableTypes(); + + // Load values back on stack. + for(int i = firstNonLoad; i < stackSize; ++i) { + final int loadSlot = localLoadsOnStack[i]; + final Type stackType = stackTypes[i]; + final boolean isLoad = loadSlot != Label.Stack.NON_LOAD; + final int lvarSlot = isLoad ? loadSlot : lastTempSlot; + final Type lvarType = localTypesList.get(lvarSlot); + method.load(lvarType, lvarSlot); + if(isLoad) { + // Conversion operators (I2L etc.) preserve "load"-ness of the value despite the fact that, in the + // strict sense they are creating a derived value from the loaded value. This special behavior of + // on-stack conversion operators is necessary to accommodate for differences in local variable types + // after deoptimization; having a conversion operator throw away "load"-ness would create different + // local variable table shapes between optimism-failed code and its deoptimized rest-of method). + // After we load the value back, we need to redo the conversion to the stack type if stack type is + // different. + // NOTE: this would only strictly be necessary for widening conversions (I2L, L2D, I2D), and not for + // narrowing ones (L2I, D2L, D2I) as only widening conversions are the ones that can get eliminated + // in a deoptimized method, as their original input argument got widened. Maybe experiment with + // throwing away "load"-ness for narrowing conversions in MethodEmitter.convert()? + method.convert(stackType); + } else { + // temporary stores never needs a convert, as their type is always the same as the stack type. + assert lvarType == stackType; + lastTempSlot += lvarType.getSlots(); + } + } + // used all temporaries + assert lastTempSlot == usedSlots + tempSlotsNeeded; + + return lastTempSlot - ignoreSlotCount; + } + + private void addUnwarrantedOptimismHandlerLabel(final Type[] localTypes, final Label label) { + final String lvarTypesDescriptor = getLvarTypesDescriptor(localTypes); + final Map<String, Collection<Label>> unwarrantedOptimismHandlers = lc.getUnwarrantedOptimismHandlers(); + Collection<Label> labels = unwarrantedOptimismHandlers.get(lvarTypesDescriptor); + if(labels == null) { + labels = new LinkedList<>(); + unwarrantedOptimismHandlers.put(lvarTypesDescriptor, labels); + } + labels.add(label); + } + + /** + * Returns the number of used local variable slots, including all live stack-store temporaries. + * @param localVariableTypes the current local variable types + * @param localLoadsOnStack the current local variable loads on the stack + * @return the number of used local variable slots, including all live stack-store temporaries. + */ + private int getUsedSlotsWithLiveTemporaries(List<Type> localVariableTypes, int[] localLoadsOnStack) { + // There are at least as many as are declared by the current blocks. + int usedSlots = lc.getUsedSlotCount(); + // Look at every load on the stack, and bump the number of used slots up by the temporaries seen there. + for(int i = 0; i < localLoadsOnStack.length; ++i) { + final int slot = localLoadsOnStack[i]; + if(slot != Label.Stack.NON_LOAD) { + int afterSlot = slot + localVariableTypes.get(slot).getSlots(); + if(afterSlot > usedSlots) { + usedSlots = afterSlot; + } + } + } + return usedSlots; + } + + abstract void loadStack(); + + // Make sure that whatever indy call site you emit from this method uses {@code getCallSiteFlagsOptimistic(node)} + // or otherwise ensure optimistic flag is correctly set in the call site, otherwise it doesn't make much sense + // to use OptimisticExpression for emitting it. + abstract void consumeStack(); + } + + private static boolean everyLocalLoadIsValid(final int[] loads, int localCount) { + for(int i = 0; i < loads.length; ++i) { + if(loads[i] < 0 || loads[i] >= localCount) { + return false; + } + } + return true; + } + + private static boolean everyTypeIsKnown(final List<Type> types, final int liveLocalsCount) { + assert types instanceof RandomAccess; + for(int i = 0; i < liveLocalsCount;) { + final Type t = types.get(i); + if(t == Type.UNKNOWN) { + return false; + } + i += t.getSlots(); + } + return true; + } + + private static boolean everyStackValueIsLocalLoad(final int[] loads) { + for(int i = 0; i < loads.length; ++i) { + if(loads[i] == Label.Stack.NON_LOAD) { + return false; + } + } + return true; + } + + private static String getLvarTypesDescriptor(final Type[] localVarTypes) { + final StringBuilder desc = new StringBuilder(localVarTypes.length); + for(int i = 0; i < localVarTypes.length;) { + i += appendType(desc, localVarTypes[i]); + } + // Trailing unknown types are unnecessary. (These don't actually occur though as long as we conservatively + // force-initialize all potentially-top values.) + for(int l = desc.length(); l-- > 0;) { + if(desc.charAt(l) != 'U') { + desc.setLength(l + 1); + break; + } + } + return desc.toString(); + } + + private static int appendType(final StringBuilder b, final Type t) { + b.append(t.getBytecodeStackType()); + return t.getSlots(); + } + + /** + * Generates all the required {@code UnwarrantedOptimismException} handlers for the current function. The employed + * strategy strives to maximize code reuse. Every handler constructs an array to hold the local variables, then + * fills in some trailing part of the local variables (those for which it has a unique suffix in the descriptor), + * then jumps to a handler for a prefix that's shared with other handlers. A handler that fills up locals up to + * position 0 will not jump to a prefix handler (as it has no prefix), but instead end with constructing and + * throwing a {@code RewriteException}. Since we lexicographically sort the entries, we only need to check every + * entry to its immediately preceding one for longest matching prefix. + * @return true if there is at least one exception handler + */ + private boolean generateUnwarrantedOptimismExceptionHandlers() { + if(!useOptimisticTypes()) { + return false; + } + + // Take the mapping of lvarSpecs -> labels, and turn them into a descending lexicographically sorted list of + // handler specifications. + final Map<String, Collection<Label>> unwarrantedOptimismHandlers = lc.popUnwarrantedOptimismHandlers(); + if(unwarrantedOptimismHandlers.isEmpty()) { + return false; + } + final List<OptimismExceptionHandlerSpec> handlerSpecs = new ArrayList<>(unwarrantedOptimismHandlers.size() * 4/3); + for(final String spec: unwarrantedOptimismHandlers.keySet()) { + handlerSpecs.add(new OptimismExceptionHandlerSpec(spec, true)); + } + Collections.sort(handlerSpecs, Collections.reverseOrder()); + + // Map of local variable specifications to labels for populating the array for that local variable spec. + final Map<String, Label> delegationLabels = new HashMap<>(); + + // Do everything in a single pass over the handlerSpecs list. Note that the list can actually grow as we're + // passing through it as we might add new prefix handlers into it, so can't hoist size() outside of the loop. + for(int handlerIndex = 0; handlerIndex < handlerSpecs.size(); ++handlerIndex) { + final OptimismExceptionHandlerSpec spec = handlerSpecs.get(handlerIndex); + final String lvarSpec = spec.lvarSpec; + if(spec.catchTarget) { + // Start a catch block and assign the labels for this lvarSpec with it. + method._catch(unwarrantedOptimismHandlers.get(lvarSpec)); + // This spec is a catch target, so emit array creation code + method.load(spec.lvarSpec.length()); + method.newarray(Type.OBJECT_ARRAY); + } + if(spec.delegationTarget) { + // If another handler can delegate to this handler as its prefix, then put a jump target here for the + // shared code (after the array creation code, which is never shared). + method.label(delegationLabels.get(lvarSpec)); // label must exist + } + + final boolean lastHandler = handlerIndex == handlerSpecs.size() - 1; + + int lvarIndex; + final int firstArrayIndex; + Label delegationLabel; + final String commonLvarSpec; + if(lastHandler) { + // Last handler block, doesn't delegate to anything. + lvarIndex = 0; + firstArrayIndex = 0; + delegationLabel = null; + commonLvarSpec = null; + } else { + // Not yet the last handler block, will definitely delegate to another handler; let's figure out which + // one. It can be an already declared handler further down the list, or it might need to declare a new + // prefix handler. + + // Since we're lexicographically ordered, the common prefix handler is defined by the common prefix of + // this handler and the next handler on the list. + final int nextHandlerIndex = handlerIndex + 1; + final String nextLvarSpec = handlerSpecs.get(nextHandlerIndex).lvarSpec; + commonLvarSpec = commonPrefix(lvarSpec, nextLvarSpec); + + // Let's find if we already have a declaration for such handler, or we need to insert it. + { + boolean addNewHandler = true; + int commonHandlerIndex = nextHandlerIndex; + for(; commonHandlerIndex < handlerSpecs.size(); ++commonHandlerIndex) { + final OptimismExceptionHandlerSpec forwardHandlerSpec = handlerSpecs.get(commonHandlerIndex); + final String forwardLvarSpec = forwardHandlerSpec.lvarSpec; + if(forwardLvarSpec.equals(commonLvarSpec)) { + // We already have a handler for the common prefix. + addNewHandler = false; + // Make sure we mark it as a delegation target. + forwardHandlerSpec.delegationTarget = true; + break; + } else if(!forwardLvarSpec.startsWith(commonLvarSpec)) { + break; + } + } + if(addNewHandler) { + // We need to insert a common prefix handler. Note handlers created with catchTarget == false + // will automatically have delegationTarget == true (because that's the only reason for their + // existence). + handlerSpecs.add(commonHandlerIndex, new OptimismExceptionHandlerSpec(commonLvarSpec, false)); + } + } + + // Calculate the local variable index at the end of the common prefix + firstArrayIndex = commonLvarSpec.length(); + lvarIndex = 0; + for(int j = 0; j < firstArrayIndex; ++j) { + lvarIndex += CodeGeneratorLexicalContext.getTypeForSlotDescriptor(commonLvarSpec.charAt(j)).getSlots(); + } + + // Create a delegation label if not already present + delegationLabel = delegationLabels.get(commonLvarSpec); + if(delegationLabel == null) { + // uo_pa == "unwarranted optimism, populate array" + delegationLabel = new Label("uo_pa_" + commonLvarSpec); + delegationLabels.put(commonLvarSpec, delegationLabel); + } + } + + // Load local variables handled by this handler on stack + int args = 0; + for(int arrayIndex = firstArrayIndex; arrayIndex < lvarSpec.length(); ++arrayIndex) { + final Type lvarType = CodeGeneratorLexicalContext.getTypeForSlotDescriptor(lvarSpec.charAt(arrayIndex)); + if (!lvarType.isUnknown()) { + method.load(lvarType, lvarIndex); + args++; + } + lvarIndex += lvarType.getSlots(); + } + // Delegate actual storing into array to an array populator utility method. These are reused within a + // compilation unit. + //on the stack: + // object array to be populated + // start index + // a lot of types + method.dynamicArrayPopulatorCall(args + 1, firstArrayIndex); + + if(delegationLabel != null) { + // We cascade to a prefix handler to fill out the rest of the local variables and throw the + // RewriteException. + assert !lastHandler; + assert commonLvarSpec != null; + final OptimismExceptionHandlerSpec nextSpec = handlerSpecs.get(handlerIndex + 1); + // If the delegate immediately follows, and it's not a catch target (so it doesn't have array setup + // code) don't bother emitting a jump, as we'd just jump to the next instruction. + if(!nextSpec.lvarSpec.equals(commonLvarSpec) || nextSpec.catchTarget) { + method._goto(delegationLabel); + } + } else { + assert lastHandler; + // Nothing to delegate to, so this handler must create and throw the RewriteException. + // At this point we have the UnwarrantedOptimismException and the Object[] with local variables on + // stack. We need to create a RewriteException, push two references to it below the constructor + // arguments, invoke the constructor, and throw the exception. + method._new(RewriteException.class); + method.dup(2); + method.dup(2); + method.pop(); + final CompilationEnvironment env = compiler.getCompilationEnvironment(); + if(env.isCompileRestOf()) { + loadConstant(env.getContinuationEntryPoints()); + method.invoke(INIT_REWRITE_EXCEPTION_REST_OF); + } else { + method.invoke(INIT_REWRITE_EXCEPTION); + } + method.athrow(); + } + } + return true; + } + + private static String commonPrefix(String s1, String s2) { + final int l1 = s1.length(); + final int l = Math.min(l1, s2.length()); + for(int i = 0; i < l; ++i) { + if(s1.charAt(i) != s2.charAt(i)) { + return s1.substring(0, i); + } + } + return l == l1 ? s1 : s2; + } + + private static class OptimismExceptionHandlerSpec implements Comparable<OptimismExceptionHandlerSpec> { + private final String lvarSpec; + private final boolean catchTarget; + private boolean delegationTarget; + + OptimismExceptionHandlerSpec(final String lvarSpec, boolean catchTarget) { + this.lvarSpec = lvarSpec; + this.catchTarget = catchTarget; + if(!catchTarget) { + delegationTarget = true; + } + } + + @Override + public int compareTo(OptimismExceptionHandlerSpec o) { + return lvarSpec.compareTo(o.lvarSpec); + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(64).append("[HandlerSpec ").append(lvarSpec); + if(catchTarget) { + b.append(", catchTarget"); + } + if(delegationTarget) { + b.append(", delegationTarget"); + } + return b.append("]").toString(); + } + } + + private static class ContinuationInfo { + final Label handlerLabel; + Label targetLabel; // Label for the target instruction. + // Types the local variable slots have to have when this node completes + Type[] localVariableTypes; + // Indices of local variables that need to be loaded on the stack when this node completes + int[] stackStoreSpec; + // Types of values loaded on the stack + Type[] stackTypes; + // If non-null, this node should perform the requisite type conversion + Type returnValueType; + // If we are in the middle of an object literal initialization, we need to update + // the map + PropertyMap objectLiteralMap; + + ContinuationInfo() { + this.handlerLabel = new Label("continuation_handler"); + } + + @Override + public String toString() { + return "[localVariableTypes=" + Arrays.toString(localVariableTypes) + ", stackStoreSpec=" + + Arrays.toString(stackStoreSpec) + ", returnValueType=" + returnValueType + "]"; + } + } + + private ContinuationInfo getContinuationInfo() { + return fnIdToContinuationInfo.get(lc.getCurrentFunction().getId()); + } + + private void generateContinuationHandler() { + if (!compiler.getCompilationEnvironment().isCompileRestOf()) { + return; + } + + final ContinuationInfo ci = getContinuationInfo(); + method.label(ci.handlerLabel); + + // There should never be an exception thrown from the continuation handler, but in case there is (meaning, + // Nashorn has a bug), then line number 0 will be an indication of where it came from (line numbers are Uint16). + method.lineNumber(0); + + final Type[] lvarTypes = ci.localVariableTypes; + final int lvarCount = lvarTypes.length; + + final Type exceptionType = Type.typeFor(RewriteException.class); + method.load(exceptionType, 0); + method.dup(); + // Get local variable array + method.invoke(RewriteException.GET_BYTECODE_SLOTS); + // Store local variables + for(int lvarIndex = 0, arrayIndex = 0; lvarIndex < lvarCount; ++arrayIndex) { + final Type lvarType = lvarTypes[lvarIndex]; + final int nextLvarIndex = lvarIndex + lvarType.getSlots(); + if(nextLvarIndex < lvarCount) { + // keep local variable array on the stack unless this is the last lvar + method.dup(); + } + method.load(arrayIndex).arrayload(); + method.convert(lvarType); + method.store(lvarType, lvarIndex); + lvarIndex = nextLvarIndex; + } + + final int[] stackStoreSpec = ci.stackStoreSpec; + final Type[] stackTypes = ci.stackTypes; + final boolean isStackEmpty = stackStoreSpec.length == 0; + if(!isStackEmpty) { + // Store the RewriteException into an unused local variable slot. + method.store(exceptionType, lvarCount); + // Load arguments on the stack + for(int i = 0; i < stackStoreSpec.length; ++i) { + final int slot = stackStoreSpec[i]; + method.load(lvarTypes[slot], slot); + method.convert(stackTypes[i]); + } + + // stack: s0=object literal being initialized + // change map of s0 so that the property we are initilizing when we failed + // is now ci.returnValueType + if (ci.objectLiteralMap != null) { + method.dup(); //dup script object + assert ScriptObject.class.isAssignableFrom(method.peekType().getTypeClass()) : method.peekType().getTypeClass() + " is not a script object"; + loadConstant(ci.objectLiteralMap); + method.invoke(ScriptObject.SET_MAP); + } + + // Load RewriteException back; get rid of the stored reference. + method.load(Type.OBJECT, lvarCount); + method.loadNull(); + method.store(Type.OBJECT, lvarCount); + } + + // Load return value on the stack + method.invoke(RewriteException.GET_RETURN_VALUE); + method.convert(ci.returnValueType); + + // Jump to continuation point + method._goto(ci.targetLabel); + } }
--- a/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/CodeGeneratorLexicalContext.java Wed Feb 26 13:17:57 2014 +0100 @@ -31,13 +31,14 @@ import java.util.Deque; import java.util.HashMap; import java.util.Map; - +import jdk.nashorn.internal.IntDeque; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.Block; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.LexicalContextNode; import jdk.nashorn.internal.ir.Node; +import jdk.nashorn.internal.ir.SplitNode; import jdk.nashorn.internal.ir.Symbol; import jdk.nashorn.internal.ir.WithNode; @@ -63,6 +64,10 @@ * i.e. should we keep it or throw it away */ private final Deque<Node> discard = new ArrayDeque<>(); + private final Deque<Map<String, Collection<Label>>> unwarrantedOptimismHandlers = new ArrayDeque<>(); + private final Deque<StringBuilder> slotTypesDescriptors = new ArrayDeque<>(); + private final IntDeque splitNodes = new IntDeque(); + /** A stack tracking the next free local variable slot in the blocks. There's one entry for every block * currently on the lexical context stack. */ private int[] nextFreeSlots = new int[16]; @@ -75,17 +80,33 @@ if (isDynamicScopeBoundary(node)) { ++dynamicScopeCount; } + if(node instanceof FunctionNode) { + splitNodes.push(0); + } else if(node instanceof SplitNode) { + enterSplitNode(); + } return super.push(node); } + void enterSplitNode() { + splitNodes.getAndIncrement(); + } + + void exitSplitNode() { + splitNodes.decrementAndGet(); + } + @Override public <T extends LexicalContextNode> T pop(final T node) { final T popped = super.pop(node); if (isDynamicScopeBoundary(popped)) { --dynamicScopeCount; } - if (node instanceof Block) { - --nextFreeSlotsSize; + if(node instanceof FunctionNode) { + assert splitNodes.peek() == 0; + splitNodes.pop(); + } else if(node instanceof SplitNode) { + exitSplitNode(); } return popped; } @@ -108,6 +129,10 @@ return dynamicScopeCount > 0; } + boolean inSplitNode() { + return !splitNodes.isEmpty() && splitNodes.peek() > 0; + } + static boolean isFunctionDynamicScope(FunctionNode fn) { return fn.hasEval() && !fn.isStrict(); } @@ -123,6 +148,20 @@ return methodEmitters.isEmpty() ? null : methodEmitters.peek(); } + void pushUnwarrantedOptimismHandlers() { + unwarrantedOptimismHandlers.push(new HashMap<String, Collection<Label>>()); + slotTypesDescriptors.push(new StringBuilder()); + } + + Map<String, Collection<Label>> getUnwarrantedOptimismHandlers() { + return unwarrantedOptimismHandlers.peek(); + } + + Map<String, Collection<Label>> popUnwarrantedOptimismHandlers() { + slotTypesDescriptors.pop(); + return unwarrantedOptimismHandlers.pop(); + } + CompileUnit pushCompileUnit(final CompileUnit newUnit) { compileUnits.push(newUnit); return newUnit; @@ -167,33 +206,18 @@ * Get a shared static method representing a dynamic scope get access. * * @param unit current compile unit - * @param type the type of the variable * @param symbol the symbol + * @param valueType the type of the variable * @param flags the callsite flags * @return an object representing a shared scope call */ - SharedScopeCall getScopeGet(final CompileUnit unit, final Type type, final Symbol symbol, final int flags) { - final SharedScopeCall scopeCall = new SharedScopeCall(symbol, type, type, null, flags); - if (scopeCalls.containsKey(scopeCall)) { - return scopeCalls.get(scopeCall); - } - scopeCall.setClassAndName(unit, getCurrentFunction().uniqueName(":scopeCall")); - scopeCalls.put(scopeCall, scopeCall); - return scopeCall; + SharedScopeCall getScopeGet(final CompileUnit unit, final Symbol symbol, final Type valueType, final int flags) { + return getScopeCall(unit, symbol, valueType, valueType, null, flags); } void nextFreeSlot(final Block block) { - final boolean isFunctionBody = isFunctionBody(); - - final int nextFreeSlot; - if (isFunctionBody) { - // On entry to function, start with slot 0 - nextFreeSlot = 0; - } else { - // Otherwise, continue from previous block's first free slot - nextFreeSlot = nextFreeSlots[nextFreeSlotsSize - 1]; - } + final int nextFreeSlot = isFunctionBody() ? 0 : getUsedSlotCount(); if (nextFreeSlotsSize == nextFreeSlots.length) { final int[] newNextFreeSlots = new int[nextFreeSlotsSize * 2]; System.arraycopy(nextFreeSlots, 0, newNextFreeSlots, 0, nextFreeSlotsSize); @@ -202,7 +226,18 @@ nextFreeSlots[nextFreeSlotsSize++] = assignSlots(block, nextFreeSlot); } - private static int assignSlots(final Block block, final int firstSlot) { + int getUsedSlotCount() { + return nextFreeSlots[nextFreeSlotsSize - 1]; + } + + void releaseBlockSlots(boolean optimistic) { + --nextFreeSlotsSize; + if(optimistic) { + slotTypesDescriptors.peek().setLength(nextFreeSlots[nextFreeSlotsSize]); + } + } + + private int assignSlots(final Block block, final int firstSlot) { int nextSlot = firstSlot; for (final Symbol symbol : block.getSymbols()) { if (symbol.hasSlot()) { @@ -210,9 +245,33 @@ nextSlot += symbol.slotCount(); } } + methodEmitters.peek().ensureLocalVariableCount(nextSlot); return nextSlot; } + static Type getTypeForSlotDescriptor(final char typeDesc) { + switch(typeDesc) { + case 'I': { + return Type.INT; + } + case 'J': { + return Type.LONG; + } + case 'D': { + return Type.NUMBER; + } + case 'A': { + return Type.OBJECT; + } + case 'U': { + return Type.UNKNOWN; + } + default: { + throw new AssertionError(); + } + } + } + void pushDiscard(final Node node) { discard.push(node); } @@ -228,6 +287,7 @@ int quickSlot(final Symbol symbol) { final int quickSlot = nextFreeSlots[nextFreeSlotsSize - 1]; nextFreeSlots[nextFreeSlotsSize - 1] = quickSlot + symbol.slotCount(); + methodEmitters.peek().ensureLocalVariableCount(quickSlot); return quickSlot; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/codegen/CompilationEnvironment.java Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,363 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.nashorn.internal.codegen; + +import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import jdk.nashorn.internal.codegen.types.Type; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.ir.Optimistic; +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; + +/** + * Class for managing metadata during a compilation, e.g. which phases + * should be run, should we use optimistic types, is this a lazy compilation + * and various parameter types known to the runtime system + */ +public final class CompilationEnvironment { + private final CompilationPhases phases; + private final boolean optimistic; + + private final ParamTypeMap paramTypes; + + private final RecompilableScriptFunctionData compiledFunction; + + private boolean strict; + + /** + * If this is a recompilation, this is how we pass in the invalidations, e.g. programPoint=17, Type == int means + * that using whatever was at program point 17 as an int failed. + */ + private final Map<Integer, Type> invalidatedProgramPoints; + + /** + * Contains the program point that should be used as the continuation entry point, as well as all previous + * continuation entry points executed as part of a single logical invocation of the function. In practical terms, if + * we execute a rest-of method from the program point 17, but then we hit deoptimization again during it at program + * point 42, and execute a rest-of method from the program point 42, and then we hit deoptimization again at program + * point 57 and are compiling a rest-of method for it, the values in the array will be [57, 42, 17]. This is only + * set when compiling a rest-of method. If this method is a rest-of for a non-rest-of method, the array will have + * one element. If it is a rest-of for a rest-of, the array will have two elements, and so on. + */ + private final int[] continuationEntryPoints; + + /** + * Compilation phases that a compilation goes through + */ + public static final class CompilationPhases implements Iterable<CompilationPhase> { + + /** + * Standard (non-lazy) compilation, that basically will take an entire script + * and JIT it at once. This can lead to long startup time and fewer type + * specializations + */ + final static CompilationPhase[] SEQUENCE_EAGER_ARRAY = new CompilationPhase[] { + CompilationPhase.CONSTANT_FOLDING_PHASE, + CompilationPhase.LOWERING_PHASE, + CompilationPhase.SPLITTING_PHASE, + CompilationPhase.ATTRIBUTION_PHASE, + CompilationPhase.RANGE_ANALYSIS_PHASE, + CompilationPhase.TYPE_FINALIZATION_PHASE, + CompilationPhase.BYTECODE_GENERATION_PHASE + }; + + private final static List<CompilationPhase> SEQUENCE_EAGER; + static { + LinkedList<CompilationPhase> eager = new LinkedList<>(); + for (final CompilationPhase phase : SEQUENCE_EAGER_ARRAY) { + eager.add(phase); + } + SEQUENCE_EAGER = Collections.unmodifiableList(eager); + } + + /** Singleton that describes a standard eager compilation */ + public static CompilationPhases EAGER = new CompilationPhases(SEQUENCE_EAGER); + + private final List<CompilationPhase> phases; + + private CompilationPhases(final List<CompilationPhase> phases) { + this.phases = phases; + } + + @SuppressWarnings("unused") + private CompilationPhases addFirst(final CompilationPhase phase) { + if (phases.contains(phase)) { + return this; + } + final LinkedList<CompilationPhase> list = new LinkedList<>(phases); + list.addFirst(phase); + return new CompilationPhases(Collections.unmodifiableList(list)); + } + + private CompilationPhases addAfter(final CompilationPhase phase, final CompilationPhase newPhase) { + final LinkedList<CompilationPhase> list = new LinkedList<>(); + for (CompilationPhase p : phases) { + list.add(p); + if (p == phase) { + list.add(newPhase); + } + } + return new CompilationPhases(Collections.unmodifiableList(list)); + } + + /** + * Turn a CompilationPhases into an optimistic one. NOP if already optimistic + * @param isOptimistic should this be optimistic + * @return new CompilationPhases that is optimistic or same if already optimistic + */ + public CompilationPhases makeOptimistic(final boolean isOptimistic) { + return isOptimistic ? addAfter(CompilationPhase.LOWERING_PHASE, CompilationPhase.PROGRAM_POINT_PHASE) : this; + } + + /** + * Turn a CompilationPhases into an optimistic one. NOP if already optimistic + * @return new CompilationPhases that is optimistic or same if already optimistic + */ + public CompilationPhases makeOptimistic() { + return makeOptimistic(true); + } + + private boolean contains(final CompilationPhase phase) { + return phases.contains(phase); + } + + @Override + public Iterator<CompilationPhase> iterator() { + return phases.iterator(); + } + + } + + /** + * Constructor + * @param phases compilation phases + * @param strict strict mode + */ + public CompilationEnvironment( + final CompilationPhases phases, + final boolean strict) { + this(phases, null, null, null, null, strict); + } + + /** + * Constructor for compilation environment of the rest-of method + * @param phases compilation phases + * @param strict strict mode + * @param recompiledFunction recompiled function + * @param continuationEntryPoint program points used as the continuation entry points in the current rest-of sequence + * @param invalidatedProgramPoints map of invalidated program points to their type + * @param paramTypeMap known parameter types if any exist + */ + public CompilationEnvironment( + final CompilationPhases phases, + final boolean strict, + final RecompilableScriptFunctionData recompiledFunction, + final ParamTypeMap paramTypeMap, + final Map<Integer, Type> invalidatedProgramPoints, + final int[] continuationEntryPoint) { + this(phases, paramTypeMap, invalidatedProgramPoints, recompiledFunction, continuationEntryPoint, strict); + } + + /** + * Constructor + * @param phases compilation phases + * @param strict strict mode + * @param recompiledFunction recompiled function + * @param paramTypeMap known parameter types + * @param invalidatedProgramPoints map of invalidated program points to their type + */ + public CompilationEnvironment( + final CompilationPhases phases, + final boolean strict, + final RecompilableScriptFunctionData recompiledFunction, + final ParamTypeMap paramTypeMap, + final Map<Integer, Type> invalidatedProgramPoints) { + this(phases, paramTypeMap, invalidatedProgramPoints, recompiledFunction, null, strict); + } + + @SuppressWarnings("null") + private CompilationEnvironment( + final CompilationPhases phases, + final ParamTypeMap paramTypes, + final Map<Integer, Type> invalidatedProgramPoints, + final RecompilableScriptFunctionData compiledFunction, + final int[] continuationEntryPoints, + final boolean strict) { + this.phases = phases; + this.paramTypes = paramTypes; + this.continuationEntryPoints = continuationEntryPoints; + this.invalidatedProgramPoints = + invalidatedProgramPoints == null ? + Collections.unmodifiableMap(new HashMap<Integer, Type>()) : + invalidatedProgramPoints; + this.compiledFunction = compiledFunction; + this.strict = strict; + this.optimistic = phases.contains(CompilationPhase.PROGRAM_POINT_PHASE); + + // If entry point array is passed, it must have at least one element + assert continuationEntryPoints == null || continuationEntryPoints.length > 0; + assert !isCompileRestOf() || isOnDemandCompilation(); // isCompileRestOf => isRecompilation + // continuation entry points must be among the invalidated program points + assert !isCompileRestOf() || (invalidatedProgramPoints != null && containsAll(invalidatedProgramPoints.keySet(), continuationEntryPoints)); + } + + private static boolean containsAll(Set<Integer> set, final int[] array) { + for(int i = 0; i < array.length; ++i) { + if(!set.contains(array[i])) { + return false; + } + } + return true; + } + + boolean isStrict() { + return strict; + } + + void setIsStrict(final boolean strict) { + this.strict = strict; + } + + CompilationPhases getPhases() { + return phases; + } + + /** + * Check if a program point was invalidated during a previous run + * of this method, i.e. we did an optimistic assumption that now is wrong. + * This is the basis of generating a wider type. getOptimisticType + * in this class will query for invalidation and suggest a wider type + * upon recompilation if this info exists. + * @param programPoint program point to check + * @return true if it was invalidated during a previous run + */ + boolean isInvalidated(final int programPoint) { + return invalidatedProgramPoints.get(programPoint) != null; + } + + /** + * Get the parameter type at a parameter position if known from previous runtime calls + * or optimistic profiles. + * + * @param functionNode function node to query + * @param pos parameter position + * @return known type of parameter 'pos' or null if no information exists + */ + Type getParamType(final FunctionNode functionNode, final int pos) { + return paramTypes == null ? null : paramTypes.get(functionNode, pos); + } + + /** + * Is this a compilation that generates the rest of method + * @return true if rest of generation + */ + boolean isCompileRestOf() { + return continuationEntryPoints != null; + } + + /** + * Is this an on-demand compilation triggered by a {@code RecompilableScriptFunctionData} - either a type + * specializing compilation, a deoptimizing recompilation, or a rest-of method compilation. + * @return true if this is an on-demand compilation, false if this is an eager compilation. + */ + boolean isOnDemandCompilation() { + return compiledFunction != null; + } + + /** + * Is this program point one of the continuation entry points for the rest-of method being compiled? + * @param programPoint program point + * @return true if it is a continuation entry point + */ + boolean isContinuationEntryPoint(final int programPoint) { + if(continuationEntryPoints != null) { + for(int i = 0; i < continuationEntryPoints.length; ++i) { + if(continuationEntryPoints[i] == programPoint) { + return true; + } + } + } + return false; + } + + int[] getContinuationEntryPoints() { + return continuationEntryPoints; + } + + /** + * Is this program point the continuation entry points for the current rest-of method being compiled? + * @param programPoint program point + * @return true if it is the current continuation entry point + */ + boolean isCurrentContinuationEntryPoint(final int programPoint) { + return hasCurrentContinuationEntryPoint() && getCurrentContinuationEntryPoint() == programPoint; + } + + boolean hasCurrentContinuationEntryPoint() { + return continuationEntryPoints != null; + } + + int getCurrentContinuationEntryPoint() { + // NOTE: we assert in the constructor that if the array is non-null, it has at least one element + return hasCurrentContinuationEntryPoint() ? continuationEntryPoints[0] : INVALID_PROGRAM_POINT; + } + + /** + * Are optimistic types enabled ? + * @param node get the optimistic type for a node + * @return most optimistic type in current environment + */ + Type getOptimisticType(final Optimistic node) { + assert useOptimisticTypes(); + final Type invalidType = invalidatedProgramPoints.get(node.getProgramPoint()); + if (invalidType != null) { + return invalidType;//.nextWider(); + } + return node.getMostOptimisticType(); + } + + /** + * Should this compilation use optimistic types in general. + * If this is false we will only set non-object types to things that can + * be statically proven to be true. + * @return true if optimistic types should be used. + */ + boolean useOptimisticTypes() { + return optimistic; + } + + RecompilableScriptFunctionData getScriptFunctionData(final int functionId) { + return compiledFunction == null ? null : compiledFunction.getScriptFunctionData(functionId); + } + +}
--- a/src/jdk/nashorn/internal/codegen/CompilationPhase.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/CompilationPhase.java Wed Feb 26 13:17:57 2014 +0100 @@ -8,19 +8,13 @@ import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED; import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.EnumSet; -import java.util.HashSet; import java.util.List; -import java.util.Set; import jdk.nashorn.internal.codegen.types.Range; import jdk.nashorn.internal.codegen.types.Type; -import jdk.nashorn.internal.ir.CallNode; import jdk.nashorn.internal.ir.Expression; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; @@ -32,7 +26,6 @@ import jdk.nashorn.internal.ir.debug.ASTWriter; import jdk.nashorn.internal.ir.debug.PrintVisitor; import jdk.nashorn.internal.ir.visitor.NodeVisitor; -import jdk.nashorn.internal.runtime.ECMAErrors; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.Timing; @@ -41,102 +34,7 @@ * FunctionNode into bytecode. It has an optional return value. */ enum CompilationPhase { - - /* - * Lazy initialization - tag all function nodes not the script as lazy as - * default policy. The will get trampolines and only be generated when - * called - */ - LAZY_INITIALIZATION_PHASE(EnumSet.of(INITIALIZED, PARSED)) { - @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - - /* - * For lazy compilation, we might be given a node previously marked - * as lazy to compile as the outermost function node in the - * compiler. Unmark it so it can be compiled and not cause - * recursion. Make sure the return type is unknown so it can be - * correctly deduced. Return types are always Objects in Lazy nodes - * as we haven't got a change to generate code for them and decude - * its parameter specialization - * - * TODO: in the future specializations from a callsite will be - * passed here so we can generate a better non-lazy version of a - * function from a trampoline - */ - - final FunctionNode outermostFunctionNode = fn; - - final Set<FunctionNode> neverLazy = new HashSet<>(); - final Set<FunctionNode> lazy = new HashSet<>(); - - FunctionNode newFunctionNode = outermostFunctionNode; - - newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - // self references are done with invokestatic and thus cannot - // have trampolines - never lazy - @Override - public boolean enterCallNode(final CallNode node) { - final Node callee = node.getFunction(); - if (callee instanceof FunctionNode) { - neverLazy.add(((FunctionNode)callee)); - return false; - } - return true; - } - - //any function that isn't the outermost one must be marked as lazy - @Override - public boolean enterFunctionNode(final FunctionNode node) { - assert compiler.isLazy(); - lazy.add(node); - return true; - } - }); - - //at least one method is non lazy - the outermost one - neverLazy.add(newFunctionNode); - - for (final FunctionNode node : neverLazy) { - Compiler.LOG.fine( - "Marking ", - node.getName(), - " as non lazy, as it's a self reference"); - lazy.remove(node); - } - - newFunctionNode = (FunctionNode)newFunctionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { - @Override - public Node leaveFunctionNode(final FunctionNode functionNode) { - if (lazy.contains(functionNode)) { - Compiler.LOG.fine( - "Marking ", - functionNode.getName(), - " as lazy"); - final FunctionNode parent = lc.getParentFunction(functionNode); - assert parent != null; - lc.setFlag(parent, FunctionNode.HAS_LAZY_CHILDREN); - lc.setBlockNeedsScope(parent.getBody()); - lc.setFlag(functionNode, FunctionNode.IS_LAZY); - return functionNode; - } - - return functionNode. - clearFlag(lc, FunctionNode.IS_LAZY). - setReturnType(lc, Type.UNKNOWN); - } - }); - - return newFunctionNode; - } - - @Override - public String toString() { - return "[Lazy JIT Initialization]"; - } - }, - - /* + /** * Constant folding pass Simple constant folding that will make elementary * constructs go away */ @@ -152,7 +50,7 @@ } }, - /* + /** * Lower (Control flow pass) Finalizes the control flow. Clones blocks for * finally constructs and similar things. Establishes termination criteria * for nodes Guarantee return instructions to method making sure control @@ -171,14 +69,58 @@ } }, - /* + /** + * Phase used only when doing optimistic code generation. It assigns all potentially + * optimistic ops a program point so that an UnwarrantedException knows from where + * a guess went wrong when creating the continuation to roll back this execution + */ + PROGRAM_POINT_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) { + @Override + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { + return (FunctionNode)fn.accept(new ProgramPoints()); + } + + @Override + public String toString() { + return "[Program Point Calculation]"; + } + }, + + /** + * Splitter Split the AST into several compile units based on a heuristic size calculation. + * Split IR can lead to scope information being changed. + */ + SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) { + @Override + FunctionNode transform(final Compiler compiler, final FunctionNode fn) { + final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName()); + + final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn, true); + + assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn=" + fn.getName() + ", fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit; + + if (newFunctionNode.isStrict()) { + assert compiler.getCompilationEnvironment().isStrict(); + compiler.getCompilationEnvironment().setIsStrict(true); + } + + return newFunctionNode; + } + + @Override + public String toString() { + return "[Code Splitting]"; + } + }, + + /** * Attribution Assign symbols and types to all nodes. */ - ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED)) { + ATTRIBUTION_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT)) { @Override FunctionNode transform(final Compiler compiler, final FunctionNode fn) { final TemporarySymbols ts = compiler.getTemporarySymbols(); - final FunctionNode newFunctionNode = (FunctionNode)enterAttr(fn, ts).accept(new Attr(ts)); + final FunctionNode newFunctionNode = (FunctionNode)enterAttr(fn, ts).accept(new Attr(compiler.getCompilationEnvironment(), ts)); if (compiler.getEnv()._print_mem_usage) { Compiler.LOG.info("Attr temporary symbol count: " + ts.getTotalSymbolCount()); } @@ -194,12 +136,6 @@ return (FunctionNode)functionNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) { @Override public Node leaveFunctionNode(final FunctionNode node) { - if (node.isLazy()) { - FunctionNode newNode = node.setReturnType(lc, Type.OBJECT); - return ts.ensureSymbol(lc, Type.OBJECT, newNode); - } - //node may have a reference here that needs to be nulled if it was referred to by - //its outer context, if it is lazy and not attributed return node.setReturnType(lc, Type.UNKNOWN).setSymbol(lc, null); } }); @@ -211,12 +147,12 @@ } }, - /* + /** * Range analysis * Conservatively prove that certain variables can be narrower than * the most generic number type */ - RANGE_ANALYSIS_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) { + RANGE_ANALYSIS_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, SPLIT, ATTR)) { @Override FunctionNode transform(final Compiler compiler, final FunctionNode fn) { if (!compiler.getEnv()._range_analysis) { @@ -261,13 +197,15 @@ final Expression expr = (Expression)node; final Symbol symbol = expr.getSymbol(); if (symbol != null) { - final Range range = symbol.getRange(); + final Range range = symbol.getRange(); final Type symbolType = symbol.getSymbolType(); - if (!symbolType.isNumeric()) { + + if (!symbolType.isUnknown() && !symbolType.isNumeric()) { return expr; } - final Type rangeType = range.getType(); - if (!Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range + + final Type rangeType = range.getType(); + if (!rangeType.isUnknown() && !Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range RangeAnalyzer.LOG.info("[", lc.getCurrentFunction().getName(), "] ", symbol, " can be ", range.getType(), " ", symbol.getRange()); return expr.setSymbol(lc, symbol.setTypeOverrideShared(range.getType(), compiler.getTemporarySymbols())); } @@ -296,37 +234,7 @@ } }, - - /* - * Splitter Split the AST into several compile units based on a size - * heuristic Splitter needs attributed AST for weight calculations (e.g. is - * a + b a ScriptRuntime.ADD with call overhead or a dadd with much less). - * Split IR can lead to scope information being changed. - */ - SPLITTING_PHASE(EnumSet.of(INITIALIZED, PARSED, CONSTANT_FOLDED, LOWERED, ATTR)) { - @Override - FunctionNode transform(final Compiler compiler, final FunctionNode fn) { - final CompileUnit outermostCompileUnit = compiler.addCompileUnit(compiler.firstCompileUnitName()); - - final FunctionNode newFunctionNode = new Splitter(compiler, fn, outermostCompileUnit).split(fn); - - assert newFunctionNode.getCompileUnit() == outermostCompileUnit : "fn.compileUnit (" + newFunctionNode.getCompileUnit() + ") != " + outermostCompileUnit; - - if (newFunctionNode.isStrict()) { - assert compiler.getStrictMode(); - compiler.setStrictMode(true); - } - - return newFunctionNode; - } - - @Override - public String toString() { - return "[Code Splitting]"; - } - }, - - /* + /** * FinalizeTypes * * This pass finalizes the types for nodes. If Attr created wider types than @@ -344,7 +252,7 @@ FunctionNode transform(final Compiler compiler, final FunctionNode fn) { final ScriptEnvironment env = compiler.getEnv(); - final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes(compiler.getTemporarySymbols())); + final FunctionNode newFunctionNode = (FunctionNode)fn.accept(new FinalizeTypes()); if (env._print_lower_ast) { env.getErr().println(new ASTWriter(newFunctionNode)); @@ -363,7 +271,7 @@ } }, - /* + /** * Bytecode generation: * * Generate the byte code class(es) resulting from the compiled FunctionNode @@ -400,50 +308,12 @@ compiler.addClass(className, bytecode); - // should could be printed to stderr for generate class? - if (env._print_code) { - final StringBuilder sb = new StringBuilder(); - sb.append("class: " + className).append('\n') - .append(ClassEmitter.disassemble(bytecode)) - .append("====="); - env.getErr().println(sb); - } - // should we verify the generated code? if (env._verify_code) { compiler.getCodeInstaller().verify(bytecode); } - // should code be dumped to disk - only valid in compile_only mode? - if (env._dest_dir != null && env._compile_only) { - final String fileName = className.replace('.', File.separatorChar) + ".class"; - final int index = fileName.lastIndexOf(File.separatorChar); - - final File dir; - if (index != -1) { - dir = new File(env._dest_dir, fileName.substring(0, index)); - } else { - dir = new File(env._dest_dir); - } - - try { - if (!dir.exists() && !dir.mkdirs()) { - throw new IOException(dir.toString()); - } - final File file = new File(env._dest_dir, fileName); - try (final FileOutputStream fos = new FileOutputStream(file)) { - fos.write(bytecode); - } - Compiler.LOG.info("Wrote class to '" + file.getAbsolutePath() + '\''); - } catch (final IOException e) { - Compiler.LOG.warning("Skipping class dump for ", - className, - ": ", - ECMAErrors.getMessage( - "io.error.cant.write", - dir.toString())); - } - } + DumpBytecode.dumpBytecode(env, bytecode, className); } return newFunctionNode; @@ -468,6 +338,11 @@ return functionNode.hasState(pre); } + /** + * Start a compilation phase + * @param functionNode function to compile + * @return function node + */ protected FunctionNode begin(final FunctionNode functionNode) { if (pre != null) { // check that everything in pre is present @@ -484,6 +359,11 @@ return functionNode; } + /** + * End a compilation phase + * @param functionNode function node to compile + * @return fucntion node + */ protected FunctionNode end(final FunctionNode functionNode) { endTime = System.currentTimeMillis(); Timing.accumulateTime(toString(), endTime - startTime);
--- a/src/jdk/nashorn/internal/codegen/CompileUnit.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/CompileUnit.java Wed Feb 26 13:17:57 2014 +0100 @@ -25,10 +25,16 @@ package jdk.nashorn.internal.codegen; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import jdk.nashorn.internal.ir.FunctionNode; +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; + /** * Used to track split class compilation. */ -public class CompileUnit implements Comparable<CompileUnit> { +public final class CompileUnit implements Comparable<CompileUnit> { /** Current class name */ private final String className; @@ -39,6 +45,38 @@ private Class<?> clazz; + private Set<FunctionInitializer> functionInitializers = new LinkedHashSet<>(); + + private static class FunctionInitializer { + final RecompilableScriptFunctionData data; + final FunctionNode functionNode; + + FunctionInitializer(final RecompilableScriptFunctionData data, final FunctionNode functionNode) { + this.data = data; + this.functionNode = functionNode; + } + + void initializeCode() { + data.initializeCode(functionNode); + } + + @Override + public int hashCode() { + return data.hashCode() + 31 * functionNode.hashCode(); + } + + @Override + public boolean equals(final Object obj) { + if(obj == null || obj.getClass() != FunctionInitializer.class) { + return false; + } + final FunctionInitializer other = (FunctionInitializer)obj; + return data == other.data && functionNode == other.functionNode; + } + + + } + CompileUnit(final String className, final ClassEmitter classEmitter) { this(className, classEmitter, 0L); } @@ -71,6 +109,29 @@ this.classEmitter = null; } + void addFunctionInitializer(final RecompilableScriptFunctionData data, final FunctionNode functionNode) { + functionInitializers.add(new FunctionInitializer(data, functionNode)); + } + + /** + * Returns true if this compile unit is responsible for initializing the specified function data with specified + * function node. + * @param data the function data to check + * @param functionNode the function node to check + * @return true if this unit is responsible for initializing the function data with the function node, otherwise + * false + */ + public boolean isInitializing(final RecompilableScriptFunctionData data, final FunctionNode functionNode) { + return functionInitializers.contains(new FunctionInitializer(data, functionNode)); + } + + void initializeFunctionsCode() { + for(final FunctionInitializer init: functionInitializers) { + init.initializeCode(); + } + functionInitializers = Collections.emptySet(); + } + /** * Add weight to this compile unit * @param w weight to add
--- a/src/jdk/nashorn/internal/codegen/Compiler.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/Compiler.java Wed Feb 26 13:17:57 2014 +0100 @@ -28,8 +28,6 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS; -import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME; -import static jdk.nashorn.internal.codegen.CompilerConstants.LAZY; import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE; @@ -41,13 +39,12 @@ import java.security.AccessController; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.HashMap; +import java.util.IdentityHashMap; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -56,6 +53,7 @@ import java.util.logging.Level; import jdk.internal.dynalink.support.NameCodec; import jdk.nashorn.internal.codegen.ClassEmitter.Flag; +import jdk.nashorn.internal.codegen.CompilationEnvironment.CompilationPhases; import jdk.nashorn.internal.codegen.types.Type; import jdk.nashorn.internal.ir.FunctionNode; import jdk.nashorn.internal.ir.FunctionNode.CompilationState; @@ -64,10 +62,10 @@ import jdk.nashorn.internal.ir.debug.ObjectSizeCalculator; import jdk.nashorn.internal.runtime.CodeInstaller; import jdk.nashorn.internal.runtime.DebugLogger; +import jdk.nashorn.internal.runtime.RecompilableScriptFunctionData; import jdk.nashorn.internal.runtime.ScriptEnvironment; import jdk.nashorn.internal.runtime.Source; import jdk.nashorn.internal.runtime.Timing; -import jdk.nashorn.internal.runtime.options.Options; /** * Responsible for converting JavaScripts to java byte code. Main entry @@ -86,6 +84,7 @@ private Source source; private String sourceName; + private String sourceURL; private final Map<String, byte[]> bytecode; @@ -93,14 +92,12 @@ private final ConstantData constantData; - private final CompilationSequence sequence; + private final CompilationEnvironment compilationEnv; - private final ScriptEnvironment env; + private final ScriptEnvironment scriptEnv; private String scriptName; - private boolean strict; - private final CodeInstaller<ScriptEnvironment> installer; private final TemporarySymbols temporarySymbols = new TemporarySymbols(); @@ -109,6 +106,12 @@ * that affect classes */ public static final DebugLogger LOG = new DebugLogger("compiler"); + static { + if (ScriptEnvironment.globalOptimistic()) { + LOG.warning("Running with optimistic types. This is experimental. To switch off, use -Dnashorn.optimistic=false"); + } + } + /** * This array contains names that need to be reserved at the start * of a compile, to avoid conflict with variable names later introduced. @@ -124,181 +127,47 @@ ARGUMENTS.symbolName() }; - /** - * This class makes it possible to do your own compilation sequence - * from the code generation package. There are predefined compilation - * sequences already - */ - @SuppressWarnings("serial") - static class CompilationSequence extends LinkedList<CompilationPhase> { + private void initCompiler(final String className, final FunctionNode functionNode) { + this.source = functionNode.getSource(); + this.sourceName = functionNode.getSourceName(); + this.sourceURL = functionNode.getSourceURL(); - CompilationSequence(final CompilationPhase... phases) { - super(Arrays.asList(phases)); - } - - CompilationSequence(final CompilationSequence sequence) { - this(sequence.toArray(new CompilationPhase[sequence.size()])); + if (functionNode.isStrict()) { + compilationEnv.setIsStrict(true); } - CompilationSequence insertAfter(final CompilationPhase phase, final CompilationPhase newPhase) { - final CompilationSequence newSeq = new CompilationSequence(); - for (final CompilationPhase elem : this) { - newSeq.add(phase); - if (elem.equals(phase)) { - newSeq.add(newPhase); - } - } - assert newSeq.contains(newPhase); - return newSeq; - } + final StringBuilder sb = new StringBuilder(); + sb.append(functionNode.uniqueName(className)). + append('$'). + append(safeSourceName(functionNode.getSource())); + this.scriptName = sb.toString(); + } - CompilationSequence insertBefore(final CompilationPhase phase, final CompilationPhase newPhase) { - final CompilationSequence newSeq = new CompilationSequence(); - for (final CompilationPhase elem : this) { - if (elem.equals(phase)) { - newSeq.add(newPhase); - } - newSeq.add(phase); - } - assert newSeq.contains(newPhase); - return newSeq; - } - - CompilationSequence insertFirst(final CompilationPhase phase) { - final CompilationSequence newSeq = new CompilationSequence(this); - newSeq.addFirst(phase); - return newSeq; - } - - CompilationSequence insertLast(final CompilationPhase phase) { - final CompilationSequence newSeq = new CompilationSequence(this); - newSeq.addLast(phase); - return newSeq; - } + private Compiler(final CompilationEnvironment compilationEnv, final ScriptEnvironment scriptEnv, final CodeInstaller<ScriptEnvironment> installer) { + this.scriptEnv = scriptEnv; + this.compilationEnv = compilationEnv; + this.installer = installer; + this.constantData = new ConstantData(); + this.compileUnits = new TreeSet<>(); + this.bytecode = new LinkedHashMap<>(); } /** - * Environment information known to the compile, e.g. params + * Constructor - common entry point for generating code. + * @param env compilation environment + * @param installer code installer */ - public static class Hints { - private final Type[] paramTypes; - - /** singleton empty hints */ - public static final Hints EMPTY = new Hints(); - - private Hints() { - this.paramTypes = null; - } - - /** - * Constructor - * @param paramTypes known parameter types for this callsite - */ - public Hints(final Type[] paramTypes) { - this.paramTypes = paramTypes; - } - - /** - * Get the parameter type for this parameter position, or - * null if now known - * @param pos position - * @return parameter type for this callsite if known - */ - public Type getParameterType(final int pos) { - if (paramTypes != null && pos < paramTypes.length) { - return paramTypes[pos]; - } - return null; - } + public Compiler(final CompilationEnvironment env, final CodeInstaller<ScriptEnvironment> installer) { + this(env, installer.getOwner(), installer); } /** - * Standard (non-lazy) compilation, that basically will take an entire script - * and JIT it at once. This can lead to long startup time and fewer type - * specializations - */ - final static CompilationSequence SEQUENCE_EAGER = new CompilationSequence( - CompilationPhase.CONSTANT_FOLDING_PHASE, - CompilationPhase.LOWERING_PHASE, - CompilationPhase.ATTRIBUTION_PHASE, - CompilationPhase.RANGE_ANALYSIS_PHASE, - CompilationPhase.SPLITTING_PHASE, - CompilationPhase.TYPE_FINALIZATION_PHASE, - CompilationPhase.BYTECODE_GENERATION_PHASE); - - final static CompilationSequence SEQUENCE_LAZY = - SEQUENCE_EAGER.insertFirst(CompilationPhase.LAZY_INITIALIZATION_PHASE); - - 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()) { - return '$' + LAZY.symbolName() + '$' + functionNode.getName(); - } - return ""; - } - - /** - * Constructor - * - * @param env script environment - * @param installer code installer - * @param sequence {@link Compiler.CompilationSequence} of {@link CompilationPhase}s to apply as this compilation - * @param strict should this compilation use strict mode semantics + * ScriptEnvironment constructor for compiler. Used only from Shell and --compile-only flag + * No code installer supplied + * @param scriptEnv script environment */ - //TODO support an array of FunctionNodes for batch lazy compilation - Compiler(final ScriptEnvironment env, final CodeInstaller<ScriptEnvironment> installer, final CompilationSequence sequence, final boolean strict) { - this.env = env; - this.sequence = sequence; - this.installer = installer; - this.constantData = new ConstantData(); - this.compileUnits = new TreeSet<>(); - this.bytecode = new LinkedHashMap<>(); - } - - private void initCompiler(final FunctionNode functionNode) { - this.strict = strict || functionNode.isStrict(); - final StringBuilder sb = new StringBuilder(); - sb.append(functionNode.uniqueName(DEFAULT_SCRIPT_NAME.symbolName() + lazyTag(functionNode))). - append('$'). - append(safeSourceName(functionNode.getSource())); - this.source = functionNode.getSource(); - this.sourceName = functionNode.getSourceName(); - this.scriptName = sb.toString(); - } - - /** - * Constructor - * - * @param installer code installer - * @param strict should this compilation use strict mode semantics - */ - public Compiler(final CodeInstaller<ScriptEnvironment> installer, final boolean strict) { - this(installer.getOwner(), installer, sequence(installer.getOwner()._lazy_compilation), strict); - } - - /** - * Constructor - compilation will use the same strict semantics as in script environment - * - * @param installer code installer - */ - public Compiler(final CodeInstaller<ScriptEnvironment> installer) { - this(installer.getOwner(), installer, sequence(installer.getOwner()._lazy_compilation), installer.getOwner()._strict); - } - - /** - * Constructor - compilation needs no installer, but uses a script environment - * Used in "compile only" scenarios - * @param env a script environment - */ - public Compiler(final ScriptEnvironment env) { - this(env, null, sequence(env._lazy_compilation), env._strict); + public Compiler(final ScriptEnvironment scriptEnv) { + this(new CompilationEnvironment(CompilationPhases.EAGER, scriptEnv._strict), scriptEnv, null); } private static void printMemoryUsage(final String phaseName, final FunctionNode functionNode) { @@ -337,30 +206,53 @@ } } + CompilationEnvironment getCompilationEnvironment() { + return compilationEnv; + } + /** - * Execute the compilation this Compiler was created with + * Execute the compilation this Compiler was created with with default class name * @param functionNode function node to compile from its current state * @throws CompilationException if something goes wrong * @return function node that results from code transforms */ public FunctionNode compile(final FunctionNode functionNode) throws CompilationException { + return compile(CompilerConstants.DEFAULT_SCRIPT_NAME.symbolName(), functionNode); + } + + /** + * Execute the compilation this Compiler was created with + * @param className class name for the compile + * @param functionNode function node to compile from its current state + * @throws CompilationException if something goes wrong + * @return function node that results from code transforms + */ + public FunctionNode compile(final String className, final FunctionNode functionNode) throws CompilationException { + try { + return compileInternal(className, functionNode); + } catch(AssertionError e) { + throw new AssertionError("Assertion failure compiling " + functionNode.getSource(), e); + } + } + + private FunctionNode compileInternal(final String className, final FunctionNode functionNode) throws CompilationException { FunctionNode newFunctionNode = functionNode; - initCompiler(newFunctionNode); //TODO move this state into functionnode? + initCompiler(className, newFunctionNode); //TODO move this state into functionnode? for (final String reservedName : RESERVED_NAMES) { newFunctionNode.uniqueName(reservedName); } - final boolean fine = !LOG.levelAbove(Level.FINE); - final boolean info = !LOG.levelAbove(Level.INFO); + final boolean fine = LOG.levelFinerThanOrEqual(Level.FINE); + final boolean info = LOG.levelFinerThanOrEqual(Level.INFO); long time = 0L; - for (final CompilationPhase phase : sequence) { + for (final CompilationPhase phase : compilationEnv.getPhases()) { newFunctionNode = phase.apply(this, newFunctionNode); - if (env._print_mem_usage) { + if (scriptEnv._print_mem_usage) { printMemoryUsage(phase.toString(), newFunctionNode); } @@ -441,7 +333,7 @@ public Class<?> install(final FunctionNode functionNode) { final long t0 = Timing.isEnabled() ? System.currentTimeMillis() : 0L; - assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " has no bytecode and cannot be installed"; + assert functionNode.hasState(CompilationState.EMITTED) : functionNode.getName() + " has unexpected compilation state"; final Map<String, Class<?>> installedClasses = new HashMap<>(); @@ -464,8 +356,17 @@ installedClasses.put(className, install(className, code)); } + final Map<RecompilableScriptFunctionData, RecompilableScriptFunctionData> rfns = new IdentityHashMap<>(); + for(final Object constant: getConstantData().constants) { + if(constant instanceof RecompilableScriptFunctionData) { + final RecompilableScriptFunctionData rfn = (RecompilableScriptFunctionData)constant; + rfns.put(rfn, rfn); + } + } + for (final CompileUnit unit : compileUnits) { unit.setCode(installedClasses.get(unit.getUnitClassName())); + unit.initializeFunctionsCode(); } final StringBuilder sb; @@ -503,14 +404,6 @@ return compileUnits; } - boolean getStrictMode() { - return strict; - } - - void setStrictMode(final boolean strict) { - this.strict = strict; - } - ConstantData getConstantData() { return constantData; } @@ -528,7 +421,11 @@ } ScriptEnvironment getEnv() { - return this.env; + return this.scriptEnv; + } + + String getSourceURL() { + return sourceURL; } private String safeSourceName(final Source src) { @@ -540,7 +437,7 @@ } baseName = baseName.replace('.', '_').replace('-', '_'); - if (! env._loader_per_compile) { + if (! scriptEnv._loader_per_compile) { baseName = baseName + installer.getUniqueScriptId(); } final String mangled = NameCodec.encode(baseName); @@ -576,7 +473,7 @@ } private CompileUnit initCompileUnit(final String unitClassName, final long initialWeight) { - final ClassEmitter classEmitter = new ClassEmitter(env, sourceName, unitClassName, strict); + final ClassEmitter classEmitter = new ClassEmitter(scriptEnv, sourceName, unitClassName, compilationEnv.isStrict()); final CompileUnit compileUnit = new CompileUnit(unitClassName, classEmitter, initialWeight); classEmitter.begin(); @@ -611,23 +508,4 @@ public static String binaryName(final String name) { return name.replace('/', '.'); } - - /** - * Should we use integers for arithmetic operations as well? - * TODO: We currently generate no overflow checks so this is - * disabled - * - * @return true if arithmetic operations should not widen integer - * operands by default. - */ - static boolean shouldUseIntegerArithmetic() { - return USE_INT_ARITH; - } - - private static final boolean USE_INT_ARITH; - - static { - USE_INT_ARITH = Options.getBooleanProperty("nashorn.compiler.intarithmetic"); - assert !USE_INT_ARITH : "Integer arithmetic is not enabled"; - } }
--- a/src/jdk/nashorn/internal/codegen/CompilerConstants.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/CompilerConstants.java Wed Feb 26 13:17:57 2014 +0100 @@ -51,9 +51,6 @@ /** the __LINE__ variable */ __LINE__, - /** lazy prefix for classes of jitted methods */ - LAZY("Lazy"), - /** constructor name */ INIT("<init>"), @@ -78,8 +75,11 @@ /** function prefix for anonymous functions */ ANON_FUNCTION_PREFIX("L:"), - /** method name for Java method that is script entry point */ - RUN_SCRIPT("runScript"), + /** method name for Java method that is the program entry point */ + PROGRAM(":program"), + + /** method name for Java method that creates the script function for the program */ + CREATE_PROGRAM_FUNCTION(":createProgramFunction"), /** * "this" name symbol for a parameter representing ECMAScript "this" in static methods that are compiled @@ -161,7 +161,7 @@ /** get map */ GET_MAP(":getMap"), - /** get map */ + /** set map */ SET_MAP(":setMap"), /** get array prefix */ @@ -173,7 +173,7 @@ /** * Prefix used for internal methods generated in script clases. */ - public static final String INTERNAL_METHOD_PREFIX = ":"; + private static final String INTERNAL_METHOD_PREFIX = ":"; private final String symbolName; private final Class<?> type; @@ -198,9 +198,23 @@ } private CompilerConstants(final String symbolName, final Class<?> type, final int slot) { - this.symbolName = symbolName; - this.type = type; - this.slot = slot; + this.symbolName = symbolName; + this.type = type; + this.slot = slot; + } + + /** + * Check whether a name is that of a reserved compiler constnat + * @param name name + * @return true if compiler constant name + */ + public static boolean isCompilerConstant(final String name) { + for (final CompilerConstants cc : CompilerConstants.values()) { + if (name.equals(cc.symbolName())) { + return true; + } + } + return false; } /** @@ -539,6 +553,18 @@ } /** + * Returns true if the passed string looks like a method name of an internally generated Nashorn method. Basically, + * if it starts with a colon character {@code :} but is not the name of the program method {@code :program}. + * Program function is not considered internal as we want it to show up in exception stack traces. + * @param methodName the name of a method + * @return true if it looks like an internal Nashorn method name. + * @throws NullPointerException if passed null + */ + public static boolean isInternalMethodName(final String methodName) { + return methodName.startsWith(INTERNAL_METHOD_PREFIX) && !methodName.equals(PROGRAM.symbolName); + } + + /** * Private class representing an access. This can generate code into a method code or * a field access. */
--- a/src/jdk/nashorn/internal/codegen/Condition.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/Condition.java Wed Feb 26 13:17:57 2014 +0100 @@ -66,8 +66,7 @@ case GT: return IFGT; default: - assert false; - return -1; + throw new UnsupportedOperationException("toUnary:" + c.toString()); } } @@ -86,8 +85,7 @@ case GT: return IF_ICMPGT; default: - assert false; - return -1; + throw new UnsupportedOperationException("toBinary:" + c.toString()); } } }
--- a/src/jdk/nashorn/internal/codegen/ConstantData.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/ConstantData.java Wed Feb 26 13:17:57 2014 +0100 @@ -145,6 +145,7 @@ * @return the index in the constant pool that the object was given */ public int add(final Object object) { + assert object != null; final Object entry = object.getClass().isArray() ? new ArrayWrapper(object) : object; final Integer value = objectMap.get(entry);
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/jdk/nashorn/internal/codegen/DumpBytecode.java Wed Feb 26 13:17:57 2014 +0100 @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.nashorn.internal.codegen; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.PrintWriter; +import jdk.nashorn.internal.runtime.ECMAErrors; +import jdk.nashorn.internal.runtime.ScriptEnvironment; + +/** + * Class that facilitates dumping bytecode to disk + */ +final class DumpBytecode { + static void dumpBytecode(final ScriptEnvironment env, final byte[] bytecode, final String className) { + File dir = null; + try { + // should could be printed to stderr for generate class? + if (env._print_code) { + + final StringBuilder sb = new StringBuilder(); + sb.append("class: " + className). + append('\n'). + append(ClassEmitter.disassemble(bytecode)). + append("====="); + + if (env._print_code_dir != null) { + + String name = className; + int dollar = name.lastIndexOf('$'); + if (dollar != -1) { + name = name.substring(dollar + 1); + } + + dir = new File(env._print_code_dir); + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException(dir.toString()); + } + + File file; + String fileName; + int uniqueId = 0; + do { + fileName = name + (uniqueId == 0 ? "" : "_" + uniqueId) + ".bytecode"; + file = new File(env._print_code_dir, fileName); + uniqueId++; + } while (file.exists()); + + try (final PrintWriter pw = new PrintWriter(new FileOutputStream(file))) { + pw.print(sb.toString()); + pw.flush(); + } + } else { + env.getErr().println(sb); + } + } + + + // should code be dumped to disk - only valid in compile_only mode? + if (env._dest_dir != null && env._compile_only) { + final String fileName = className.replace('.', File.separatorChar) + ".class"; + final int index = fileName.lastIndexOf(File.separatorChar); + + if (index != -1) { + dir = new File(env._dest_dir, fileName.substring(0, index)); + } else { + dir = new File(env._dest_dir); + } + + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException(dir.toString()); + } + final File file = new File(env._dest_dir, fileName); + try (final FileOutputStream fos = new FileOutputStream(file)) { + fos.write(bytecode); + } + Compiler.LOG.info("Wrote class to '" + file.getAbsolutePath() + '\''); + } + } catch (final IOException e) { + Compiler.LOG.warning("Skipping class dump for ", + className, + ": ", + ECMAErrors.getMessage( + "io.error.cant.write", + dir.toString())); + } + } + +}
--- a/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/FieldObjectCreator.java Wed Feb 26 13:17:57 2014 +0100 @@ -28,8 +28,9 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS; import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup; import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor; +import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getFieldName; import static jdk.nashorn.internal.codegen.ObjectClassGenerator.getPaddedFieldCount; -import static jdk.nashorn.internal.codegen.types.Type.OBJECT; +import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.getArrayIndex; import static jdk.nashorn.internal.runtime.arrays.ArrayIndex.isValidArrayIndex; @@ -51,19 +52,16 @@ * @param <T> the value type for the fields being written on object creation, e.g. Node * @see jdk.nashorn.internal.ir.Node */ -public abstract class FieldObjectCreator<T> extends ObjectCreator { +public abstract class FieldObjectCreator<T> extends ObjectCreator<T> { - private String fieldObjectClassName; - private Class<?> fieldObjectClass; - private int fieldCount; - private int paddedFieldCount; - private int paramCount; - - /** array of corresponding values to symbols (null for no values) */ - private final List<T> values; + private String fieldObjectClassName; + private Class<? extends ScriptObject> fieldObjectClass; + private int fieldCount; + private int paddedFieldCount; + private int paramCount; /** call site flags to be used for invocations */ - private final int callSiteFlags; + private final int callSiteFlags; /** * Constructor @@ -73,8 +71,8 @@ * @param symbols symbols for fields in object * @param values list of values corresponding to keys */ - FieldObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final List<T> values) { - this(codegen, keys, symbols, values, false, false); + FieldObjectCreator(final CodeGenerator codegen, final List<MapTuple<T>> tuples) { + this(codegen, tuples, false, false); } /** @@ -87,9 +85,8 @@ * @param isScope is this a scope object * @param hasArguments does the created object have an "arguments" property */ - FieldObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final List<T> values, final boolean isScope, final boolean hasArguments) { - super(codegen, keys, symbols, isScope, hasArguments); - this.values = values; + FieldObjectCreator(final CodeGenerator codegen, final List<MapTuple<T>> tuples, final boolean isScope, final boolean hasArguments) { + super(codegen, tuples, isScope, hasArguments); this.callSiteFlags = codegen.getCallSiteFlags(); countFields(); @@ -105,7 +102,19 @@ protected void makeObject(final MethodEmitter method) { makeMap(); - method._new(getClassName()).dup(); // create instance + final String className = getClassName(); + try { + // NOTE: we must load the actual structure class here, because the API operates with Nashorn Type objects, + // and Type objects need a loaded class, for better or worse. We also have to be specific and use the type + // of the actual structure class, we can't generalize it to e.g. Type.typeFor(ScriptObject.class) as the + // exact type information is needed for generating continuations in rest-of methods. If we didn't do this, + // object initializers like { x: arr[i] } would fail during deoptimizing compilation on arr[i], as the + // values restored from the RewriteException would be cast to "ScriptObject" instead of to e.g. "JO4", and + // subsequently the "PUTFIELD J04.L0" instruction in the continuation code would fail bytecode verification. + method._new(Context.forStructureClass(className.replace('/', '.'))).dup(); + } catch (final ClassNotFoundException e) { + throw new AssertionError(e); + } loadMap(method); //load the map if (isScope()) { @@ -113,31 +122,27 @@ if (hasArguments()) { method.loadCompilerConstant(ARGUMENTS); - method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class, ARGUMENTS.type())); + method.invoke(constructorNoLookup(className, PropertyMap.class, ScriptObject.class, ARGUMENTS.type())); } else { - method.invoke(constructorNoLookup(getClassName(), PropertyMap.class, ScriptObject.class)); + method.invoke(constructorNoLookup(className, PropertyMap.class, ScriptObject.class)); } } else { - method.invoke(constructorNoLookup(getClassName(), PropertyMap.class)); + method.invoke(constructorNoLookup(className, PropertyMap.class)); } // Set values. - final Iterator<Symbol> symbolIter = symbols.iterator(); - final Iterator<String> keyIter = keys.iterator(); - final Iterator<T> valueIter = values.iterator(); + final Iterator<MapTuple<T>> iter = tuples.iterator(); - while (symbolIter.hasNext()) { - final Symbol symbol = symbolIter.next(); - final String key = keyIter.next(); - final T value = valueIter.next(); - - if (symbol != null && value != null) { - final int index = getArrayIndex(key); - + while (iter.hasNext()) { + final MapTuple<T> tuple = iter.next(); + //we only load when we have both symbols and values (which can be == the symbol) + //if we didn't load, we need an array property + if (tuple.symbol != null && tuple.value != null) { + final int index = getArrayIndex(tuple.key); if (!isValidArrayIndex(index)) { - putField(method, key, symbol.getFieldIndex(), value); + putField(method, tuple.key, tuple.symbol.getFieldIndex(), tuple); } else { - putSlot(method, ArrayIndex.toLongIndex(index), value); + putSlot(method, ArrayIndex.toLongIndex(index), tuple); } } } @@ -151,13 +156,6 @@ } /** - * Technique for loading an initial value. Defined by anonymous subclasses in code gen. - * - * @param value Value to load. - */ - protected abstract void loadValue(T value); - - /** * Store a value in a field of the generated class object. * * @param method Script method. @@ -165,12 +163,18 @@ * @param fieldIndex Field number. * @param value Value to store. */ - private void putField(final MethodEmitter method, final String key, final int fieldIndex, final T value) { + private void putField(final MethodEmitter method, final String key, final int fieldIndex, final MapTuple<T> tuple) { method.dup(); - loadValue(value); - method.convert(OBJECT); - method.putField(getClassName(), ObjectClassGenerator.getFieldName(fieldIndex, Type.OBJECT), typeDescriptor(Object.class)); + loadTuple(method, tuple); + + final boolean isPrimitive = tuple.isPrimitive(); + final Type fieldType = isPrimitive ? PRIMITIVE_FIELD_TYPE : Type.OBJECT; + final String fieldClass = getClassName(); + final String fieldName = getFieldName(fieldIndex, fieldType); + final String fieldDesc = typeDescriptor(fieldType.getTypeClass()); + + method.putField(fieldClass, fieldName, fieldDesc); } /** @@ -180,14 +184,14 @@ * @param index Slot index. * @param value Value to store. */ - private void putSlot(final MethodEmitter method, final long index, final T value) { + private void putSlot(final MethodEmitter method, final long index, final MapTuple<T> tuple) { method.dup(); if (JSType.isRepresentableAsInt(index)) { - method.load((int) index); + method.load((int)index); } else { method.load(index); } - loadValue(value); + loadTuple(method, tuple, false); //we don't pack array like objects method.dynamicSetIndex(callSiteFlags); } @@ -220,7 +224,8 @@ * Tally the number of fields and parameters. */ private void countFields() { - for (final Symbol symbol : this.symbols) { + for (final MapTuple<T> tuple : tuples) { + final Symbol symbol = tuple.symbol; if (symbol != null) { if (hasArguments() && symbol.isParam()) { symbol.setFieldIndex(paramCount++);
--- a/src/jdk/nashorn/internal/codegen/FinalizeTypes.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/FinalizeTypes.java Wed Feb 26 13:17:57 2014 +0100 @@ -26,6 +26,7 @@ package jdk.nashorn.internal.codegen; import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE; +import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN; import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE; import jdk.nashorn.internal.ir.BinaryNode; @@ -38,7 +39,6 @@ import jdk.nashorn.internal.ir.LexicalContext; import jdk.nashorn.internal.ir.Node; import jdk.nashorn.internal.ir.Symbol; -import jdk.nashorn.internal.ir.TemporarySymbols; import jdk.nashorn.internal.ir.UnaryNode; import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor; import jdk.nashorn.internal.parser.Token; @@ -62,11 +62,8 @@ private static final DebugLogger LOG = new DebugLogger("finalize"); - private final TemporarySymbols temporarySymbols; - - FinalizeTypes(final TemporarySymbols temporarySymbols) { + FinalizeTypes() { super(new LexicalContext()); - this.temporarySymbols = temporarySymbols; } @Override @@ -106,29 +103,41 @@ @Override public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) { - temporarySymbols.reuse(); return expressionStatement.setExpression(discard(expressionStatement.getExpression())); } @Override public boolean enterFunctionNode(final FunctionNode functionNode) { - if (functionNode.isLazy()) { - return false; - } + // TODO: now that Splitter comes before Attr, these can probably all be moved to Attr. - // If the function doesn't need a callee, we ensure its __callee__ symbol doesn't get a slot. We can't do - // this earlier, as access to scoped variables, self symbol, etc. in previous phases can all trigger the - // need for the callee. + // If the function doesn't need a callee, we ensure its CALLEE symbol doesn't get a slot. We can't do this + // earlier, as access to scoped variables, self symbol, etc. in previous phases can all trigger the need for the + // callee. if (!functionNode.needsCallee()) { functionNode.compilerConstant(CALLEE).setNeedsSlot(false); } - // Similar reasoning applies to __scope__ symbol: if the function doesn't need either parent scope and none of - // its blocks create a scope, we ensure it doesn't get a slot, but we can't determine whether it needs a scope + // Similar reasoning applies to SCOPE symbol: if the function doesn't need either parent scope and none of its + // blocks create a scope, we ensure it doesn't get a slot, but we can't determine whether it needs a scope // earlier than this phase. if (!(functionNode.hasScopeBlock() || functionNode.needsParentScope())) { functionNode.compilerConstant(SCOPE).setNeedsSlot(false); } - + // Also, we must wait until after Splitter to see if the function ended up needing the RETURN symbol. + if (!functionNode.usesReturnSymbol()) { + functionNode.compilerConstant(RETURN).setNeedsSlot(false); + } + // Named function expressions that end up not referencing themselves won't need a local slot for the self symbol. + if(!functionNode.isDeclared() && !functionNode.usesSelfSymbol() && !functionNode.isAnonymous()) { + final Symbol selfSymbol = functionNode.getBody().getExistingSymbol(functionNode.getIdent().getName()); + if(selfSymbol != null) { + if(selfSymbol.isFunctionSelf()) { + selfSymbol.setNeedsSlot(false); + selfSymbol.clearFlag(Symbol.IS_VAR); + } + } else { + assert functionNode.isProgram(); + } + } return true; } @@ -183,16 +192,16 @@ } } - private static Expression discard(final Expression node) { - if (node.getSymbol() != null) { - final UnaryNode discard = new UnaryNode(Token.recast(node.getToken(), TokenType.DISCARD), node); + private static Expression discard(final Expression expr) { + if (expr.getSymbol() != null) { + UnaryNode discard = new UnaryNode(Token.recast(expr.getToken(), TokenType.DISCARD), expr); //discard never has a symbol in the discard node - then it would be a nop - assert !node.isTerminal(); + assert !expr.isTerminal(); return discard; } // node has no result (symbol) so we can keep it the way it is - return node; + return expr; }
--- a/src/jdk/nashorn/internal/codegen/FoldConstants.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/FoldConstants.java Wed Feb 26 13:17:57 2014 +0100 @@ -80,11 +80,6 @@ } @Override - public boolean enterFunctionNode(final FunctionNode functionNode) { - return !functionNode.isLazy(); - } - - @Override public Node leaveFunctionNode(final FunctionNode functionNode) { return functionNode.setState(lc, CompilationState.CONSTANT_FOLDED); } @@ -163,7 +158,7 @@ @Override protected LiteralNode<?> eval() { - final Node rhsNode = parent.rhs(); + final Node rhsNode = parent.getExpression(); if (!(rhsNode instanceof LiteralNode)) { return null; @@ -311,8 +306,8 @@ return null; } - isInteger &= value != 0.0 && JSType.isRepresentableAsInt(value); - isLong &= value != 0.0 && JSType.isRepresentableAsLong(value); + isInteger &= JSType.isRepresentableAsInt(value) && !JSType.isNegativeZero(value); + isLong &= JSType.isRepresentableAsLong(value) && !JSType.isNegativeZero(value); if (isInteger) { return LiteralNode.newInstance(token, finish, (int)value);
--- a/src/jdk/nashorn/internal/codegen/FunctionSignature.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/FunctionSignature.java Wed Feb 26 13:17:57 2014 +0100 @@ -195,6 +195,14 @@ } /** + * Get the param types for this function signature + * @return cloned vector of param types + */ + public Type[] getParamTypes() { + return paramTypes.clone(); + } + + /** * Return the {@link MethodType} for this function signature * @return the method type */
--- a/src/jdk/nashorn/internal/codegen/Label.java Tue Feb 25 18:56:10 2014 +0530 +++ b/src/jdk/nashorn/internal/codegen/Label.java Wed Feb 26 13:17:57 2014 +0100 @@ -25,7 +25,6 @@ package jdk.nashorn.internal.codegen; import jdk.nashorn.internal.codegen.types.Type; -import jdk.nashorn.internal.runtime.Debug; /** * Abstraction for labels, separating a label from the underlying @@ -39,19 +38,22 @@ //and correct opcode selection. one per label as a label may be a //join point static final class Stack { + static final int NON_LOAD = -1; + Type[] data = new Type[8]; + int[] localLoads = new int[8]; int sp = 0; Stack() { } - private Stack(final Type[] type, final int sp) { + private Stack(final Stack original) { this(); - this.data = new Type[type.length]; - this.sp = sp; - for (int i = 0; i < sp; i++) { - data[i] = type[i]; - } + this.sp = original.sp; + this.data = new Type[original.data.length]; + System.arraycopy(original.data, 0, data, 0, sp); + this.localLoads = new int[original.localLoads.length]; + System.arraycopy(original.localLoads, 0, localLoads, 0, sp); } boolean isEmpty() { @@ -62,7 +64,7 @@ return sp; } - boolean isEquivalentTo(final Stack other) { + boolean isEquivalentInTypesTo(final Stack other) { if (sp != other.sp) { return false;