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();
|
||||
}
|
||||
});
|
||||
TraceClassVisitor tcv = new TraceClassVisitor(null, new NashornTextifier(ctx.getEnv(), cr), pw);
|
||||
cr.accept(tcv, 0);
|
||||
}
|
||||
return new String(baos.toByteArray());
|
||||
|
||||
final String str = new String(baos.toByteArray());
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -475,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) {
|
||||
@ -261,13 +197,15 @@ enum CompilationPhase {
|
||||
final Expression expr = (Expression)node;
|
||||
final Symbol symbol = expr.getSymbol();
|
||||
if (symbol != null) {
|
||||
final Range range = symbol.getRange();
|
||||
final Range range = symbol.getRange();
|
||||
final Type symbolType = symbol.getSymbolType();
|
||||
if (!symbolType.isNumeric()) {
|
||||
|
||||
if (!symbolType.isUnknown() && !symbolType.isNumeric()) {
|
||||
return expr;
|
||||
}
|
||||
final Type rangeType = range.getType();
|
||||
if (!Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range
|
||||
|
||||
final Type rangeType = range.getType();
|
||||
if (!rangeType.isUnknown() && !Type.areEquivalent(symbolType, rangeType) && Type.widest(symbolType, rangeType) == symbolType) { //we can narrow range
|
||||
RangeAnalyzer.LOG.info("[", lc.getCurrentFunction().getName(), "] ", symbol, " can be ", range.getType(), " ", symbol.getRange());
|
||||
return expr.setSymbol(lc, symbol.setTypeOverrideShared(range.getType(), compiler.getTemporarySymbols()));
|
||||
}
|
||||
@ -296,37 +234,7 @@ 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> {
|
||||
|
||||
CompilationSequence(final CompilationPhase... phases) {
|
||||
super(Arrays.asList(phases));
|
||||
}
|
||||
|
||||
CompilationSequence(final CompilationSequence sequence) {
|
||||
this(sequence.toArray(new CompilationPhase[sequence.size()]));
|
||||
}
|
||||
|
||||
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;
|
||||
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()));
|
||||
private void initCompiler(final String className, final FunctionNode functionNode) {
|
||||
this.source = functionNode.getSource();
|
||||
this.sourceName = functionNode.getSourceName();
|
||||
this.sourceURL = functionNode.getSourceURL();
|
||||
|
||||
if (functionNode.isStrict()) {
|
||||
compilationEnv.setIsStrict(true);
|
||||
}
|
||||
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(functionNode.uniqueName(className)).
|
||||
append('$').
|
||||
append(safeSourceName(functionNode.getSource()));
|
||||
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);
|
||||
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<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor - compilation will use the same strict semantics as in script environment
|
||||
*
|
||||
* @param installer code installer
|
||||
* 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;
|
||||
@ -198,9 +198,23 @@ public enum CompilerConstants {
|
||||
}
|
||||
|
||||
private CompilerConstants(final String symbolName, final Class<?> type, final int slot) {
|
||||
this.symbolName = symbolName;
|
||||
this.type = type;
|
||||
this.slot = slot;
|
||||
this.symbolName = symbolName;
|
||||
this.type = type;
|
||||
this.slot = slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a name is that of a reserved compiler constnat
|
||||
* @param name name
|
||||
* @return true if compiler constant name
|
||||
*/
|
||||
public static boolean isCompilerConstant(final String name) {
|
||||
for (final CompilerConstants cc : CompilerConstants.values()) {
|
||||
if (name.equals(cc.symbolName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -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,19 +52,16 @@ 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 int fieldCount;
|
||||
private int paddedFieldCount;
|
||||
private int paramCount;
|
||||
|
||||
/** array of corresponding values to symbols (null for no values) */
|
||||
private final List<T> values;
|
||||
private String fieldObjectClassName;
|
||||
private Class<? extends ScriptObject> fieldObjectClass;
|
||||
private int fieldCount;
|
||||
private int paddedFieldCount;
|
||||
private int paramCount;
|
||||
|
||||
/** call site flags to be used for invocations */
|
||||
private final int callSiteFlags;
|
||||
private final int callSiteFlags;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@ -73,8 +71,8 @@ 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);
|
||||
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));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean enterFunctionNode(final FunctionNode functionNode) {
|
||||
return !functionNode.isLazy();
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
popType();
|
||||
pushType(type);
|
||||
}
|
||||
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,7 +1940,8 @@ public class MethodEmitter implements Emitter {
|
||||
* @param flags call site flags
|
||||
*/
|
||||
void dynamicSet(final String name, final int flags) {
|
||||
debug("dynamic_set", name, peekType());
|
||||
assert !isOptimistic(flags);
|
||||
debug("dynamic_set", name, peekType());
|
||||
|
||||
Type type = peekType();
|
||||
if (type.isObject() || type.isBoolean()) { //promote strings to objects etc
|
||||
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
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());
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -235,7 +218,7 @@ public final class ObjectClassGenerator {
|
||||
public static int getFieldCount(Class<?> clazz) {
|
||||
final String name = clazz.getSimpleName();
|
||||
final String prefix = JS_OBJECT_PREFIX.symbolName();
|
||||
if(prefix.equals(name)) {
|
||||
if (prefix.equals(name)) {
|
||||
return 0;
|
||||
}
|
||||
final int scopeMarker = name.indexOf(SCOPE_MARKER);
|
||||
@ -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);
|
||||
|
||||
@ -340,8 +327,8 @@ public final class ObjectClassGenerator {
|
||||
* @return Byte codes for generated class.
|
||||
*/
|
||||
public byte[] generate(final int fieldCount, final int paramCount) {
|
||||
final String className = getClassName(fieldCount, paramCount);
|
||||
final String superName = className(FunctionScope.class);
|
||||
final String className = getClassName(fieldCount, paramCount);
|
||||
final String superName = className(FunctionScope.class);
|
||||
final ClassEmitter classEmitter = newClassEmitter(className, superName);
|
||||
final List<String> initFields = addFields(classEmitter, fieldCount);
|
||||
|
||||
@ -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 {
|
||||
return MH.findStatic(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types));
|
||||
} catch (final MethodHandleFactory.LookupException e) {
|
||||
return MH.findVirtual(MethodHandles.lookup(), RuntimeCallSite.class, name, MH.type(rtype, types));
|
||||
}
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
int pos = 0;
|
||||
for (final MapTuple<Expression> tuple : tuples) {
|
||||
final String key = tuple.key;
|
||||
final Expression value = tuple.value;
|
||||
|
||||
if (value == null) {
|
||||
continue; // getter or setter
|
||||
}
|
||||
if (value != null) {
|
||||
final Object constantValue = LiteralNode.objectAsConstant(value);
|
||||
if (constantValue == LiteralNode.POSTSET_MARKER) {
|
||||
postsetValues.add(pos);
|
||||
} else {
|
||||
final Property property = propertyMap.findProperty(key);
|
||||
if (property != null) {
|
||||
// normal property key
|
||||
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);
|
||||
final long longIndex = ArrayIndex.toLongIndex(index);
|
||||
|
||||
final Object constantValue = LiteralNode.objectAsConstant(value);
|
||||
if (constantValue == LiteralNode.POSTSET_MARKER) {
|
||||
postsetValues.add(i);
|
||||
continue;
|
||||
}
|
||||
assert ArrayIndex.isValidArrayIndex(index);
|
||||
|
||||
final Property property = propertyMap.findProperty(key);
|
||||
if (property != null) {
|
||||
// normal property key
|
||||
presetValues[property.getSlot()] = 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);
|
||||
if (longIndex >= oldLength) {
|
||||
arrayData = arrayData.ensure(longIndex);
|
||||
}
|
||||
arrayData = arrayData.set(index, constantValue, false);
|
||||
if (longIndex > oldLength) {
|
||||
arrayData = arrayData.delete(oldLength, longIndex - 1);
|
||||
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
|
||||
method.dup();
|
||||
codegen.loadConstant(presetValues);
|
||||
method.putField(Type.getInternalName(ScriptObject.class), "spill", Type.OBJECT_ARRAY.getDescriptor());
|
||||
//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();
|
||||
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));
|
||||
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;
|
||||
@ -69,24 +68,25 @@ final class WeighNodes extends NodeOperatorVisitor<LexicalContext> {
|
||||
* Weight constants.
|
||||
*/
|
||||
static final long FUNCTION_WEIGHT = 40;
|
||||
static final long AASTORE_WEIGHT = 2;
|
||||
static final long ACCESS_WEIGHT = 4;
|
||||
static final long AASTORE_WEIGHT = 2;
|
||||
static final long ACCESS_WEIGHT = 4;
|
||||
static final long ADD_WEIGHT = 10;
|
||||
static final long BREAK_WEIGHT = 1;
|
||||
static final long BREAK_WEIGHT = 1;
|
||||
static final long CALL_WEIGHT = 10;
|
||||
static final long CATCH_WEIGHT = 10;
|
||||
static final long CONTINUE_WEIGHT = 1;
|
||||
static final long IF_WEIGHT = 2;
|
||||
static final long COMPARE_WEIGHT = 6;
|
||||
static final long CONTINUE_WEIGHT = 1;
|
||||
static final long IF_WEIGHT = 2;
|
||||
static final long LITERAL_WEIGHT = 10;
|
||||
static final long LOOP_WEIGHT = 4;
|
||||
static final long NEW_WEIGHT = 6;
|
||||
static final long LOOP_WEIGHT = 4;
|
||||
static final long NEW_WEIGHT = 6;
|
||||
static final long FUNC_EXPR_WEIGHT = 20;
|
||||
static final long RETURN_WEIGHT = 2;
|
||||
static final long RETURN_WEIGHT = 2;
|
||||
static final long SPLIT_WEIGHT = 40;
|
||||
static final long SWITCH_WEIGHT = 8;
|
||||
static final long THROW_WEIGHT = 2;
|
||||
static final long SWITCH_WEIGHT = 8;
|
||||
static final long THROW_WEIGHT = 2;
|
||||
static final long VAR_WEIGHT = 40;
|
||||
static final long WITH_WEIGHT = 8;
|
||||
static final long WITH_WEIGHT = 8;
|
||||
|
||||
/** Accumulated weight. */
|
||||
private long weight;
|
||||
@ -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;
|
||||
@ -86,36 +85,36 @@ class IntType extends BitwiseType {
|
||||
final int value = ((Integer) c).intValue();
|
||||
|
||||
switch (value) {
|
||||
case -1:
|
||||
method.visitInsn(ICONST_M1);
|
||||
break;
|
||||
case 0:
|
||||
method.visitInsn(ICONST_0);
|
||||
break;
|
||||
case 1:
|
||||
method.visitInsn(ICONST_1);
|
||||
break;
|
||||
case 2:
|
||||
method.visitInsn(ICONST_2);
|
||||
break;
|
||||
case 3:
|
||||
method.visitInsn(ICONST_3);
|
||||
break;
|
||||
case 4:
|
||||
method.visitInsn(ICONST_4);
|
||||
break;
|
||||
case 5:
|
||||
method.visitInsn(ICONST_5);
|
||||
break;
|
||||
default:
|
||||
if (value == (byte) value) {
|
||||
method.visitIntInsn(BIPUSH, value);
|
||||
} else if (value == (short) value) {
|
||||
method.visitIntInsn(SIPUSH, value);
|
||||
} else {
|
||||
method.visitLdcInsn(c);
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
method.visitInsn(ICONST_M1);
|
||||
break;
|
||||
case 0:
|
||||
method.visitInsn(ICONST_0);
|
||||
break;
|
||||
case 1:
|
||||
method.visitInsn(ICONST_1);
|
||||
break;
|
||||
case 2:
|
||||
method.visitInsn(ICONST_2);
|
||||
break;
|
||||
case 3:
|
||||
method.visitInsn(ICONST_3);
|
||||
break;
|
||||
case 4:
|
||||
method.visitInsn(ICONST_4);
|
||||
break;
|
||||
case 5:
|
||||
method.visitInsn(ICONST_5);
|
||||
break;
|
||||
default:
|
||||
if (value == (byte) value) {
|
||||
method.visitIntInsn(BIPUSH, value);
|
||||
} else if (value == (short) value) {
|
||||
method.visitIntInsn(SIPUSH, value);
|
||||
} else {
|
||||
method.visitLdcInsn(c);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return Type.INT;
|
||||
@ -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
|
||||
*
|
||||
@ -51,8 +63,11 @@ public abstract class BaseNode extends Expression implements FunctionCall {
|
||||
*/
|
||||
public BaseNode(final long token, final int finish, final Expression base, final boolean isFunction) {
|
||||
super(token, base.getStart(), finish);
|
||||
this.base = base;
|
||||
this.isFunction = isFunction;
|
||||
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.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
|
||||
*/
|
||||
@ -145,20 +154,24 @@ public final class CallNode extends LexicalContextExpression {
|
||||
public CallNode(final int lineNumber, final long token, final int finish, final Expression function, final List<Expression> args) {
|
||||
super(token, finish);
|
||||
|
||||
this.function = function;
|
||||
this.args = args;
|
||||
this.flags = 0;
|
||||
this.evalArgs = null;
|
||||
this.lineNumber = lineNumber;
|
||||
this.function = function;
|
||||
this.args = args;
|
||||
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
|
||||
@ -164,11 +161,11 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
public static final int HAS_EVAL = 1 << 5;
|
||||
|
||||
/** Does a nested function contain eval? If it does, then all variables in this function might be get/set by it. */
|
||||
public static final int HAS_NESTED_EVAL = 1 << 6;
|
||||
public static final int HAS_NESTED_EVAL = 1 << 6;
|
||||
|
||||
/** Does this function have any blocks that create a scope? This is used to determine if the function needs to
|
||||
* have a local variable slot for the scope symbol. */
|
||||
public static final int HAS_SCOPE_BLOCK = 1 << 7;
|
||||
public static final int HAS_SCOPE_BLOCK = 1 << 7;
|
||||
|
||||
/**
|
||||
* Flag this function as one that defines the identifier "arguments" as a function parameter or nested function
|
||||
@ -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,11 +824,8 @@ public final class FunctionNode extends LexicalContextExpression implements Flag
|
||||
type,
|
||||
compileUnit,
|
||||
compilationState,
|
||||
body.setReturnType(type),
|
||||
parameters,
|
||||
snapshot,
|
||||
hints));
|
||||
}
|
||||
body.setReturnType(type), parameters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the function is generated in strict mode
|
||||
@ -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
|
||||
*
|
||||
@ -60,16 +67,18 @@ 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.flags = 0;
|
||||
this.name = name.intern();
|
||||
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.flags = flags;
|
||||
this.name = name;
|
||||
this.optimisticType = callSiteType;
|
||||
this.flags = flags;
|
||||
this.programPoint = programPoint;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -79,14 +88,15 @@ public final class IdentNode extends Expression implements PropertyKey, Function
|
||||
*/
|
||||
public IdentNode(final IdentNode identNode) {
|
||||
super(identNode);
|
||||
this.name = identNode.getName();
|
||||
this.callSiteType = null;
|
||||
this.flags = identNode.flags;
|
||||
this.name = identNode.getName();
|
||||
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,15 +427,23 @@ 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()) {
|
||||
n++;
|
||||
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.
|
||||
*/
|
||||
@ -56,27 +56,27 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
public static final int KINDMASK = (1 << 3) - 1; // Kinds are represented by lower three bits
|
||||
|
||||
/** Is this scope */
|
||||
public static final int IS_SCOPE = 1 << 4;
|
||||
public static final int IS_SCOPE = 1 << 4;
|
||||
/** Is this a this symbol */
|
||||
public static final int IS_THIS = 1 << 5;
|
||||
public static final int IS_THIS = 1 << 5;
|
||||
/** Can this symbol ever be undefined */
|
||||
public static final int CAN_BE_UNDEFINED = 1 << 6;
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -557,7 +552,7 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* Flag this symbol as a let
|
||||
*/
|
||||
public void setIsLet() {
|
||||
if(!isLet()) {
|
||||
if (!isLet()) {
|
||||
assert !isShared();
|
||||
flags |= IS_LET;
|
||||
}
|
||||
@ -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) {
|
||||
if(this.fieldIndex != 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) {
|
||||
if(this.flags != flags) {
|
||||
assert !isShared();
|
||||
public Symbol setFlags(final int flags) {
|
||||
if (this.flags != flags) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -647,33 +674,39 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
|
||||
/**
|
||||
* Set the bytecode slot for this symbol
|
||||
* @param slot valid bytecode slot, or -1 if not available
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -690,15 +723,17 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* constraint - otherwise a type can only be
|
||||
* widened
|
||||
*
|
||||
* @param type the type
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -711,8 +746,8 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* be changed.
|
||||
*/
|
||||
public Symbol setTypeOverrideShared(final Type type, final TemporarySymbols ts) {
|
||||
if(getSymbolType() != type) {
|
||||
if(isShared()) {
|
||||
if (getSymbolType() != type) {
|
||||
if (isShared()) {
|
||||
assert !hasSlot();
|
||||
return ts.getTypedTemporarySymbol(type);
|
||||
}
|
||||
@ -728,14 +763,16 @@ public final class Symbol implements Comparable<Symbol> {
|
||||
* when flags need to be tagged, but block is in the
|
||||
* middle of evaluation and cannot be modified.
|
||||
*
|
||||
* @param lc lexical context
|
||||
* @param symbol 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
|
||||
@ -56,20 +79,26 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param token token
|
||||
* @param start start
|
||||
* @param finish finish
|
||||
* @param rhs expression
|
||||
* @param token token
|
||||
* @param start start
|
||||
* @param finish finish
|
||||
* @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() {
|
||||
return isAssignment() ? Type.NUMBER : Type.OBJECT;
|
||||
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;
|
||||
@ -131,20 +166,20 @@ public final class UnaryNode extends Expression implements Assignment<Expression
|
||||
@Override
|
||||
public boolean isLocal() {
|
||||
switch (tokenType()) {
|
||||
case NEW:
|
||||
return false;
|
||||
case ADD:
|
||||
case SUB:
|
||||
case NOT:
|
||||
case BIT_NOT:
|
||||
return rhs.isLocal() && rhs.getType().isJSPrimitive();
|
||||
case DECPOSTFIX:
|
||||
case DECPREFIX:
|
||||
case INCPOSTFIX:
|
||||
case INCPREFIX:
|
||||
return rhs instanceof IdentNode && rhs.isLocal() && rhs.getType().isJSPrimitive();
|
||||
default:
|
||||
return rhs.isLocal();
|
||||
case NEW:
|
||||
return false;
|
||||
case ADD:
|
||||
case SUB:
|
||||
case NOT:
|
||||
case BIT_NOT:
|
||||
return expression.isLocal() && expression.getType().isJSPrimitive();
|
||||
case DECPOSTFIX:
|
||||
case DECPREFIX:
|
||||
case INCPOSTFIX:
|
||||
case INCPREFIX:
|
||||
return expression instanceof IdentNode && expression.isLocal() && expression.getType().isJSPrimitive();
|
||||
default:
|
||||
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,19 +108,27 @@ 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) {
|
||||
if (node instanceof Expression) {
|
||||
symbol = ((Expression)node).getSymbol();
|
||||
} else {
|
||||
symbol = null;
|
||||
}
|
||||
|
||||
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…
Reference in New Issue
Block a user