package test.asm; /*** * 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>(); } }