blob: fdf080aabcfcac287189a9222e14c902bf155402 [file] [log] [blame]
/***
* Copyright (c) 2000-2011 INRIA, France Telecom
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of the copyright holders nor the names of its
* 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 THE COPYRIGHT OWNER OR CONTRIBUTORS 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.
*/
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.lang.invoke.MutableCallSite;
import java.util.HashMap;
/**
* There are 3 bootstrap methods: - one for the constant that are initialized
* once the first time the bootstrap method is called and after always reuse the
* same constant. It's almost equivalent to an LDC but here the constant are
* stored in boxed form e.g a java.lang.Integer containing 0 instead of an int
* containing 0.
*
* - one for the unary operation 'not' and 'asBoolean', here the semantics is
* hard coded, all primitive value are transformed as object by applying this
* operation: (v == 0)? false: true
*
* - one for the binary operation 'add', 'mul' and 'gt', here the semantics can
* be changed by adding more static methods in {@link BinaryOps}. This bootstrap
* method is a little more complex because it creates an inlining cache to avoid
* to recompute the binary method to call if the type of the two arguments
* doesn't change. Also, if the expression is used a lot and trigger the JIT, it
* will be able to inline the code of the operation directly at callsite.
*
* @author Remi Forax
*/
public class RT {
/**
* bootstrap method for constant
*/
public static CallSite cst(Lookup lookup, String name, MethodType type,
Object constant) {
return new ConstantCallSite(MethodHandles.constant(Object.class,
constant));
}
/**
* bootstrap method for unary operation 'asBoolean' and 'not'
*/
public static CallSite unary(Lookup lookup, String name, MethodType type) {
MethodHandle target;
if (name.equals("asBoolean")) {
target = MethodHandles.explicitCastArguments(
MethodHandles.identity(Object.class),
MethodType.methodType(boolean.class, Object.class));
} else { // "not"
target = MethodHandles.explicitCastArguments(NOT,
MethodType.methodType(Object.class, Object.class));
}
return new ConstantCallSite(target);
}
/**
* bootstrap method for binary operation 'add', 'mul' and 'gt'
*
* This bootstrap method doesn't install the target method handle directly,
* because we want to install an inlining cache and we can't create an
* inlining cache without knowing the class of the arguments. So this method
* first installs a method handle that will call
* {@link BinaryOpCallSite#fallback(Object, Object)} and the fallback method
* will be called with the arguments and thus can install the inlining
* cache. Also, the fallback has to be bound to a specific callsite to be
* able to change its target after the first call, this part is done in the
* constructor of {@link BinaryOpCallSite}.
*/
public static CallSite binary(Lookup lookup, String name, MethodType type) {
BinaryOpCallSite callSite = new BinaryOpCallSite(name, type);
callSite.setTarget(callSite.fallback);
return callSite;
}
/**
* Garbage class containing the method used to apply 'not' on a boolean. See
* {@link RT#unary(Lookup, String, MethodType)}
*/
public static class UnayOps {
public static Object not(boolean b) {
return !b;
}
}
private static final MethodHandle NOT;
static {
try {
NOT = MethodHandles.publicLookup().findStatic(UnayOps.class, "not",
MethodType.methodType(Object.class, boolean.class));
} catch (ReflectiveOperationException e) {
throw new LinkageError(e.getMessage(), e);
}
}
/**
* A specific callsite that will install an 'inlining cache'. Because we
* don't know until runtime which method handle to call, the lookup
* depending on the dynamic type of the argument will be done at runtime
* when the method {@link #fallback(Object, Object)} is called. To avoid to
* do this dynamic lookup at each call, the fallback install two guards in
* front of dispatch call that will check if the arguments class change or
* not. If the arguments class don't change, the previously computed method
* handle will be called again. Otherwise, a new method handle will be
* computed and two new guards will be installed.
*/
static class BinaryOpCallSite extends MutableCallSite {
private final String opName;
final MethodHandle fallback;
public BinaryOpCallSite(String opName, MethodType type) {
super(type);
this.opName = opName;
this.fallback = FALLBACK.bindTo(this);
}
Object fallback(Object v1, Object v2) throws Throwable {
// when you debug with this message
// don't forget that && and || are lazy !!
// System.out.println("fallback called with "+opName+'('+v1.getClass()+','+v2.getClass()+')');
Class<? extends Object> class1 = v1.getClass();
Class<? extends Object> class2 = v2.getClass();
MethodHandle op = lookupBinaryOp(opName, class1, class2);
// convert arguments
MethodType type = type();
MethodType opType = op.type();
if (opType.parameterType(0) == String.class) {
if (opType.parameterType(1) == String.class) {
op = MethodHandles.filterArguments(op, 0, TO_STRING,
TO_STRING);
} else {
op = MethodHandles.filterArguments(op, 0, TO_STRING);
op = MethodHandles.explicitCastArguments(op, type);
}
} else {
if (opType.parameterType(1) == String.class) {
op = MethodHandles.filterArguments(op, 1, TO_STRING);
}
op = MethodHandles.explicitCastArguments(op, type);
}
// prepare guard
MethodHandle guard = MethodHandles.guardWithTest(TEST1
.bindTo(class1), MethodHandles.guardWithTest(
TEST2.bindTo(class2), op, fallback), fallback);
// install the inlining cache
setTarget(guard);
return op.invokeWithArguments(v1, v2);
}
public static boolean test1(Class<?> v1Class, Object v1, Object v2) {
return v1.getClass() == v1Class;
}
public static boolean test2(Class<?> v2Class, Object v1, Object v2) {
return v2.getClass() == v2Class;
}
private static final MethodHandle TO_STRING;
private static final MethodHandle TEST1;
private static final MethodHandle TEST2;
private static final MethodHandle FALLBACK;
static {
Lookup lookup = MethodHandles.lookup();
try {
TO_STRING = lookup.findVirtual(Object.class, "toString",
MethodType.methodType(String.class));
MethodType testType = MethodType.methodType(boolean.class,
Class.class, Object.class, Object.class);
TEST1 = lookup.findStatic(BinaryOpCallSite.class, "test1",
testType);
TEST2 = lookup.findStatic(BinaryOpCallSite.class, "test2",
testType);
FALLBACK = lookup.findVirtual(BinaryOpCallSite.class,
"fallback", MethodType.genericMethodType(2));
} catch (ReflectiveOperationException e) {
throw new LinkageError(e.getMessage(), e);
}
}
}
/**
* Garbage class that contains the raw operations used for binary
* operations. All methods must be static returns an Object and takes the
* same type for the two parameter types.
*
* See {@link RT#lookupBinaryOp(String, Class, Class)} for more info.
*/
public static class BinaryOps {
public static Object add(int v1, int v2) {
return v1 + v2;
}
public static Object add(double v1, double v2) {
return v1 + v2;
}
public static Object add(String v1, String v2) {
return v1 + v2;
}
public static Object mul(int v1, int v2) {
return v1 * v2;
}
public static Object mul(double v1, double v2) {
return v1 * v2;
}
public static Object gt(int v1, int v2) {
return v1 > v2;
}
public static Object gt(double v1, double v2) {
return v1 > v2;
}
public static Object gt(String v1, String v2) {
return v1.compareTo(v2) > 0;
}
}
/**
* Select a most specific method among the ones defined in
* {@link RT.BinaryOps}. The algorithm first find the most specific subtype
* between class1 and class2. The order of the types is defined in
* {@link RT#RANK_MAP}: Boolean < Byte < Short < Character < Integer < Long
* < Float < Double < String then the algorithm lookup in
* {@link RT.BinaryOps} to find a method with the name opName taking as
* argument the primitive corresponding to the most specific subtype. If no
* such method exist, the algorithm retry but looking for a method with a
* more specific type (using the same order). The result of the lookup is
* cached in {@link RT#BINARY_CACHE} to avoid to avoid to do a lookup (a
* reflective call) on the same method twice.
*/
static MethodHandle lookupBinaryOp(String opName, Class<?> class1,
Class<?> class2) {
int rank = Math.max(RANK_MAP.get(class1), RANK_MAP.get(class2));
String mangledName = opName + rank;
MethodHandle mh = BINARY_CACHE.get(mangledName);
if (mh != null) {
return mh;
}
for (; rank < PRIMITIVE_ARRAY.length;) {
Class<?> primitive = PRIMITIVE_ARRAY[rank];
try {
mh = MethodHandles.publicLookup().findStatic(
BinaryOps.class,
opName,
MethodType.methodType(Object.class, primitive,
primitive));
} catch (NoSuchMethodException e) {
rank = rank + 1;
continue;
} catch (IllegalAccessException e) {
throw new LinkageError(e.getMessage(), e);
}
BINARY_CACHE.put(mangledName, mh);
return mh;
}
throw new LinkageError("unknown operation " + opName + " ("
+ class1.getName() + ',' + class2.getName() + ')');
}
private static final HashMap<Class<?>, Integer> RANK_MAP;
private static final Class<?>[] PRIMITIVE_ARRAY;
private static final HashMap<String, MethodHandle> BINARY_CACHE;
static {
Class<?>[] primitives = new Class<?>[] { boolean.class, byte.class,
short.class, char.class, int.class, long.class, float.class,
double.class, String.class };
Class<?>[] wrappers = new Class<?>[] { Boolean.class, Byte.class,
Short.class, Character.class, Integer.class, Long.class,
Float.class, Double.class, String.class };
HashMap<Class<?>, Integer> rankMap = new HashMap<Class<?>, Integer>();
for (int i = 0; i < wrappers.length; i++) {
rankMap.put(wrappers[i], i);
}
RANK_MAP = rankMap;
PRIMITIVE_ARRAY = primitives;
BINARY_CACHE = new HashMap<String, MethodHandle>();
}
}