8035820: Optimistic recompilation

Co-authored-by: Marcus Lagergren <marcus.lagergren@oracle.com>
Reviewed-by: hannesw, jlaskey, sundar
This commit is contained in:
Attila Szegedi 2014-02-26 13:17:57 +01:00
parent 18489cc7a4
commit e9e7dd2ec1
213 changed files with 15692 additions and 4915 deletions

View File

@ -13,6 +13,8 @@ webrev.zip
*.clazz
*.log
*.orig
*.rej
*~
genfiles.properties
hotspot.log
.DS_Store*

25
nashorn/bin/rundiff.sh Normal file
View 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
View 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 ${@}

View 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
View 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 ${@}

View 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 ${@}

View 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 ${@}

View File

@ -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);
}
}

View File

@ -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"/>

View File

@ -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>

View File

@ -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}

View File

@ -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

View File

@ -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() {

View 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);
}

View File

@ -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);
}

View File

@ -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());
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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());

View File

@ -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)

View File

@ -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.

View File

@ -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));
}
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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() {

View File

@ -26,7 +26,6 @@
package jdk.nashorn.api.scripting;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
/**

View File

@ -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!

View File

@ -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));
}

View File

@ -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();

View 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

View File

@ -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:

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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

View File

@ -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";
}
}

View File

@ -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.

View File

@ -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());
}
}
}

View File

@ -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);

View 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()));
}
}
}

View File

@ -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++);

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View 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()) +")]";
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View 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;
}
}

View 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());
}
}

View File

@ -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;
}

View File

@ -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));
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}

View File

@ -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);

View File

@ -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.
*

View File

@ -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");
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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';
}
}

View File

@ -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");
}
};

View File

@ -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);
}
}

View File

@ -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.

View File

@ -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);
}
}

View File

@ -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

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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));
}
/**

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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('}');
}
}
}

View 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);
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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);
}
}

View File

@ -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);
}

View File

@ -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()).

View File

@ -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();

View File

@ -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"
};
}

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