8035820: Optimistic recompilation
Co-authored-by: Marcus Lagergren <marcus.lagergren@oracle.com> Reviewed-by: hannesw, jlaskey, sundar
This commit is contained in:
parent
18489cc7a4
commit
e9e7dd2ec1
@ -13,6 +13,8 @@ webrev.zip
|
||||
*.clazz
|
||||
*.log
|
||||
*.orig
|
||||
*.rej
|
||||
*~
|
||||
genfiles.properties
|
||||
hotspot.log
|
||||
.DS_Store*
|
||||
|
25
nashorn/bin/rundiff.sh
Normal file
25
nashorn/bin/rundiff.sh
Normal file
@ -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
|
3
nashorn/bin/runnormal.sh
Normal file
3
nashorn/bin/runnormal.sh
Normal file
@ -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 ${@}
|
3
nashorn/bin/runnormaldual.sh
Normal file
3
nashorn/bin/runnormaldual.sh
Normal file
@ -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 ${@}
|
3
nashorn/bin/runopt.sh
Normal file
3
nashorn/bin/runopt.sh
Normal file
@ -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 ${@}
|
3
nashorn/bin/runoptdual.sh
Normal file
3
nashorn/bin/runoptdual.sh
Normal file
@ -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 ${@}
|
25
nashorn/bin/runoptdualcatch.sh
Normal file
25
nashorn/bin/runoptdualcatch.sh
Normal file
@ -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 ${@}
|
||||
|
@ -413,7 +413,8 @@ public class MethodGenerator extends MethodVisitor {
|
||||
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 @@ public class MethodGenerator extends MethodVisitor {
|
||||
super.visitMethodInsn(INVOKEVIRTUAL,
|
||||
"java/io/PrintStream",
|
||||
"println",
|
||||
"(Ljava/lang/Object;)V", false);
|
||||
"(Ljava/lang/Object;)V",
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
@ -365,18 +365,6 @@ grant codeBase "file:/${basedir}/test/script/markdown.js" {
|
||||
</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 @@ grant codeBase "file:/${basedir}/test/script/markdown.js" {
|
||||
</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"/>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -175,7 +175,7 @@ octane-test-sys-prop.test.js.exclude.list=\
|
||||
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 @@ run.test.classpath=\
|
||||
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}
|
||||
|
@ -140,7 +140,6 @@ import jdk.internal.dynalink.support.RuntimeContextLinkRequestImpl;
|
||||
* @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 @@ public class DynamicLinker {
|
||||
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 @@ public class DynamicLinker {
|
||||
* 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.runtimeContextArgCount = runtimeContextArgCount;
|
||||
this.linkerServices = linkerServices;
|
||||
this.prelinkFilter = prelinkFilter;
|
||||
this.runtimeContextArgCount = runtimeContextArgCount;
|
||||
this.syncOnRelink = syncOnRelink;
|
||||
this.unstableRelinkThreshold = unstableRelinkThreshold;
|
||||
}
|
||||
@ -224,11 +226,10 @@ public class DynamicLinker {
|
||||
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 @@ public class DynamicLinker {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
|
@ -102,14 +102,15 @@ import jdk.internal.dynalink.support.BottomGuardingDynamicLinker;
|
||||
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 @@ public class DynamicLinkerFactory {
|
||||
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 @@ public class DynamicLinkerFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class DynamicLinkerFactory {
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
|
105
nashorn/src/jdk/internal/dynalink/GuardedInvocationFilter.java
Normal file
105
nashorn/src/jdk/internal/dynalink/GuardedInvocationFilter.java
Normal file
@ -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);
|
||||
}
|
@ -97,7 +97,6 @@ import java.util.Collections;
|
||||
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.linker.LinkerServices;
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
// 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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
// 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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
// 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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
// 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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
}
|
||||
}
|
||||
|
||||
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 @@ abstract class AbstractJavaLinker implements GuardingDynamicLinker {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -235,8 +235,9 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
||||
} 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 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
||||
}
|
||||
|
||||
/*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 @@ class BeanLinker extends AbstractJavaLinker implements TypeBasedGuardingDynamicL
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
|
@ -148,7 +148,6 @@ class OverloadedDynamicMethod extends DynamicMethod {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("fallthrough")
|
||||
@Override
|
||||
public MethodHandle getInvocation(final CallSiteDescriptor callSiteDescriptor, final LinkerServices linkerServices) {
|
||||
final MethodType callSiteType = callSiteDescriptor.getMethodType();
|
||||
@ -207,7 +206,7 @@ class OverloadedDynamicMethod extends DynamicMethod {
|
||||
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
|
||||
|
@ -93,6 +93,7 @@ import java.util.Map;
|
||||
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 @@ class OverloadedMethod {
|
||||
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 @@ class OverloadedMethod {
|
||||
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 @@ class OverloadedMethod {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +156,9 @@ abstract class SingleDynamicMethod extends DynamicMethod {
|
||||
/**
|
||||
* 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 @@ abstract class SingleDynamicMethod extends DynamicMethod {
|
||||
|
||||
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) {
|
||||
|
@ -90,7 +90,9 @@ import java.lang.invoke.SwitchPoint;
|
||||
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,10 +104,23 @@ import jdk.internal.dynalink.support.Guards;
|
||||
* @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.
|
||||
*
|
||||
@ -116,7 +131,18 @@ public class GuardedInvocation {
|
||||
* @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 @@ public class GuardedInvocation {
|
||||
* @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.
|
||||
*
|
||||
@ -176,6 +206,15 @@ public class GuardedInvocation {
|
||||
return switchPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 @@ public class GuardedInvocation {
|
||||
* @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) {
|
||||
@ -240,6 +279,20 @@ public class GuardedInvocation {
|
||||
Guards.asType(linkerServices, guard, newType));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -303,9 +356,17 @@ public class GuardedInvocation {
|
||||
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());
|
||||
|
@ -101,10 +101,16 @@ public interface GuardingDynamicLinker {
|
||||
* @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)
|
||||
|
@ -100,6 +100,17 @@ public interface LinkRequest {
|
||||
*/
|
||||
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.
|
||||
|
@ -87,7 +87,9 @@ import java.lang.invoke.MethodHandle;
|
||||
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,17 +105,33 @@ public interface LinkerServices {
|
||||
* 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
|
||||
@ -161,4 +179,23 @@ public interface LinkerServices {
|
||||
* 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -95,6 +95,7 @@ import jdk.internal.dynalink.linker.LinkRequest;
|
||||
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 @@ public class LinkRequestImpl implements LinkRequest {
|
||||
* 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;
|
||||
}
|
||||
@ -126,6 +129,11 @@ public class LinkRequestImpl implements LinkRequest {
|
||||
return callSiteDescriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCallSiteToken() {
|
||||
return callSiteToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCallSiteUnstable() {
|
||||
return callSiteUnstable;
|
||||
@ -138,6 +146,6 @@ public class LinkRequestImpl implements LinkRequest {
|
||||
|
||||
@Override
|
||||
public LinkRequest replaceArguments(CallSiteDescriptor newCallSiteDescriptor, Object[] newArguments) {
|
||||
return new LinkRequestImpl(newCallSiteDescriptor, callSiteUnstable, newArguments);
|
||||
return new LinkRequestImpl(newCallSiteDescriptor, callSiteToken, callSiteUnstable, newArguments);
|
||||
}
|
||||
}
|
||||
|
@ -126,6 +126,11 @@ public class LinkerServicesImpl implements LinkerServices {
|
||||
return typeConverterFactory.asType(handle, fromType);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
@ -101,15 +101,16 @@ public class RuntimeContextLinkRequestImpl extends LinkRequestImpl {
|
||||
* 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 @@ public class RuntimeContextLinkRequestImpl extends LinkRequestImpl {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -106,38 +106,49 @@ public class TypeUtilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
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 == void.class) {
|
||||
return c2;
|
||||
} else if(c2 == void.class) {
|
||||
return c1;
|
||||
}
|
||||
Set<Class<?>> a1 = getAssignables(c1, c3);
|
||||
Set<Class<?>> a2 = getAssignables(c3, c1);
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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 @@ public class TypeUtilities {
|
||||
max.add(clazz);
|
||||
}
|
||||
if(max.size() > 1) {
|
||||
return OBJECT_CLASS;
|
||||
return Object.class;
|
||||
}
|
||||
return max.get(0);
|
||||
}
|
||||
@ -232,25 +243,60 @@ public class TypeUtilities {
|
||||
* {@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(methodType.isPrimitive()) {
|
||||
final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(callSiteType);
|
||||
if(targetType.isPrimitive()) {
|
||||
final Class<?> unboxedCallSiteType = PRIMITIVE_TYPES.get(sourceType);
|
||||
return unboxedCallSiteType != null
|
||||
&& (unboxedCallSiteType == methodType || isProperPrimitiveSubtype(unboxedCallSiteType, methodType));
|
||||
&& (unboxedCallSiteType == targetType || isProperPrimitiveSubtype(unboxedCallSiteType, targetType));
|
||||
}
|
||||
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 == targetType || isProperPrimitiveLosslessSubtype(unboxedCallSiteType, targetType));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -266,7 +312,7 @@ public class TypeUtilities {
|
||||
*/
|
||||
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()) {
|
||||
@ -286,6 +332,16 @@ public class TypeUtilities {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -353,6 +409,37 @@ public class TypeUtilities {
|
||||
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() {
|
||||
|
@ -26,7 +26,6 @@
|
||||
package jdk.nashorn.api.scripting;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
|
@ -182,7 +182,7 @@ public abstract class NashornException extends RuntimeException {
|
||||
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 @@ public abstract class NashornException extends RuntimeException {
|
||||
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!
|
||||
|
@ -61,6 +61,7 @@ import jdk.nashorn.internal.runtime.Context;
|
||||
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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
|
||||
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 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
}
|
||||
|
@ -169,6 +169,12 @@ public final class ScriptObjectMirror extends AbstractJSObject implements Bindin
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
87
nashorn/src/jdk/nashorn/internal/IntDeque.java
Normal file
87
nashorn/src/jdk/nashorn/internal/IntDeque.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -57,7 +57,7 @@ final class BranchOptimizer {
|
||||
}
|
||||
|
||||
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:
|
||||
|
@ -54,19 +54,23 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.typeDescriptor;
|
||||
|
||||
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 @@ import jdk.nashorn.internal.runtime.Source;
|
||||
* @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 @@ public class ClassEmitter implements Emitter {
|
||||
/** 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 @@ public class ClassEmitter implements Emitter {
|
||||
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();
|
||||
}
|
||||
return new String(baos.toByteArray());
|
||||
});
|
||||
TraceClassVisitor tcv = new TraceClassVisitor(null, new NashornTextifier(ctx.getEnv(), cr), pw);
|
||||
cr.accept(tcv, 0);
|
||||
}
|
||||
|
||||
final String str = new String(baos.toByteArray());
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -475,16 +488,39 @@ public class ClassEmitter implements Emitter {
|
||||
* @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
|
||||
*
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -31,13 +31,14 @@ import java.util.Collections;
|
||||
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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
* 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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
* 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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
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 @@ final class CodeGeneratorLexicalContext extends LexicalContext {
|
||||
int quickSlot(final Symbol symbol) {
|
||||
final int quickSlot = nextFreeSlots[nextFreeSlotsSize - 1];
|
||||
nextFreeSlots[nextFreeSlotsSize - 1] = quickSlot + symbol.slotCount();
|
||||
methodEmitters.peek().ensureLocalVariableCount(quickSlot);
|
||||
return quickSlot;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -8,19 +8,13 @@ import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.LOWERED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.PARSED;
|
||||
import static jdk.nashorn.internal.ir.FunctionNode.CompilationState.SPLIT;
|
||||
|
||||
import java.io.File;
|
||||
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.TemporarySymbols;
|
||||
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 @@ import jdk.nashorn.internal.runtime.Timing;
|
||||
* 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 @@ enum CompilationPhase {
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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 @@ enum CompilationPhase {
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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 @@ enum CompilationPhase {
|
||||
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 @@ enum CompilationPhase {
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* 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) {
|
||||
@ -263,11 +199,13 @@ enum CompilationPhase {
|
||||
if (symbol != null) {
|
||||
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
|
||||
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 @@ enum CompilationPhase {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
* 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 @@ enum CompilationPhase {
|
||||
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 @@ enum CompilationPhase {
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* Bytecode generation:
|
||||
*
|
||||
* Generate the byte code class(es) resulting from the compiled FunctionNode
|
||||
@ -400,50 +308,12 @@ enum CompilationPhase {
|
||||
|
||||
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 @@ enum CompilationPhase {
|
||||
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 @@ enum CompilationPhase {
|
||||
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);
|
||||
|
@ -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 @@ public class CompileUnit implements Comparable<CompileUnit> {
|
||||
|
||||
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 @@ public class CompileUnit implements Comparable<CompileUnit> {
|
||||
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
|
||||
|
@ -28,8 +28,6 @@ package jdk.nashorn.internal.codegen;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.ARGUMENTS;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.CALLEE;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.CONSTANTS;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.DEFAULT_SCRIPT_NAME;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.LAZY;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.SOURCE;
|
||||
@ -41,13 +39,12 @@ import java.lang.reflect.Field;
|
||||
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.TreeSet;
|
||||
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.ClassHistogramElement;
|
||||
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 @@ public final class Compiler {
|
||||
private Source source;
|
||||
|
||||
private String sourceName;
|
||||
private String sourceURL;
|
||||
|
||||
private final Map<String, byte[]> bytecode;
|
||||
|
||||
@ -93,14 +92,12 @@ public final class Compiler {
|
||||
|
||||
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 @@ public final class Compiler {
|
||||
* 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 @@ public final class Compiler {
|
||||
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));
|
||||
if (functionNode.isStrict()) {
|
||||
compilationEnv.setIsStrict(true);
|
||||
}
|
||||
|
||||
CompilationSequence(final CompilationSequence sequence) {
|
||||
this(sequence.toArray(new CompilationPhase[sequence.size()]));
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(functionNode.uniqueName(className)).
|
||||
append('$').
|
||||
append(safeSourceName(functionNode.getSource()));
|
||||
this.scriptName = sb.toString();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment information known to the compile, e.g. params
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
//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;
|
||||
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<>();
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* Constructor - common entry point for generating code.
|
||||
* @param env compilation environment
|
||||
* @param installer code installer
|
||||
*/
|
||||
public Compiler(final CodeInstaller<ScriptEnvironment> installer) {
|
||||
this(installer.getOwner(), installer, sequence(installer.getOwner()._lazy_compilation), installer.getOwner()._strict);
|
||||
public Compiler(final CompilationEnvironment env, final CodeInstaller<ScriptEnvironment> installer) {
|
||||
this(env, installer.getOwner(), installer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - compilation needs no installer, but uses a script environment
|
||||
* Used in "compile only" scenarios
|
||||
* @param env a script environment
|
||||
* ScriptEnvironment constructor for compiler. Used only from Shell and --compile-only flag
|
||||
* No code installer supplied
|
||||
* @param scriptEnv 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 @@ public final class Compiler {
|
||||
}
|
||||
}
|
||||
|
||||
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 final class Compiler {
|
||||
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 @@ public final class Compiler {
|
||||
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 @@ public final class Compiler {
|
||||
return compileUnits;
|
||||
}
|
||||
|
||||
boolean getStrictMode() {
|
||||
return strict;
|
||||
}
|
||||
|
||||
void setStrictMode(final boolean strict) {
|
||||
this.strict = strict;
|
||||
}
|
||||
|
||||
ConstantData getConstantData() {
|
||||
return constantData;
|
||||
}
|
||||
@ -528,7 +421,11 @@ public final class Compiler {
|
||||
}
|
||||
|
||||
ScriptEnvironment getEnv() {
|
||||
return this.env;
|
||||
return this.scriptEnv;
|
||||
}
|
||||
|
||||
String getSourceURL() {
|
||||
return sourceURL;
|
||||
}
|
||||
|
||||
private String safeSourceName(final Source src) {
|
||||
@ -540,7 +437,7 @@ public final class Compiler {
|
||||
}
|
||||
|
||||
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 @@ public final class Compiler {
|
||||
}
|
||||
|
||||
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 final class Compiler {
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
@ -51,9 +51,6 @@ public enum CompilerConstants {
|
||||
/** the __LINE__ variable */
|
||||
__LINE__,
|
||||
|
||||
/** lazy prefix for classes of jitted methods */
|
||||
LAZY("Lazy"),
|
||||
|
||||
/** constructor name */
|
||||
INIT("<init>"),
|
||||
|
||||
@ -78,8 +75,11 @@ public enum CompilerConstants {
|
||||
/** 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 @@ public enum CompilerConstants {
|
||||
/** get map */
|
||||
GET_MAP(":getMap"),
|
||||
|
||||
/** get map */
|
||||
/** set map */
|
||||
SET_MAP(":setMap"),
|
||||
|
||||
/** get array prefix */
|
||||
@ -173,7 +173,7 @@ public enum CompilerConstants {
|
||||
/**
|
||||
* 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;
|
||||
@ -203,6 +203,20 @@ public enum CompilerConstants {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the tag for this compile constant. Deliberately avoiding "name" here
|
||||
* not to conflate with enum implementation. This is the master string for the
|
||||
@ -538,6 +552,18 @@ public enum CompilerConstants {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
@ -66,8 +66,7 @@ enum Condition {
|
||||
case GT:
|
||||
return IFGT;
|
||||
default:
|
||||
assert false;
|
||||
return -1;
|
||||
throw new UnsupportedOperationException("toUnary:" + c.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@ -86,8 +85,7 @@ enum Condition {
|
||||
case GT:
|
||||
return IF_ICMPGT;
|
||||
default:
|
||||
assert false;
|
||||
return -1;
|
||||
throw new UnsupportedOperationException("toBinary:" + c.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,7 @@ class ConstantData {
|
||||
* @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);
|
||||
|
||||
|
113
nashorn/src/jdk/nashorn/internal/codegen/DumpBytecode.java
Normal file
113
nashorn/src/jdk/nashorn/internal/codegen/DumpBytecode.java
Normal file
@ -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()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -28,8 +28,9 @@ package jdk.nashorn.internal.codegen;
|
||||
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,17 +52,14 @@ import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
|
||||
* @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 Class<? extends ScriptObject> 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;
|
||||
|
||||
/** call site flags to be used for invocations */
|
||||
private final int callSiteFlags;
|
||||
|
||||
@ -73,8 +71,8 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
* @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 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
* @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 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
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 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
|
||||
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();
|
||||
|
||||
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);
|
||||
final Iterator<MapTuple<T>> iter = tuples.iterator();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -150,13 +155,6 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
return propertyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
@ -165,12 +163,18 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
* @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 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
* @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);
|
||||
} else {
|
||||
method.load(index);
|
||||
}
|
||||
loadValue(value);
|
||||
loadTuple(method, tuple, false); //we don't pack array like objects
|
||||
method.dynamicSetIndex(callSiteFlags);
|
||||
}
|
||||
|
||||
@ -220,7 +224,8 @@ public abstract class FieldObjectCreator<T> extends ObjectCreator {
|
||||
* 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++);
|
||||
|
@ -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.FunctionNode.CompilationState;
|
||||
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 @@ final class FinalizeTypes extends NodeOperatorVisitor<LexicalContext> {
|
||||
|
||||
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 @@ final class FinalizeTypes extends NodeOperatorVisitor<LexicalContext> {
|
||||
|
||||
@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 @@ final class FinalizeTypes extends NodeOperatorVisitor<LexicalContext> {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
@ -79,11 +79,6 @@ final class FoldConstants extends NodeVisitor<LexicalContext> {
|
||||
return binaryNode;
|
||||
}
|
||||
|
||||
@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 @@ final class FoldConstants extends NodeVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
protected LiteralNode<?> eval() {
|
||||
final Node rhsNode = parent.rhs();
|
||||
final Node rhsNode = parent.getExpression();
|
||||
|
||||
if (!(rhsNode instanceof LiteralNode)) {
|
||||
return null;
|
||||
@ -311,8 +306,8 @@ final class FoldConstants extends NodeVisitor<LexicalContext> {
|
||||
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);
|
||||
|
@ -194,6 +194,14 @@ public final class FunctionSignature {
|
||||
return paramTypes.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -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 @@ public final class Label {
|
||||
//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 @@ public final class Label {
|
||||
return sp;
|
||||
}
|
||||
|
||||
boolean isEquivalentTo(final Stack other) {
|
||||
boolean isEquivalentInTypesTo(final Stack other) {
|
||||
if (sp != other.sp) {
|
||||
return false;
|
||||
}
|
||||
@ -81,12 +83,15 @@ public final class Label {
|
||||
void push(final Type type) {
|
||||
if (data.length == sp) {
|
||||
final Type[] newData = new Type[sp * 2];
|
||||
for (int i = 0; i < sp; i++) {
|
||||
newData[i] = data[i];
|
||||
}
|
||||
final int[] newLocalLoad = new int[sp * 2];
|
||||
System.arraycopy(data, 0, newData, 0, sp);
|
||||
System.arraycopy(localLoads, 0, newLocalLoad, 0, sp);
|
||||
data = newData;
|
||||
localLoads = newLocalLoad;
|
||||
}
|
||||
data[sp++] = type;
|
||||
data[sp] = type;
|
||||
localLoads[sp] = NON_LOAD;
|
||||
sp++;
|
||||
}
|
||||
|
||||
Type peek() {
|
||||
@ -98,12 +103,67 @@ public final class Label {
|
||||
return pos < 0 ? null : data[pos];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the top <tt>count</tt> types on the stack without modifying it.
|
||||
*
|
||||
* @param count number of types to return
|
||||
* @return array of Types
|
||||
*/
|
||||
Type[] getTopTypes(final int count) {
|
||||
final Type[] topTypes = new Type[count];
|
||||
System.arraycopy(data, sp - count, topTypes, 0, count);
|
||||
return topTypes;
|
||||
}
|
||||
|
||||
int[] getLocalLoads(final int from, final int to) {
|
||||
final int count = to - from;
|
||||
final int[] topLocalLoads = new int[count];
|
||||
System.arraycopy(localLoads, from, topLocalLoads, 0, count);
|
||||
return topLocalLoads;
|
||||
}
|
||||
|
||||
/**
|
||||
* When joining branches, local loads that differ on different branches are invalidated.
|
||||
* @param other the stack from the other branch.
|
||||
*/
|
||||
void mergeLocalLoads(final Stack other) {
|
||||
final int[] otherLoads = other.localLoads;
|
||||
for(int i = 0; i < sp; ++i) {
|
||||
if(localLoads[i] != otherLoads[i]) {
|
||||
localLoads[i] = NON_LOAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Type pop() {
|
||||
return data[--sp];
|
||||
}
|
||||
|
||||
Stack copy() {
|
||||
return new Stack(data, sp);
|
||||
return new Stack(this);
|
||||
}
|
||||
|
||||
int getTopLocalLoad() {
|
||||
return localLoads[sp - 1];
|
||||
}
|
||||
|
||||
void markLocalLoad(final int slot) {
|
||||
localLoads[sp - 1] = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we store a value in a local slot, it invalidates any on-stack loads from that same slot, as the values
|
||||
* could have changed.
|
||||
* @param slot the slot written to
|
||||
* @param slotCount the size of the value, either 1 or 2 slots
|
||||
*/
|
||||
void markLocalStore(final int slot, final int slotCount) {
|
||||
for(int i = 0; i < sp; ++i) {
|
||||
final int load = localLoads[i];
|
||||
if(load == slot || load == slot + slotCount - 1) {
|
||||
localLoads[i] = NON_LOAD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -128,6 +188,12 @@ public final class Label {
|
||||
/** ASM representation of this label */
|
||||
private jdk.internal.org.objectweb.asm.Label label;
|
||||
|
||||
/** Id for debugging purposes, remove if footprint becomes unmanageable */
|
||||
private final int id;
|
||||
|
||||
/** Next id for debugging purposes, remove if footprint becomes unmanageable */
|
||||
private static int nextId = 0;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -136,6 +202,7 @@ public final class Label {
|
||||
public Label(final String name) {
|
||||
super();
|
||||
this.name = name;
|
||||
this.id = nextId++;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,6 +213,7 @@ public final class Label {
|
||||
public Label(final Label label) {
|
||||
super();
|
||||
this.name = label.name;
|
||||
this.id = label.id;
|
||||
}
|
||||
|
||||
|
||||
@ -166,6 +234,6 @@ public final class Label {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + '_' + Debug.id(this);
|
||||
return name + '_' + id;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. 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 jdk.nashorn.internal.codegen.types.Type;
|
||||
|
||||
/**
|
||||
* Encapsulates the information for restoring the local state when continuing execution after a rewrite triggered by
|
||||
* an optimistic assumption failure. An instance of this class is specific to a program point.
|
||||
*
|
||||
*/
|
||||
public class LocalStateRestorationInfo {
|
||||
private final Type[] localVariableTypes;
|
||||
private final int[] stackLoads;
|
||||
|
||||
LocalStateRestorationInfo(Type[] localVariableTypes, final int[] stackLoads) {
|
||||
this.localVariableTypes = localVariableTypes;
|
||||
this.stackLoads = stackLoads;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the types of the local variables at the continuation of a program point.
|
||||
* @return the types of the local variables at the continuation of a program point.
|
||||
*/
|
||||
public Type[] getLocalVariableTypes() {
|
||||
return localVariableTypes.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the indices of local variables that need to be loaded on stack before jumping to the continuation of the
|
||||
* program point.
|
||||
* @return the indices of local variables that need to be loaded on stack.
|
||||
*/
|
||||
public int[] getStackLoads() {
|
||||
return stackLoads.clone();
|
||||
}
|
||||
}
|
@ -31,6 +31,7 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.THIS;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
import jdk.nashorn.internal.ir.BaseNode;
|
||||
@ -235,12 +236,18 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> {
|
||||
newForNode = forNode.setTest(lc, null);
|
||||
}
|
||||
|
||||
return addStatement(checkEscape(newForNode));
|
||||
newForNode = checkEscape(newForNode);
|
||||
if(newForNode.isForIn()) {
|
||||
// Wrap it in a block so its internally created iterator is restricted in scope
|
||||
BlockStatement b = BlockStatement.createReplacement(newForNode, Collections.singletonList((Statement)newForNode));
|
||||
if(newForNode.isTerminal()) {
|
||||
b = b.setBlock(b.getBlock().setIsTerminal(null, true));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode functionNode) {
|
||||
return !functionNode.isLazy();
|
||||
addStatement(b);
|
||||
} else {
|
||||
addStatement(newForNode);
|
||||
}
|
||||
return newForNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -308,7 +315,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> {
|
||||
final long token = tryNode.getToken();
|
||||
final int finish = tryNode.getFinish();
|
||||
|
||||
final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName("catch_all"));
|
||||
final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName()));
|
||||
|
||||
final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), ThrowNode.IS_SYNTHETIC_RETHROW));
|
||||
assert catchBody.isTerminal(); //ends with throw, so terminal
|
||||
@ -340,6 +347,8 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> {
|
||||
private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
|
||||
assert tryNode.getFinallyBody() == null;
|
||||
|
||||
final LexicalContext lowerLc = lc;
|
||||
|
||||
final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
|
||||
final List<Node> insideTry = new ArrayList<>();
|
||||
|
||||
@ -388,6 +397,7 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> {
|
||||
//still in the try block, store it in a result value and return it afterwards
|
||||
resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
|
||||
newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
|
||||
lowerLc.setFlag(lowerLc.getCurrentFunction(), FunctionNode.USES_RETURN_SYMBOL);
|
||||
} else {
|
||||
resultNode = null;
|
||||
}
|
||||
@ -620,10 +630,11 @@ final class Lower extends NodeOperatorVisitor<BlockLexicalContext> {
|
||||
return !escapes.isEmpty();
|
||||
}
|
||||
|
||||
private LoopNode checkEscape(final LoopNode loopNode) {
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends LoopNode> T checkEscape(final T loopNode) {
|
||||
final boolean escapes = controlFlowEscapes(lc, loopNode.getBody());
|
||||
if (escapes) {
|
||||
return loopNode.
|
||||
return (T)loopNode.
|
||||
setBody(lc, loopNode.getBody().setIsTerminal(lc, false)).
|
||||
setControlFlowEscapes(lc, escapes);
|
||||
}
|
||||
|
@ -34,19 +34,19 @@ import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.runtime.AccessorProperty;
|
||||
import jdk.nashorn.internal.runtime.Property;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.SpillProperty;
|
||||
|
||||
/**
|
||||
* Class that creates PropertyMap sent to script object constructors.
|
||||
* @param <T> value type for tuples, e.g. Symbol
|
||||
*/
|
||||
public class MapCreator {
|
||||
public class MapCreator<T> {
|
||||
/** Object structure for objects associated with this map */
|
||||
private final Class<?> structure;
|
||||
|
||||
/** key set for object map */
|
||||
final List<String> keys;
|
||||
|
||||
/** corresponding symbol set for object map */
|
||||
final List<Symbol> symbols;
|
||||
private final List<MapTuple<T>> tuples;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -55,10 +55,9 @@ public class MapCreator {
|
||||
* @param keys list of keys for map
|
||||
* @param symbols list of symbols for map
|
||||
*/
|
||||
MapCreator(final Class<?> structure, final List<String> keys, final List<Symbol> symbols) {
|
||||
MapCreator(final Class<? extends ScriptObject> structure, final List<MapTuple<T>> tuples) {
|
||||
this.structure = structure;
|
||||
this.keys = keys;
|
||||
this.symbols = symbols;
|
||||
this.tuples = tuples;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,14 +71,14 @@ public class MapCreator {
|
||||
*/
|
||||
PropertyMap makeFieldMap(final boolean hasArguments, final int fieldCount, final int fieldMaximum) {
|
||||
final List<Property> properties = new ArrayList<>();
|
||||
assert keys != null;
|
||||
|
||||
for (int i = 0, length = keys.size(); i < length; i++) {
|
||||
final String key = keys.get(i);
|
||||
final Symbol symbol = symbols.get(i);
|
||||
assert tuples != null;
|
||||
|
||||
for (final MapTuple<T> tuple : tuples) {
|
||||
final String key = tuple.key;
|
||||
final Symbol symbol = tuple.symbol;
|
||||
final Class<?> initialType = tuple.getValueType();
|
||||
if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) {
|
||||
properties.add(new AccessorProperty(key, getPropertyFlags(symbol, hasArguments), structure, symbol.getFieldIndex()));
|
||||
properties.add(new AccessorProperty(key, getPropertyFlags(symbol, hasArguments), structure, symbol.getFieldIndex(), initialType));
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,14 +88,14 @@ public class MapCreator {
|
||||
PropertyMap makeSpillMap(final boolean hasArguments) {
|
||||
final List<Property> properties = new ArrayList<>();
|
||||
int spillIndex = 0;
|
||||
assert keys != null;
|
||||
assert tuples != null;
|
||||
|
||||
for (int i = 0, length = keys.size(); i < length; i++) {
|
||||
final String key = keys.get(i);
|
||||
final Symbol symbol = symbols.get(i);
|
||||
for (final MapTuple<T> tuple : tuples) {
|
||||
final String key = tuple.key;
|
||||
final Symbol symbol = tuple.symbol;
|
||||
|
||||
if (symbol != null && !isValidArrayIndex(getArrayIndex(key))) {
|
||||
properties.add(new AccessorProperty(key, getPropertyFlags(symbol, hasArguments), spillIndex++));
|
||||
properties.add(new SpillProperty(key, getPropertyFlags(symbol, hasArguments), spillIndex++));
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,17 +118,13 @@ public class MapCreator {
|
||||
}
|
||||
|
||||
if (hasArguments) {
|
||||
flags |= Property.IS_ALWAYS_OBJECT | Property.HAS_ARGUMENTS;
|
||||
flags |= Property.HAS_ARGUMENTS;
|
||||
}
|
||||
|
||||
if (symbol.isScope()) {
|
||||
flags |= Property.NOT_CONFIGURABLE;
|
||||
}
|
||||
|
||||
if (symbol.canBePrimitive()) {
|
||||
flags |= Property.CAN_BE_PRIMITIVE;
|
||||
}
|
||||
|
||||
if (symbol.canBeUndefined()) {
|
||||
flags |= Property.CAN_BE_UNDEFINED;
|
||||
}
|
||||
@ -140,5 +135,4 @@ public class MapCreator {
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
}
|
||||
|
62
nashorn/src/jdk/nashorn/internal/codegen/MapTuple.java
Normal file
62
nashorn/src/jdk/nashorn/internal/codegen/MapTuple.java
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
|
||||
/**
|
||||
* A tuple of values used for map creation
|
||||
* @param <T> value type
|
||||
*/
|
||||
class MapTuple<T> {
|
||||
final String key;
|
||||
final Symbol symbol;
|
||||
final T value;
|
||||
|
||||
MapTuple(final String key, final Symbol symbol) {
|
||||
this(key, symbol, null);
|
||||
}
|
||||
|
||||
MapTuple(final String key, final Symbol symbol, final T value) {
|
||||
this.key = key;
|
||||
this.symbol = symbol;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Class<?> getValueType() {
|
||||
return OBJECT_FIELDS_ONLY ? Object.class : null; //until proven otherwise we are undefined, see NASHORN-592 int.class;
|
||||
}
|
||||
|
||||
boolean isPrimitive() {
|
||||
return !OBJECT_FIELDS_ONLY && getValueType().isPrimitive() && getValueType() != boolean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[key=" + key + ", symbol=" + symbol + ", value=" + value + " (" + (value == null ? "null" : value.getClass().getSimpleName()) +")]";
|
||||
}
|
||||
}
|
@ -64,9 +64,14 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.methodDescriptor;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticField;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.PRIMITIVE_FIELD_TYPE;
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC;
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_PROGRAM_POINT_SHIFT;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import jdk.internal.dynalink.support.NameCodec;
|
||||
@ -89,6 +94,7 @@ import jdk.nashorn.internal.runtime.ArgumentSetter;
|
||||
import jdk.nashorn.internal.runtime.Debug;
|
||||
import jdk.nashorn.internal.runtime.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.RewriteException;
|
||||
import jdk.nashorn.internal.runtime.ScriptEnvironment;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.linker.Bootstrap;
|
||||
@ -126,6 +132,8 @@ public class MethodEmitter implements Emitter {
|
||||
/** The script environment */
|
||||
private final ScriptEnvironment env;
|
||||
|
||||
private final List<Type> localVariableTypes = new ArrayList<>();
|
||||
|
||||
/** Threshold in chars for when string constants should be split */
|
||||
static final int LARGE_STRING_THRESHOLD = 32 * 1024;
|
||||
|
||||
@ -153,6 +161,9 @@ public class MethodEmitter implements Emitter {
|
||||
/** Bootstrap for runtime node indy:s */
|
||||
private static final Handle RUNTIMEBOOTSTRAP = new Handle(H_INVOKESTATIC, RuntimeCallSite.BOOTSTRAP.className(), RuntimeCallSite.BOOTSTRAP.name(), RuntimeCallSite.BOOTSTRAP.descriptor());
|
||||
|
||||
/** Bootstrap for array populators */
|
||||
private static final Handle POPULATE_ARRAY_BOOTSTRAP = new Handle(H_INVOKESTATIC, RewriteException.BOOTSTRAP.className(), RewriteException.BOOTSTRAP.name(), RewriteException.BOOTSTRAP.descriptor());
|
||||
|
||||
/**
|
||||
* Constructor - internal use from ClassEmitter only
|
||||
* @see ClassEmitter#method
|
||||
@ -203,6 +214,11 @@ public class MethodEmitter implements Emitter {
|
||||
classEmitter.endMethod(this);
|
||||
}
|
||||
|
||||
void createNewStack() {
|
||||
assert stack == null;
|
||||
newStack();
|
||||
}
|
||||
|
||||
private void newStack() {
|
||||
stack = new Label.Stack();
|
||||
}
|
||||
@ -216,7 +232,7 @@ public class MethodEmitter implements Emitter {
|
||||
* Push a type to the existing stack
|
||||
* @param type the type
|
||||
*/
|
||||
private void pushType(final Type type) {
|
||||
void pushType(final Type type) {
|
||||
if (type != null) {
|
||||
stack.push(type);
|
||||
}
|
||||
@ -230,7 +246,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the type that was retrieved
|
||||
*/
|
||||
private Type popType(final Type expected) {
|
||||
final Type type = stack.pop();
|
||||
final Type type = popType();
|
||||
assert type.isObject() && expected.isObject() ||
|
||||
type.isEquivalentTo(expected) : type + " is not compatible with " + expected;
|
||||
return type;
|
||||
@ -252,7 +268,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the type
|
||||
*/
|
||||
private NumericType popNumeric() {
|
||||
final Type type = stack.pop();
|
||||
final Type type = popType();
|
||||
assert type.isNumeric() : type + " is not numeric";
|
||||
return (NumericType)type;
|
||||
}
|
||||
@ -264,7 +280,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the type
|
||||
*/
|
||||
private BitwiseType popInteger() {
|
||||
final Type type = stack.pop();
|
||||
final Type type = popType();
|
||||
assert type.isInteger() || type.isLong() : type + " is not an integer or long";
|
||||
return (BitwiseType)type;
|
||||
}
|
||||
@ -276,7 +292,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the type
|
||||
*/
|
||||
private ArrayType popArray() {
|
||||
final Type type = stack.pop();
|
||||
final Type type = popType();
|
||||
assert type.isArray() : type;
|
||||
return (ArrayType)type;
|
||||
}
|
||||
@ -307,13 +323,14 @@ public class MethodEmitter implements Emitter {
|
||||
* object type on the stack
|
||||
*
|
||||
* @param classDescriptor class descriptor for the object type
|
||||
* @param type the type of the new object
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter _new(final String classDescriptor) {
|
||||
MethodEmitter _new(final String classDescriptor, final Type type) {
|
||||
debug("new", classDescriptor);
|
||||
method.visitTypeInsn(NEW, classDescriptor);
|
||||
pushType(Type.OBJECT);
|
||||
pushType(type);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -326,7 +343,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter _new(final Class<?> clazz) {
|
||||
return _new(className(clazz));
|
||||
return _new(className(clazz), Type.typeFor(clazz));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -358,25 +375,40 @@ public class MethodEmitter implements Emitter {
|
||||
debug("dup", depth);
|
||||
|
||||
switch (depth) {
|
||||
case 0:
|
||||
case 0: {
|
||||
final int l0 = stack.getTopLocalLoad();
|
||||
pushType(peekType());
|
||||
stack.markLocalLoad(l0);
|
||||
break;
|
||||
}
|
||||
case 1: {
|
||||
final int l0 = stack.getTopLocalLoad();
|
||||
final Type p0 = popType();
|
||||
final int l1 = stack.getTopLocalLoad();
|
||||
final Type p1 = popType();
|
||||
pushType(p0);
|
||||
stack.markLocalLoad(l0);
|
||||
pushType(p1);
|
||||
stack.markLocalLoad(l1);
|
||||
pushType(p0);
|
||||
stack.markLocalLoad(l0);
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
final int l0 = stack.getTopLocalLoad();
|
||||
final Type p0 = popType();
|
||||
final int l1 = stack.getTopLocalLoad();
|
||||
final Type p1 = popType();
|
||||
final int l2 = stack.getTopLocalLoad();
|
||||
final Type p2 = popType();
|
||||
pushType(p0);
|
||||
stack.markLocalLoad(l0);
|
||||
pushType(p2);
|
||||
stack.markLocalLoad(l2);
|
||||
pushType(p1);
|
||||
stack.markLocalLoad(l1);
|
||||
pushType(p0);
|
||||
stack.markLocalLoad(l0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@ -398,13 +430,22 @@ public class MethodEmitter implements Emitter {
|
||||
debug("dup2");
|
||||
|
||||
if (peekType().isCategory2()) {
|
||||
final int l0 = stack.getTopLocalLoad();
|
||||
pushType(peekType());
|
||||
stack.markLocalLoad(l0);
|
||||
} else {
|
||||
final Type type = get2();
|
||||
pushType(type);
|
||||
pushType(type);
|
||||
pushType(type);
|
||||
pushType(type);
|
||||
final int l0 = stack.getTopLocalLoad();
|
||||
final Type p0 = popType();
|
||||
final int l1 = stack.getTopLocalLoad();
|
||||
final Type p1 = popType();
|
||||
pushType(p0);
|
||||
stack.markLocalLoad(l0);
|
||||
pushType(p1);
|
||||
stack.markLocalLoad(l1);
|
||||
pushType(p0);
|
||||
stack.markLocalLoad(l0);
|
||||
pushType(p1);
|
||||
stack.markLocalLoad(l1);
|
||||
}
|
||||
method.visitInsn(DUP2);
|
||||
return this;
|
||||
@ -454,16 +495,34 @@ public class MethodEmitter implements Emitter {
|
||||
MethodEmitter swap() {
|
||||
debug("swap");
|
||||
|
||||
final int l0 = stack.getTopLocalLoad();
|
||||
final Type p0 = popType();
|
||||
final int l1 = stack.getTopLocalLoad();
|
||||
final Type p1 = popType();
|
||||
p0.swap(method, p1);
|
||||
|
||||
pushType(p0);
|
||||
stack.markLocalLoad(l0);
|
||||
pushType(p1);
|
||||
stack.markLocalLoad(l1);
|
||||
debug("after ", p0, p1);
|
||||
return this;
|
||||
}
|
||||
|
||||
void pack() {
|
||||
final Type type = peekType();
|
||||
if (type.isInteger()) {
|
||||
convert(PRIMITIVE_FIELD_TYPE);
|
||||
} else if (type.isLong()) {
|
||||
//nop
|
||||
} else if (type.isNumber()) {
|
||||
invokestatic("java/lang/Double", "doubleToRawLongBits", "(D)J");
|
||||
} else {
|
||||
assert false : type + " cannot be packed!";
|
||||
}
|
||||
//all others are nops, objects aren't packed
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a local variable. This is a nop if the symbol has no slot
|
||||
*
|
||||
@ -586,9 +645,9 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter neg() {
|
||||
MethodEmitter neg(final int programPoint) {
|
||||
debug("neg");
|
||||
pushType(popNumeric().neg(method));
|
||||
pushType(popNumeric().neg(method, programPoint));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -599,9 +658,23 @@ public class MethodEmitter implements Emitter {
|
||||
* @param recovery label pointing to start of catch block
|
||||
*/
|
||||
void _catch(final Label recovery) {
|
||||
stack.clear();
|
||||
stack.push(Type.OBJECT);
|
||||
label(recovery);
|
||||
stack.clear();
|
||||
pushType(Type.typeFor(Throwable.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add any number of labels for the start of a catch block and push the exception to the
|
||||
* stack
|
||||
*
|
||||
* @param recoveries labels pointing to start of catch block
|
||||
*/
|
||||
void _catch(final Collection<Label> recoveries) {
|
||||
for(final Label l: recoveries) {
|
||||
label(l);
|
||||
}
|
||||
stack.clear();
|
||||
pushType(Type.OBJECT);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -665,6 +738,12 @@ public class MethodEmitter implements Emitter {
|
||||
return this;
|
||||
}
|
||||
|
||||
MethodEmitter loadForcedInitializer(final Type type) {
|
||||
debug("load forced initializer ", type);
|
||||
pushType(type.loadForcedInitializer(method));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push the empty value for the given type, i.e. EMPTY.
|
||||
*
|
||||
@ -816,9 +895,10 @@ public class MethodEmitter implements Emitter {
|
||||
assert symbol != null;
|
||||
if (symbol.hasSlot()) {
|
||||
final int slot = symbol.getSlot();
|
||||
debug("load symbol", symbol.getName(), " slot=", slot);
|
||||
final Type type = symbol.getSymbolType().load(method, slot);
|
||||
pushType(type == Type.OBJECT && symbol.isThis() ? Type.THIS : type);
|
||||
debug("load symbol", symbol.getName(), " slot=", slot, "type=", symbol.getSymbolType());
|
||||
load(symbol.getSymbolType(), slot);
|
||||
// _try(new Label("dummy"), new Label("dummy2"), recovery);
|
||||
// method.visitTryCatchBlock(new Label(), arg1, arg2, arg3);
|
||||
} else if (symbol.isParam()) {
|
||||
assert !symbol.isScope();
|
||||
assert functionNode.isVarArg() : "Non-vararg functions have slotted parameters";
|
||||
@ -853,7 +933,9 @@ public class MethodEmitter implements Emitter {
|
||||
MethodEmitter load(final Type type, final int slot) {
|
||||
debug("explicit load", type, slot);
|
||||
final Type loadType = type.load(method, slot);
|
||||
assert loadType != null;
|
||||
pushType(loadType == Type.OBJECT && isThisSlot(slot) ? Type.THIS : loadType);
|
||||
stack.markLocalLoad(slot);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -949,7 +1031,7 @@ public class MethodEmitter implements Emitter {
|
||||
if (symbol.hasSlot()) {
|
||||
final int slot = symbol.getSlot();
|
||||
debug("store symbol", symbol.getName(), " slot=", slot);
|
||||
popType(symbol.getSymbolType()).store(method, slot);
|
||||
store(symbol.getSymbolType(), slot);
|
||||
} else if (symbol.isParam()) {
|
||||
assert !symbol.isScope();
|
||||
assert functionNode.isVarArg() : "Non-vararg functions have slotted parameters";
|
||||
@ -977,8 +1059,37 @@ public class MethodEmitter implements Emitter {
|
||||
* @param slot the slot
|
||||
*/
|
||||
void store(final Type type, final int slot) {
|
||||
debug("explicit store", type, slot);
|
||||
popType(type);
|
||||
type.store(method, slot);
|
||||
// TODO: disable this when not running with optimistic types?
|
||||
final int slotCount = type.getSlots();
|
||||
ensureLocalVariableCount(slot + slotCount);
|
||||
localVariableTypes.set(slot, type);
|
||||
if(slotCount == 2) {
|
||||
localVariableTypes.set(slot + 1, Type.SLOT_2);
|
||||
}
|
||||
stack.markLocalStore(slot, slotCount);
|
||||
}
|
||||
|
||||
void ensureLocalVariableCount(final int slotCount) {
|
||||
while(localVariableTypes.size() < slotCount) {
|
||||
localVariableTypes.add(Type.UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
List<Type> getLocalVariableTypes() {
|
||||
return localVariableTypes;
|
||||
}
|
||||
|
||||
void setParameterTypes(final Type... paramTypes) {
|
||||
assert localVariableTypes.isEmpty();
|
||||
for(final Type type: paramTypes) {
|
||||
localVariableTypes.add(type);
|
||||
if(type.isCategory2()) {
|
||||
localVariableTypes.add(Type.SLOT_2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -999,7 +1110,7 @@ public class MethodEmitter implements Emitter {
|
||||
public void athrow() {
|
||||
debug("athrow");
|
||||
final Type receiver = popType(Type.OBJECT);
|
||||
assert receiver.isObject();
|
||||
assert Throwable.class.isAssignableFrom(receiver.getTypeClass()) : receiver.getTypeClass();
|
||||
method.visitInsn(ATHROW);
|
||||
stack = null;
|
||||
}
|
||||
@ -1130,11 +1241,7 @@ public class MethodEmitter implements Emitter {
|
||||
popType(Type.OBJECT);
|
||||
}
|
||||
|
||||
if (opcode == INVOKEINTERFACE) {
|
||||
method.visitMethodInsn(opcode, className, methodName, methodDescriptor, true);
|
||||
} else {
|
||||
method.visitMethodInsn(opcode, className, methodName, methodDescriptor, false);
|
||||
}
|
||||
method.visitMethodInsn(opcode, className, methodName, methodDescriptor, opcode == INVOKEINTERFACE);
|
||||
|
||||
if (returnType != null) {
|
||||
pushType(returnType);
|
||||
@ -1197,7 +1304,7 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter invokeStatic(final String className, final String methodName, final String methodDescriptor, final Type returnType) {
|
||||
MethodEmitter invokestatic(final String className, final String methodName, final String methodDescriptor, final Type returnType) {
|
||||
invokestatic(className, methodName, methodDescriptor);
|
||||
popType();
|
||||
pushType(returnType);
|
||||
@ -1235,8 +1342,9 @@ public class MethodEmitter implements Emitter {
|
||||
*/
|
||||
void lookupswitch(final Label defaultLabel, final int[] values, final Label... table) {//Collection<Label> table) {
|
||||
debug("lookupswitch", peekType());
|
||||
popType(Type.INT);
|
||||
adjustStackForSwitch(defaultLabel, table);
|
||||
method.visitLookupSwitchInsn(defaultLabel.getLabel(), values, getLabels(table));
|
||||
stack = null; //whoever reaches the point after us provides the stack, because we don't
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1248,8 +1356,17 @@ public class MethodEmitter implements Emitter {
|
||||
*/
|
||||
void tableswitch(final int lo, final int hi, final Label defaultLabel, final Label... table) {
|
||||
debug("tableswitch", peekType());
|
||||
popType(Type.INT);
|
||||
adjustStackForSwitch(defaultLabel, table);
|
||||
method.visitTableSwitchInsn(lo, hi, defaultLabel.getLabel(), getLabels(table));
|
||||
stack = null; //whoever reaches the point after us provides the stack, because we don't
|
||||
}
|
||||
|
||||
private void adjustStackForSwitch(final Label defaultLabel, final Label... table) {
|
||||
popType(Type.INT);
|
||||
mergeStackTo(defaultLabel);
|
||||
for(final Label label: table) {
|
||||
mergeStackTo(label);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1489,7 +1606,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @param label destination label
|
||||
*/
|
||||
void _goto(final Label label) {
|
||||
//debug("goto", label);
|
||||
debug("goto", label);
|
||||
jump(GOTO, label, 0);
|
||||
stack = null; //whoever reaches the point after us provides the stack, because we don't
|
||||
}
|
||||
@ -1526,7 +1643,8 @@ public class MethodEmitter implements Emitter {
|
||||
label.setStack(stack.copy());
|
||||
return;
|
||||
}
|
||||
assert stack.isEquivalentTo(labelStack) : "stacks " + stack + " is not equivalent with " + labelStack + " at join point";
|
||||
assert stack.isEquivalentInTypesTo(labelStack) : "stacks " + stack + " is not equivalent with " + labelStack + " at join point " + label;
|
||||
stack.mergeLocalLoads(labelStack);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1561,13 +1679,32 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter convert(final Type to) {
|
||||
final Type type = peekType().convert(method, to);
|
||||
final Type from = peekType();
|
||||
final Type type = from.convert(method, to);
|
||||
if (type != null) {
|
||||
if (!peekType().isEquivalentTo(to)) {
|
||||
debug("convert", peekType(), "->", to);
|
||||
if (!from.isEquivalentTo(to)) {
|
||||
debug("convert", from, "->", to);
|
||||
}
|
||||
if (type != from) {
|
||||
final int l0 = stack.getTopLocalLoad();
|
||||
popType();
|
||||
pushType(type);
|
||||
// NOTE: conversions from a primitive type are considered to preserve the "load" property of the value
|
||||
// on the stack. Otherwise we could introduce temporary locals in a deoptimized rest-of (e.g. doing an
|
||||
// "i < x.length" where "i" is int and ".length" gets deoptimized to long would end up converting i to
|
||||
// long with "ILOAD i; I2L; LSTORE tmp; LLOAD tmp;"). Such additional temporary would cause an error
|
||||
// when restoring the state of the function for rest-of execution, as the not-yet deoptimized variant
|
||||
// would have the (now invalidated) assumption that "x.length" is an int, so it wouldn't have the I2L,
|
||||
// and therefore neither the subsequent LSTORE tmp; LLOAD tmp;. By making sure conversions from a
|
||||
// primitive type don't erase the "load" information, we don't introduce temporaries in the deoptimized
|
||||
// rest-of that didn't exist in the more optimistic version that triggered the deoptimization.
|
||||
// NOTE: as a more general observation, we could theoretically track the operations required to
|
||||
// reproduce any stack value as long as they are all local loads, constant loads, and stack operations.
|
||||
// We won't go there in the current system
|
||||
if(!from.isObject()) {
|
||||
stack.markLocalLoad(l0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@ -1613,9 +1750,9 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter add() {
|
||||
MethodEmitter add(final int programPoint) {
|
||||
debug("add");
|
||||
pushType(get2().add(method));
|
||||
pushType(get2().add(method, programPoint));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1624,9 +1761,9 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter sub() {
|
||||
MethodEmitter sub(final int programPoint) {
|
||||
debug("sub");
|
||||
pushType(get2n().sub(method));
|
||||
pushType(get2n().sub(method, programPoint));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1635,9 +1772,9 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter mul() {
|
||||
MethodEmitter mul(final int programPoint) {
|
||||
debug("mul ");
|
||||
pushType(get2n().mul(method));
|
||||
pushType(get2n().mul(method, programPoint));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1646,9 +1783,9 @@ public class MethodEmitter implements Emitter {
|
||||
*
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter div() {
|
||||
MethodEmitter div(final int programPoint) {
|
||||
debug("div");
|
||||
pushType(get2n().div(method));
|
||||
pushType(get2n().div(method, programPoint));
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -1670,13 +1807,15 @@ public class MethodEmitter implements Emitter {
|
||||
* @return array of Types
|
||||
*/
|
||||
protected Type[] getTypesFromStack(final int count) {
|
||||
final Type[] types = new Type[count];
|
||||
int pos = 0;
|
||||
for (int i = count - 1; i >= 0; i--) {
|
||||
types[i] = stack.peek(pos++);
|
||||
return stack.getTopTypes(count);
|
||||
}
|
||||
|
||||
return types;
|
||||
int[] getLocalLoadsOnStack(final int from, final int to) {
|
||||
return stack.getLocalLoads(from, to);
|
||||
}
|
||||
|
||||
int getStackSize() {
|
||||
return stack.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1712,6 +1851,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter dynamicNew(final int argCount, final int flags) {
|
||||
assert !isOptimistic(flags);
|
||||
debug("dynamic_new", "argcount=", argCount);
|
||||
final String signature = getDynamicSignature(Type.OBJECT, argCount);
|
||||
method.visitInvokeDynamicInsn("dyn:new", signature, LINKERBOOTSTRAP, flags);
|
||||
@ -1738,6 +1878,13 @@ public class MethodEmitter implements Emitter {
|
||||
return this;
|
||||
}
|
||||
|
||||
MethodEmitter dynamicArrayPopulatorCall(final int argCount, final int startIndex) {
|
||||
final String signature = getDynamicSignature(Type.OBJECT_ARRAY, argCount);
|
||||
method.visitInvokeDynamicInsn("populateArray", signature, POPULATE_ARRAY_BOOTSTRAP, startIndex);
|
||||
pushType(Type.OBJECT_ARRAY);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a dynamic call for a runtime node
|
||||
*
|
||||
@ -1768,7 +1915,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter dynamicGet(final Type valueType, final String name, final int flags, final boolean isMethod) {
|
||||
debug("dynamic_get", name, valueType);
|
||||
debug("dynamic_get", name, valueType, getProgramPoint(flags));
|
||||
|
||||
Type type = valueType;
|
||||
if (type.isObject() || type.isBoolean()) {
|
||||
@ -1780,7 +1927,6 @@ public class MethodEmitter implements Emitter {
|
||||
NameCodec.encode(name), Type.getMethodDescriptor(type, Type.OBJECT), LINKERBOOTSTRAP, flags);
|
||||
|
||||
pushType(type);
|
||||
|
||||
convert(valueType); //most probably a nop
|
||||
|
||||
return this;
|
||||
@ -1794,6 +1940,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @param flags call site flags
|
||||
*/
|
||||
void dynamicSet(final String name, final int flags) {
|
||||
assert !isOptimistic(flags);
|
||||
debug("dynamic_set", name, peekType());
|
||||
|
||||
Type type = peekType();
|
||||
@ -1818,7 +1965,8 @@ public class MethodEmitter implements Emitter {
|
||||
* @return the method emitter
|
||||
*/
|
||||
MethodEmitter dynamicGetIndex(final Type result, final int flags, final boolean isMethod) {
|
||||
debug("dynamic_get_index", peekType(1), "[", peekType(), "]");
|
||||
assert result.getTypeClass().isPrimitive() || result.getTypeClass() == Object.class;
|
||||
debug("dynamic_get_index", peekType(1), "[", peekType(), "]", getProgramPoint(flags));
|
||||
|
||||
Type resultType = result;
|
||||
if (result.isBoolean()) {
|
||||
@ -1836,8 +1984,7 @@ public class MethodEmitter implements Emitter {
|
||||
|
||||
final String signature = Type.getMethodDescriptor(resultType, Type.OBJECT /*e.g STRING->OBJECT*/, index);
|
||||
|
||||
method.visitInvokeDynamicInsn(isMethod ? "dyn:getMethod|getElem|getProp" : "dyn:getElem|getProp|getMethod",
|
||||
signature, LINKERBOOTSTRAP, flags);
|
||||
method.visitInvokeDynamicInsn(isMethod ? "dyn:getMethod|getElem|getProp" : "dyn:getElem|getProp|getMethod", signature, LINKERBOOTSTRAP, flags);
|
||||
pushType(resultType);
|
||||
|
||||
if (result.isBoolean()) {
|
||||
@ -1847,6 +1994,14 @@ public class MethodEmitter implements Emitter {
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
private static String getProgramPoint(int flags) {
|
||||
if((flags & CALLSITE_OPTIMISTIC) == 0) {
|
||||
return "";
|
||||
}
|
||||
return "pp=" + String.valueOf((flags & (-1 << CALLSITE_PROGRAM_POINT_SHIFT)) >> CALLSITE_PROGRAM_POINT_SHIFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamic setter for indexed structures. Pop value, index and receiver from
|
||||
* stack, generate appropriate signature based on types
|
||||
@ -1854,6 +2009,7 @@ public class MethodEmitter implements Emitter {
|
||||
* @param flags call site flags for setter
|
||||
*/
|
||||
void dynamicSetIndex(final int flags) {
|
||||
assert !isOptimistic(flags);
|
||||
debug("dynamic_set_index", peekType(2), "[", peekType(1), "] =", peekType());
|
||||
|
||||
Type value = peekType();
|
||||
@ -2148,7 +2304,10 @@ public class MethodEmitter implements Emitter {
|
||||
} else {
|
||||
sb.append(t.getDescriptor());
|
||||
}
|
||||
|
||||
final int loadIndex = stack.localLoads[stack.sp - 1 - pos];
|
||||
if(loadIndex != Label.Stack.NON_LOAD) {
|
||||
sb.append('(').append(loadIndex).append(')');
|
||||
}
|
||||
if (pos + 1 < stack.size()) {
|
||||
sb.append(' ');
|
||||
}
|
||||
@ -2193,4 +2352,7 @@ public class MethodEmitter implements Emitter {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isOptimistic(final int flags) {
|
||||
return (flags & CALLSITE_OPTIMISTIC) != 0;
|
||||
}
|
||||
}
|
||||
|
@ -35,27 +35,39 @@ import static jdk.nashorn.internal.codegen.CompilerConstants.JS_OBJECT_PREFIX;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.className;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
|
||||
import static jdk.nashorn.internal.lookup.Lookup.MH;
|
||||
import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT;
|
||||
import static jdk.nashorn.internal.runtime.JSType.CONVERT_OBJECT_OPTIMISTIC;
|
||||
import static jdk.nashorn.internal.runtime.JSType.GET_UNDEFINED;
|
||||
import static jdk.nashorn.internal.runtime.JSType.getAccessorTypeIndex;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_UNDEFINED_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_INT_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_LONG_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_DOUBLE_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.JSType.TYPE_OBJECT_INDEX;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.isValid;
|
||||
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import jdk.nashorn.internal.codegen.ClassEmitter.Flag;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.runtime.AccessorProperty;
|
||||
import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.DebugLogger;
|
||||
import jdk.nashorn.internal.runtime.FunctionScope;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
import jdk.nashorn.internal.runtime.ScriptEnvironment;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
import jdk.nashorn.internal.runtime.Undefined;
|
||||
import jdk.nashorn.internal.runtime.UnwarrantedOptimismException;
|
||||
import jdk.nashorn.internal.runtime.options.Options;
|
||||
|
||||
/**
|
||||
@ -64,9 +76,9 @@ import jdk.nashorn.internal.runtime.options.Options;
|
||||
public final class ObjectClassGenerator {
|
||||
|
||||
/**
|
||||
* Marker for scope parameters.
|
||||
* Marker for scope parameters.g
|
||||
*/
|
||||
static final String SCOPE_MARKER = "P";
|
||||
private static final String SCOPE_MARKER = "P";
|
||||
|
||||
/**
|
||||
* Minimum number of extra fields in an object.
|
||||
@ -79,27 +91,55 @@ public final class ObjectClassGenerator {
|
||||
*/
|
||||
public static final DebugLogger LOG = new DebugLogger("fields", "nashorn.fields.debug");
|
||||
|
||||
private static final Set<String> FIELDS_TO_INSTRUMENT;
|
||||
static {
|
||||
final String fields = Options.getStringProperty("nashorn.fields", null);
|
||||
final Set<String> fti = new HashSet<>();
|
||||
if (fields != null) {
|
||||
final StringTokenizer st = new StringTokenizer(fields, ",");
|
||||
while (st.hasMoreTokens()) {
|
||||
fti.add(st.nextToken());
|
||||
}
|
||||
}
|
||||
FIELDS_TO_INSTRUMENT = fti.isEmpty() ? null : fti;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this particular field be instrumented with --log=fields
|
||||
* Internal use only
|
||||
* @param field field name
|
||||
* @return true if it should be instrumented
|
||||
*/
|
||||
public static boolean shouldInstrument(final String field) {
|
||||
//if no explicit fields to imstrument are given, instrument all fields
|
||||
return FIELDS_TO_INSTRUMENT == null || FIELDS_TO_INSTRUMENT.contains(field);
|
||||
}
|
||||
/**
|
||||
* is field debugging enabled. Several modules in codegen and properties use this, hence
|
||||
* public access.
|
||||
*/
|
||||
public static final boolean DEBUG_FIELDS = LOG.isEnabled();
|
||||
|
||||
private static final boolean EXPLICIT_OBJECT = Options.getBooleanProperty("nashorn.fields.objects");
|
||||
|
||||
/**
|
||||
* Should the runtime only use java.lang.Object slots for fields? If this is false, the representation
|
||||
* will be a primitive 64-bit long value used for all primitives and a java.lang.Object for references.
|
||||
* This introduces a larger number of method handles in the system, as we need to have different getters
|
||||
* and setters for the different fields. Currently this introduces significant overhead in Hotspot.
|
||||
* and setters for the different fields.
|
||||
*
|
||||
* This is engineered to plug into the TaggedArray implementation, when it's done.
|
||||
*/
|
||||
public static final boolean OBJECT_FIELDS_ONLY = !Options.getBooleanProperty("nashorn.fields.dual");
|
||||
public static final boolean OBJECT_FIELDS_ONLY = EXPLICIT_OBJECT || !ScriptEnvironment.globalOptimistic();
|
||||
|
||||
/** The field types in the system */
|
||||
private static final List<Type> FIELD_TYPES = new LinkedList<>();
|
||||
|
||||
/** What type is the primitive type in dual representation */
|
||||
public static final Type PRIMITIVE_TYPE = Type.LONG;
|
||||
public static final Type PRIMITIVE_FIELD_TYPE = Type.LONG;
|
||||
|
||||
private static final MethodHandle GET_DIFFERENT = findOwnMH("getDifferent", Object.class, Object.class, Class.class, MethodHandle.class, MethodHandle.class, int.class);
|
||||
private static final MethodHandle GET_DIFFERENT_UNDEFINED = findOwnMH("getDifferentUndefined", Object.class, int.class);
|
||||
|
||||
/**
|
||||
* The list of field types that we support - one type creates one field. This is currently either
|
||||
@ -107,8 +147,10 @@ public final class ObjectClassGenerator {
|
||||
*/
|
||||
static {
|
||||
if (!OBJECT_FIELDS_ONLY) {
|
||||
System.err.println("WARNING!!! Running with primitive fields - there is untested functionality!");
|
||||
FIELD_TYPES.add(PRIMITIVE_TYPE);
|
||||
LOG.warning("Running with primitive fields - there is untested functionality!");
|
||||
FIELD_TYPES.add(PRIMITIVE_FIELD_TYPE);
|
||||
} else {
|
||||
System.err.println("Running with object fields only");
|
||||
}
|
||||
FIELD_TYPES.add(Type.OBJECT);
|
||||
}
|
||||
@ -116,23 +158,6 @@ public final class ObjectClassGenerator {
|
||||
/** The context */
|
||||
private final Context context;
|
||||
|
||||
/**
|
||||
* The list of available accessor types in width order. This order is used for type guesses narrow{@literal ->} wide
|
||||
* in the dual--fields world
|
||||
*/
|
||||
public static final List<Type> ACCESSOR_TYPES = Collections.unmodifiableList(
|
||||
Arrays.asList(
|
||||
Type.INT,
|
||||
Type.LONG,
|
||||
Type.NUMBER,
|
||||
Type.OBJECT));
|
||||
|
||||
//these are hard coded for speed and so that we can switch on them
|
||||
private static final int TYPE_INT_INDEX = 0; //getAccessorTypeIndex(int.class);
|
||||
private static final int TYPE_LONG_INDEX = 1; //getAccessorTypeIndex(long.class);
|
||||
private static final int TYPE_DOUBLE_INDEX = 2; //getAccessorTypeIndex(double.class);
|
||||
private static final int TYPE_OBJECT_INDEX = 3; //getAccessorTypeIndex(Object.class);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -144,61 +169,19 @@ public final class ObjectClassGenerator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a type of an accessor, return its index in [0..getNumberOfAccessorTypes())
|
||||
*
|
||||
* @param type the type
|
||||
*
|
||||
* @return the accessor index, or -1 if no accessor of this type exists
|
||||
* Pack a number into a primitive long field
|
||||
* @param n number object
|
||||
* @return primitive long value with all the bits in the number
|
||||
*/
|
||||
public static int getAccessorTypeIndex(final Type type) {
|
||||
return getAccessorTypeIndex(type.getTypeClass());
|
||||
public static long pack(final Number n) {
|
||||
if (n instanceof Integer) {
|
||||
return n.intValue();
|
||||
} else if (n instanceof Long) {
|
||||
return n.longValue();
|
||||
} else if (n instanceof Double) {
|
||||
return Double.doubleToRawLongBits(n.doubleValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a class of an accessor, return its index in [0..getNumberOfAccessorTypes())
|
||||
*
|
||||
* Note that this is hardcoded with respect to the dynamic contents of the accessor
|
||||
* types array for speed. Hotspot got stuck with this as 5% of the runtime in
|
||||
* a benchmark when it looped over values and increased an index counter. :-(
|
||||
*
|
||||
* @param type the type
|
||||
*
|
||||
* @return the accessor index, or -1 if no accessor of this type exists
|
||||
*/
|
||||
public static int getAccessorTypeIndex(final Class<?> type) {
|
||||
if (type == int.class) {
|
||||
return 0;
|
||||
} else if (type == long.class) {
|
||||
return 1;
|
||||
} else if (type == double.class) {
|
||||
return 2;
|
||||
} else if (!type.isPrimitive()) {
|
||||
return 3;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of accessor types available.
|
||||
*
|
||||
* @return number of accessor types in system
|
||||
*/
|
||||
public static int getNumberOfAccessorTypes() {
|
||||
return ACCESSOR_TYPES.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the accessor type based on its index in [0..getNumberOfAccessorTypes())
|
||||
* Indexes are ordered narrower{@literal ->}wider / optimistic{@literal ->}pessimistic. Invalidations always
|
||||
* go to a type of higher index
|
||||
*
|
||||
* @param index accessor type index
|
||||
*
|
||||
* @return a type corresponding to the index.
|
||||
*/
|
||||
|
||||
public static Type getAccessorType(final int index) {
|
||||
return ACCESSOR_TYPES.get(index);
|
||||
throw new AssertionError("cannot pack" + n);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -324,6 +307,10 @@ public final class ObjectClassGenerator {
|
||||
init.returnVoid();
|
||||
init.end();
|
||||
|
||||
final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, ScriptObject.class);
|
||||
initWithSpillArrays.returnVoid();
|
||||
initWithSpillArrays.end();
|
||||
|
||||
newEmptyInit(classEmitter, className);
|
||||
newAllocate(classEmitter, className);
|
||||
|
||||
@ -350,6 +337,11 @@ public final class ObjectClassGenerator {
|
||||
init.returnVoid();
|
||||
init.end();
|
||||
|
||||
final MethodEmitter initWithSpillArrays = newInitWithSpillArraysMethod(classEmitter, FunctionScope.class);
|
||||
initializeToUndefined(initWithSpillArrays, className, initFields);
|
||||
initWithSpillArrays.returnVoid();
|
||||
initWithSpillArrays.end();
|
||||
|
||||
final MethodEmitter initWithArguments = newInitScopeWithArgumentsMethod(classEmitter);
|
||||
initializeToUndefined(initWithArguments, className, initFields);
|
||||
initWithArguments.returnVoid();
|
||||
@ -414,6 +406,18 @@ public final class ObjectClassGenerator {
|
||||
return init;
|
||||
}
|
||||
|
||||
private static MethodEmitter newInitWithSpillArraysMethod(final ClassEmitter classEmitter, final Class<?> superClass) {
|
||||
final MethodEmitter init = classEmitter.init(PropertyMap.class, long[].class, Object[].class);
|
||||
init.begin();
|
||||
init.load(Type.OBJECT, JAVA_THIS.slot());
|
||||
init.load(Type.OBJECT, INIT_MAP.slot());
|
||||
init.load(Type.LONG_ARRAY, 2);
|
||||
init.load(Type.OBJECT_ARRAY, 3);
|
||||
init.invoke(constructorNoLookup(superClass, PropertyMap.class, long[].class, Object[].class));
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate and initialize a new <init> method for scopes.
|
||||
* @param classEmitter Open class emitter.
|
||||
@ -472,7 +476,7 @@ public final class ObjectClassGenerator {
|
||||
private static void newAllocate(final ClassEmitter classEmitter, final String className) {
|
||||
final MethodEmitter allocate = classEmitter.method(EnumSet.of(Flag.PUBLIC, Flag.STATIC), ALLOCATE.symbolName(), ScriptObject.class, PropertyMap.class);
|
||||
allocate.begin();
|
||||
allocate._new(className);
|
||||
allocate._new(className, Type.typeFor(ScriptObject.class));
|
||||
allocate.dup();
|
||||
allocate.load(Type.typeFor(PropertyMap.class), 0);
|
||||
allocate.invoke(constructorNoLookup(className, PropertyMap.class));
|
||||
@ -492,7 +496,7 @@ public final class ObjectClassGenerator {
|
||||
final byte[] code = classEmitter.toByteArray();
|
||||
final ScriptEnvironment env = context.getEnv();
|
||||
|
||||
if (env._print_code) {
|
||||
if (env._print_code && env._print_code_dir == null) {
|
||||
env.getErr().println(ClassEmitter.disassemble(code));
|
||||
}
|
||||
|
||||
@ -504,20 +508,172 @@ public final class ObjectClassGenerator {
|
||||
}
|
||||
|
||||
/** Double to long bits, used with --dual-fields for primitive double values */
|
||||
private static final MethodHandle PACK_DOUBLE =
|
||||
public static final MethodHandle PACK_DOUBLE =
|
||||
MH.explicitCastArguments(MH.findStatic(MethodHandles.publicLookup(), Double.class, "doubleToRawLongBits", MH.type(long.class, double.class)), MH.type(long.class, double.class));
|
||||
|
||||
/** double bits to long, used with --dual-fields for primitive double values */
|
||||
private static MethodHandle UNPACK_DOUBLE =
|
||||
public static final MethodHandle UNPACK_DOUBLE =
|
||||
MH.findStatic(MethodHandles.publicLookup(), Double.class, "longBitsToDouble", MH.type(double.class, long.class));
|
||||
|
||||
/** object conversion quickies with JS semantics - used for return value and parameter filter */
|
||||
private static MethodHandle[] CONVERT_OBJECT = {
|
||||
JSType.TO_INT32.methodHandle(),
|
||||
JSType.TO_UINT32.methodHandle(),
|
||||
JSType.TO_NUMBER.methodHandle(),
|
||||
null
|
||||
};
|
||||
//type != forType, so use the correct getter for forType, box it and throw
|
||||
@SuppressWarnings("unused")
|
||||
private static Object getDifferent(final Object receiver, final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) {
|
||||
//create the sametype getter, and upcast to value. no matter what the store format is,
|
||||
//
|
||||
final MethodHandle sameTypeGetter = getterForType(forType, primitiveGetter, objectGetter);
|
||||
final MethodHandle mh = MH.asType(sameTypeGetter, sameTypeGetter.type().changeReturnType(Object.class));
|
||||
try {
|
||||
@SuppressWarnings("cast")
|
||||
final Object value = (Object)mh.invokeExact(receiver);
|
||||
throw new UnwarrantedOptimismException(value, programPoint);
|
||||
} catch (final Error | RuntimeException e) {
|
||||
throw e;
|
||||
} catch (final Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Object getDifferentUndefined(final int programPoint) {
|
||||
throw new UnwarrantedOptimismException(Undefined.getUndefined(), programPoint);
|
||||
}
|
||||
|
||||
private static MethodHandle getterForType(final Class<?> forType, final MethodHandle primitiveGetter, final MethodHandle objectGetter) {
|
||||
switch (getAccessorTypeIndex(forType)) {
|
||||
case TYPE_INT_INDEX:
|
||||
assert !OBJECT_FIELDS_ONLY : "this can only happen with dual fields";
|
||||
return MH.explicitCastArguments(primitiveGetter, primitiveGetter.type().changeReturnType(int.class));
|
||||
case TYPE_LONG_INDEX:
|
||||
assert !OBJECT_FIELDS_ONLY : "this can only happen with dual fields";
|
||||
return primitiveGetter;
|
||||
case TYPE_DOUBLE_INDEX:
|
||||
assert !OBJECT_FIELDS_ONLY : "this can only happen with dual fields";
|
||||
return MH.filterReturnValue(primitiveGetter, UNPACK_DOUBLE);
|
||||
case TYPE_OBJECT_INDEX:
|
||||
return objectGetter;
|
||||
default:
|
||||
throw new AssertionError(forType);
|
||||
}
|
||||
}
|
||||
|
||||
//no optimism here. we do unconditional conversion to types
|
||||
private static MethodHandle createGetterInner(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final MethodHandle[] converters, final int programPoint) {
|
||||
final int fti = forType == null ? TYPE_UNDEFINED_INDEX : getAccessorTypeIndex(forType);
|
||||
final int ti = getAccessorTypeIndex(type);
|
||||
//this means fail if forType != type
|
||||
final boolean isOptimistic = converters == CONVERT_OBJECT_OPTIMISTIC;
|
||||
final boolean isPrimitiveStorage = forType != null && forType.isPrimitive();
|
||||
|
||||
//which is the primordial getter
|
||||
final MethodHandle getter = OBJECT_FIELDS_ONLY ? objectGetter : (isPrimitiveStorage ? primitiveGetter : objectGetter);
|
||||
|
||||
if (forType == null) {
|
||||
if (isOptimistic) {
|
||||
//return undefined if asking for object. otherwise throw UnwarrantedOptimismException
|
||||
if (ti == TYPE_OBJECT_INDEX) {
|
||||
return MH.dropArguments(GET_UNDEFINED[TYPE_OBJECT_INDEX], 0, Object.class);
|
||||
}
|
||||
//throw exception
|
||||
return MH.asType(
|
||||
MH.dropArguments(
|
||||
MH.insertArguments(
|
||||
GET_DIFFERENT_UNDEFINED,
|
||||
0,
|
||||
programPoint),
|
||||
0,
|
||||
Object.class),
|
||||
getter.type().changeReturnType(type));
|
||||
}
|
||||
//return an undefined and coerce it to the appropriate type
|
||||
return MH.dropArguments(GET_UNDEFINED[ti], 0, Object.class);
|
||||
}
|
||||
|
||||
assert forType != null;
|
||||
assert !OBJECT_FIELDS_ONLY || forType == Object.class : forType;
|
||||
|
||||
if (isOptimistic) {
|
||||
if (fti < ti) {
|
||||
//asking for a wider type than currently stored. then it's OK to coerce.
|
||||
//e.g. stored as int, ask for long or double
|
||||
//e.g. stored as long, ask for double
|
||||
assert fti != TYPE_UNDEFINED_INDEX;
|
||||
final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter);
|
||||
return MH.asType(tgetter, tgetter.type().changeReturnType(type));
|
||||
} else if (fti == ti) {
|
||||
//Fast path, never throw exception - exact getter, just unpack if needed
|
||||
return getterForType(forType, primitiveGetter, objectGetter);
|
||||
} else {
|
||||
assert fti > ti;
|
||||
//if asking for a narrower type than the storage - throw exception
|
||||
//unless FTI is object, in that case we have to go through the converters
|
||||
//there is no
|
||||
if (fti == TYPE_OBJECT_INDEX) {
|
||||
return MH.filterReturnValue(
|
||||
objectGetter,
|
||||
MH.insertArguments(
|
||||
converters[ti],
|
||||
1,
|
||||
programPoint));
|
||||
}
|
||||
|
||||
//asking for narrower primitive than we have stored, that is an
|
||||
//UnwarrantedOptimismException
|
||||
return MH.asType(
|
||||
MH.filterArguments(
|
||||
objectGetter,
|
||||
0,
|
||||
MH.insertArguments(
|
||||
GET_DIFFERENT,
|
||||
1,
|
||||
forType,
|
||||
primitiveGetter,
|
||||
objectGetter,
|
||||
programPoint)),
|
||||
objectGetter.type().changeReturnType(type));
|
||||
}
|
||||
}
|
||||
|
||||
assert !isOptimistic;
|
||||
//freely coerce the result to whatever you asked for, this is e.g. Object->int for a & b
|
||||
final MethodHandle tgetter = getterForType(forType, primitiveGetter, objectGetter);
|
||||
if (fti == TYPE_OBJECT_INDEX) {
|
||||
if (fti != ti) {
|
||||
return MH.filterReturnValue(tgetter, CONVERT_OBJECT[ti]);
|
||||
}
|
||||
return tgetter;
|
||||
}
|
||||
|
||||
assert !OBJECT_FIELDS_ONLY;
|
||||
//final MethodType pmt = primitiveGetter.type();
|
||||
assert primitiveGetter != null;
|
||||
final MethodType tgetterType = tgetter.type();
|
||||
switch (fti) {
|
||||
case TYPE_INT_INDEX:
|
||||
case TYPE_LONG_INDEX:
|
||||
switch (ti) {
|
||||
case TYPE_INT_INDEX:
|
||||
//get int while an int, truncating cast of long value
|
||||
return MH.explicitCastArguments(tgetter, tgetterType.changeReturnType(int.class));
|
||||
case TYPE_LONG_INDEX:
|
||||
return primitiveGetter;
|
||||
default:
|
||||
return MH.asType(tgetter, tgetterType.changeReturnType(type));
|
||||
}
|
||||
case TYPE_DOUBLE_INDEX:
|
||||
switch (ti) {
|
||||
case TYPE_INT_INDEX:
|
||||
case TYPE_LONG_INDEX:
|
||||
return MH.explicitCastArguments(tgetter, tgetterType.changeReturnType(type));
|
||||
case TYPE_DOUBLE_INDEX:
|
||||
assert tgetterType.returnType() == double.class;
|
||||
return tgetter;
|
||||
default:
|
||||
return MH.asType(tgetter, tgetterType.changeReturnType(Object.class));
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException(forType + "=>" + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a primitiveGetter (optional for non dual fields) and an objectSetter that retrieve
|
||||
@ -526,7 +682,7 @@ public final class ObjectClassGenerator {
|
||||
* and we want an Object getter, in the dual fields world we'd pick the primitiveGetter,
|
||||
* which reads a long, use longBitsToDouble on the result to unpack it, and then change the
|
||||
* return type to Object, boxing it. In the objects only world there are only object fields,
|
||||
* primtives are boxed when asked for them and we don't need to bother with primitive encoding
|
||||
* primitives are boxed when asked for them and we don't need to bother with primitive encoding
|
||||
* (or even undefined, which if forType==null) representation, so we just return whatever is
|
||||
* in the object field. The object field is always initiated to Undefined, so here, where we have
|
||||
* the representation for Undefined in all our bits, this is not a problem.
|
||||
@ -543,110 +699,18 @@ public final class ObjectClassGenerator {
|
||||
* @param type type to retrieve it as
|
||||
* @param primitiveGetter getter to read the primitive version of this field (null if Objects Only)
|
||||
* @param objectGetter getter to read the object version of this field
|
||||
* @param programPoint program point for getter, if program point is INVALID_PROGRAM_POINT, then this is not an optimistic getter
|
||||
*
|
||||
* @return getter for the given representation that returns the given type
|
||||
*/
|
||||
public static MethodHandle createGetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter) {
|
||||
final int fti = forType == null ? -1 : getAccessorTypeIndex(forType);
|
||||
final int ti = getAccessorTypeIndex(type);
|
||||
|
||||
if (fti == TYPE_OBJECT_INDEX || OBJECT_FIELDS_ONLY) {
|
||||
if (ti == TYPE_OBJECT_INDEX) {
|
||||
return objectGetter;
|
||||
}
|
||||
|
||||
return MH.filterReturnValue(objectGetter, CONVERT_OBJECT[ti]);
|
||||
}
|
||||
|
||||
assert !OBJECT_FIELDS_ONLY;
|
||||
if (forType == null) {
|
||||
return GET_UNDEFINED[ti];
|
||||
}
|
||||
|
||||
final MethodType pmt = primitiveGetter.type();
|
||||
|
||||
switch (fti) {
|
||||
case TYPE_INT_INDEX:
|
||||
case TYPE_LONG_INDEX:
|
||||
switch (ti) {
|
||||
case TYPE_INT_INDEX:
|
||||
//get int while an int, truncating cast of long value
|
||||
return MH.explicitCastArguments(primitiveGetter, pmt.changeReturnType(int.class));
|
||||
case TYPE_LONG_INDEX:
|
||||
return primitiveGetter;
|
||||
default:
|
||||
return MH.asType(primitiveGetter, pmt.changeReturnType(type));
|
||||
}
|
||||
case TYPE_DOUBLE_INDEX:
|
||||
final MethodHandle getPrimitiveAsDouble = MH.filterReturnValue(primitiveGetter, UNPACK_DOUBLE);
|
||||
switch (ti) {
|
||||
case TYPE_INT_INDEX:
|
||||
case TYPE_LONG_INDEX:
|
||||
return MH.explicitCastArguments(getPrimitiveAsDouble, pmt.changeReturnType(type));
|
||||
case TYPE_DOUBLE_INDEX:
|
||||
return getPrimitiveAsDouble;
|
||||
default:
|
||||
return MH.asType(getPrimitiveAsDouble, pmt.changeReturnType(Object.class));
|
||||
}
|
||||
default:
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final MethodHandle IS_TYPE_GUARD = findOwnMH("isType", boolean.class, Class.class, Object.class);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static boolean isType(final Class<?> boxedForType, final Object x) {
|
||||
return x.getClass() == boxedForType;
|
||||
}
|
||||
|
||||
private static Class<? extends Number> getBoxedType(final Class<?> forType) {
|
||||
if (forType == int.class) {
|
||||
return Integer.class;
|
||||
}
|
||||
|
||||
if (forType == long.class) {
|
||||
return Long.class;
|
||||
}
|
||||
|
||||
if (forType == double.class) {
|
||||
return Double.class;
|
||||
}
|
||||
|
||||
assert false;
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we are setting boxed types (because the compiler couldn't determine which they were) to
|
||||
* a primitive field, we can reuse the primitive field getter, as long as we are setting an element
|
||||
* of the same boxed type as the primitive type representation
|
||||
*
|
||||
* @param forType the current type
|
||||
* @param primitiveSetter primitive setter for the current type with an element of the current type
|
||||
* @param objectSetter the object setter
|
||||
*
|
||||
* @return method handle that checks if the element to be set is of the currenttype, even though it's boxed
|
||||
* and instead of using the generic object setter, that would blow up the type and invalidate the map,
|
||||
* unbox it and call the primitive setter instead
|
||||
*/
|
||||
public static MethodHandle createGuardBoxedPrimitiveSetter(final Class<?> forType, final MethodHandle primitiveSetter, final MethodHandle objectSetter) {
|
||||
final Class<? extends Number> boxedForType = getBoxedType(forType);
|
||||
//object setter that checks for primitive if current type is primitive
|
||||
|
||||
return MH.guardWithTest(
|
||||
MH.insertArguments(
|
||||
MH.dropArguments(
|
||||
IS_TYPE_GUARD,
|
||||
1,
|
||||
Object.class),
|
||||
0,
|
||||
boxedForType),
|
||||
MH.asType(
|
||||
primitiveSetter,
|
||||
objectSetter.type()),
|
||||
objectSetter);
|
||||
public static MethodHandle createGetter(final Class<?> forType, final Class<?> type, final MethodHandle primitiveGetter, final MethodHandle objectGetter, final int programPoint) {
|
||||
return createGetterInner(
|
||||
forType,
|
||||
type,
|
||||
primitiveGetter,
|
||||
objectGetter,
|
||||
isValid(programPoint) ? CONVERT_OBJECT_OPTIMISTIC : CONVERT_OBJECT,
|
||||
programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -699,8 +763,30 @@ public final class ObjectClassGenerator {
|
||||
}
|
||||
return MH.asType(MH.filterArguments(primitiveSetter, 1, PACK_DOUBLE), pmt.changeParameterType(1, type));
|
||||
default:
|
||||
assert false;
|
||||
return null;
|
||||
throw new UnsupportedOperationException(forType + "=>" + type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unboxed (primitive) type for an object
|
||||
* @param o object
|
||||
* @return primive type or Object.class if not primitive
|
||||
*/
|
||||
public static Class<?> unboxedFieldType(final Object o) {
|
||||
if (OBJECT_FIELDS_ONLY) {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
if (o == null) {
|
||||
return Object.class;
|
||||
} else if (o.getClass() == Integer.class) {
|
||||
return int.class;
|
||||
} else if (o.getClass() == Long.class) {
|
||||
return long.class;
|
||||
} else if (o.getClass() == Double.class) {
|
||||
return double.class;
|
||||
} else {
|
||||
return Object.class;
|
||||
}
|
||||
}
|
||||
|
||||
@ -713,80 +799,9 @@ public final class ObjectClassGenerator {
|
||||
return count / FIELD_PADDING * FIELD_PADDING + FIELD_PADDING;
|
||||
}
|
||||
|
||||
//
|
||||
// Provide generic getters and setters for undefined types. If a type is undefined, all
|
||||
// and marshals the set to the correct setter depending on the type of the value being set.
|
||||
// Note that there are no actual undefined versions of int, long and double in JavaScript,
|
||||
// but executing toInt32, toLong and toNumber always returns a working result, 0, 0L or NaN
|
||||
//
|
||||
|
||||
/** The value of Undefined cast to an int32 */
|
||||
public static final int UNDEFINED_INT = 0;
|
||||
/** The value of Undefined cast to a long */
|
||||
public static final long UNDEFINED_LONG = 0L;
|
||||
/** The value of Undefined cast to a double */
|
||||
public static final double UNDEFINED_DOUBLE = Double.NaN;
|
||||
|
||||
/**
|
||||
* Compute type name for correct undefined getter
|
||||
* @param type the type
|
||||
* @return name of getter
|
||||
*/
|
||||
private static String typeName(final Type type) {
|
||||
String name = type.getTypeClass().getName();
|
||||
final int dot = name.lastIndexOf('.');
|
||||
if (dot != -1) {
|
||||
name = name.substring(dot + 1);
|
||||
}
|
||||
return Character.toUpperCase(name.charAt(0)) + name.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles for undefined getters of the different types
|
||||
*/
|
||||
private static final MethodHandle[] GET_UNDEFINED = new MethodHandle[ObjectClassGenerator.getNumberOfAccessorTypes()];
|
||||
|
||||
/**
|
||||
* Used to wrap getters for undefined values, where this matters. Currently only in dual fields.
|
||||
* If an object starts out as undefined it needs special getters until it has been assigned
|
||||
* something the first time
|
||||
*
|
||||
* @param returnType type to cast the undefined to
|
||||
*
|
||||
* @return undefined as returnType
|
||||
*/
|
||||
public static MethodHandle getUndefined(final Class<?> returnType) {
|
||||
return GET_UNDEFINED[ObjectClassGenerator.getAccessorTypeIndex(returnType)];
|
||||
}
|
||||
|
||||
static {
|
||||
int pos = 0;
|
||||
for (final Type type : ACCESSOR_TYPES) {
|
||||
GET_UNDEFINED[pos++] = findOwnMH("getUndefined" + typeName(type), type.getTypeClass(), Object.class);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static int getUndefinedInt(final Object obj) {
|
||||
return UNDEFINED_INT;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static long getUndefinedLong(final Object obj) {
|
||||
return UNDEFINED_LONG;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static double getUndefinedDouble(final Object obj) {
|
||||
return UNDEFINED_DOUBLE;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Object getUndefinedObject(final Object obj) {
|
||||
return ScriptRuntime.UNDEFINED;
|
||||
}
|
||||
|
||||
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
|
||||
return MH.findStatic(MethodHandles.lookup(), ObjectClassGenerator.class, name, MH.type(rtype, types));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -28,22 +28,21 @@ package jdk.nashorn.internal.codegen;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.SCOPE;
|
||||
|
||||
import java.util.List;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
|
||||
/**
|
||||
* Base class for object creation code generation.
|
||||
* @param <T> value type
|
||||
*/
|
||||
public abstract class ObjectCreator {
|
||||
public abstract class ObjectCreator<T> {
|
||||
|
||||
/** List of keys to initiate in this ObjectCreator */
|
||||
protected final List<String> keys;
|
||||
|
||||
/** List of symbols to initiate in this ObjectCreator */
|
||||
protected final List<Symbol> symbols;
|
||||
/** List of keys & symbols to initiate in this ObjectCreator */
|
||||
final List<MapTuple<T>> tuples;
|
||||
|
||||
/** Code generator */
|
||||
protected final CodeGenerator codegen;
|
||||
final CodeGenerator codegen;
|
||||
|
||||
/** Property map */
|
||||
protected PropertyMap propertyMap;
|
||||
@ -55,15 +54,13 @@ public abstract class ObjectCreator {
|
||||
* Constructor
|
||||
*
|
||||
* @param codegen the code generator
|
||||
* @param keys the keys
|
||||
* @param symbols the symbols corresponding to keys, same index
|
||||
* @param tuples key,symbol,value (optional) tuples
|
||||
* @param isScope is this object scope
|
||||
* @param hasArguments does the created object have an "arguments" property
|
||||
*/
|
||||
protected ObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final boolean isScope, final boolean hasArguments) {
|
||||
ObjectCreator(final CodeGenerator codegen, final List<MapTuple<T>> tuples, final boolean isScope, final boolean hasArguments) {
|
||||
this.codegen = codegen;
|
||||
this.keys = keys;
|
||||
this.symbols = symbols;
|
||||
this.tuples = tuples;
|
||||
this.isScope = isScope;
|
||||
this.hasArguments = hasArguments;
|
||||
}
|
||||
@ -85,8 +82,8 @@ public abstract class ObjectCreator {
|
||||
* @param clazz type of MapCreator
|
||||
* @return map creator instantiated by type
|
||||
*/
|
||||
protected MapCreator newMapCreator(final Class<?> clazz) {
|
||||
return new MapCreator(clazz, keys, symbols);
|
||||
protected MapCreator<?> newMapCreator(final Class<? extends ScriptObject> clazz) {
|
||||
return new MapCreator<>(clazz, tuples);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,6 +104,10 @@ public abstract class ObjectCreator {
|
||||
return method;
|
||||
}
|
||||
|
||||
PropertyMap getMap() {
|
||||
return propertyMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a scope object
|
||||
* @return true if scope
|
||||
@ -122,4 +123,26 @@ public abstract class ObjectCreator {
|
||||
protected boolean hasArguments() {
|
||||
return hasArguments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Technique for loading an initial value. Defined by anonymous subclasses in code gen.
|
||||
*
|
||||
* @param value Value to load.
|
||||
*/
|
||||
protected abstract void loadValue(T value);
|
||||
|
||||
MethodEmitter loadTuple(final MethodEmitter method, final MapTuple<T> tuple, final boolean pack) {
|
||||
loadValue(tuple.value);
|
||||
if (pack && tuple.isPrimitive()) {
|
||||
method.pack();
|
||||
} else {
|
||||
method.convert(Type.OBJECT);
|
||||
}
|
||||
return method;
|
||||
}
|
||||
|
||||
MethodEmitter loadTuple(final MethodEmitter method, final MapTuple<T> tuple) {
|
||||
return loadTuple(method, tuple, true);
|
||||
}
|
||||
|
||||
}
|
||||
|
86
nashorn/src/jdk/nashorn/internal/codegen/ParamTypeMap.java
Normal file
86
nashorn/src/jdk/nashorn/internal/codegen/ParamTypeMap.java
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2010-2014, 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.lang.invoke.MethodType;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
|
||||
/**
|
||||
* A data structure that maps one or several function nodes (by their unique id:s, not by
|
||||
* the FunctionNode object itself, due to copy on write changing it several times through
|
||||
* code generation.
|
||||
*/
|
||||
public class ParamTypeMap {
|
||||
final Map<Integer, Type[]> map = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param functionNode functionNode
|
||||
* @param type method type found at runtime corresponding to parameter guess
|
||||
*/
|
||||
public ParamTypeMap(final FunctionNode functionNode, final MethodType type) {
|
||||
this(functionNode.getId(), type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param functionNodeId function node id
|
||||
* @param type method type found at runtime corresponding to parameter guess
|
||||
*/
|
||||
public ParamTypeMap(final int functionNodeId, final MethodType type) {
|
||||
final Type[] types = new Type[type.parameterCount()];
|
||||
int pos = 0;
|
||||
for (final Class<?> p : type.parameterArray()) {
|
||||
types[pos++] = Type.typeFor(p);
|
||||
}
|
||||
map.put(functionNodeId, types);
|
||||
}
|
||||
|
||||
ParamTypeMap(final Map<FunctionNode, Type[]> typeMap) {
|
||||
for (Map.Entry<FunctionNode, Type[]> entry : typeMap.entrySet()) {
|
||||
map.put(entry.getKey().getId(), entry.getValue());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the parameter type for this parameter position, or
|
||||
* null if now known
|
||||
* @param functionNode functionNode
|
||||
* @param pos position
|
||||
* @return parameter type for this callsite if known
|
||||
*/
|
||||
Type get(final FunctionNode functionNode, final int pos) {
|
||||
final Type[] types = map.get(functionNode.getId());
|
||||
assert types == null || pos < types.length;
|
||||
if (types != null && pos < types.length) {
|
||||
return types[pos];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
110
nashorn/src/jdk/nashorn/internal/codegen/ProgramPoints.java
Normal file
110
nashorn/src/jdk/nashorn/internal/codegen/ProgramPoints.java
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.FIRST_PROGRAM_POINT;
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.MAX_PROGRAM_POINT_VALUE;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Deque;
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.CallNode;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.FunctionNode;
|
||||
import jdk.nashorn.internal.ir.IdentNode;
|
||||
import jdk.nashorn.internal.ir.IndexNode;
|
||||
import jdk.nashorn.internal.ir.LexicalContext;
|
||||
import jdk.nashorn.internal.ir.Node;
|
||||
import jdk.nashorn.internal.ir.Optimistic;
|
||||
import jdk.nashorn.internal.ir.UnaryNode;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
/**
|
||||
* Find program points in the code that are needed for optimistic assumptions
|
||||
*/
|
||||
class ProgramPoints extends NodeVisitor<LexicalContext> {
|
||||
|
||||
private final Deque<int[]> nextProgramPoint = new ArrayDeque<>();
|
||||
|
||||
ProgramPoints() {
|
||||
super(new LexicalContext());
|
||||
}
|
||||
|
||||
private int next() {
|
||||
final int next = nextProgramPoint.peek()[0]++;
|
||||
if(next > MAX_PROGRAM_POINT_VALUE) {
|
||||
throw new AssertionError("Function has more than " + MAX_PROGRAM_POINT_VALUE + " program points");
|
||||
}
|
||||
return next;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode functionNode) {
|
||||
nextProgramPoint.push(new int[] { FIRST_PROGRAM_POINT });
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveFunctionNode(final FunctionNode functionNode) {
|
||||
nextProgramPoint.pop();
|
||||
return functionNode;
|
||||
}
|
||||
|
||||
private static Optimistic setProgramPoint(final Optimistic optimistic, final int programPoint) {
|
||||
final Expression node = (Expression)optimistic.setProgramPoint(programPoint);
|
||||
return (Optimistic)node;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveIdentNode(final IdentNode identNode) {
|
||||
return (Node)setProgramPoint(identNode, next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveCallNode(final CallNode callNode) {
|
||||
return (Node)setProgramPoint(callNode, next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveAccessNode(final AccessNode accessNode) {
|
||||
return (Node)setProgramPoint(accessNode, next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveIndexNode(final IndexNode indexNode) {
|
||||
return (Node)setProgramPoint(indexNode, next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveBinaryNode(final BinaryNode binaryNode) {
|
||||
return (Node)setProgramPoint(binaryNode, next());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveUnaryNode(final UnaryNode unaryNode) {
|
||||
return (Node)setProgramPoint(unaryNode, next());
|
||||
}
|
||||
}
|
@ -28,6 +28,7 @@ package jdk.nashorn.internal.codegen;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Range;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.Assignment;
|
||||
@ -242,7 +243,7 @@ final class RangeAnalyzer extends NodeOperatorVisitor<LexicalContext> {
|
||||
}
|
||||
|
||||
private Node leaveSelfModifyingAssign(final UnaryNode node, final Range range) {
|
||||
setRange(node.rhs(), range);
|
||||
setRange(node.getExpression(), range);
|
||||
setRange(node, range);
|
||||
return node;
|
||||
}
|
||||
@ -307,19 +308,18 @@ final class RangeAnalyzer extends NodeOperatorVisitor<LexicalContext> {
|
||||
switch (node.tokenType()) {
|
||||
case DECPREFIX:
|
||||
case DECPOSTFIX:
|
||||
return leaveSelfModifyingAssign(node, RANGE.sub(node.rhs().getSymbol().getRange(), Range.createRange(1)));
|
||||
return leaveSelfModifyingAssign(node, RANGE.sub(node.getExpression().getSymbol().getRange(), Range.createRange(1)));
|
||||
case INCPREFIX:
|
||||
case INCPOSTFIX:
|
||||
return leaveSelfModifyingAssign(node, RANGE.add(node.rhs().getSymbol().getRange(), Range.createRange(1)));
|
||||
return leaveSelfModifyingAssign(node, RANGE.add(node.getExpression().getSymbol().getRange(), Range.createRange(1)));
|
||||
default:
|
||||
assert false;
|
||||
return node;
|
||||
throw new UnsupportedOperationException("" + node.tokenType());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveADD(final UnaryNode node) {
|
||||
Range range = node.rhs().getSymbol().getRange();
|
||||
Range range = node.getExpression().getSymbol().getRange();
|
||||
if (!range.getType().isNumeric()) {
|
||||
range = Range.createTypeRange(Type.NUMBER);
|
||||
}
|
||||
@ -341,7 +341,7 @@ final class RangeAnalyzer extends NodeOperatorVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public Node leaveSUB(final UnaryNode node) {
|
||||
setRange(node, RANGE.neg(node.rhs().getSymbol().getRange()));
|
||||
setRange(node, RANGE.neg(node.getExpression().getSymbol().getRange()));
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -42,7 +42,6 @@ import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode;
|
||||
import jdk.nashorn.internal.ir.RuntimeNode.Request;
|
||||
import jdk.nashorn.internal.lookup.Lookup;
|
||||
import jdk.nashorn.internal.lookup.MethodHandleFactory;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
import jdk.nashorn.internal.runtime.linker.Bootstrap;
|
||||
|
||||
@ -59,7 +58,7 @@ import jdk.nashorn.internal.runtime.linker.Bootstrap;
|
||||
public final class RuntimeCallSite extends MutableCallSite {
|
||||
static final Call BOOTSTRAP = staticCallNoLookup(Bootstrap.class, "runtimeBootstrap", CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class);
|
||||
|
||||
private static final MethodHandle NEXT = findOwnMH("next", MethodHandle.class, String.class);
|
||||
private static final MethodHandle NEXT = findOwnMH_V("next", MethodHandle.class, String.class);
|
||||
|
||||
private final RuntimeNode.Request request;
|
||||
|
||||
@ -89,7 +88,7 @@ public final class RuntimeCallSite extends MutableCallSite {
|
||||
}
|
||||
|
||||
/**
|
||||
* The first type to try to use for this genrated runtime node
|
||||
* The first type to try to use for this generated runtime node
|
||||
*
|
||||
* @return a type
|
||||
*/
|
||||
@ -351,19 +350,19 @@ public final class RuntimeCallSite extends MutableCallSite {
|
||||
/** Unbox cache */
|
||||
private static final Map<Class<?>, MethodHandle> UNBOX;
|
||||
|
||||
private static final MethodHandle CHECKCAST = findOwnMH("checkcast", boolean.class, Class.class, Object.class);
|
||||
private static final MethodHandle CHECKCAST2 = findOwnMH("checkcast", boolean.class, Class.class, Object.class, Object.class);
|
||||
private static final MethodHandle ADDCHECK = findOwnMH("ADDcheck", boolean.class, int.class, int.class);
|
||||
private static final MethodHandle CHECKCAST = findOwnMH_S("checkcast", boolean.class, Class.class, Object.class);
|
||||
private static final MethodHandle CHECKCAST2 = findOwnMH_S("checkcast", boolean.class, Class.class, Object.class, Object.class);
|
||||
private static final MethodHandle ADDCHECK = findOwnMH_S("ADDcheck", boolean.class, int.class, int.class);
|
||||
|
||||
/**
|
||||
* Build maps of correct boxing operations
|
||||
*/
|
||||
static {
|
||||
UNBOX = new HashMap<>();
|
||||
UNBOX.put(Boolean.class, findOwnMH("unboxZ", int.class, Object.class));
|
||||
UNBOX.put(Integer.class, findOwnMH("unboxI", int.class, Object.class));
|
||||
UNBOX.put(Long.class, findOwnMH("unboxJ", long.class, Object.class));
|
||||
UNBOX.put(Number.class, findOwnMH("unboxD", double.class, Object.class));
|
||||
UNBOX.put(Boolean.class, findOwnMH_S("unboxZ", int.class, Object.class));
|
||||
UNBOX.put(Integer.class, findOwnMH_S("unboxI", int.class, Object.class));
|
||||
UNBOX.put(Long.class, findOwnMH_S("unboxJ", long.class, Object.class));
|
||||
UNBOX.put(Number.class, findOwnMH_S("unboxD", double.class, Object.class));
|
||||
|
||||
METHODS = new HashMap<>();
|
||||
|
||||
@ -375,9 +374,9 @@ public final class RuntimeCallSite extends MutableCallSite {
|
||||
|
||||
final boolean isCmp = Request.isComparison(req);
|
||||
|
||||
METHODS.put(req.name() + "int", findOwnMH(req.name(), (isCmp ? boolean.class : int.class), int.class, int.class));
|
||||
METHODS.put(req.name() + "long", findOwnMH(req.name(), (isCmp ? boolean.class : long.class), long.class, long.class));
|
||||
METHODS.put(req.name() + "double", findOwnMH(req.name(), (isCmp ? boolean.class : double.class), double.class, double.class));
|
||||
METHODS.put(req.name() + "int", findOwnMH_S(req.name(), (isCmp ? boolean.class : int.class), int.class, int.class));
|
||||
METHODS.put(req.name() + "long", findOwnMH_S(req.name(), (isCmp ? boolean.class : long.class), long.class, long.class));
|
||||
METHODS.put(req.name() + "double", findOwnMH_S(req.name(), (isCmp ? boolean.class : double.class), double.class, double.class));
|
||||
}
|
||||
}
|
||||
|
||||
@ -674,12 +673,11 @@ public final class RuntimeCallSite extends MutableCallSite {
|
||||
return ((Number)obj).doubleValue();
|
||||
}
|
||||
|
||||
private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
|
||||
try {
|
||||
private static MethodHandle findOwnMH_S(final String name, final Class<?> rtype, final Class<?>... types) {
|
||||
return MH.findStatic(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types));
|
||||
} catch (final MethodHandleFactory.LookupException e) {
|
||||
}
|
||||
|
||||
private static MethodHandle findOwnMH_V(final String name, final Class<?> rtype, final Class<?>... types) {
|
||||
return MH.findVirtual(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package jdk.nashorn.internal.codegen;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.linker.NashornCallSiteDescriptor.CALLSITE_OPTIMISTIC;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
@ -81,6 +83,7 @@ class SharedScopeCall {
|
||||
this.valueType = valueType;
|
||||
this.returnType = returnType;
|
||||
this.paramTypes = paramTypes;
|
||||
assert (flags & CALLSITE_OPTIMISTIC) == 0;
|
||||
this.flags = flags;
|
||||
// If paramTypes is not null this is a call, otherwise it's just a get.
|
||||
this.isCall = paramTypes != null;
|
||||
@ -150,7 +153,10 @@ class SharedScopeCall {
|
||||
method._goto(parentLoopStart);
|
||||
method.label(parentLoopDone);
|
||||
|
||||
method.dynamicGet(valueType, symbol.getName(), flags, isCall);
|
||||
assert !isCall || valueType.isObject(); // Callables are always objects
|
||||
// If flags are optimistic, but we're doing a call, remove optimistic flags from the getter, as they obviously
|
||||
// only apply to the call.
|
||||
method.dynamicGet(valueType, symbol.getName(), isCall ? CodeGenerator.nonOptimisticFlags(flags) : flags, isCall);
|
||||
|
||||
// If this is a get we're done, otherwise call the value as function.
|
||||
if (isCall) {
|
||||
@ -164,6 +170,7 @@ class SharedScopeCall {
|
||||
slot++;
|
||||
}
|
||||
}
|
||||
// Shared scope calls disabled in optimistic world.
|
||||
method.dynamicCall(returnType, 2 + paramTypes.length, flags);
|
||||
}
|
||||
|
||||
@ -179,17 +186,16 @@ class SharedScopeCall {
|
||||
final Type[] params = new Type[paramTypes.length + 2];
|
||||
params[0] = Type.typeFor(ScriptObject.class);
|
||||
params[1] = Type.INT;
|
||||
int i = 2;
|
||||
for (Type type : paramTypes) {
|
||||
if (type.isObject()) {
|
||||
type = Type.OBJECT;
|
||||
}
|
||||
params[i++] = type;
|
||||
}
|
||||
System.arraycopy(paramTypes, 0, params, 2, paramTypes.length);
|
||||
staticSignature = Type.getMethodDescriptor(returnType, params);
|
||||
}
|
||||
}
|
||||
return staticSignature;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return methodName + " " + staticSignature;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,18 +27,18 @@ package jdk.nashorn.internal.codegen;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.constructorNoLookup;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.virtualCallNoLookup;
|
||||
import static jdk.nashorn.internal.codegen.types.Type.OBJECT;
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.OBJECT_FIELDS_ONLY;
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.unboxedFieldType;
|
||||
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.Expression;
|
||||
import jdk.nashorn.internal.ir.LiteralNode;
|
||||
import jdk.nashorn.internal.ir.Symbol;
|
||||
import jdk.nashorn.internal.runtime.Property;
|
||||
import jdk.nashorn.internal.runtime.PropertyMap;
|
||||
import jdk.nashorn.internal.runtime.ScriptObject;
|
||||
import jdk.nashorn.internal.runtime.ScriptRuntime;
|
||||
import jdk.nashorn.internal.runtime.arrays.ArrayData;
|
||||
import jdk.nashorn.internal.runtime.arrays.ArrayIndex;
|
||||
import jdk.nashorn.internal.scripts.JO;
|
||||
@ -46,21 +46,16 @@ import jdk.nashorn.internal.scripts.JO;
|
||||
/**
|
||||
* An object creator that uses spill properties.
|
||||
*/
|
||||
public class SpillObjectCreator extends ObjectCreator {
|
||||
|
||||
private final List<Expression> values;
|
||||
public final class SpillObjectCreator extends ObjectCreator<Expression> {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param codegen code generator
|
||||
* @param keys keys for fields in object
|
||||
* @param symbols symbols for fields in object
|
||||
* @param values list of values corresponding to keys
|
||||
* @param tuples tuples for key, symbol, value
|
||||
*/
|
||||
protected SpillObjectCreator(final CodeGenerator codegen, final List<String> keys, final List<Symbol> symbols, final List<Expression> values) {
|
||||
super(codegen, keys, symbols, false, false);
|
||||
this.values = values;
|
||||
SpillObjectCreator(final CodeGenerator codegen, final List<MapTuple<Expression>> tuples) {
|
||||
super(codegen, tuples, false, false);
|
||||
makeMap();
|
||||
}
|
||||
|
||||
@ -68,82 +63,120 @@ public class SpillObjectCreator extends ObjectCreator {
|
||||
protected void makeObject(final MethodEmitter method) {
|
||||
assert !isScope() : "spill scope objects are not currently supported";
|
||||
|
||||
final int length = keys.size();
|
||||
final Object[] presetValues = new Object[length];
|
||||
final int length = tuples.size();
|
||||
final long[] jpresetValues = new long[ScriptObject.spillAllocationLength(length)];
|
||||
final Object[] opresetValues = new Object[ScriptObject.spillAllocationLength(length)];
|
||||
final Set<Integer> postsetValues = new LinkedHashSet<>();
|
||||
final int callSiteFlags = codegen.getCallSiteFlags();
|
||||
ArrayData arrayData = ArrayData.allocate(new Object[0]);
|
||||
ArrayData arrayData = ArrayData.allocate(ScriptRuntime.EMPTY_ARRAY);
|
||||
|
||||
// Compute constant property values
|
||||
for (int i = 0; i < length; i++) {
|
||||
final String key = keys.get(i);
|
||||
final Expression value = values.get(i);
|
||||
|
||||
if (value == null) {
|
||||
continue; // getter or setter
|
||||
}
|
||||
int pos = 0;
|
||||
for (final MapTuple<Expression> tuple : tuples) {
|
||||
final String key = tuple.key;
|
||||
final Expression value = tuple.value;
|
||||
|
||||
if (value != null) {
|
||||
final Object constantValue = LiteralNode.objectAsConstant(value);
|
||||
if (constantValue == LiteralNode.POSTSET_MARKER) {
|
||||
postsetValues.add(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
postsetValues.add(pos);
|
||||
} else {
|
||||
final Property property = propertyMap.findProperty(key);
|
||||
if (property != null) {
|
||||
// normal property key
|
||||
presetValues[property.getSlot()] = constantValue;
|
||||
property.setCurrentType(unboxedFieldType(constantValue));
|
||||
final int slot = property.getSlot();
|
||||
if (!OBJECT_FIELDS_ONLY && constantValue instanceof Number) {
|
||||
jpresetValues[slot] = ObjectClassGenerator.pack((Number)constantValue);
|
||||
} else {
|
||||
opresetValues[slot] = constantValue;
|
||||
}
|
||||
} else {
|
||||
// array index key
|
||||
final long oldLength = arrayData.length();
|
||||
final int index = ArrayIndex.getArrayIndex(key);
|
||||
assert ArrayIndex.isValidArrayIndex(index);
|
||||
final long longIndex = ArrayIndex.toLongIndex(index);
|
||||
|
||||
assert ArrayIndex.isValidArrayIndex(index);
|
||||
|
||||
if (longIndex >= oldLength) {
|
||||
arrayData = arrayData.ensure(longIndex);
|
||||
}
|
||||
|
||||
//avoid blowing up the array if we can
|
||||
if (constantValue instanceof Integer) {
|
||||
arrayData = arrayData.set(index, ((Integer)constantValue).intValue(), false);
|
||||
} else if (constantValue instanceof Long) {
|
||||
arrayData = arrayData.set(index, ((Long)constantValue).longValue(), false);
|
||||
} else if (constantValue instanceof Double) {
|
||||
arrayData = arrayData.set(index, ((Double)constantValue).doubleValue(), false);
|
||||
} else {
|
||||
arrayData = arrayData.set(index, constantValue, false);
|
||||
}
|
||||
|
||||
if (longIndex > oldLength) {
|
||||
arrayData = arrayData.delete(oldLength, longIndex - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pos++;
|
||||
}
|
||||
|
||||
//assert postsetValues.isEmpty() : "test me " + postsetValues;
|
||||
|
||||
// create object and invoke constructor
|
||||
method._new(JO.class).dup();
|
||||
codegen.loadConstant(propertyMap);
|
||||
method.invoke(constructorNoLookup(JO.class, PropertyMap.class));
|
||||
|
||||
// Set spill array with preset values
|
||||
//load primitive values to j spill array
|
||||
codegen.loadConstant(jpresetValues);
|
||||
for (final int i : postsetValues) {
|
||||
final MapTuple<Expression> tuple = tuples.get(i);
|
||||
final Property property = propertyMap.findProperty(tuple.key);
|
||||
if (property != null && tuple.isPrimitive()) {
|
||||
method.dup();
|
||||
codegen.loadConstant(presetValues);
|
||||
method.putField(Type.getInternalName(ScriptObject.class), "spill", Type.OBJECT_ARRAY.getDescriptor());
|
||||
method.load(property.getSlot());
|
||||
loadTuple(method, tuple);
|
||||
method.arraystore();
|
||||
}
|
||||
}
|
||||
|
||||
// Set array data if any
|
||||
//load object values to o spill array
|
||||
codegen.loadConstant(opresetValues);
|
||||
for (final int i : postsetValues) {
|
||||
final MapTuple<Expression> tuple = tuples.get(i);
|
||||
final Property property = propertyMap.findProperty(tuple.key);
|
||||
if (property != null && !tuple.isPrimitive()) {
|
||||
method.dup();
|
||||
method.load(property.getSlot());
|
||||
loadTuple(method, tuple);
|
||||
method.arraystore();
|
||||
}
|
||||
}
|
||||
|
||||
//instantiate the script object with spill objects
|
||||
method.invoke(constructorNoLookup(JO.class, PropertyMap.class, long[].class, Object[].class));
|
||||
|
||||
// Set prefix array data if any
|
||||
if (arrayData.length() > 0) {
|
||||
method.dup();
|
||||
codegen.loadConstant(arrayData);
|
||||
method.invoke(virtualCallNoLookup(ScriptObject.class, "setArray", void.class, ArrayData.class));
|
||||
}
|
||||
|
||||
// Create properties with non-constant values
|
||||
for (int i : postsetValues) {
|
||||
final String key = keys.get(i);
|
||||
final Property property = propertyMap.findProperty(key);
|
||||
|
||||
// set postfix
|
||||
for (final int i : postsetValues) {
|
||||
final MapTuple<Expression> tuple = tuples.get(i);
|
||||
final Property property = propertyMap.findProperty(tuple.key);
|
||||
if (property == null) {
|
||||
final int index = ArrayIndex.getArrayIndex(key);
|
||||
final int index = ArrayIndex.getArrayIndex(tuple.key);
|
||||
assert ArrayIndex.isValidArrayIndex(index);
|
||||
method.dup();
|
||||
method.load(ArrayIndex.toLongIndex(index));
|
||||
codegen.load(values.get(i));
|
||||
//method.println("putting " + tuple + " into arraydata");
|
||||
loadTuple(method, tuple);
|
||||
method.dynamicSetIndex(callSiteFlags);
|
||||
} else {
|
||||
method.dup();
|
||||
method.getField(Type.getInternalName(ScriptObject.class), "spill", Type.OBJECT_ARRAY.getDescriptor());
|
||||
method.load(property.getSlot());
|
||||
codegen.load(values.get(i), OBJECT);
|
||||
method.arraystore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -151,14 +184,12 @@ public class SpillObjectCreator extends ObjectCreator {
|
||||
@Override
|
||||
protected PropertyMap makeMap() {
|
||||
assert propertyMap == null : "property map already initialized";
|
||||
|
||||
propertyMap = new MapCreator(JO.class, keys, symbols) {
|
||||
@Override
|
||||
protected int getPropertyFlags(Symbol symbol, boolean hasArguments) {
|
||||
return super.getPropertyFlags(symbol, hasArguments) | Property.IS_SPILL | Property.IS_ALWAYS_OBJECT;
|
||||
}
|
||||
}.makeSpillMap(false);
|
||||
|
||||
propertyMap = new MapCreator<>(JO.class, tuples).makeSpillMap(false);
|
||||
return propertyMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void loadValue(final Expression node) {
|
||||
codegen.load(node);
|
||||
}
|
||||
}
|
||||
|
@ -81,20 +81,16 @@ final class Splitter extends NodeVisitor<LexicalContext> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the split
|
||||
* Execute the split.
|
||||
* @param fn the function to split
|
||||
* @param top whether this is the topmost compiled function (it's either a program, or we're doing a recompilation).
|
||||
*/
|
||||
FunctionNode split(final FunctionNode fn) {
|
||||
FunctionNode split(final FunctionNode fn, boolean top) {
|
||||
FunctionNode functionNode = fn;
|
||||
|
||||
if (functionNode.isLazy()) {
|
||||
LOG.finest("Postponing split of '", functionNode.getName(), "' as it's lazy");
|
||||
return functionNode;
|
||||
}
|
||||
|
||||
LOG.finest("Initiating split of '", functionNode.getName(), "'");
|
||||
|
||||
long weight = WeighNodes.weigh(functionNode);
|
||||
final boolean top = fn.isProgram(); //compiler.getFunctionNode() == outermost;
|
||||
|
||||
// We know that our LexicalContext is empty outside the call to functionNode.accept(this) below,
|
||||
// so we can pass null to all methods expecting a LexicalContext parameter.
|
||||
@ -138,7 +134,7 @@ final class Splitter extends NodeVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public Node leaveFunctionNode(final FunctionNode nestedFunction) {
|
||||
FunctionNode split = new Splitter(compiler, nestedFunction, outermostCompileUnit).split(nestedFunction);
|
||||
FunctionNode split = new Splitter(compiler, nestedFunction, outermostCompileUnit).split(nestedFunction, false);
|
||||
lc.replace(nestedFunction, split);
|
||||
return split;
|
||||
}
|
||||
@ -319,10 +315,7 @@ final class Splitter extends NodeVisitor<LexicalContext> {
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode node) {
|
||||
//only go into the function node for this splitter. any subfunctions are rejected
|
||||
if (node == outermost && !node.isLazy()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return node == outermost;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,6 @@ package jdk.nashorn.internal.codegen;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.AccessNode;
|
||||
import jdk.nashorn.internal.ir.BinaryNode;
|
||||
import jdk.nashorn.internal.ir.Block;
|
||||
@ -75,6 +74,7 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
|
||||
static final long BREAK_WEIGHT = 1;
|
||||
static final long CALL_WEIGHT = 10;
|
||||
static final long CATCH_WEIGHT = 10;
|
||||
static final long COMPARE_WEIGHT = 6;
|
||||
static final long CONTINUE_WEIGHT = 1;
|
||||
static final long IF_WEIGHT = 2;
|
||||
static final long LITERAL_WEIGHT = 10;
|
||||
@ -446,22 +446,22 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public Node leaveEQ(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveEQ_STRICT(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveGE(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveGT(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -478,12 +478,12 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public Node leaveLE(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveLT(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -498,12 +498,12 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
|
||||
|
||||
@Override
|
||||
public Node leaveNE(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Node leaveNE_STRICT(final BinaryNode binaryNode) {
|
||||
return runtimeNodeWeight(binaryNode);
|
||||
return compareWeight(binaryNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -546,8 +546,8 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
|
||||
return unaryNode;
|
||||
}
|
||||
|
||||
private Node runtimeNodeWeight(final BinaryNode binaryNode) {
|
||||
weight += Type.widest(binaryNode.lhs().getType(), binaryNode.rhs().getType()).isObject() ? CALL_WEIGHT : 1;
|
||||
private Node compareWeight(final BinaryNode binaryNode) {
|
||||
weight += COMPARE_WEIGHT;
|
||||
return binaryNode;
|
||||
}
|
||||
}
|
||||
|
@ -56,10 +56,9 @@ import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_INT;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
|
||||
/**
|
||||
@ -86,9 +85,20 @@ public final class BooleanType extends Type {
|
||||
return Boolean.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getBytecodeStackType() {
|
||||
return 'I';
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadUndefined(final MethodVisitor method) {
|
||||
method.visitLdcInsn(ObjectClassGenerator.UNDEFINED_INT);
|
||||
method.visitLdcInsn(UNDEFINED_INT);
|
||||
return BOOLEAN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadForcedInitializer(MethodVisitor method) {
|
||||
method.visitInsn(ICONST_0);
|
||||
return BOOLEAN;
|
||||
}
|
||||
|
||||
@ -125,30 +135,29 @@ public final class BooleanType extends Type {
|
||||
|
||||
if (to.isNumber()) {
|
||||
convert(method, OBJECT);
|
||||
invokeStatic(method, JSType.TO_NUMBER);
|
||||
invokestatic(method, JSType.TO_NUMBER);
|
||||
} else if (to.isInteger()) {
|
||||
return to; // do nothing.
|
||||
} else if (to.isLong()) {
|
||||
convert(method, OBJECT);
|
||||
invokeStatic(method, JSType.TO_UINT32);
|
||||
invokestatic(method, JSType.TO_UINT32);
|
||||
} else if (to.isLong()) {
|
||||
convert(method, OBJECT);
|
||||
invokeStatic(method, JSType.TO_LONG);
|
||||
invokestatic(method, JSType.TO_LONG);
|
||||
} else if (to.isString()) {
|
||||
invokeStatic(method, VALUE_OF);
|
||||
invokeStatic(method, JSType.TO_PRIMITIVE_TO_STRING);
|
||||
invokestatic(method, VALUE_OF);
|
||||
invokestatic(method, JSType.TO_PRIMITIVE_TO_STRING);
|
||||
} else if (to.isObject()) {
|
||||
invokeStatic(method, VALUE_OF);
|
||||
invokestatic(method, VALUE_OF);
|
||||
} else {
|
||||
assert false : "Illegal conversion " + this + " -> " + to;
|
||||
throw new UnsupportedOperationException("Illegal conversion " + this + " -> " + to);
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
throw new UnsupportedOperationException("add");
|
||||
}
|
||||
}
|
||||
|
@ -36,47 +36,52 @@ interface BytecodeNumericOps {
|
||||
* Pop and negate the value on top of the stack and push the result
|
||||
*
|
||||
* @param method method visitor
|
||||
*
|
||||
* @param programPoint program point id
|
||||
* @return result type
|
||||
*/
|
||||
Type neg(MethodVisitor method);
|
||||
Type neg(MethodVisitor method, int programPoint);
|
||||
|
||||
/**
|
||||
* Pop two values on top of the stack and subtract the first from the
|
||||
* second, pushing the result on the stack
|
||||
*
|
||||
* @param method method visitor
|
||||
*
|
||||
* @param programPoint program point id
|
||||
* @return result type
|
||||
*/
|
||||
Type sub(MethodVisitor method);
|
||||
Type sub(MethodVisitor method, int programPoint);
|
||||
|
||||
/**
|
||||
* Pop and multiply the two values on top of the stack and push the result
|
||||
* on the stack
|
||||
*
|
||||
* @param method method visitor
|
||||
*
|
||||
* @param programPoint program point id
|
||||
* @return result type
|
||||
*/
|
||||
Type mul(MethodVisitor method);
|
||||
Type mul(MethodVisitor method, int programPoint);
|
||||
|
||||
/**
|
||||
* Pop two values on top of the stack and divide the first with the second,
|
||||
* pushing the result on the stack
|
||||
*
|
||||
* @param method method visitor
|
||||
*
|
||||
* @param programPoint program point id
|
||||
* @return result type
|
||||
*/
|
||||
Type div(MethodVisitor method);
|
||||
Type div(MethodVisitor method, int programPoint);
|
||||
|
||||
/**
|
||||
* Pop two values on top of the stack and compute the modulo of the first
|
||||
* with the second, pushing the result on the stack
|
||||
*
|
||||
* @param method method visitor
|
||||
* Note that the rem method never takes a program point, because it
|
||||
* can never be more optimistic than its widest operand - an int/int
|
||||
* rem operation or a long/long rem operation can never return a
|
||||
* winder remainder than the int or the long
|
||||
*
|
||||
* @param method method visitor
|
||||
* @param programPoint program point id
|
||||
* @return result type
|
||||
*/
|
||||
Type rem(MethodVisitor method);
|
||||
|
@ -85,9 +85,10 @@ interface BytecodeOps {
|
||||
* first to the second, pushing the result on the stack
|
||||
*
|
||||
* @param method method visitor
|
||||
* @param programPoint program point id
|
||||
* @return result type
|
||||
*/
|
||||
Type add(MethodVisitor method);
|
||||
Type add(MethodVisitor method, int programPoint);
|
||||
|
||||
/**
|
||||
* Load a variable from a local slot to the stack
|
||||
@ -128,6 +129,17 @@ interface BytecodeOps {
|
||||
*/
|
||||
Type loadUndefined(MethodVisitor method);
|
||||
|
||||
/**
|
||||
* Load the "forced initializer" value to the stack, used to ensure that a local variable has a value when it is
|
||||
* read by the unwarranted optimism catch block.
|
||||
*
|
||||
* @param method method visitor.
|
||||
*
|
||||
* @return the forced initialization type at the top of the stack
|
||||
*/
|
||||
Type loadForcedInitializer(MethodVisitor method);
|
||||
|
||||
|
||||
/**
|
||||
* Load the "empty" value to the stack.
|
||||
*
|
||||
|
@ -28,7 +28,6 @@ package jdk.nashorn.internal.codegen.types;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.BIPUSH;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.I2D;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.I2L;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IADD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IAND;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_0;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_1;
|
||||
@ -37,25 +36,20 @@ import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_3;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_4;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_5;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ICONST_M1;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IDIV;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ILOAD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IMUL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.INEG;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IOR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IREM;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IRETURN;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISHL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISTORE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.ISUB;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IUSHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IXOR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.SIPUSH;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_INT;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
|
||||
/**
|
||||
* Type class: INT
|
||||
@ -79,6 +73,11 @@ class IntType extends BitwiseType {
|
||||
return Integer.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getBytecodeStackType() {
|
||||
return 'I';
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type ldc(final MethodVisitor method, final Object c) {
|
||||
assert c instanceof Integer;
|
||||
@ -134,19 +133,19 @@ class IntType extends BitwiseType {
|
||||
} else if (to.isBoolean()) {
|
||||
//nop
|
||||
} else if (to.isString()) {
|
||||
invokeStatic(method, TO_STRING);
|
||||
invokestatic(method, TO_STRING);
|
||||
} else if (to.isObject()) {
|
||||
invokeStatic(method, VALUE_OF);
|
||||
invokestatic(method, VALUE_OF);
|
||||
} else {
|
||||
assert false : "Illegal conversion " + this + " -> " + to;
|
||||
throw new UnsupportedOperationException("Illegal conversion " + this + " -> " + to);
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method) {
|
||||
method.visitInsn(IADD);
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("iadd", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@ -200,20 +199,20 @@ class IntType extends BitwiseType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type sub(final MethodVisitor method) {
|
||||
method.visitInsn(ISUB);
|
||||
public Type sub(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("isub", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type mul(final MethodVisitor method) {
|
||||
method.visitInsn(IMUL);
|
||||
public Type mul(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("imul", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type div(final MethodVisitor method) {
|
||||
method.visitInsn(IDIV);
|
||||
public Type div(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("idiv", "(II)I", MATHBOOTSTRAP, programPoint);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@ -224,8 +223,8 @@ class IntType extends BitwiseType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type neg(final MethodVisitor method) {
|
||||
method.visitInsn(INEG);
|
||||
public Type neg(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("ineg", "(I)I", MATHBOOTSTRAP, programPoint);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@ -236,19 +235,24 @@ class IntType extends BitwiseType {
|
||||
|
||||
@Override
|
||||
public Type loadUndefined(final MethodVisitor method) {
|
||||
method.visitLdcInsn(ObjectClassGenerator.UNDEFINED_INT);
|
||||
method.visitLdcInsn(UNDEFINED_INT);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadForcedInitializer(MethodVisitor method) {
|
||||
method.visitInsn(ICONST_0);
|
||||
return INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type cmp(final MethodVisitor method, final boolean isCmpG) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
throw new UnsupportedOperationException("cmp" + (isCmpG ? 'g' : 'l'));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type cmp(final MethodVisitor method) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
throw new UnsupportedOperationException("cmp");
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,29 +27,25 @@ package jdk.nashorn.internal.codegen.types;
|
||||
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.L2D;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.L2I;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LADD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LAND;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LCMP;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LCONST_0;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LCONST_1;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LDIV;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LLOAD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LMUL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LNEG;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LOR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LREM;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LRETURN;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSHL;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSTORE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LSUB;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LUSHR;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.LXOR;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_LONG;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
|
||||
/**
|
||||
* Type class: LONG
|
||||
@ -76,6 +72,11 @@ class LongType extends BitwiseType {
|
||||
return Long.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getBytecodeStackType() {
|
||||
return 'J';
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type cmp(final MethodVisitor method) {
|
||||
method.visitInsn(LCMP);
|
||||
@ -121,11 +122,11 @@ class LongType extends BitwiseType {
|
||||
if (to.isNumber()) {
|
||||
method.visitInsn(L2D);
|
||||
} else if (to.isInteger()) {
|
||||
method.visitInsn(L2I);
|
||||
invokestatic(method, JSType.TO_INT32_L);
|
||||
} else if (to.isBoolean()) {
|
||||
method.visitInsn(L2I);
|
||||
} else if (to.isObject()) {
|
||||
invokeStatic(method, VALUE_OF);
|
||||
invokestatic(method, VALUE_OF);
|
||||
} else {
|
||||
assert false : "Illegal conversion " + this + " -> " + to;
|
||||
}
|
||||
@ -134,26 +135,26 @@ class LongType extends BitwiseType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method) {
|
||||
method.visitInsn(LADD);
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("ladd", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type sub(final MethodVisitor method) {
|
||||
method.visitInsn(LSUB);
|
||||
public Type sub(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("lsub", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type mul(final MethodVisitor method) {
|
||||
method.visitInsn(LMUL);
|
||||
public Type mul(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("lmul", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type div(final MethodVisitor method) {
|
||||
method.visitInsn(LDIV);
|
||||
public Type div(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("ldiv", "(JJ)J", MATHBOOTSTRAP, programPoint);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@ -200,8 +201,8 @@ class LongType extends BitwiseType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type neg(final MethodVisitor method) {
|
||||
method.visitInsn(LNEG);
|
||||
public Type neg(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInvokeDynamicInsn("lneg", "(J)J", MATHBOOTSTRAP, programPoint);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@ -212,7 +213,13 @@ class LongType extends BitwiseType {
|
||||
|
||||
@Override
|
||||
public Type loadUndefined(final MethodVisitor method) {
|
||||
method.visitLdcInsn(ObjectClassGenerator.UNDEFINED_LONG);
|
||||
method.visitLdcInsn(UNDEFINED_LONG);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadForcedInitializer(MethodVisitor method) {
|
||||
method.visitInsn(LCONST_0);
|
||||
return LONG;
|
||||
}
|
||||
|
||||
|
@ -39,10 +39,10 @@ import static jdk.internal.org.objectweb.asm.Opcodes.DRETURN;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.DSTORE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.DSUB;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
import static jdk.nashorn.internal.runtime.JSType.UNDEFINED_DOUBLE;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants;
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
import jdk.nashorn.internal.runtime.JSType;
|
||||
|
||||
class NumberType extends NumericType {
|
||||
@ -63,6 +63,11 @@ class NumberType extends NumericType {
|
||||
return Double.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getBytecodeStackType() {
|
||||
return 'D';
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type cmp(final MethodVisitor method, final boolean isCmpG) {
|
||||
method.visitInsn(isCmpG ? DCMPG : DCMPL);
|
||||
@ -84,7 +89,13 @@ class NumberType extends NumericType {
|
||||
|
||||
@Override
|
||||
public Type loadUndefined(final MethodVisitor method) {
|
||||
method.visitLdcInsn(ObjectClassGenerator.UNDEFINED_DOUBLE);
|
||||
method.visitLdcInsn(UNDEFINED_DOUBLE);
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadForcedInitializer(MethodVisitor method) {
|
||||
method.visitInsn(DCONST_0);
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
@ -112,42 +123,42 @@ class NumberType extends NumericType {
|
||||
}
|
||||
|
||||
if (to.isInteger()) {
|
||||
invokeStatic(method, JSType.TO_INT32_D);
|
||||
invokestatic(method, JSType.TO_INT32_D);
|
||||
} else if (to.isLong()) {
|
||||
invokeStatic(method, JSType.TO_INT64_D);
|
||||
invokestatic(method, JSType.TO_LONG_D);
|
||||
} else if (to.isBoolean()) {
|
||||
invokeStatic(method, JSType.TO_BOOLEAN_D);
|
||||
invokestatic(method, JSType.TO_BOOLEAN_D);
|
||||
} else if (to.isString()) {
|
||||
invokeStatic(method, JSType.TO_STRING_D);
|
||||
invokestatic(method, JSType.TO_STRING_D);
|
||||
} else if (to.isObject()) {
|
||||
invokeStatic(method, VALUE_OF);
|
||||
invokestatic(method, VALUE_OF);
|
||||
} else {
|
||||
assert false : "Illegal conversion " + this + " -> " + to;
|
||||
throw new UnsupportedOperationException("Illegal conversion " + this + " -> " + to);
|
||||
}
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method) {
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInsn(DADD);
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type sub(final MethodVisitor method) {
|
||||
public Type sub(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInsn(DSUB);
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type mul(final MethodVisitor method) {
|
||||
public Type mul(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInsn(DMUL);
|
||||
return NUMBER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type div(final MethodVisitor method) {
|
||||
public Type div(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInsn(DDIV);
|
||||
return NUMBER;
|
||||
}
|
||||
@ -159,7 +170,7 @@ class NumberType extends NumericType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type neg(final MethodVisitor method) {
|
||||
public Type neg(final MethodVisitor method, final int programPoint) {
|
||||
method.visitInsn(DNEG);
|
||||
return NUMBER;
|
||||
}
|
||||
|
@ -65,8 +65,13 @@ class ObjectType extends Type {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method) {
|
||||
invokeStatic(method, ScriptRuntime.ADD);
|
||||
public String getShortDescriptor() {
|
||||
return getTypeClass() == Object.class ? "Object" : getTypeClass().getSimpleName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
invokestatic(method, ScriptRuntime.ADD);
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@ -74,7 +79,7 @@ class ObjectType extends Type {
|
||||
public Type load(final MethodVisitor method, final int slot) {
|
||||
assert slot != -1;
|
||||
method.visitVarInsn(ALOAD, slot);
|
||||
return Type.OBJECT;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -86,13 +91,21 @@ class ObjectType extends Type {
|
||||
@Override
|
||||
public Type loadUndefined(final MethodVisitor method) {
|
||||
method.visitFieldInsn(GETSTATIC, className(ScriptRuntime.class), "UNDEFINED", typeDescriptor(Undefined.class));
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadForcedInitializer(MethodVisitor method) {
|
||||
method.visitInsn(ACONST_NULL);
|
||||
// TODO: do we need a special type for null, e.g. Type.NULL? It should be assignable to any other object type
|
||||
// without a checkast in convert.
|
||||
return OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadEmpty(final MethodVisitor method) {
|
||||
method.visitFieldInsn(GETSTATIC, className(ScriptRuntime.class), "EMPTY", typeDescriptor(Undefined.class));
|
||||
return OBJECT;
|
||||
return UNDEFINED;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -108,10 +121,10 @@ class ObjectType extends Type {
|
||||
method.visitLdcInsn(c);
|
||||
return Type.typeFor(MethodHandle.class);
|
||||
} else {
|
||||
assert false : "implementation missing for class " + c.getClass() + " value=" + c;
|
||||
throw new UnsupportedOperationException("implementation missing for class " + c.getClass() + " value=" + c);
|
||||
}
|
||||
|
||||
return OBJECT;
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -138,6 +151,10 @@ class ObjectType extends Type {
|
||||
}
|
||||
return to;
|
||||
} else if (to.isObject()) {
|
||||
final Class<?> toClass = to.getTypeClass();
|
||||
if(!toClass.isAssignableFrom(getTypeClass())) {
|
||||
method.visitTypeInsn(CHECKCAST, CompilerConstants.className(toClass));
|
||||
}
|
||||
return to;
|
||||
}
|
||||
} else if (isString()) {
|
||||
@ -145,17 +162,17 @@ class ObjectType extends Type {
|
||||
}
|
||||
|
||||
if (to.isInteger()) {
|
||||
invokeStatic(method, JSType.TO_INT32);
|
||||
invokestatic(method, JSType.TO_INT32);
|
||||
} else if (to.isNumber()) {
|
||||
invokeStatic(method, JSType.TO_NUMBER);
|
||||
invokestatic(method, JSType.TO_NUMBER);
|
||||
} else if (to.isLong()) {
|
||||
invokeStatic(method, JSType.TO_INT64);
|
||||
invokestatic(method, JSType.TO_LONG);
|
||||
} else if (to.isBoolean()) {
|
||||
invokeStatic(method, JSType.TO_BOOLEAN);
|
||||
invokestatic(method, JSType.TO_BOOLEAN);
|
||||
} else if (to.isString()) {
|
||||
invokeStatic(method, JSType.TO_PRIMITIVE_TO_STRING);
|
||||
invokestatic(method, JSType.TO_PRIMITIVE_TO_STRING);
|
||||
} else {
|
||||
assert false : "Illegal conversion " + this + " -> " + to + " " + isString() + " " + toString;
|
||||
throw new UnsupportedOperationException("Illegal conversion " + this + " -> " + to + " " + isString() + " " + toString);
|
||||
}
|
||||
|
||||
return to;
|
||||
@ -165,4 +182,9 @@ class ObjectType extends Type {
|
||||
public void _return(final MethodVisitor method) {
|
||||
method.visitInsn(ARETURN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public char getBytecodeStackType() {
|
||||
return 'A';
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ import static jdk.internal.org.objectweb.asm.Opcodes.DUP2_X1;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.DUP2_X2;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.DUP_X1;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.DUP_X2;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.H_INVOKESTATIC;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IALOAD;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.IASTORE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.INVOKESTATIC;
|
||||
@ -45,13 +46,20 @@ import static jdk.internal.org.objectweb.asm.Opcodes.SWAP;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.T_DOUBLE;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.T_INT;
|
||||
import static jdk.internal.org.objectweb.asm.Opcodes.T_LONG;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.staticCallNoLookup;
|
||||
|
||||
import java.lang.invoke.CallSite;
|
||||
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 jdk.internal.org.objectweb.asm.Handle;
|
||||
import jdk.internal.org.objectweb.asm.MethodVisitor;
|
||||
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
|
||||
|
||||
import jdk.nashorn.internal.runtime.Undefined;
|
||||
import jdk.nashorn.internal.runtime.linker.Bootstrap;
|
||||
|
||||
/**
|
||||
* This is the representation of a JavaScript type, disassociated from java
|
||||
@ -97,6 +105,10 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
/** Set way below Integer.MAX_VALUE to prevent overflow when adding weights. Objects are still heaviest. */
|
||||
protected static final int MAX_WEIGHT = 20;
|
||||
|
||||
static final Call BOOTSTRAP = staticCallNoLookup(Bootstrap.class, "mathBootstrap", CallSite.class, MethodHandles.Lookup.class, String.class, MethodType.class, int.class);
|
||||
|
||||
static final Handle MATHBOOTSTRAP = new Handle(H_INVOKESTATIC, BOOTSTRAP.className(), "mathBootstrap", BOOTSTRAP.descriptor());
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -148,6 +160,17 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the character describing the bytecode type for this value on the stack or local variable, identical to
|
||||
* what would be used as the prefix for a bytecode {@code LOAD} or {@code STORE} instruction, therefore it must be
|
||||
* one of {@code A, F, D, I, L}. Also, the special value {@code U} is used for local variable slots that haven't
|
||||
* been initialized yet (it can't appear for a value pushed to the operand stack, those always have known values).
|
||||
* Note that while we allow all JVM internal types, Nashorn doesn't necessarily use them all - currently we don't
|
||||
* have floats, only doubles, but that might change in the future.
|
||||
* @return the character describing the bytecode type for this value on the stack.
|
||||
*/
|
||||
public abstract char getBytecodeStackType();
|
||||
|
||||
/**
|
||||
* Generate a method descriptor given a return type and a param array
|
||||
*
|
||||
@ -199,7 +222,11 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
case jdk.internal.org.objectweb.asm.Type.DOUBLE:
|
||||
return NUMBER;
|
||||
case jdk.internal.org.objectweb.asm.Type.OBJECT:
|
||||
return OBJECT;
|
||||
try {
|
||||
return Type.typeFor(Class.forName(itype.getClassName()));
|
||||
} catch(ClassNotFoundException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
case jdk.internal.org.objectweb.asm.Type.VOID:
|
||||
return null;
|
||||
case jdk.internal.org.objectweb.asm.Type.ARRAY:
|
||||
@ -260,7 +287,7 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
return jdk.internal.org.objectweb.asm.Type.getType(type);
|
||||
}
|
||||
|
||||
static void invokeStatic(final MethodVisitor method, final Call call) {
|
||||
static void invokestatic(final MethodVisitor method, final Call call) {
|
||||
method.visitMethodInsn(INVOKESTATIC, call.className(), call.name(), call.descriptor(), false);
|
||||
}
|
||||
|
||||
@ -372,6 +399,14 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
return this instanceof ObjectType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a primitive type (e.g int, long, double, boolean)
|
||||
* @return true if primitive
|
||||
*/
|
||||
public boolean isPrimitive() {
|
||||
return !isObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a type is a STRING type
|
||||
*
|
||||
@ -470,7 +505,25 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
* @return the widest type
|
||||
*/
|
||||
public static Type narrowest(final Type type0, final Type type1) {
|
||||
return type0.weight() < type1.weight() ? type0 : type1;
|
||||
return type0.narrowerThan(type1) ? type0 : type1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this type is strictly narrower than another one
|
||||
* @param type type to check against
|
||||
* @return true if this type is strictly narrower
|
||||
*/
|
||||
public boolean narrowerThan(final Type type) {
|
||||
return weight() < type.weight();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this type is strictly wider than another one
|
||||
* @param type type to check against
|
||||
* @return true if this type is strictly wider
|
||||
*/
|
||||
public boolean widerThan(final Type type) {
|
||||
return weight() > type.weight();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -549,6 +602,16 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the descriptor of a type, short version
|
||||
* Used mainly for debugging purposes
|
||||
*
|
||||
* @return the short descriptor
|
||||
*/
|
||||
public String getShortDescriptor() {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
@ -710,6 +773,11 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
*/
|
||||
public static final Type OBJECT = putInCache(new ObjectType());
|
||||
|
||||
/**
|
||||
* A undefined singleton
|
||||
*/
|
||||
public static final Type UNDEFINED = putInCache(new ObjectType(Undefined.class));
|
||||
|
||||
/**
|
||||
* This is the singleton for integer arrays
|
||||
*/
|
||||
@ -820,55 +888,83 @@ public abstract class Type implements Comparable<Type>, BytecodeOps {
|
||||
// EMPTY - used as a class that is absolutely not compatible with a type to represent "unknown"
|
||||
}
|
||||
|
||||
private abstract static class ValueLessType extends Type {
|
||||
|
||||
ValueLessType(final String name) {
|
||||
super(name, Unknown.class, MIN_WEIGHT, 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type load(final MethodVisitor method, final int slot) {
|
||||
throw new UnsupportedOperationException("load " + slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(final MethodVisitor method, final int slot) {
|
||||
throw new UnsupportedOperationException("store " + slot);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type ldc(final MethodVisitor method, final Object c) {
|
||||
throw new UnsupportedOperationException("ldc " + c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadUndefined(final MethodVisitor method) {
|
||||
throw new UnsupportedOperationException("load undefined");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadForcedInitializer(final MethodVisitor method) {
|
||||
throw new UnsupportedOperationException("load forced initializer");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type convert(final MethodVisitor method, final Type to) {
|
||||
throw new UnsupportedOperationException("convert => " + to);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void _return(final MethodVisitor method) {
|
||||
throw new UnsupportedOperationException("return");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method, final int programPoint) {
|
||||
throw new UnsupportedOperationException("add");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the unknown type which is used as initial type for type
|
||||
* inference. It has the minimum type width
|
||||
*/
|
||||
public static final Type UNKNOWN = new Type("<unknown>", Unknown.class, MIN_WEIGHT, 1) {
|
||||
|
||||
public static final Type UNKNOWN = new ValueLessType("<unknown>") {
|
||||
@Override
|
||||
public String getDescriptor() {
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type load(final MethodVisitor method, final int slot) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
public char getBytecodeStackType() {
|
||||
return 'U';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the unknown type which is used as initial type for type
|
||||
* inference. It has the minimum type width
|
||||
*/
|
||||
public static final Type SLOT_2 = new ValueLessType("<slot_2>") {
|
||||
|
||||
@Override
|
||||
public String getDescriptor() {
|
||||
return "<slot_2>";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void store(final MethodVisitor method, final int slot) {
|
||||
assert false : "unsupported operation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type ldc(final MethodVisitor method, final Object c) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type loadUndefined(final MethodVisitor method) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type convert(final MethodVisitor method, final Type to) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void _return(final MethodVisitor method) {
|
||||
assert false : "unsupported operation";
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type add(final MethodVisitor method) {
|
||||
assert false : "unsupported operation";
|
||||
return null;
|
||||
public char getBytecodeStackType() {
|
||||
throw new UnsupportedOperationException("getBytecodeStackType");
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -25,6 +25,10 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS;
|
||||
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
@ -49,8 +53,8 @@ public final class AccessNode extends BaseNode {
|
||||
this.property = property.setIsPropertyName();
|
||||
}
|
||||
|
||||
private AccessNode(final AccessNode accessNode, final Expression base, final IdentNode property, final boolean isFunction) {
|
||||
super(accessNode, base, isFunction);
|
||||
private AccessNode(final AccessNode accessNode, final Expression base, final IdentNode property, final boolean isFunction, final Type optimisticType, final boolean isOptimistic, final int id) {
|
||||
super(accessNode, base, isFunction, optimisticType, isOptimistic, id);
|
||||
this.property = property;
|
||||
}
|
||||
|
||||
@ -72,6 +76,8 @@ public final class AccessNode extends BaseNode {
|
||||
public void toString(final StringBuilder sb) {
|
||||
final boolean needsParen = tokenType().needsParens(getBase().tokenType(), true);
|
||||
|
||||
Node.optimisticType(this, sb);
|
||||
|
||||
if (needsParen) {
|
||||
sb.append('(');
|
||||
}
|
||||
@ -99,22 +105,49 @@ public final class AccessNode extends BaseNode {
|
||||
if (this.base == base) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction());
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
private AccessNode setProperty(final IdentNode property) {
|
||||
if (this.property == property) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction());
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseNode setIsFunction() {
|
||||
public AccessNode setType(final TemporarySymbols ts, final Type optimisticType) {
|
||||
if (this.optimisticType == optimisticType) {
|
||||
return this;
|
||||
}
|
||||
if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), optimisticType)) {
|
||||
ObjectClassGenerator.LOG.info(getClass().getName(), " ", this, " => ", optimisticType, " instead of ", getType());
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessNode setProgramPoint(int programPoint) {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessNode setIsFunction() {
|
||||
if (isFunction()) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, true);
|
||||
return new AccessNode(this, base, property, true, optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccessNode setIsOptimistic(final boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new AccessNode(this, base, property, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,9 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
|
||||
/**
|
||||
@ -34,13 +37,22 @@ import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
* @see IndexNode
|
||||
*/
|
||||
@Immutable
|
||||
public abstract class BaseNode extends Expression implements FunctionCall {
|
||||
public abstract class BaseNode extends Expression implements FunctionCall, Optimistic {
|
||||
|
||||
/** Base Node. */
|
||||
protected final Expression base;
|
||||
|
||||
private final boolean isFunction;
|
||||
|
||||
/** Callsite type for this node, if overriden optimistically or conservatively depending on coercion */
|
||||
protected final Type optimisticType;
|
||||
|
||||
/** Does this node have a callsite type, and it is optimistic rather than inferred from coercion semantics */
|
||||
protected final boolean isOptimistic;
|
||||
|
||||
/** Program point id */
|
||||
protected final int programPoint;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -53,6 +65,9 @@ public abstract class BaseNode extends Expression implements FunctionCall {
|
||||
super(token, base.getStart(), finish);
|
||||
this.base = base;
|
||||
this.isFunction = isFunction;
|
||||
this.optimisticType = null;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.isOptimistic = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -60,11 +75,17 @@ public abstract class BaseNode extends Expression implements FunctionCall {
|
||||
* @param baseNode node to inherit from
|
||||
* @param base base
|
||||
* @param isFunction is this a function
|
||||
* @param callSiteType the callsite type for this base node, either optimistic or conservative
|
||||
* @param isOptimistic is the callsite type optimistic rather than based on statically known coercion semantics
|
||||
* @param programPoint program point id
|
||||
*/
|
||||
protected BaseNode(final BaseNode baseNode, final Expression base, final boolean isFunction) {
|
||||
protected BaseNode(final BaseNode baseNode, final Expression base, final boolean isFunction, final Type callSiteType, final boolean isOptimistic, final int programPoint) {
|
||||
super(baseNode);
|
||||
this.base = base;
|
||||
this.isFunction = isFunction;
|
||||
this.optimisticType = callSiteType;
|
||||
this.programPoint = programPoint;
|
||||
this.isOptimistic = isOptimistic;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,6 +101,37 @@ public abstract class BaseNode extends Expression implements FunctionCall {
|
||||
return isFunction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final Type getType() {
|
||||
return optimisticType == null ? super.getType() : optimisticType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProgramPoint() {
|
||||
return programPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostOptimisticType() {
|
||||
return Type.INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostPessimisticType() {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeOptimistic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return isOptimistic;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mark this node as being the callee operand of a {@link CallNode}.
|
||||
* @return a base node identical to this one in all aspects except with its function flag set.
|
||||
|
@ -25,7 +25,13 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Ignore;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
@ -34,12 +40,34 @@ import jdk.nashorn.internal.parser.TokenType;
|
||||
* BinaryNode nodes represent two operand operations.
|
||||
*/
|
||||
@Immutable
|
||||
public final class BinaryNode extends Expression implements Assignment<Expression> {
|
||||
public final class BinaryNode extends Expression implements Assignment<Expression>, Optimistic {
|
||||
/** Left hand side argument. */
|
||||
private final Expression lhs;
|
||||
|
||||
private final Expression rhs;
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
private final boolean isOptimistic;
|
||||
|
||||
private final Type type;
|
||||
|
||||
@Ignore
|
||||
private static final List<TokenType> CAN_OVERFLOW =
|
||||
Collections.unmodifiableList(
|
||||
Arrays.asList(new TokenType[] {
|
||||
TokenType.ADD,
|
||||
TokenType.DIV,
|
||||
TokenType.MOD,
|
||||
TokenType.MUL,
|
||||
TokenType.SUB,
|
||||
TokenType.ASSIGN_ADD,
|
||||
TokenType.ASSIGN_DIV,
|
||||
TokenType.ASSIGN_MOD,
|
||||
TokenType.ASSIGN_MUL,
|
||||
TokenType.ASSIGN_SUB
|
||||
}));
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -51,12 +79,18 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
super(token, lhs.getStart(), rhs.getFinish());
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.isOptimistic = false;
|
||||
this.type = null;
|
||||
}
|
||||
|
||||
private BinaryNode(final BinaryNode binaryNode, final Expression lhs, final Expression rhs) {
|
||||
private BinaryNode(final BinaryNode binaryNode, final Expression lhs, final Expression rhs, final Type type, final int programPoint, final boolean isOptimistic) {
|
||||
super(binaryNode);
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
this.programPoint = programPoint;
|
||||
this.isOptimistic = isOptimistic;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -109,6 +143,9 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
case ASSIGN_SUB:
|
||||
return Type.NUMBER;
|
||||
default:
|
||||
if (isComparison()) {
|
||||
return Type.BOOLEAN;
|
||||
}
|
||||
return Type.OBJECT;
|
||||
}
|
||||
}
|
||||
@ -210,10 +247,10 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
final TokenType type = tokenType();
|
||||
final TokenType tokenType = tokenType();
|
||||
|
||||
final boolean lhsParen = type.needsParens(lhs().tokenType(), true);
|
||||
final boolean rhsParen = type.needsParens(rhs().tokenType(), false);
|
||||
final boolean lhsParen = tokenType.needsParens(lhs().tokenType(), true);
|
||||
final boolean rhsParen = tokenType.needsParens(rhs().tokenType(), false);
|
||||
|
||||
if (lhsParen) {
|
||||
sb.append('(');
|
||||
@ -227,7 +264,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
|
||||
sb.append(' ');
|
||||
|
||||
switch (type) {
|
||||
switch (tokenType) {
|
||||
case COMMALEFT:
|
||||
sb.append(",<");
|
||||
break;
|
||||
@ -239,10 +276,14 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
sb.append("++");
|
||||
break;
|
||||
default:
|
||||
sb.append(type.getName());
|
||||
sb.append(tokenType.getName());
|
||||
break;
|
||||
}
|
||||
|
||||
if (isOptimistic()) {
|
||||
sb.append(Node.OPT_IDENTIFIER);
|
||||
}
|
||||
|
||||
sb.append(' ');
|
||||
|
||||
if (rhsParen) {
|
||||
@ -279,7 +320,7 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
if (this.lhs == lhs) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs);
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -291,7 +332,64 @@ public final class BinaryNode extends Expression implements Assignment<Expressio
|
||||
if (this.rhs == rhs) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs);
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProgramPoint() {
|
||||
return programPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeOptimistic() {
|
||||
return getMostOptimisticType() != getMostPessimisticType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryNode setProgramPoint(final int programPoint) {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryNode setIsOptimistic(final boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
assert isOptimistic;
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostOptimisticType() {
|
||||
if (CAN_OVERFLOW.contains(tokenType())) {
|
||||
return Type.widest(Type.INT, Type.widest(lhs.getType(), rhs.getType()));
|
||||
}
|
||||
return getMostPessimisticType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostPessimisticType() {
|
||||
return getWidestOperationType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return isOptimistic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type == null ? super.getType() : type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BinaryNode setType(TemporarySymbols ts, Type type) {
|
||||
if (this.type == type) {
|
||||
return this;
|
||||
}
|
||||
return new BinaryNode(this, lhs, rhs, type, programPoint, isOptimistic);
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -33,14 +35,11 @@ import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.nashorn.internal.codegen.Label;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
|
||||
/**
|
||||
* IR representation for a list of statements.
|
||||
*/
|
||||
@ -53,7 +52,7 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
protected final Map<String, Symbol> symbols;
|
||||
|
||||
/** Entry label. */
|
||||
protected final Label entryLabel;
|
||||
private final Label entryLabel;
|
||||
|
||||
/** Break label. */
|
||||
private final Label breakLabel;
|
||||
@ -65,13 +64,11 @@ public class Block extends Node implements BreakableNode, Flags<Block> {
|
||||
public static final int NEEDS_SCOPE = 1 << 0;
|
||||
|
||||
/**
|
||||
* Flag indicating whether this block needs
|
||||
* self symbol assignment at the start. This is used only for
|
||||
* blocks that are the bodies of function nodes who refer to themselves
|
||||
* by name. It causes codegen to insert a var [fn_name] = __callee__
|
||||
* Flag indicating whether this block uses the self symbol for the function. This is used only for blocks that are
|
||||
* bodies of function nodes who refer to themselves by name. It causes Attr to insert a var [fn_name] = __callee__
|
||||
* at the start of the body
|
||||
*/
|
||||
public static final int NEEDS_SELF_SYMBOL = 1 << 1;
|
||||
public static final int USES_SELF_SYMBOL = 1 << 1;
|
||||
|
||||
/**
|
||||
* Is this block tagged as terminal based on its contents
|
||||
|
@ -58,7 +58,7 @@ public class BlockStatement extends Statement {
|
||||
* @return a block statement with the new statements. It will have the line number, token, and finish of the
|
||||
* original statement.
|
||||
*/
|
||||
public static Statement createReplacement(final Statement stmt, final List<Statement> newStmts) {
|
||||
public static BlockStatement createReplacement(final Statement stmt, final List<Statement> newStmts) {
|
||||
return createReplacement(stmt, stmt.getFinish(), newStmts);
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ public class BlockStatement extends Statement {
|
||||
* @return a block statement with the new statements. It will have the line number, and token of the
|
||||
* original statement.
|
||||
*/
|
||||
public static Statement createReplacement(final Statement stmt, int finish, final List<Statement> newStmts) {
|
||||
public static BlockStatement createReplacement(final Statement stmt, int finish, final List<Statement> newStmts) {
|
||||
return new BlockStatement(stmt.getLineNumber(), new Block(stmt.getToken(), finish, newStmts));
|
||||
}
|
||||
|
||||
|
@ -32,11 +32,13 @@ import jdk.nashorn.internal.ir.annotations.Ignore;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
/**
|
||||
* IR representation for a function call.
|
||||
*/
|
||||
@Immutable
|
||||
public final class CallNode extends LexicalContextExpression {
|
||||
public final class CallNode extends LexicalContextExpression implements Optimistic {
|
||||
|
||||
/** Function identifier or function body. */
|
||||
private final Expression function;
|
||||
@ -45,12 +47,19 @@ public final class CallNode extends LexicalContextExpression {
|
||||
private final List<Expression> args;
|
||||
|
||||
/** Is this a "new" operation */
|
||||
public static final int IS_NEW = 0x1;
|
||||
public static final int IS_NEW = 1 << 0;
|
||||
|
||||
/** Is the callsite type for this call optimistic rather than based on statically known coercion semantics */
|
||||
public static final int OPTIMISTIC = 1 << 1;
|
||||
|
||||
private final int flags;
|
||||
|
||||
private final int lineNumber;
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
private final Type optimisticType;
|
||||
|
||||
/**
|
||||
* Arguments to be passed to builtin {@code eval} function
|
||||
*/
|
||||
@ -150,15 +159,19 @@ public final class CallNode extends LexicalContextExpression {
|
||||
this.flags = 0;
|
||||
this.evalArgs = null;
|
||||
this.lineNumber = lineNumber;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.optimisticType = null;
|
||||
}
|
||||
|
||||
private CallNode(final CallNode callNode, final Expression function, final List<Expression> args, final int flags, final EvalArgs evalArgs) {
|
||||
private CallNode(final CallNode callNode, final Expression function, final List<Expression> args, final int flags, final Type optimisticType, final EvalArgs evalArgs, final int programPoint) {
|
||||
super(callNode);
|
||||
this.lineNumber = callNode.lineNumber;
|
||||
this.function = function;
|
||||
this.args = args;
|
||||
this.flags = flags;
|
||||
this.evalArgs = evalArgs;
|
||||
this.programPoint = programPoint;
|
||||
this.optimisticType = optimisticType;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,7 +184,15 @@ public final class CallNode extends LexicalContextExpression {
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return function instanceof FunctionNode ? ((FunctionNode)function).getReturnType() : Type.OBJECT;
|
||||
return optimisticType == null ? super.getType() : optimisticType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optimistic setType(TemporarySymbols ts, Type optimisticType) {
|
||||
if (this.optimisticType == optimisticType) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, flags, optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -187,7 +208,6 @@ public final class CallNode extends LexicalContextExpression {
|
||||
final CallNode newCallNode = (CallNode)visitor.leaveCallNode(
|
||||
setFunction((Expression)function.accept(visitor)).
|
||||
setArgs(Node.accept(visitor, Expression.class, args)).
|
||||
setFlags(flags).
|
||||
setEvalArgs(evalArgs == null ?
|
||||
null :
|
||||
evalArgs.setCode((Expression)evalArgs.getCode().accept(visitor)).
|
||||
@ -204,6 +224,7 @@ public final class CallNode extends LexicalContextExpression {
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
Node.optimisticType(this, sb);
|
||||
function.toString(sb);
|
||||
|
||||
sb.append('(');
|
||||
@ -239,7 +260,7 @@ public final class CallNode extends LexicalContextExpression {
|
||||
if (this.args == args) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, flags, evalArgs);
|
||||
return new CallNode(this, function, args, flags, optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,7 +282,7 @@ public final class CallNode extends LexicalContextExpression {
|
||||
if (this.evalArgs == evalArgs) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, flags, evalArgs);
|
||||
return new CallNode(this, function, args, flags, optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,7 +310,7 @@ public final class CallNode extends LexicalContextExpression {
|
||||
if (this.function == function) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, flags, evalArgs);
|
||||
return new CallNode(this, function, args, flags, optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,6 +333,47 @@ public final class CallNode extends LexicalContextExpression {
|
||||
if (this.flags == flags) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, flags, evalArgs);
|
||||
return new CallNode(this, function, args, flags, optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProgramPoint() {
|
||||
return programPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CallNode setProgramPoint(final int programPoint) {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, flags, optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostOptimisticType() {
|
||||
return Type.INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostPessimisticType() {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeOptimistic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return (flags & OPTIMISTIC) == OPTIMISTIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optimistic setIsOptimistic(final boolean isOptimistic) {
|
||||
if (isOptimistic() == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new CallNode(this, function, args, isOptimistic ? (flags | OPTIMISTIC) : (flags & ~OPTIMISTIC), optimisticType, evalArgs, programPoint);
|
||||
}
|
||||
}
|
||||
|
@ -60,7 +60,7 @@ public final class CatchNode extends Statement {
|
||||
*/
|
||||
public CatchNode(final int lineNumber, final long token, final int finish, final IdentNode exception, final Expression exceptionCondition, final Block body, final int flags) {
|
||||
super(lineNumber, token, finish);
|
||||
this.exception = exception;
|
||||
this.exception = exception == null ? null : exception.setIsInitializedHere();
|
||||
this.exceptionCondition = exceptionCondition;
|
||||
this.body = body;
|
||||
this.flags = flags;
|
||||
|
@ -35,15 +35,15 @@ import jdk.nashorn.internal.codegen.types.Type;
|
||||
public abstract class Expression extends Node {
|
||||
private Symbol symbol;
|
||||
|
||||
Expression(long token, int start, int finish) {
|
||||
Expression(final long token, final int start, final int finish) {
|
||||
super(token, start, finish);
|
||||
}
|
||||
|
||||
Expression(long token, int finish) {
|
||||
Expression(final long token, final int finish) {
|
||||
super(token, finish);
|
||||
}
|
||||
|
||||
Expression(Expression expr) {
|
||||
Expression(final Expression expr) {
|
||||
super(expr);
|
||||
this.symbol = expr.symbol;
|
||||
}
|
||||
|
@ -26,14 +26,18 @@
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
/**
|
||||
* Interface used by AccessNodes, IndexNodes and IdentNodes to signal
|
||||
* that they are function calls
|
||||
* Interface used by AccessNodes, IndexNodes and IdentNodes to signal that when evaluated, their value will be treated
|
||||
* as a function and immediately invoked, e.g. {@code foo()}, {@code foo.bar()} or {@code foo[bar]()}. Used to customize
|
||||
* the priority of composite dynamic operations when emitting {@code INVOKEDYNAMIC} instructions that implement them,
|
||||
* namely prioritize {@code getMethod} over {@code getElem} or {@code getProp}. An access or ident node with isFunction
|
||||
* set to true will be emitted as {@code dyn:getMethod|getProp|getElem} while one with it set to false will be emitted
|
||||
* as {@code dyn:getProp|getElem|getMethod}. Similarly, an index node with isFunction set to true will be emitted as
|
||||
* {@code dyn:getMethod|getElem|getProp} while the one set to false will be emitted as {@code dyn:getElem|getProp|getMethod}.
|
||||
*/
|
||||
public interface FunctionCall {
|
||||
/**
|
||||
* Return true if this function call implementor is a function
|
||||
*
|
||||
* @return true if implements a function call
|
||||
* Returns true if the value of this expression will be treated as a function and immediately invoked.
|
||||
* @return true if the value of this expression will be treated as a function and immediately invoked.
|
||||
*/
|
||||
public boolean isFunction();
|
||||
}
|
||||
|
@ -28,6 +28,7 @@ package jdk.nashorn.internal.ir;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
@ -89,14 +90,13 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
/** Source of entity. */
|
||||
private final Source source;
|
||||
|
||||
/** Unique ID used for recompilation among other things */
|
||||
private final int id;
|
||||
|
||||
/** External function identifier. */
|
||||
@Ignore
|
||||
private final IdentNode ident;
|
||||
|
||||
/** Parsed version of functionNode */
|
||||
@Ignore
|
||||
private final FunctionNode snapshot;
|
||||
|
||||
/** The body of the function node */
|
||||
private final Block body;
|
||||
|
||||
@ -129,9 +129,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
@Ignore
|
||||
private final EnumSet<CompilationState> compilationState;
|
||||
|
||||
@Ignore
|
||||
private final Compiler.Hints hints;
|
||||
|
||||
/** Properties of this object assigned in this function */
|
||||
@Ignore
|
||||
private HashSet<String> thisProperties;
|
||||
@ -156,7 +153,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
/** Does the function use the "arguments" identifier ? */
|
||||
public static final int USES_ARGUMENTS = 1 << 3;
|
||||
|
||||
/** Has this node been split because it was too large? */
|
||||
/** Has this function been split because it was too large? */
|
||||
public static final int IS_SPLIT = 1 << 4;
|
||||
|
||||
/** Does the function call eval? If it does, then all variables in this function might be get/set by it and it can
|
||||
@ -182,33 +179,43 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
/** Does this function or any of its descendants use variables from an ancestor function's scope (incl. globals)? */
|
||||
public static final int USES_ANCESTOR_SCOPE = 1 << 9;
|
||||
|
||||
/** Is this function lazily compiled? */
|
||||
public static final int IS_LAZY = 1 << 10;
|
||||
|
||||
/** Does this function have lazy, yet uncompiled children */
|
||||
public static final int HAS_LAZY_CHILDREN = 1 << 11;
|
||||
|
||||
/** Does this function have lazy, yet uncompiled children */
|
||||
public static final int IS_PROGRAM = 1 << 12;
|
||||
|
||||
/** Does this function have nested declarations? */
|
||||
public static final int HAS_FUNCTION_DECLARATIONS = 1 << 13;
|
||||
public static final int HAS_FUNCTION_DECLARATIONS = 1 << 10;
|
||||
|
||||
/** Can this function be specialized? */
|
||||
public static final int CAN_SPECIALIZE = 1 << 14;
|
||||
public static final int CAN_SPECIALIZE = 1 << 11;
|
||||
|
||||
/** Does this function have optimistic expressions? */
|
||||
public static final int IS_OPTIMISTIC = 1 << 12;
|
||||
|
||||
/** Does this function explicitly use the {@link CompilerConstants#RETURN} symbol? Some functions are known to
|
||||
* always use the return symbol, namely a function that is a program (as it must track its last executed expression
|
||||
* statement's value) as well as functions that are split (to communicate return values from inner to outer
|
||||
* partitions). Other functions normally don't use the return symbol (so we optimize away its slot), except in some
|
||||
* very special cases, e.g. when containing a return statement in a finally block. These special cases set this
|
||||
* flag. */
|
||||
public static final int USES_RETURN_SYMBOL = 1 << 13;
|
||||
|
||||
/**
|
||||
* Is this function the top-level program?
|
||||
*/
|
||||
public static final int IS_PROGRAM = 1 << 14;
|
||||
|
||||
/** Does this function or any nested functions contain an eval? */
|
||||
private static final int HAS_DEEP_EVAL = HAS_EVAL | HAS_NESTED_EVAL;
|
||||
|
||||
/** Does this function need to store all its variables in scope? */
|
||||
private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_EVAL | IS_SPLIT | HAS_LAZY_CHILDREN;
|
||||
private static final int HAS_ALL_VARS_IN_SCOPE = HAS_DEEP_EVAL;
|
||||
|
||||
/** Does this function potentially need "arguments"? Note that this is not a full test, as further negative check of REDEFINES_ARGS is needed. */
|
||||
private static final int MAYBE_NEEDS_ARGUMENTS = USES_ARGUMENTS | HAS_EVAL;
|
||||
|
||||
/** Does this function need the parent scope? It needs it if either it or its descendants use variables from it, or have a deep eval.
|
||||
* We also pessimistically need a parent scope if we have lazy children that have not yet been compiled */
|
||||
private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL | HAS_LAZY_CHILDREN;
|
||||
private static final int NEEDS_PARENT_SCOPE = USES_ANCESTOR_SCOPE | HAS_DEEP_EVAL;
|
||||
|
||||
/** Where to start assigning global and unique function node ids */
|
||||
public static final int FIRST_FUNCTION_ID = 1;
|
||||
|
||||
/** What is the return type of this function? */
|
||||
private Type returnType = Type.UNKNOWN;
|
||||
@ -217,6 +224,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* Constructor
|
||||
*
|
||||
* @param source the source
|
||||
* @param id unique id
|
||||
* @param lineNumber line number
|
||||
* @param token token
|
||||
* @param finish finish
|
||||
@ -231,6 +239,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
*/
|
||||
public FunctionNode(
|
||||
final Source source,
|
||||
final int id,
|
||||
final int lineNumber,
|
||||
final long token,
|
||||
final int finish,
|
||||
@ -245,6 +254,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
super(token, finish);
|
||||
|
||||
this.source = source;
|
||||
this.id = id;
|
||||
this.lineNumber = lineNumber;
|
||||
this.ident = ident;
|
||||
this.name = name;
|
||||
@ -259,23 +269,19 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this.sourceURL = sourceURL;
|
||||
this.compileUnit = null;
|
||||
this.body = null;
|
||||
this.snapshot = null;
|
||||
this.hints = null;
|
||||
}
|
||||
|
||||
private FunctionNode(
|
||||
final FunctionNode functionNode,
|
||||
final long lastToken,
|
||||
final int flags,
|
||||
final String sourceURL,
|
||||
String sourceURL,
|
||||
final String name,
|
||||
final Type returnType,
|
||||
final CompileUnit compileUnit,
|
||||
final EnumSet<CompilationState> compilationState,
|
||||
final Block body,
|
||||
final List<IdentNode> parameters,
|
||||
final FunctionNode snapshot,
|
||||
final Compiler.Hints hints) {
|
||||
final List<IdentNode> parameters) {
|
||||
super(functionNode);
|
||||
this.lineNumber = functionNode.lineNumber;
|
||||
this.flags = flags;
|
||||
@ -287,11 +293,10 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
this.compilationState = compilationState;
|
||||
this.body = body;
|
||||
this.parameters = parameters;
|
||||
this.snapshot = snapshot;
|
||||
this.hints = hints;
|
||||
|
||||
// the fields below never change - they are final and assigned in constructor
|
||||
this.source = functionNode.source;
|
||||
this.id = functionNode.id;
|
||||
this.ident = functionNode.ident;
|
||||
this.namespace = functionNode.namespace;
|
||||
this.declaredSymbols = functionNode.declaredSymbols;
|
||||
@ -316,6 +321,14 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the unique ID for this function
|
||||
* @return the id
|
||||
*/
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* get source name - sourceURL or name derived from Source.
|
||||
*
|
||||
@ -345,7 +358,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return this;
|
||||
}
|
||||
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, newSourceURL, name, returnType, compileUnit, compilationState, body, parameters, null, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, newSourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -356,51 +369,12 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version of this function node's code as it looked upon construction
|
||||
* i.e typically parsed and nothing else
|
||||
* @return initial version of function node
|
||||
*/
|
||||
public FunctionNode getSnapshot() {
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw away the snapshot, if any, to save memory. Used when heuristic
|
||||
* determines that a method is not worth specializing
|
||||
*
|
||||
* @param lc lexical context
|
||||
* @return new function node if a snapshot was present, now with snapsnot null
|
||||
*/
|
||||
public FunctionNode clearSnapshot(final LexicalContext lc) {
|
||||
if (this.snapshot == null) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, null, hints));
|
||||
}
|
||||
|
||||
/**
|
||||
* Take a snapshot of this function node at a given point in time
|
||||
* and store it in the function node
|
||||
* @param lc lexical context
|
||||
* @return function node
|
||||
*/
|
||||
public FunctionNode snapshot(final LexicalContext lc) {
|
||||
if (this.snapshot == this) {
|
||||
return this;
|
||||
}
|
||||
if (isProgram() || parameters.isEmpty()) {
|
||||
return this; //never specialize anything that won't be recompiled
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, this, hints));
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this function node be regenerated with more specific type args?
|
||||
* @return true if specialization is possible
|
||||
*/
|
||||
public boolean canSpecialize() {
|
||||
return snapshot != null && getFlag(CAN_SPECIALIZE);
|
||||
return (flags & (IS_OPTIMISTIC | CAN_SPECIALIZE)) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -450,28 +424,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
final EnumSet<CompilationState> newState = EnumSet.copyOf(this.compilationState);
|
||||
newState.add(state);
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, newState, body, parameters, snapshot, hints));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any compiler hints that may associated with the function
|
||||
* @return compiler hints
|
||||
*/
|
||||
public Compiler.Hints getHints() {
|
||||
return this.hints == null ? Compiler.Hints.EMPTY : hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set compiler hints for this function
|
||||
* @param lc lexical context
|
||||
* @param hints compiler hints
|
||||
* @return new function if hints changed
|
||||
*/
|
||||
public FunctionNode setHints(final LexicalContext lc, final Compiler.Hints hints) {
|
||||
if (this.hints == hints) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, newState, body, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -486,10 +439,10 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
sb.append('[');
|
||||
sb.append(returnType);
|
||||
sb.append(']');
|
||||
sb.append(' ');
|
||||
sb.append('[').
|
||||
append(returnType).
|
||||
append(']').
|
||||
append(' ');
|
||||
|
||||
sb.append("function");
|
||||
|
||||
@ -499,16 +452,16 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
|
||||
sb.append('(');
|
||||
boolean first = true;
|
||||
|
||||
for (final IdentNode parameter : parameters) {
|
||||
if (!first) {
|
||||
sb.append(", ");
|
||||
} else {
|
||||
first = false;
|
||||
for (final Iterator<IdentNode> iter = parameters.iterator(); iter.hasNext(); ) {
|
||||
final IdentNode parameter = iter.next();
|
||||
if (parameter.getSymbol() != null) {
|
||||
sb.append('[').append(parameter.getType()).append(']').append(' ');
|
||||
}
|
||||
|
||||
parameter.toString(sb);
|
||||
if (iter.hasNext()) {
|
||||
sb.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
sb.append(')');
|
||||
@ -524,7 +477,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.flags == flags) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -546,11 +499,11 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this function node be lazily code generated, i.e. first at link time
|
||||
* @return true if lazy
|
||||
* Returns true if the function is optimistic
|
||||
* @return True if this function is optimistic
|
||||
*/
|
||||
public boolean isLazy() {
|
||||
return getFlag(IS_LAZY);
|
||||
public boolean isOptimistic() {
|
||||
return getFlag(IS_OPTIMISTIC);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -587,7 +540,15 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
* @return true if the function's generated Java method needs a {@code callee} parameter.
|
||||
*/
|
||||
public boolean needsCallee() {
|
||||
return needsParentScope() || needsSelfSymbol() || isSplit() || (needsArguments() && !isStrict());
|
||||
return needsParentScope() || usesSelfSymbol() || isSplit() || (needsArguments() && !isStrict());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this function uses the return symbol
|
||||
* @return true if uses the return symbol
|
||||
*/
|
||||
public boolean usesReturnSymbol() {
|
||||
return isProgram() || isSplit() || getFlag(USES_RETURN_SYMBOL);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -634,7 +595,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if(this.body == body) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : 0), sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags | (body.needsScope() ? FunctionNode.HAS_SCOPE_BLOCK : 0), sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -729,7 +690,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.lastToken == lastToken) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -751,7 +712,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.name.equals(name)) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -773,15 +734,6 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
return getFlag(IS_SPLIT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if this function has yet-to-be-generated child functions
|
||||
*
|
||||
* @return true if there are lazy child functions
|
||||
*/
|
||||
public boolean hasLazyChildren() {
|
||||
return getFlag(HAS_LAZY_CHILDREN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parameters to this function
|
||||
* @return a list of IdentNodes which represent the function parameters, in order
|
||||
@ -801,7 +753,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.parameters == parameters) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -821,12 +773,13 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this function need a self symbol - this is needed only for self
|
||||
* referring functions
|
||||
* @return true if function needs a symbol for self
|
||||
* Does this function use its self symbol - this is needed only for self-referencing named function expressions.
|
||||
* Self-referencing declared functions won't have this flag set, as they can access their own symbol through the
|
||||
* scope (since they're bound to the symbol with their name in their enclosing scope).
|
||||
* @return true if this function node is a named function expression that uses the symbol for itself.
|
||||
*/
|
||||
public boolean needsSelfSymbol() {
|
||||
return body.getFlag(Block.NEEDS_SELF_SYMBOL);
|
||||
public boolean usesSelfSymbol() {
|
||||
return body.getFlag(Block.USES_SELF_SYMBOL);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -871,10 +824,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
type,
|
||||
compileUnit,
|
||||
compilationState,
|
||||
body.setReturnType(type),
|
||||
parameters,
|
||||
snapshot,
|
||||
hints));
|
||||
body.setReturnType(type), parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -905,7 +855,7 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
if (this.compileUnit == compileUnit) {
|
||||
return this;
|
||||
}
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters, snapshot, hints));
|
||||
return Node.replaceInLexicalContext(lc, this, new FunctionNode(this, lastToken, flags, sourceURL, name, returnType, compileUnit, compilationState, body, parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,29 +28,36 @@ package jdk.nashorn.internal.ir;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.__DIR__;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.__FILE__;
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.__LINE__;
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS;
|
||||
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
/**
|
||||
* IR representation for an identifier.
|
||||
*/
|
||||
@Immutable
|
||||
public final class IdentNode extends Expression implements PropertyKey, FunctionCall {
|
||||
public final class IdentNode extends Expression implements PropertyKey, FunctionCall, Optimistic {
|
||||
private static final int PROPERTY_NAME = 1 << 0;
|
||||
private static final int INITIALIZED_HERE = 1 << 1;
|
||||
private static final int FUNCTION = 1 << 2;
|
||||
private static final int FUTURESTRICT_NAME = 1 << 3;
|
||||
private static final int OPTIMISTIC = 1 << 4;
|
||||
|
||||
/** Identifier. */
|
||||
private final String name;
|
||||
|
||||
/** Type for a callsite, e.g. X in a get()X or a set(X)V */
|
||||
private final Type callSiteType;
|
||||
/** Optimistic type */
|
||||
private final Type optimisticType;
|
||||
|
||||
private final int flags;
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -61,15 +68,17 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
public IdentNode(final long token, final int finish, final String name) {
|
||||
super(token, finish);
|
||||
this.name = name.intern();
|
||||
this.callSiteType = null;
|
||||
this.optimisticType = null;
|
||||
this.flags = 0;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
}
|
||||
|
||||
private IdentNode(final IdentNode identNode, final String name, final Type callSiteType, final int flags) {
|
||||
private IdentNode(final IdentNode identNode, final String name, final Type callSiteType, final int flags, final int programPoint) {
|
||||
super(identNode);
|
||||
this.name = name;
|
||||
this.callSiteType = callSiteType;
|
||||
this.optimisticType = callSiteType;
|
||||
this.flags = flags;
|
||||
this.programPoint = programPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,13 +89,14 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
public IdentNode(final IdentNode identNode) {
|
||||
super(identNode);
|
||||
this.name = identNode.getName();
|
||||
this.callSiteType = null;
|
||||
this.optimisticType = null;
|
||||
this.flags = identNode.flags;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return callSiteType == null ? super.getType() : callSiteType;
|
||||
return optimisticType == null ? super.getType() : optimisticType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -94,11 +104,6 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean hasCallSiteType() {
|
||||
//this is an identity that's part of a getter or setter
|
||||
return callSiteType != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assist in IR navigation.
|
||||
*
|
||||
@ -115,13 +120,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
|
||||
@Override
|
||||
public void toString(final StringBuilder sb) {
|
||||
if (hasCallSiteType()) {
|
||||
sb.append('{');
|
||||
final String desc = getType().getDescriptor();
|
||||
sb.append(desc.charAt(desc.length() - 1) == ';' ? 'O' : getType().getDescriptor());
|
||||
sb.append('}');
|
||||
}
|
||||
|
||||
Node.optimisticType(this, sb);
|
||||
sb.append(name);
|
||||
}
|
||||
|
||||
@ -159,7 +158,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isPropertyName()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, callSiteType, flags | PROPERTY_NAME);
|
||||
return new IdentNode(this, name, optimisticType, flags | PROPERTY_NAME, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -178,7 +177,7 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isFutureStrictName()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, callSiteType, flags | FUTURESTRICT_NAME);
|
||||
return new IdentNode(this, name, optimisticType, flags | FUTURESTRICT_NAME, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -197,16 +196,16 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isInitializedHere()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, callSiteType, flags | INITIALIZED_HERE);
|
||||
return new IdentNode(this, name, optimisticType, flags | INITIALIZED_HERE, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this IdentNode is a special identity, currently __DIR__, __FILE__
|
||||
* or __LINE__
|
||||
* Check if the name of this IdentNode is same as that of a compile-time property (currently __DIR__, __FILE__, and
|
||||
* __LINE__).
|
||||
*
|
||||
* @return true if this IdentNode is special
|
||||
* @return true if this IdentNode's name is same as that of a compile-time property
|
||||
*/
|
||||
public boolean isSpecialIdentity() {
|
||||
public boolean isCompileTimePropertyName() {
|
||||
return name.equals(__DIR__.symbolName()) || name.equals(__FILE__.symbolName()) || name.equals(__LINE__.symbolName());
|
||||
}
|
||||
|
||||
@ -215,6 +214,17 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
return (flags & FUNCTION) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IdentNode setType(final TemporarySymbols ts, final Type callSiteType) {
|
||||
if (this.optimisticType == callSiteType) {
|
||||
return this;
|
||||
}
|
||||
if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), callSiteType)) {
|
||||
ObjectClassGenerator.LOG.info(getClass().getName(), " ", this, " => ", callSiteType, " instead of ", getType());
|
||||
}
|
||||
return new IdentNode(this, name, callSiteType, flags, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this node as being the callee operand of a {@link CallNode}.
|
||||
* @return an ident node identical to this one in all aspects except with its function flag set.
|
||||
@ -223,6 +233,47 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
if (isFunction()) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, callSiteType, flags | FUNCTION);
|
||||
return new IdentNode(this, name, optimisticType, flags | FUNCTION, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProgramPoint() {
|
||||
return programPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optimistic setProgramPoint(final int programPoint) {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, flags, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostOptimisticType() {
|
||||
return Type.INT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostPessimisticType() {
|
||||
return Type.OBJECT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeOptimistic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return (flags & OPTIMISTIC) == OPTIMISTIC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optimistic setIsOptimistic(final boolean isOptimistic) {
|
||||
if (isOptimistic() == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new IdentNode(this, name, optimisticType, isOptimistic ? (flags | OPTIMISTIC) : (flags & ~OPTIMISTIC), programPoint);
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,12 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.ObjectClassGenerator.DEBUG_FIELDS;
|
||||
|
||||
import jdk.nashorn.internal.codegen.ObjectClassGenerator;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
|
||||
/**
|
||||
* IR representation of an indexed access (brackets operator.)
|
||||
*/
|
||||
@ -49,8 +52,8 @@ public final class IndexNode extends BaseNode {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
private IndexNode(final IndexNode indexNode, final Expression base, final Expression index, final boolean isFunction) {
|
||||
super(indexNode, base, isFunction);
|
||||
private IndexNode(final IndexNode indexNode, final Expression base, final Expression index, final boolean isFunction, final Type optimisticType, final boolean isOptimistic, final int programPoint) {
|
||||
super(indexNode, base, isFunction, optimisticType, isOptimistic, programPoint);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@ -72,6 +75,8 @@ public final class IndexNode extends BaseNode {
|
||||
sb.append('(');
|
||||
}
|
||||
|
||||
Node.optimisticType(this, sb);
|
||||
|
||||
base.toString(sb);
|
||||
|
||||
if (needsParen) {
|
||||
@ -95,7 +100,7 @@ public final class IndexNode extends BaseNode {
|
||||
if (this.base == base) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction());
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -107,15 +112,41 @@ public final class IndexNode extends BaseNode {
|
||||
if(this.index == index) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction());
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BaseNode setIsFunction() {
|
||||
public IndexNode setType(final TemporarySymbols ts, final Type optimisticType) {
|
||||
if (this.optimisticType == optimisticType) {
|
||||
return this;
|
||||
}
|
||||
if (DEBUG_FIELDS && getSymbol() != null && !Type.areEquivalent(getSymbol().getSymbolType(), optimisticType)) {
|
||||
ObjectClassGenerator.LOG.info(getClass().getName(), " ", this, " => ", optimisticType, " instead of ", getType());
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexNode setIsFunction() {
|
||||
if (isFunction()) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, true);
|
||||
return new IndexNode(this, base, index, true, optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexNode setProgramPoint(int programPoint) {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexNode setIsOptimistic(boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new IndexNode(this, base, index, isFunction(), optimisticType, isOptimistic, programPoint);
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +148,7 @@ public class LexicalContext {
|
||||
* @return the node that was pushed
|
||||
*/
|
||||
public <T extends LexicalContextNode> T push(final T node) {
|
||||
assert !contains(node);
|
||||
if (sp == stack.length) {
|
||||
final LexicalContextNode[] newStack = new LexicalContextNode[sp * 2];
|
||||
System.arraycopy(stack, 0, newStack, 0, sp);
|
||||
@ -201,7 +202,6 @@ public class LexicalContext {
|
||||
return (T)popped;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the top element in the context
|
||||
* @return the node that was pushed last
|
||||
@ -233,7 +233,6 @@ public class LexicalContext {
|
||||
* @return the new node
|
||||
*/
|
||||
public LexicalContextNode replace(final LexicalContextNode oldNode, final LexicalContextNode newNode) {
|
||||
//System.err.println("REPLACE old=" + Debug.id(oldNode) + " new=" + Debug.id(newNode));
|
||||
for (int i = sp - 1; i >= 0; i--) {
|
||||
if (stack[i] == oldNode) {
|
||||
assert i == (sp - 1) : "violation of contract - we always expect to find the replacement node on top of the lexical context stack: " + newNode + " has " + stack[i + 1].getClass() + " above it";
|
||||
@ -410,22 +409,6 @@ public class LexicalContext {
|
||||
return getParentBlock() == null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the expression defining the function is a callee of a CallNode that should be the second
|
||||
* element on the stack, e.g. <code>(function(){})()</code>. That is, if the stack ends with
|
||||
* {@code [..., CallNode, FunctionNode]} then {@code callNode.getFunction()} should be equal to
|
||||
* {@code functionNode}, and the top of the stack should itself be a variant of {@code functionNode}.
|
||||
* @param functionNode the function node being tested
|
||||
* @return true if the expression defining the current function is a callee of a call expression.
|
||||
*/
|
||||
public boolean isFunctionDefinedInCurrentCall(FunctionNode functionNode) {
|
||||
final LexicalContextNode parent = stack[sp - 2];
|
||||
if (parent instanceof CallNode && ((CallNode)parent).getFunction() == functionNode) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent function for a function in the lexical context
|
||||
* @param functionNode function for which to get parent
|
||||
@ -444,16 +427,24 @@ public class LexicalContext {
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of with scopes until a given node
|
||||
* @param until node to stop counting at, or null if all nodes should be counted
|
||||
* Count the number of scopes until a given node.
|
||||
* @param until node to stop counting at. Must be within the current function
|
||||
* @return number of with scopes encountered in the context
|
||||
*/
|
||||
public int getScopeNestingLevelTo(final LexicalContextNode until) {
|
||||
assert until != null;
|
||||
//count the number of with nodes until "until" is hit
|
||||
int n = 0;
|
||||
for (final Iterator<WithNode> iter = new NodeIterator<>(WithNode.class, until); iter.hasNext(); iter.next()) {
|
||||
for (final Iterator<LexicalContextNode> iter = getAllNodes(); iter.hasNext();) {
|
||||
final LexicalContextNode node = iter.next();
|
||||
if(node == until) {
|
||||
break;
|
||||
}
|
||||
assert !(node instanceof FunctionNode); // Can't go outside current function
|
||||
if(node instanceof WithNode || (node instanceof Block && ((Block)node).needsScope())) {
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,6 @@ package jdk.nashorn.internal.ir;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import jdk.nashorn.internal.codegen.CompileUnit;
|
||||
import jdk.nashorn.internal.codegen.types.ArrayType;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
@ -657,23 +656,12 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
* Compute things like widest element type needed. Internal use from compiler only
|
||||
*/
|
||||
public void analyze() {
|
||||
elementType = Type.INT;
|
||||
analyzeElements();
|
||||
|
||||
if (elementType.isInteger()) {
|
||||
presetIntArray();
|
||||
} else if (elementType.isLong()) {
|
||||
presetLongArray();
|
||||
} else if (elementType.isNumeric()) {
|
||||
presetNumberArray();
|
||||
} else {
|
||||
presetObjectArray();
|
||||
}
|
||||
assert elementType.isUnknown();
|
||||
elementType = getNarrowestElementType(value);
|
||||
}
|
||||
|
||||
private void presetIntArray() {
|
||||
private int[] presetIntArray() {
|
||||
final int[] array = new int[value.length];
|
||||
final int[] computed = new int[value.length];
|
||||
int nComputed = 0;
|
||||
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
@ -682,17 +670,16 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
if (element instanceof Number) {
|
||||
array[i] = ((Number)element).intValue();
|
||||
} else {
|
||||
computed[nComputed++] = i;
|
||||
assert getPostsets()[nComputed++] == i;
|
||||
}
|
||||
}
|
||||
|
||||
presets = array;
|
||||
postsets = Arrays.copyOf(computed, nComputed);
|
||||
assert getPostsets().length == nComputed;
|
||||
return array;
|
||||
}
|
||||
|
||||
private void presetLongArray() {
|
||||
private long[] presetLongArray() {
|
||||
final long[] array = new long[value.length];
|
||||
final int[] computed = new int[value.length];
|
||||
int nComputed = 0;
|
||||
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
@ -701,17 +688,16 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
if (element instanceof Number) {
|
||||
array[i] = ((Number)element).longValue();
|
||||
} else {
|
||||
computed[nComputed++] = i;
|
||||
assert getPostsets()[nComputed++] == i;
|
||||
}
|
||||
}
|
||||
|
||||
presets = array;
|
||||
postsets = Arrays.copyOf(computed, nComputed);
|
||||
assert getPostsets().length == nComputed;
|
||||
return array;
|
||||
}
|
||||
|
||||
private void presetNumberArray() {
|
||||
private double[] presetNumberArray() {
|
||||
final double[] array = new double[value.length];
|
||||
final int[] computed = new int[value.length];
|
||||
int nComputed = 0;
|
||||
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
@ -720,40 +706,40 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
if (element instanceof Number) {
|
||||
array[i] = ((Number)element).doubleValue();
|
||||
} else {
|
||||
computed[nComputed++] = i;
|
||||
assert getPostsets()[nComputed++] == i;
|
||||
}
|
||||
}
|
||||
|
||||
presets = array;
|
||||
postsets = Arrays.copyOf(computed, nComputed);
|
||||
assert getPostsets().length == nComputed;
|
||||
return array;
|
||||
}
|
||||
|
||||
private void presetObjectArray() {
|
||||
private Object[] presetObjectArray() {
|
||||
final Object[] array = new Object[value.length];
|
||||
final int[] computed = new int[value.length];
|
||||
int nComputed = 0;
|
||||
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
final Node node = value[i];
|
||||
|
||||
if (node == null) {
|
||||
computed[nComputed++] = i;
|
||||
assert getPostsets()[nComputed++] == i;
|
||||
} else {
|
||||
final Object element = objectAsConstant(node);
|
||||
|
||||
if (element != POSTSET_MARKER) {
|
||||
array[i] = element;
|
||||
} else {
|
||||
computed[nComputed++] = i;
|
||||
assert getPostsets()[nComputed++] == i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
presets = array;
|
||||
postsets = Arrays.copyOf(computed, nComputed);
|
||||
assert getPostsets().length == nComputed;
|
||||
return array;
|
||||
}
|
||||
|
||||
private void analyzeElements() {
|
||||
private static Type getNarrowestElementType(final Expression[] value) {
|
||||
Type elementType = Type.INT;
|
||||
for (final Expression node : value) {
|
||||
if (node == null) {
|
||||
elementType = elementType.widest(Type.OBJECT); //no way to represent undefined as number
|
||||
@ -777,6 +763,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return elementType;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -789,6 +776,10 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
* @return array element type
|
||||
*/
|
||||
public ArrayType getArrayType() {
|
||||
return getArrayType(getElementType());
|
||||
}
|
||||
|
||||
private static ArrayType getArrayType(final Type elementType) {
|
||||
if (elementType.isInteger()) {
|
||||
return Type.INT_ARRAY;
|
||||
} else if (elementType.isLong()) {
|
||||
@ -810,6 +801,7 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
* @return element type
|
||||
*/
|
||||
public Type getElementType() {
|
||||
assert !elementType.isUnknown();
|
||||
return elementType;
|
||||
}
|
||||
|
||||
@ -819,6 +811,18 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
* @return post set indices
|
||||
*/
|
||||
public int[] getPostsets() {
|
||||
if(postsets == null) {
|
||||
final int[] computed = new int[value.length];
|
||||
int nComputed = 0;
|
||||
|
||||
for (int i = 0; i < value.length; i++) {
|
||||
final Expression element = value[i];
|
||||
if(element == null || objectAsConstant(element) == POSTSET_MARKER) {
|
||||
computed[nComputed++] = i;
|
||||
}
|
||||
}
|
||||
postsets = Arrays.copyOf(computed, nComputed);
|
||||
}
|
||||
return postsets;
|
||||
}
|
||||
|
||||
@ -827,6 +831,18 @@ public abstract class LiteralNode<T> extends Expression implements PropertyKey {
|
||||
* @return presets array, always returns an array type
|
||||
*/
|
||||
public Object getPresets() {
|
||||
if(presets == null) {
|
||||
final Type type = getElementType();
|
||||
if (type.isInteger()) {
|
||||
presets = presetIntArray();
|
||||
} else if (type.isLong()) {
|
||||
presets = presetLongArray();
|
||||
} else if (type.isNumeric()) {
|
||||
presets = presetNumberArray();
|
||||
} else {
|
||||
presets = presetObjectArray();
|
||||
}
|
||||
}
|
||||
return presets;
|
||||
}
|
||||
|
||||
|
@ -282,6 +282,21 @@ public abstract class Node implements Cloneable {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag an expression as optimistic or not. This is a convenience wrapper
|
||||
* that is a no op of the expression cannot be optimistic
|
||||
* @param expr expression
|
||||
* @param isOptimistic is optimistic flag
|
||||
* @return the new expression, or same if unmodified state
|
||||
*/
|
||||
//SAM method in Java 8
|
||||
public static Expression setIsOptimistic(final Expression expr, final boolean isOptimistic) {
|
||||
if (expr instanceof Optimistic) {
|
||||
return (Expression)((Optimistic)expr).setIsOptimistic(isOptimistic);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
//on change, we have to replace the entire list, that's we can't simple do ListIterator.set
|
||||
static <T extends Node> List<T> accept(final NodeVisitor<? extends LexicalContext> visitor, final Class<T> clazz, final List<T> list) {
|
||||
boolean changed = false;
|
||||
@ -304,4 +319,21 @@ public abstract class Node implements Cloneable {
|
||||
}
|
||||
return newNode;
|
||||
}
|
||||
|
||||
static final String OPT_IDENTIFIER = "%";
|
||||
|
||||
static void optimisticType(final Node node, final StringBuilder sb) {
|
||||
if (node instanceof Optimistic && ((Optimistic)node).isOptimistic()) {
|
||||
sb.append('{');
|
||||
final String desc = (((Expression)node).getType()).getDescriptor();
|
||||
sb.append(desc.charAt(desc.length() - 1) == ';' ? "O" : desc);
|
||||
if (node instanceof Optimistic && ((Optimistic)node).isOptimistic()) {
|
||||
sb.append(OPT_IDENTIFIER);
|
||||
sb.append('_');
|
||||
sb.append(((Optimistic)node).getProgramPoint());
|
||||
}
|
||||
sb.append('}');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
112
nashorn/src/jdk/nashorn/internal/ir/Optimistic.java
Normal file
112
nashorn/src/jdk/nashorn/internal/ir/Optimistic.java
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
|
||||
/**
|
||||
* Is this a node that can be optimistically typed? This means that it
|
||||
* has a probable type but it's not available through static analysis
|
||||
*
|
||||
* The follow nodes are optimistic, with reasons therefore given within
|
||||
* parenthesis
|
||||
*
|
||||
* @see IndexNode (dynamicGetIndex)
|
||||
* @see BinaryNode (local calculations to strongly typed bytecode)
|
||||
* @see UnaryNode (local calculations to strongly typed bytecode)
|
||||
* @see CallNode (dynamicCall)
|
||||
*
|
||||
* TODO : to be implemented are
|
||||
*
|
||||
* @see AccessNode (dynamicGet)
|
||||
* @see IdentNode (dynamicGet)
|
||||
*/
|
||||
public interface Optimistic {
|
||||
/**
|
||||
* Unique node ID that is associated with an invokedynamic call that mail
|
||||
* fail and its callsite. This is so that nodes can be regenerated less
|
||||
* pessimistically the next generation if an assumption failed
|
||||
*
|
||||
* @return unique node id
|
||||
*/
|
||||
public int getProgramPoint();
|
||||
|
||||
/**
|
||||
* Set the node number for this node, associating with a unique per-function
|
||||
* program point
|
||||
* @param programPoint the node number
|
||||
* @return new node, or same if unchanged
|
||||
*/
|
||||
public Optimistic setProgramPoint(final int programPoint);
|
||||
|
||||
/**
|
||||
* Is it possible for this particular implementor to actually have any optimism?
|
||||
* SHIFT operators for instance are binary nodes, but never optimistic. Multiply
|
||||
* operators are. We might want to refurbish the type hierarchy to fix this.
|
||||
* @return true if theoretically optimistic
|
||||
*/
|
||||
public boolean canBeOptimistic();
|
||||
|
||||
/**
|
||||
* Is this op optimistic, i.e. narrower than its narrowest statically provable
|
||||
* type
|
||||
* @return true if optimistic
|
||||
*/
|
||||
public boolean isOptimistic();
|
||||
|
||||
/**
|
||||
* Get the most optimistic type for this node. Typically we start out as
|
||||
* an int, and then at runtime we bump this up to number and then Object
|
||||
*
|
||||
* @return optimistic type to be used in code generation
|
||||
*/
|
||||
public Type getMostOptimisticType();
|
||||
|
||||
/**
|
||||
* Most pessimistic type that is guaranteed to be safe. Typically this is
|
||||
* number for arithmetic operations that can overflow, or Object for an add
|
||||
*
|
||||
* @return pessimistic type guaranteed to never overflow
|
||||
*/
|
||||
public Type getMostPessimisticType();
|
||||
|
||||
|
||||
/**
|
||||
* Tag this type override as optimistic rather than based on statically known
|
||||
* type coercion semantics
|
||||
* @param isOptimistic is this function optimistic
|
||||
* @return new optimistic function
|
||||
*/
|
||||
public Optimistic setIsOptimistic(final boolean isOptimistic);
|
||||
|
||||
/**
|
||||
* Set the override type
|
||||
*
|
||||
* @param ts temporary symbols
|
||||
* @param type the type
|
||||
* @return a node equivalent to this one except for the requested change.
|
||||
*/
|
||||
public Optimistic setType(final TemporarySymbols ts, final Type type);
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import jdk.nashorn.internal.IntDeque;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
|
||||
/**
|
||||
* Lexical context that keeps track of optimistic assumptions (if any)
|
||||
* made during code generation. Used from Attr and FinalizeTypes
|
||||
*/
|
||||
public class OptimisticLexicalContext extends LexicalContext {
|
||||
|
||||
private final boolean isEnabled;
|
||||
|
||||
class Assumption {
|
||||
Symbol symbol;
|
||||
Type type;
|
||||
|
||||
Assumption(final Symbol symbol, final Type type) {
|
||||
this.symbol = symbol;
|
||||
this.type = type;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return symbol.getName() + "=" + type;
|
||||
}
|
||||
}
|
||||
|
||||
/** Optimistic assumptions that could be made per function */
|
||||
private final Deque<List<Assumption>> optimisticAssumptions = new ArrayDeque<>();
|
||||
private final IntDeque splitNodes = new IntDeque();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param isEnabled are optimistic types enabled?
|
||||
*/
|
||||
public OptimisticLexicalContext(final boolean isEnabled) {
|
||||
super();
|
||||
this.isEnabled = isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are optimistic types enabled
|
||||
* @return true if optimistic types
|
||||
*/
|
||||
public boolean isEnabled() {
|
||||
return isEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Log an optimistic assumption during codegen
|
||||
* TODO : different parameters and more info about the assumption for future profiling
|
||||
* needs
|
||||
* @param symbol symbol
|
||||
* @param type type
|
||||
*/
|
||||
public void logOptimisticAssumption(final Symbol symbol, final Type type) {
|
||||
if (isEnabled) {
|
||||
final List<Assumption> peek = optimisticAssumptions.peek();
|
||||
peek.add(new Assumption(symbol, type));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of optimistic assumptions made
|
||||
* @return optimistic assumptions
|
||||
*/
|
||||
public List<Assumption> getOptimisticAssumptions() {
|
||||
return Collections.unmodifiableList(optimisticAssumptions.peek());
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this method have optimistic assumptions made during codegen?
|
||||
* @return true if optimistic assumptions were made
|
||||
*/
|
||||
public boolean hasOptimisticAssumptions() {
|
||||
return !optimisticAssumptions.isEmpty() && !getOptimisticAssumptions().isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends LexicalContextNode> T push(final T node) {
|
||||
if (isEnabled) {
|
||||
if(node instanceof FunctionNode) {
|
||||
optimisticAssumptions.push(new ArrayList<Assumption>());
|
||||
splitNodes.push(0);
|
||||
} else if(node instanceof SplitNode) {
|
||||
splitNodes.getAndIncrement();
|
||||
}
|
||||
}
|
||||
|
||||
return super.push(node);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends LexicalContextNode> T pop(final T node) {
|
||||
T popped = super.pop(node);
|
||||
if (isEnabled) {
|
||||
if(node instanceof FunctionNode) {
|
||||
optimisticAssumptions.pop();
|
||||
assert splitNodes.peek() == 0;
|
||||
splitNodes.pop();
|
||||
} else if(node instanceof SplitNode) {
|
||||
splitNodes.decrementAndGet();
|
||||
}
|
||||
}
|
||||
return popped;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the lexical context is inside a split node
|
||||
* @return true if we are traversing a SplitNode
|
||||
*/
|
||||
public boolean isInSplitNode() {
|
||||
return !splitNodes.isEmpty() && splitNodes.peek() > 0;
|
||||
}
|
||||
}
|
@ -34,11 +34,13 @@ import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.parser.TokenType;
|
||||
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
/**
|
||||
* IR representation for a runtime call.
|
||||
*/
|
||||
@Immutable
|
||||
public class RuntimeNode extends Expression {
|
||||
public class RuntimeNode extends Expression implements Optimistic {
|
||||
|
||||
/**
|
||||
* Request enum used for meta-information about the runtime request
|
||||
@ -77,7 +79,9 @@ public class RuntimeNode extends Expression {
|
||||
/** !== operator with at least one object */
|
||||
NE_STRICT(TokenType.NE_STRICT, Type.BOOLEAN, 2, true),
|
||||
/** != operator with at least one object */
|
||||
NE(TokenType.NE, Type.BOOLEAN, 2, true);
|
||||
NE(TokenType.NE, Type.BOOLEAN, 2, true),
|
||||
/** Verify type */
|
||||
TYPE_GUARD(TokenType.VOID, Type.INT, 2);
|
||||
|
||||
/** token type */
|
||||
private final TokenType tokenType;
|
||||
@ -210,6 +214,17 @@ public class RuntimeNode extends Expression {
|
||||
return request == NE || request == NE_STRICT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this strict?
|
||||
*
|
||||
* @param request a request
|
||||
*
|
||||
* @return true if script
|
||||
*/
|
||||
public static boolean isStrict(final Request request) {
|
||||
return request == EQ_STRICT || request == NE_STRICT;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this request can be reversed, return the reverse request
|
||||
* Eq EQ {@literal ->} NE.
|
||||
@ -301,6 +316,8 @@ public class RuntimeNode extends Expression {
|
||||
/** is final - i.e. may not be removed again, lower in the code pipeline */
|
||||
private final boolean isFinal;
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
@ -315,14 +332,16 @@ public class RuntimeNode extends Expression {
|
||||
this.request = request;
|
||||
this.args = args;
|
||||
this.isFinal = false;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
}
|
||||
|
||||
private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final boolean isFinal, final List<Expression> args) {
|
||||
private RuntimeNode(final RuntimeNode runtimeNode, final Request request, final boolean isFinal, final List<Expression> args, final int programPoint) {
|
||||
super(runtimeNode);
|
||||
|
||||
this.request = request;
|
||||
this.args = args;
|
||||
this.isFinal = isFinal;
|
||||
this.programPoint = programPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -361,6 +380,7 @@ public class RuntimeNode extends Expression {
|
||||
this.request = request;
|
||||
this.args = args;
|
||||
this.isFinal = false;
|
||||
this.programPoint = parent instanceof Optimistic ? ((Optimistic)parent).getProgramPoint() : INVALID_PROGRAM_POINT;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -370,7 +390,7 @@ public class RuntimeNode extends Expression {
|
||||
* @param request the request
|
||||
*/
|
||||
public RuntimeNode(final UnaryNode parent, final Request request) {
|
||||
this(parent, request, parent.rhs());
|
||||
this(parent, request, parent.getExpression());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -400,7 +420,7 @@ public class RuntimeNode extends Expression {
|
||||
if (this.isFinal == isFinal) {
|
||||
return this;
|
||||
}
|
||||
return new RuntimeNode(this, request, isFinal, args);
|
||||
return new RuntimeNode(this, request, isFinal, args, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -457,7 +477,7 @@ public class RuntimeNode extends Expression {
|
||||
if (this.args == args) {
|
||||
return this;
|
||||
}
|
||||
return new RuntimeNode(this, request, isFinal, args);
|
||||
return new RuntimeNode(this, request, isFinal, args, programPoint);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -483,4 +503,49 @@ public class RuntimeNode extends Expression {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
//TODO these are blank for now:
|
||||
|
||||
@Override
|
||||
public int getProgramPoint() {
|
||||
return programPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeNode setProgramPoint(final int programPoint) {
|
||||
if(this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new RuntimeNode(this, request, isFinal, args, programPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeOptimistic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostOptimisticType() {
|
||||
return getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostPessimisticType() {
|
||||
return getType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeNode setIsOptimistic(final boolean isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RuntimeNode setType(final TemporarySymbols ts, final Type type) {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,8 @@
|
||||
|
||||
package jdk.nashorn.internal.ir;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
@ -35,8 +37,6 @@ import jdk.nashorn.internal.runtime.Context;
|
||||
import jdk.nashorn.internal.runtime.Debug;
|
||||
import jdk.nashorn.internal.runtime.options.Options;
|
||||
|
||||
import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
|
||||
|
||||
/**
|
||||
* Maps a name to specific data.
|
||||
*/
|
||||
@ -62,21 +62,21 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
/** Can this symbol ever be undefined */
|
||||
public static final int CAN_BE_UNDEFINED = 1 << 6;
|
||||
/** Is this symbol always defined? */
|
||||
public static final int IS_ALWAYS_DEFINED = 1 << 8;
|
||||
/** Can this symbol ever have primitive types */
|
||||
public static final int CAN_BE_PRIMITIVE = 1 << 9;
|
||||
public static final int IS_ALWAYS_DEFINED = 1 << 7;
|
||||
/** Is this a let */
|
||||
public static final int IS_LET = 1 << 10;
|
||||
public static final int IS_LET = 1 << 8;
|
||||
/** Is this an internal symbol, never represented explicitly in source code */
|
||||
public static final int IS_INTERNAL = 1 << 11;
|
||||
public static final int IS_INTERNAL = 1 << 9;
|
||||
/** Is this a function self-reference symbol */
|
||||
public static final int IS_FUNCTION_SELF = 1 << 12;
|
||||
/** Is this a specialized param? */
|
||||
public static final int IS_SPECIALIZED_PARAM = 1 << 13;
|
||||
public static final int IS_FUNCTION_SELF = 1 << 10;
|
||||
/** Is this a specialized param, i.e. known type base on runtime callsite? */
|
||||
public static final int IS_SPECIALIZED_PARAM = 1 << 11;
|
||||
/** Is this symbol a shared temporary? */
|
||||
public static final int IS_SHARED = 1 << 14;
|
||||
public static final int IS_SHARED = 1 << 12;
|
||||
/** Is this a function declaration? */
|
||||
public static final int IS_FUNCTION_DECLARATION = 1 << 15;
|
||||
public static final int IS_FUNCTION_DECLARATION = 1 << 13;
|
||||
/** Is this a program level symbol? */
|
||||
public static final int IS_PROGRAM_LEVEL = 1 << 14;
|
||||
|
||||
/** Null or name identifying symbol. */
|
||||
private final String name;
|
||||
@ -142,7 +142,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
this.slot = slot;
|
||||
this.fieldIndex = -1;
|
||||
this.range = Range.createUnknownRange();
|
||||
trace("CREATE SYMBOL");
|
||||
trace("CREATE SYMBOL " + type);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,57 +205,61 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*/
|
||||
|
||||
void print(final PrintWriter stream) {
|
||||
final String printName = align(name, 20);
|
||||
final String printType = align(type.toString(), 10);
|
||||
final String printSlot = align(slot == -1 ? "none" : "" + slot, 10);
|
||||
String printFlags = "";
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
|
||||
sb.append(align(name, 20)).
|
||||
append(": ").
|
||||
append(align(type.toString(), 10)).
|
||||
append(", ").
|
||||
append(align(slot == -1 ? "none" : "" + slot, 10));
|
||||
|
||||
switch (flags & KINDMASK) {
|
||||
case IS_TEMP:
|
||||
printFlags = "temp " + printFlags;
|
||||
sb.append(" temp");
|
||||
break;
|
||||
case IS_GLOBAL:
|
||||
printFlags = "global " + printFlags;
|
||||
sb.append(" global");
|
||||
break;
|
||||
case IS_VAR:
|
||||
printFlags = "var " + printFlags;
|
||||
sb.append(" var");
|
||||
break;
|
||||
case IS_PARAM:
|
||||
printFlags = "param " + printFlags;
|
||||
sb.append(" param");
|
||||
break;
|
||||
case IS_CONSTANT:
|
||||
printFlags = "CONSTANT " + printFlags;
|
||||
sb.append(" const");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (isScope()) {
|
||||
printFlags += "scope ";
|
||||
sb.append(" scope");
|
||||
}
|
||||
|
||||
if (isInternal()) {
|
||||
printFlags += "internal ";
|
||||
sb.append(" internal");
|
||||
}
|
||||
|
||||
if (isLet()) {
|
||||
printFlags += "let ";
|
||||
sb.append(" let");
|
||||
}
|
||||
|
||||
if (isThis()) {
|
||||
printFlags += "this ";
|
||||
sb.append(" this");
|
||||
}
|
||||
|
||||
if (!canBeUndefined()) {
|
||||
printFlags += "always_def ";
|
||||
sb.append(" def'd");
|
||||
}
|
||||
|
||||
if (canBePrimitive()) {
|
||||
printFlags += "can_be_prim ";
|
||||
if (isProgramLevel()) {
|
||||
sb.append(" program");
|
||||
}
|
||||
|
||||
stream.print(printName + ": " + printType + ", " + printSlot + ", " + printFlags);
|
||||
stream.println();
|
||||
sb.append('\n');
|
||||
|
||||
stream.print(sb.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -272,9 +276,11 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* Allocate a slot for this symbol.
|
||||
*
|
||||
* @param needsSlot True if symbol needs a slot.
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setNeedsSlot(final boolean needsSlot) {
|
||||
public Symbol setNeedsSlot(final boolean needsSlot) {
|
||||
setSlot(needsSlot ? 0 : -1);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -312,10 +318,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
}
|
||||
}
|
||||
|
||||
if (canBePrimitive()) {
|
||||
sb.append(" P?");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@ -381,27 +383,28 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
|
||||
/**
|
||||
* Flag this symbol as scope as described in {@link Symbol#isScope()}
|
||||
* @return the symbol
|
||||
*/
|
||||
/**
|
||||
* Flag this symbol as scope as described in {@link Symbol#isScope()}
|
||||
*/
|
||||
public void setIsScope() {
|
||||
public Symbol setIsScope() {
|
||||
if (!isScope()) {
|
||||
trace("SET IS SCOPE");
|
||||
assert !isShared();
|
||||
flags |= IS_SCOPE;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark this symbol as one being shared by multiple expressions. The symbol must be a temporary.
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setIsShared() {
|
||||
public Symbol setIsShared() {
|
||||
if (!isShared()) {
|
||||
assert isTemp();
|
||||
trace("SET IS SHARED");
|
||||
flags |= IS_SHARED;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@ -447,6 +450,14 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
return isParam() || (flags & IS_ALWAYS_DEFINED) == IS_ALWAYS_DEFINED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a program (script) level definition
|
||||
* @return true if program level
|
||||
*/
|
||||
public boolean isProgramLevel() {
|
||||
return (flags & IS_PROGRAM_LEVEL) == IS_PROGRAM_LEVEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the range for this symbol
|
||||
* @return range for symbol
|
||||
@ -458,9 +469,11 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
/**
|
||||
* Set the range for this symbol
|
||||
* @param range range
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setRange(final Range range) {
|
||||
public Symbol setRange(final Range range) {
|
||||
this.range = range;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -480,14 +493,6 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
return (flags & IS_SPECIALIZED_PARAM) == IS_SPECIALIZED_PARAM;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this symbol ever has primitive assignments. Conservative
|
||||
* @return true if primitive assignments exist
|
||||
*/
|
||||
public boolean canBePrimitive() {
|
||||
return (flags & CAN_BE_PRIMITIVE) == CAN_BE_PRIMITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this symbol can ever be undefined
|
||||
* @return true if can be undefined
|
||||
@ -498,26 +503,16 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
|
||||
/**
|
||||
* Flag this symbol as potentially undefined in parts of the program
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setCanBeUndefined() {
|
||||
assert type.isObject() : type;
|
||||
public Symbol setCanBeUndefined() {
|
||||
if (isAlwaysDefined()) {
|
||||
return;
|
||||
return this;
|
||||
} else if (!canBeUndefined()) {
|
||||
assert !isShared();
|
||||
flags |= CAN_BE_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Flag this symbol as potentially primitive
|
||||
* @param type the primitive type it occurs with, currently unused but can be used for width guesses
|
||||
*/
|
||||
public void setCanBePrimitive(final Type type) {
|
||||
if(!canBePrimitive()) {
|
||||
assert !isShared();
|
||||
flags |= CAN_BE_PRIMITIVE;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -587,12 +582,14 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* and get allocated in a JO-prefixed ScriptObject subclass.
|
||||
*
|
||||
* @param fieldIndex field index - a positive integer
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setFieldIndex(final int fieldIndex) {
|
||||
public Symbol setFieldIndex(final int fieldIndex) {
|
||||
if (this.fieldIndex != fieldIndex) {
|
||||
assert !isShared();
|
||||
this.fieldIndex = fieldIndex;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -606,12 +603,40 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
/**
|
||||
* Set the symbol flags
|
||||
* @param flags flags
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setFlags(final int flags) {
|
||||
public Symbol setFlags(final int flags) {
|
||||
if (this.flags != flags) {
|
||||
assert !isShared();
|
||||
assert !isShared() : this;
|
||||
this.flags = flags;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a single symbol flag
|
||||
* @param flag flag to set
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol setFlag(final int flag) {
|
||||
if ((this.flags & flag) == 0) {
|
||||
assert !isShared() : this;
|
||||
this.flags |= flag;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a single symbol flag
|
||||
* @param flag flag to set
|
||||
* @return the symbol
|
||||
*/
|
||||
public Symbol clearFlag(final int flag) {
|
||||
if ((this.flags & flag) != 0) {
|
||||
assert !isShared() : this;
|
||||
this.flags &= ~flag;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -632,9 +657,11 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
|
||||
/**
|
||||
* Increase the symbol's use count by one.
|
||||
* @return the symbol
|
||||
*/
|
||||
public void increaseUseCount() {
|
||||
public Symbol increaseUseCount() {
|
||||
useCount++;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -648,32 +675,38 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
/**
|
||||
* Set the bytecode slot for this symbol
|
||||
* @param slot valid bytecode slot, or -1 if not available
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setSlot(final int slot) {
|
||||
public Symbol setSlot(final int slot) {
|
||||
if (slot != this.slot) {
|
||||
assert !isShared();
|
||||
trace("SET SLOT " + slot);
|
||||
this.slot = slot;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a specific subclass of Object to the symbol
|
||||
*
|
||||
* @param type the type
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setType(final Class<?> type) {
|
||||
public Symbol setType(final Class<?> type) {
|
||||
assert !type.isPrimitive() && !Number.class.isAssignableFrom(type) : "Class<?> types can only be subclasses of object";
|
||||
setType(Type.typeFor(type));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign a type to the symbol
|
||||
*
|
||||
* @param type the type
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setType(final Type type) {
|
||||
public Symbol setType(final Type type) {
|
||||
setTypeOverride(Type.widest(this.type, type));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -691,14 +724,16 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* widened
|
||||
*
|
||||
* @param type the type
|
||||
* @return the symbol
|
||||
*/
|
||||
public void setTypeOverride(final Type type) {
|
||||
public Symbol setTypeOverride(final Type type) {
|
||||
final Type old = this.type;
|
||||
if (old != type) {
|
||||
assert !isShared();
|
||||
assert !isShared() : this + " is a shared symbol and cannot have its type overridden to " + type;
|
||||
trace("TYPE CHANGE: " + old + "=>" + type + " == " + type);
|
||||
this.type = type;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -730,12 +765,14 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
*
|
||||
* @param lc lexical context
|
||||
* @param symbol symbol
|
||||
* @return the symbol
|
||||
*/
|
||||
public static void setSymbolIsScope(final LexicalContext lc, final Symbol symbol) {
|
||||
public static Symbol setSymbolIsScope(final LexicalContext lc, final Symbol symbol) {
|
||||
symbol.setIsScope();
|
||||
if (!symbol.isGlobal()) {
|
||||
lc.setBlockNeedsScope(lc.getDefiningBlock(symbol));
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
private void trace(final String desc) {
|
||||
|
@ -28,8 +28,13 @@ package jdk.nashorn.internal.ir;
|
||||
import static jdk.nashorn.internal.parser.TokenType.BIT_NOT;
|
||||
import static jdk.nashorn.internal.parser.TokenType.DECPOSTFIX;
|
||||
import static jdk.nashorn.internal.parser.TokenType.INCPOSTFIX;
|
||||
import static jdk.nashorn.internal.runtime.UnwarrantedOptimismException.INVALID_PROGRAM_POINT;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import jdk.nashorn.internal.codegen.types.Type;
|
||||
import jdk.nashorn.internal.ir.annotations.Ignore;
|
||||
import jdk.nashorn.internal.ir.annotations.Immutable;
|
||||
import jdk.nashorn.internal.ir.visitor.NodeVisitor;
|
||||
import jdk.nashorn.internal.parser.Token;
|
||||
@ -39,9 +44,27 @@ import jdk.nashorn.internal.parser.TokenType;
|
||||
* UnaryNode nodes represent single operand operations.
|
||||
*/
|
||||
@Immutable
|
||||
public final class UnaryNode extends Expression implements Assignment<Expression> {
|
||||
public final class UnaryNode extends Expression implements Assignment<Expression>, Optimistic {
|
||||
/** Right hand side argument. */
|
||||
private final Expression rhs;
|
||||
private final Expression expression;
|
||||
|
||||
private final int programPoint;
|
||||
|
||||
private final boolean isOptimistic;
|
||||
|
||||
private final Type type;
|
||||
|
||||
@Ignore
|
||||
private static final List<TokenType> CAN_OVERFLOW =
|
||||
Collections.unmodifiableList(
|
||||
Arrays.asList(new TokenType[] {
|
||||
TokenType.ADD,
|
||||
TokenType.SUB, //negate
|
||||
TokenType.DECPREFIX,
|
||||
TokenType.DECPOSTFIX,
|
||||
TokenType.INCPREFIX,
|
||||
TokenType.INCPOSTFIX,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -59,17 +82,23 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
* @param token token
|
||||
* @param start start
|
||||
* @param finish finish
|
||||
* @param rhs expression
|
||||
* @param expression expression
|
||||
*/
|
||||
public UnaryNode(final long token, final int start, final int finish, final Expression rhs) {
|
||||
public UnaryNode(final long token, final int start, final int finish, final Expression expression) {
|
||||
super(token, start, finish);
|
||||
this.rhs = rhs;
|
||||
this.expression = expression;
|
||||
this.programPoint = INVALID_PROGRAM_POINT;
|
||||
this.isOptimistic = false;
|
||||
this.type = null;
|
||||
}
|
||||
|
||||
|
||||
private UnaryNode(final UnaryNode unaryNode, final Expression rhs) {
|
||||
private UnaryNode(final UnaryNode unaryNode, final Expression expression, final Type type, final int programPoint, final boolean isOptimistic) {
|
||||
super(unaryNode);
|
||||
this.rhs = rhs;
|
||||
this.expression = expression;
|
||||
this.programPoint = programPoint;
|
||||
this.isOptimistic = isOptimistic;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,17 +126,23 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
|
||||
@Override
|
||||
public Type getWidestOperationType() {
|
||||
switch (tokenType()) {
|
||||
case ADD:
|
||||
case SUB:
|
||||
return Type.NUMBER;
|
||||
default:
|
||||
return isAssignment() ? Type.NUMBER : Type.OBJECT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Expression getAssignmentDest() {
|
||||
return isAssignment() ? rhs() : null;
|
||||
return isAssignment() ? getExpression() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnaryNode setAssignmentDest(Expression n) {
|
||||
return setRHS(n);
|
||||
return setExpression(n);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -122,7 +157,7 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
@Override
|
||||
public Node accept(final NodeVisitor<? extends LexicalContext> visitor) {
|
||||
if (visitor.enterUnaryNode(this)) {
|
||||
return visitor.leaveUnaryNode(setRHS((Expression)rhs.accept(visitor)));
|
||||
return visitor.leaveUnaryNode(setExpression((Expression)expression.accept(visitor)));
|
||||
}
|
||||
|
||||
return this;
|
||||
@ -137,14 +172,14 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
case SUB:
|
||||
case NOT:
|
||||
case BIT_NOT:
|
||||
return rhs.isLocal() && rhs.getType().isJSPrimitive();
|
||||
return expression.isLocal() && expression.getType().isJSPrimitive();
|
||||
case DECPOSTFIX:
|
||||
case DECPREFIX:
|
||||
case INCPOSTFIX:
|
||||
case INCPREFIX:
|
||||
return rhs instanceof IdentNode && rhs.isLocal() && rhs.getType().isJSPrimitive();
|
||||
return expression instanceof IdentNode && expression.isLocal() && expression.getType().isJSPrimitive();
|
||||
default:
|
||||
return rhs.isLocal();
|
||||
return expression.isLocal();
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +188,7 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
toString(sb, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sb.append(rhs().toString());
|
||||
sb.append(getExpression().toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -166,20 +201,23 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
* when invoked.
|
||||
*/
|
||||
public void toString(final StringBuilder sb, final Runnable rhsStringBuilder) {
|
||||
final TokenType type = tokenType();
|
||||
final String name = type.getName();
|
||||
final boolean isPostfix = type == DECPOSTFIX || type == INCPOSTFIX;
|
||||
final TokenType tokenType = tokenType();
|
||||
final String name = tokenType.getName();
|
||||
final boolean isPostfix = tokenType == DECPOSTFIX || tokenType == INCPOSTFIX;
|
||||
|
||||
boolean rhsParen = type.needsParens(rhs().tokenType(), false);
|
||||
if (isOptimistic()) {
|
||||
sb.append(Node.OPT_IDENTIFIER);
|
||||
}
|
||||
boolean rhsParen = tokenType.needsParens(getExpression().tokenType(), false);
|
||||
|
||||
if (!isPostfix) {
|
||||
if (name == null) {
|
||||
sb.append(type.name());
|
||||
sb.append(tokenType.name());
|
||||
rhsParen = true;
|
||||
} else {
|
||||
sb.append(name);
|
||||
|
||||
if (type.ordinal() > BIT_NOT.ordinal()) {
|
||||
if (tokenType.ordinal() > BIT_NOT.ordinal()) {
|
||||
sb.append(' ');
|
||||
}
|
||||
}
|
||||
@ -194,7 +232,7 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
}
|
||||
|
||||
if (isPostfix) {
|
||||
sb.append(type == DECPOSTFIX ? "--" : "++");
|
||||
sb.append(tokenType == DECPOSTFIX ? "--" : "++");
|
||||
}
|
||||
}
|
||||
|
||||
@ -206,8 +244,8 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
*
|
||||
* @return right hand side or expression node
|
||||
*/
|
||||
public Expression rhs() {
|
||||
return rhs;
|
||||
public Expression getExpression() {
|
||||
return expression;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -216,13 +254,72 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
*
|
||||
* @see BinaryNode
|
||||
*
|
||||
* @param rhs right hand side or expression node
|
||||
* @param expression right hand side or expression node
|
||||
* @return a node equivalent to this one except for the requested change.
|
||||
*/
|
||||
public UnaryNode setRHS(final Expression rhs) {
|
||||
if (this.rhs == rhs) {
|
||||
public UnaryNode setExpression(final Expression expression) {
|
||||
if (this.expression == expression) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, rhs);
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProgramPoint() {
|
||||
return programPoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnaryNode setProgramPoint(final int programPoint) {
|
||||
if (this.programPoint == programPoint) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnaryNode setIsOptimistic(final boolean isOptimistic) {
|
||||
if (this.isOptimistic == isOptimistic) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canBeOptimistic() {
|
||||
return getMostOptimisticType() != getMostPessimisticType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostOptimisticType() {
|
||||
if (CAN_OVERFLOW.contains(tokenType())) {
|
||||
return Type.INT;
|
||||
}
|
||||
return getMostPessimisticType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getMostPessimisticType() {
|
||||
return getWidestOperationType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOptimistic() {
|
||||
//return hasType() && canBeOptimistic() && getType().narrowerThan(getMostPessimisticType());
|
||||
return isOptimistic;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Type getType() {
|
||||
return type == null ? super.getType() : type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnaryNode setType(TemporarySymbols ts, Type type) {
|
||||
if (this.type == type) {
|
||||
return this;
|
||||
}
|
||||
return new UnaryNode(this, expression, type, programPoint, isOptimistic);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ public final class VarNode extends Statement implements Assignment<IdentNode> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public VarNode setAssignmentDest(IdentNode n) {
|
||||
public VarNode setAssignmentDest(final IdentNode n) {
|
||||
return setName(n);
|
||||
}
|
||||
|
||||
|
@ -108,10 +108,18 @@ public final class ASTWriter {
|
||||
String type = clazz.getName();
|
||||
|
||||
type = type.substring(type.lastIndexOf('.') + 1, type.length());
|
||||
int truncate = type.indexOf("Node");
|
||||
if (truncate == -1) {
|
||||
truncate = type.indexOf("Statement");
|
||||
}
|
||||
if (truncate != -1) {
|
||||
type = type.substring(0, truncate);
|
||||
}
|
||||
type = type.toLowerCase();
|
||||
|
||||
if (isReference) {
|
||||
type = "ref: " + type;
|
||||
}
|
||||
type += "@" + Debug.id(node);
|
||||
final Symbol symbol;
|
||||
if (node instanceof Expression) {
|
||||
symbol = ((Expression)node).getSymbol();
|
||||
@ -120,7 +128,7 @@ public final class ASTWriter {
|
||||
}
|
||||
|
||||
if (symbol != null) {
|
||||
type += "#" + symbol;
|
||||
type += ">" + symbol;
|
||||
}
|
||||
|
||||
if (node instanceof Block && ((Block)node).needsScope()) {
|
||||
@ -160,6 +168,8 @@ public final class ASTWriter {
|
||||
status += " (" + tname + ")";
|
||||
}
|
||||
|
||||
status += " @" + Debug.id(node);
|
||||
|
||||
if (children.isEmpty()) {
|
||||
sb.append("[").
|
||||
append(type).
|
||||
@ -200,7 +210,7 @@ public final class ASTWriter {
|
||||
} else if (value instanceof Collection) {
|
||||
int pos = 0;
|
||||
ASTWriter.indent(sb, indent + 1);
|
||||
sb.append("[Collection ").
|
||||
sb.append('[').
|
||||
append(child.getName()).
|
||||
append("[0..").
|
||||
append(((Collection<Node>)value).size()).
|
||||
|
@ -91,7 +91,7 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
final Parser parser = new Parser(env, new Source(name, code), new Context.ThrowErrorManager(), env._strict);
|
||||
final JSONWriter jsonWriter = new JSONWriter(includeLoc);
|
||||
try {
|
||||
final FunctionNode functionNode = parser.parse(CompilerConstants.RUN_SCRIPT.symbolName());
|
||||
final FunctionNode functionNode = parser.parse(CompilerConstants.PROGRAM.symbolName());
|
||||
functionNode.accept(jsonWriter);
|
||||
return jsonWriter.getString();
|
||||
} catch (final ParserException e) {
|
||||
@ -802,7 +802,7 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
type("NewExpression");
|
||||
comma();
|
||||
|
||||
final CallNode callNode = (CallNode)unaryNode.rhs();
|
||||
final CallNode callNode = (CallNode)unaryNode.getExpression();
|
||||
property("callee");
|
||||
callNode.getFunction().accept(this);
|
||||
comma();
|
||||
@ -844,7 +844,7 @@ public final class JSONWriter extends NodeVisitor<LexicalContext> {
|
||||
comma();
|
||||
|
||||
property("argument");
|
||||
unaryNode.rhs().accept(this);
|
||||
unaryNode.getExpression().accept(this);
|
||||
}
|
||||
|
||||
return leave();
|
||||
|
@ -0,0 +1,552 @@
|
||||
/*
|
||||
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation. Oracle designates this
|
||||
* particular file as subject to the "Classpath" exception as provided
|
||||
* by Oracle in the LICENSE file that accompanied this code.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*/
|
||||
|
||||
package jdk.nashorn.internal.ir.debug;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import jdk.internal.org.objectweb.asm.Attribute;
|
||||
import jdk.internal.org.objectweb.asm.ClassReader;
|
||||
import jdk.internal.org.objectweb.asm.ClassVisitor;
|
||||
import jdk.internal.org.objectweb.asm.Label;
|
||||
import jdk.nashorn.internal.ir.debug.NashornTextifier.NashornLabel;
|
||||
|
||||
/**
|
||||
* Subclass of the ASM classs reader that retains more info, such
|
||||
* as bytecode offsets
|
||||
*/
|
||||
public class NashornClassReader extends ClassReader {
|
||||
|
||||
private final Map<String, List<Label>> labelMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param bytecode bytecode for class
|
||||
*/
|
||||
public NashornClassReader(final byte[] bytecode) {
|
||||
super(bytecode);
|
||||
parse(bytecode);
|
||||
}
|
||||
|
||||
List<Label> getExtraLabels(final String className, final String methodName, final String methodDesc) {
|
||||
final String key = fullyQualifiedName(className, methodName, methodDesc);
|
||||
return labelMap.get(key);
|
||||
}
|
||||
|
||||
private static int readByte(final byte[] bytecode, int index) {
|
||||
return (byte)(bytecode[index] & 0xff);
|
||||
}
|
||||
|
||||
private static int readShort(final byte[] bytecode, int index) {
|
||||
return (short)((bytecode[index] & 0xff) << 8) | (bytecode[index + 1] & 0xff);
|
||||
}
|
||||
|
||||
private static int readInt(final byte[] bytecode, int index) {
|
||||
return ((bytecode[index] & 0xff) << 24) | ((bytecode[index + 1] & 0xff) << 16) | ((bytecode[index + 2] & 0xff) << 8) | (bytecode[index + 3] & 0xff);
|
||||
}
|
||||
|
||||
private static long readLong(final byte[] bytecode, int index) {
|
||||
int hi = readInt(bytecode, index);
|
||||
int lo = readInt(bytecode, index + 4);
|
||||
return ((long)hi << 32) | lo;
|
||||
}
|
||||
|
||||
private static String readUTF(int index, final int utfLen, final byte[] bytecode) {
|
||||
int endIndex = index + utfLen;
|
||||
char buf[] = new char[utfLen * 2];
|
||||
int strLen = 0;
|
||||
int c;
|
||||
int st = 0;
|
||||
char cc = 0;
|
||||
int i = index;
|
||||
|
||||
while (i < endIndex) {
|
||||
c = bytecode[i++];
|
||||
switch (st) {
|
||||
case 0:
|
||||
c = c & 0xFF;
|
||||
if (c < 0x80) { // 0xxxxxxx
|
||||
buf[strLen++] = (char) c;
|
||||
} else if (c < 0xE0 && c > 0xBF) { // 110x xxxx 10xx xxxx
|
||||
cc = (char) (c & 0x1F);
|
||||
st = 1;
|
||||
} else { // 1110 xxxx 10xx xxxx 10xx xxxx
|
||||
cc = (char) (c & 0x0F);
|
||||
st = 2;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // byte 2 of 2-byte char or byte 3 of 3-byte char
|
||||
buf[strLen++] = (char) ((cc << 6) | (c & 0x3F));
|
||||
st = 0;
|
||||
break;
|
||||
|
||||
case 2: // byte 2 of 3-byte char
|
||||
cc = (char) ((cc << 6) | (c & 0x3F));
|
||||
st = 1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return new String(buf, 0, strLen);
|
||||
}
|
||||
|
||||
private String parse(final byte[] bytecode) {
|
||||
String thisClassName;
|
||||
|
||||
int u = 0;
|
||||
|
||||
int magic = readInt(bytecode, u);
|
||||
u += 4; //magic
|
||||
assert magic == 0xcafebabe : Integer.toHexString(magic);
|
||||
readShort(bytecode, u); //minor
|
||||
u += 2;
|
||||
readShort(bytecode, u); //major
|
||||
u += 2; //minor
|
||||
|
||||
int cpc = readShort(bytecode, u);
|
||||
u += 2;
|
||||
ArrayList<Constant> cp = new ArrayList<>(cpc);
|
||||
cp.add(null);
|
||||
|
||||
for (int i = 1; i < cpc; i++) {
|
||||
//constant pool entries
|
||||
final int tag = readByte(bytecode, u);
|
||||
u += 1;
|
||||
switch (tag) {
|
||||
case 7: //class
|
||||
cp.add(new IndexInfo(cp, tag, readShort(bytecode, u)));
|
||||
u += 2;
|
||||
break;
|
||||
case 9: //fieldref
|
||||
case 10: //methodref
|
||||
case 11: //interfacemethodref
|
||||
cp.add(new IndexInfo2(cp, tag, readShort(bytecode, u), readShort(bytecode, u + 2)));
|
||||
u += 4;
|
||||
break;
|
||||
case 8: //string
|
||||
cp.add(new IndexInfo(cp, tag, readShort(bytecode, u))); //string index
|
||||
u += 2;
|
||||
break;
|
||||
case 3: //int
|
||||
cp.add(new DirectInfo<>(cp, tag, readInt(bytecode, u)));
|
||||
u += 4;
|
||||
break;
|
||||
case 4: //float
|
||||
cp.add(new DirectInfo<>(cp, tag, Float.intBitsToFloat(readInt(bytecode, u))));
|
||||
u += 4;
|
||||
break;
|
||||
case 5: //long
|
||||
cp.add(new DirectInfo<>(cp, tag, readLong(bytecode, u)));
|
||||
cp.add(null);
|
||||
i++;
|
||||
u += 8;
|
||||
break;
|
||||
case 6: //double
|
||||
cp.add(new DirectInfo<>(cp, tag, Double.longBitsToDouble(readLong(bytecode, u))));
|
||||
cp.add(null);
|
||||
i++;
|
||||
u += 8;
|
||||
break;
|
||||
case 12: //name and type
|
||||
cp.add(new IndexInfo2(cp, tag, readShort(bytecode, u), readShort(bytecode, u + 2)));
|
||||
u += 4;
|
||||
break;
|
||||
case 1: //utf8
|
||||
int len = readShort(bytecode, u);
|
||||
u += 2;
|
||||
cp.add(new DirectInfo<>(cp, tag, readUTF(u, len, bytecode)));
|
||||
u += len;
|
||||
break;
|
||||
case 16: //methodtype
|
||||
cp.add(new IndexInfo(cp, tag, readShort(bytecode, u)));
|
||||
u += 2;
|
||||
break;
|
||||
case 18: //indy
|
||||
cp.add(new IndexInfo2(cp, tag, readShort(bytecode, u), readShort(bytecode, u + 2)) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "#" + index + ' ' + cp.get(index2).toString();
|
||||
}
|
||||
|
||||
});
|
||||
u += 4;
|
||||
break;
|
||||
case 15: //methodhandle
|
||||
int kind = readByte(bytecode, u);
|
||||
assert kind >= 1 && kind <= 9 : kind;
|
||||
cp.add(new IndexInfo2(cp, tag, kind, readShort(bytecode, u + 1)) {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "#" + index + ' ' + cp.get(index2).toString();
|
||||
}
|
||||
});
|
||||
|
||||
u += 3;
|
||||
break;
|
||||
default:
|
||||
assert false : tag;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
readShort(bytecode, u); //access flags
|
||||
u += 2; //access
|
||||
int cls = readShort(bytecode, u);
|
||||
u += 2; //this_class
|
||||
thisClassName = cp.get(cls).toString();
|
||||
u += 2; //super
|
||||
|
||||
int ifc = readShort(bytecode, u);
|
||||
u += 2;
|
||||
u += ifc * 2;
|
||||
|
||||
int fc = readShort(bytecode, u);
|
||||
u += 2; //fields
|
||||
|
||||
for (int i = 0 ; i < fc ; i++) {
|
||||
u += 2; //access
|
||||
readShort(bytecode, u); //fieldname
|
||||
u += 2; //name
|
||||
u += 2; //descriptor
|
||||
int ac = readShort(bytecode, u);
|
||||
u += 2;
|
||||
//field attributes
|
||||
for (int j = 0; j < ac; j++) {
|
||||
u += 2; //attribute name
|
||||
int len = readInt(bytecode, u);
|
||||
u += 4;
|
||||
u += len;
|
||||
}
|
||||
}
|
||||
|
||||
int mc = readShort(bytecode, u);
|
||||
u += 2;
|
||||
for (int i = 0 ; i < mc ; i++) {
|
||||
readShort(bytecode, u);
|
||||
u += 2; //access
|
||||
|
||||
int methodNameIndex = readShort(bytecode, u);
|
||||
u += 2;
|
||||
final String methodName = cp.get(methodNameIndex).toString();
|
||||
|
||||
int methodDescIndex = readShort(bytecode, u);
|
||||
u += 2;
|
||||
final String methodDesc = cp.get(methodDescIndex).toString();
|
||||
|
||||
int ac = readShort(bytecode, u);
|
||||
u += 2;
|
||||
|
||||
//method attributes
|
||||
for (int j = 0; j < ac; j++) {
|
||||
int nameIndex = readShort(bytecode, u);
|
||||
u += 2;
|
||||
String attrName = cp.get(nameIndex).toString();
|
||||
|
||||
int attrLen = readInt(bytecode, u);
|
||||
u += 4;
|
||||
|
||||
if ("Code".equals(attrName)) {
|
||||
readShort(bytecode, u);
|
||||
u += 2; //max stack
|
||||
readShort(bytecode, u);
|
||||
u += 2; //max locals
|
||||
int len = readInt(bytecode, u);
|
||||
u += 4;
|
||||
parseCode(bytecode, u, len, fullyQualifiedName(thisClassName, methodName, methodDesc));
|
||||
u += len;
|
||||
int elen = readShort(bytecode, u); //exception table length
|
||||
u += 2;
|
||||
u += elen * 8;
|
||||
|
||||
//method attributes
|
||||
int ac2 = readShort(bytecode, u);
|
||||
u += 2;
|
||||
for (int k = 0; k < ac2; k++) {
|
||||
u += 2; //name;
|
||||
int aclen = readInt(bytecode, u);
|
||||
u += 4; //length
|
||||
u += aclen; //bytes;
|
||||
}
|
||||
} else {
|
||||
u += attrLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int ac = readShort(bytecode, u);
|
||||
u += 2;
|
||||
//other attributes
|
||||
for (int i = 0 ; i < ac ; i++) {
|
||||
readShort(bytecode, u); //name index
|
||||
u += 2;
|
||||
int len = readInt(bytecode, u);
|
||||
u += 4;
|
||||
u += len;
|
||||
//attribute
|
||||
}
|
||||
|
||||
return thisClassName;
|
||||
}
|
||||
|
||||
private static String fullyQualifiedName(final String className, final String methodName, final String methodDesc) {
|
||||
return className + '.' + methodName + methodDesc;
|
||||
}
|
||||
|
||||
private void parseCode(final byte[] bytecode, final int index, final int len, final String desc) {
|
||||
final List<Label> labels = new ArrayList<>();
|
||||
labelMap.put(desc, labels);
|
||||
|
||||
boolean wide = false;
|
||||
|
||||
for (int i = index; i < index + len;) {
|
||||
int opcode = bytecode[i];
|
||||
labels.add(new NashornLabel(opcode, i - index));
|
||||
|
||||
switch (opcode & 0xff) {
|
||||
case 0xc4: //wide
|
||||
wide = true;
|
||||
i += 1;
|
||||
break;
|
||||
case 0xa9: //ret
|
||||
i += wide ? 4 : 2;
|
||||
break;
|
||||
case 0xab: //lookupswitch
|
||||
i += 1;
|
||||
while (((i - index) & 3) != 0) {
|
||||
i++;
|
||||
}
|
||||
readInt(bytecode, i);
|
||||
i += 4; //defaultbyte
|
||||
int npairs = readInt(bytecode, i);
|
||||
i += 4;
|
||||
i += 8 * npairs;
|
||||
break;
|
||||
case 0xaa: //tableswitch
|
||||
i += 1;
|
||||
while (((i - index) & 3) != 0) {
|
||||
i++;
|
||||
}
|
||||
readInt(bytecode, i); //default
|
||||
i += 4;
|
||||
int lo = readInt(bytecode, i);
|
||||
i += 4;
|
||||
int hi = readInt(bytecode, i);
|
||||
i += 4;
|
||||
i += 4 * (hi - lo + 1);
|
||||
break;
|
||||
case 0xc5: //multianewarray
|
||||
i += 4;
|
||||
break;
|
||||
case 0x19: //aload (wide)
|
||||
case 0x18: //dload
|
||||
case 0x17: //fload
|
||||
case 0x15: //iload
|
||||
case 0x16: //lload
|
||||
case 0x3a: //astore wide
|
||||
case 0x39: //dstore
|
||||
case 0x38: //fstore
|
||||
case 0x36: //istore
|
||||
case 0x37: //lstore
|
||||
i += wide ? 3 : 2;
|
||||
break;
|
||||
case 0x10: //bipush
|
||||
case 0x12: //ldc
|
||||
case 0xbc: //anewarrayu
|
||||
i += 2;
|
||||
break;
|
||||
case 0xb4: //getfield
|
||||
case 0xb2: //getstatic
|
||||
case 0xbd: //anewarray
|
||||
case 0xc0: //checkcast
|
||||
case 0xa5: //ifacmp_eq
|
||||
case 0xa6: //ifacmp_ne
|
||||
case 0x9f: //all ifs and ifcmps
|
||||
case 0xa0:
|
||||
case 0xa1:
|
||||
case 0xa2:
|
||||
case 0xa3:
|
||||
case 0xa4:
|
||||
case 0x99:
|
||||
case 0x9a:
|
||||
case 0x9b:
|
||||
case 0x9c:
|
||||
case 0x9d:
|
||||
case 0x9e:
|
||||
case 0xc7:
|
||||
case 0xc6:
|
||||
case 0xc1: //instanceof
|
||||
case 0xa7: //goto
|
||||
case 0xb7: //special
|
||||
case 0xb8: //static
|
||||
case 0xb6: //virtual
|
||||
case 0xa8: //jsr
|
||||
case 0x13: //ldc_w
|
||||
case 0x14: //ldc2_w
|
||||
case 0xbb: //new
|
||||
case 0xb5: //putfield
|
||||
case 0xb3: //putstatic
|
||||
case 0x11: //sipush
|
||||
i += 3;
|
||||
break;
|
||||
case 0x84: //iinc (wide)
|
||||
i += wide ? 5 : 3;
|
||||
break;
|
||||
case 0xba: //indy
|
||||
case 0xb9: //interface
|
||||
case 0xc8:
|
||||
case 0xc9: //jsr_w
|
||||
i += 5; //goto_w
|
||||
break;
|
||||
default:
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
|
||||
if (wide) {
|
||||
wide = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(final ClassVisitor classVisitor, Attribute[] attrs, final int flags) {
|
||||
super.accept(classVisitor, attrs, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Label readLabel(final int offset, final Label[] labels) {
|
||||
Label label = super.readLabel(offset, labels);
|
||||
label.info = (int)offset;
|
||||
return label;
|
||||
}
|
||||
|
||||
private abstract static class Constant {
|
||||
protected ArrayList<Constant> cp;
|
||||
protected int tag;
|
||||
protected Constant(final ArrayList<Constant> cp, int tag) {
|
||||
this.cp = cp;
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
final String getType() {
|
||||
String str = type[tag];
|
||||
while (str.length() < 16) {
|
||||
str += " ";
|
||||
}
|
||||
return str;
|
||||
}
|
||||
}
|
||||
|
||||
private static class IndexInfo extends Constant {
|
||||
protected final int index;
|
||||
|
||||
IndexInfo(final ArrayList<Constant> cp, int tag, int index) {
|
||||
super(cp, tag);
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return cp.get(index).toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class IndexInfo2 extends IndexInfo {
|
||||
protected final int index2;
|
||||
|
||||
IndexInfo2(final ArrayList<Constant> cp, int tag, int index, int index2) {
|
||||
super(cp, tag, index);
|
||||
this.index2 = index2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return super.toString() + ' ' + cp.get(index2).toString();
|
||||
}
|
||||
}
|
||||
|
||||
private static class DirectInfo<T> extends Constant {
|
||||
protected final T info;
|
||||
|
||||
DirectInfo(final ArrayList<Constant> cp, int tag, T info) {
|
||||
super(cp, tag);
|
||||
this.info = info;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return info.toString();// + " [class=" + info.getClass().getSimpleName() + ']';
|
||||
}
|
||||
}
|
||||
|
||||
private static String type[] = {
|
||||
//0
|
||||
"<error>",
|
||||
//1
|
||||
"UTF8",
|
||||
//2
|
||||
"<error>",
|
||||
//3
|
||||
"Integer",
|
||||
//4
|
||||
"Float",
|
||||
//5
|
||||
"Long",
|
||||
//6
|
||||
"Double",
|
||||
//7
|
||||
"Class",
|
||||
//8
|
||||
"String",
|
||||
//9
|
||||
"Fieldref",
|
||||
//10
|
||||
"Methodref",
|
||||
//11
|
||||
"InterfaceMethodRef",
|
||||
//12
|
||||
"NameAndType",
|
||||
//13
|
||||
"<error>",
|
||||
//14
|
||||
"<error>",
|
||||
//15
|
||||
"MethodHandle",
|
||||
//16
|
||||
"MethodType",
|
||||
//17
|
||||
"<error>",
|
||||
//18
|
||||
"Invokedynamic"
|
||||
};
|
||||
|
||||
}
|
1224
nashorn/src/jdk/nashorn/internal/ir/debug/NashornTextifier.java
Normal file
1224
nashorn/src/jdk/nashorn/internal/ir/debug/NashornTextifier.java
Normal file
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user