001/* 002 * Copyright (c) 2012-2017 Institut National des Sciences Appliquées de Lyon (INSA-Lyon) 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 */ 009 010package org.eclipse.golo.runtime; 011 012import gololang.DynamicObject; 013 014import java.lang.invoke.*; 015import java.util.Arrays; 016import java.util.HashSet; 017import java.util.WeakHashMap; 018 019import static java.lang.invoke.MethodHandles.*; 020import static java.lang.invoke.MethodType.methodType; 021 022 023public final class MethodInvocationSupport { 024 025 private MethodInvocationSupport() { 026 throw new UnsupportedOperationException("Don't instantiate invokedynamic bootstrap class"); 027 } 028 029 /* 030 * This code is heavily inspired from the inline cache construction from 031 * Remi Forax's JSR292 cookbooks. 032 */ 033 034 static final class InlineCache extends MutableCallSite { 035 036 static final int MEGAMORPHIC_THRESHOLD = 5; 037 038 final MethodHandles.Lookup callerLookup; 039 final String name; 040 final boolean nullSafeGuarded; 041 final String[] argumentNames; 042 043 int depth = 0; 044 MethodHandle resetFallback; 045 WeakHashMap<Class<?>, MethodHandle> vtable; 046 047 InlineCache(Lookup callerLookup, String name, MethodType type, boolean nullSafeGuarded, String... argumentNames) { 048 super(type); 049 this.callerLookup = callerLookup; 050 this.name = name; 051 this.nullSafeGuarded = nullSafeGuarded; 052 this.argumentNames = argumentNames; 053 } 054 055 boolean isMegaMorphic() { 056 return depth > MEGAMORPHIC_THRESHOLD; 057 } 058 059 public MethodInvocation toMethodInvocation(Object[] args) { 060 return new MethodInvocation(name, type(), args, argumentNames); 061 } 062 } 063 064 private static final MethodHandle CLASS_GUARD; 065 private static final MethodHandle FALLBACK; 066 private static final MethodHandle RESET_FALLBACK; 067 private static final MethodHandle VTABLE_LOOKUP; 068 069 private static final MethodHandle OVERLOADED_GUARD_GENERIC; 070 private static final MethodHandle OVERLOADED_GUARD_1; 071 private static final MethodHandle OVERLOADED_GUARD_2; 072 private static final MethodHandle OVERLOADED_GUARD_3; 073 private static final MethodHandle OVERLOADED_GUARD_4; 074 075 076 private static final HashSet<String> DYNAMIC_OBJECT_RESERVED_METHOD_NAMES = new HashSet<String>() { 077 { 078 add("get"); 079 add("define"); 080 add("undefine"); 081 add("mixin"); 082 add("copy"); 083 add("freeze"); 084 add("properties"); 085 add("invoker"); 086 add("hasMethod"); 087 add("fallback"); 088 add("hasKind"); 089 add("sameKind"); 090 } 091 }; 092 093 static { 094 try { 095 MethodHandles.Lookup lookup = MethodHandles.lookup(); 096 097 CLASS_GUARD = lookup.findStatic( 098 MethodInvocationSupport.class, 099 "classGuard", 100 methodType(boolean.class, Class.class, Object.class)); 101 102 FALLBACK = lookup.findStatic( 103 MethodInvocationSupport.class, 104 "fallback", 105 methodType(Object.class, InlineCache.class, Object[].class)); 106 107 RESET_FALLBACK = lookup.findStatic( 108 MethodInvocationSupport.class, 109 "resetFallback", 110 methodType(Object.class, InlineCache.class, Object[].class)); 111 112 VTABLE_LOOKUP = lookup.findStatic( 113 MethodInvocationSupport.class, 114 "vtableLookup", 115 methodType(MethodHandle.class, InlineCache.class, Object[].class)); 116 117 OVERLOADED_GUARD_GENERIC = lookup.findStatic( 118 MethodInvocationSupport.class, 119 "overloadedGuard_generic", 120 methodType(boolean.class, Class[].class, Object[].class)); 121 122 OVERLOADED_GUARD_1 = lookup.findStatic( 123 MethodInvocationSupport.class, 124 "overloadedGuard_1", 125 methodType(boolean.class, Class.class, Class.class, Object.class, Object.class)); 126 127 OVERLOADED_GUARD_2 = lookup.findStatic( 128 MethodInvocationSupport.class, 129 "overloadedGuard_2", 130 methodType(boolean.class, Class.class, Class.class, Class.class, Object.class, Object.class, Object.class)); 131 132 OVERLOADED_GUARD_3 = lookup.findStatic( 133 MethodInvocationSupport.class, 134 "overloadedGuard_3", 135 methodType(boolean.class, Class.class, Class.class, Class.class, Class.class, Object.class, Object.class, Object.class, Object.class)); 136 137 OVERLOADED_GUARD_4 = lookup.findStatic( 138 MethodInvocationSupport.class, 139 "overloadedGuard_4", 140 methodType(boolean.class, Class.class, Class.class, Class.class, Class.class, Class.class, Object.class, Object.class, Object.class, Object.class, Object.class)); 141 142 } catch (NoSuchMethodException | IllegalAccessException e) { 143 throw new Error("Could not bootstrap the required method handles", e); 144 } 145 } 146 147 public static CallSite bootstrap(Lookup caller, String name, MethodType type, Object... bsmArgs) { 148 boolean nullSafeGuarded = ((int) bsmArgs[0]) == 1; 149 String[] argumentNames = new String[bsmArgs.length - 1]; 150 for (int i = 0; i < bsmArgs.length - 1; i++) { 151 argumentNames[i] = (String) bsmArgs[i + 1]; 152 } 153 InlineCache callSite = new InlineCache(caller, name, type, nullSafeGuarded, argumentNames); 154 MethodHandle fallbackHandle = FALLBACK 155 .bindTo(callSite) 156 .asCollector(Object[].class, type.parameterCount()) 157 .asType(type); 158 callSite.resetFallback = RESET_FALLBACK 159 .bindTo(callSite) 160 .asCollector(Object[].class, type.parameterCount()) 161 .asType(type); 162 callSite.setTarget(fallbackHandle); 163 return callSite; 164 } 165 166 public static boolean classGuard(Class<?> expected, Object receiver) { 167 return receiver.getClass() == expected; 168 } 169 170 public static boolean overloadedGuard_generic(Class<?>[] types, Object[] arguments) { 171 if (arguments[0].getClass() != types[0]) { 172 return false; 173 } 174 for (int i = 1; i < types.length; i++) { 175 if ((arguments[i] != null) && (arguments[i].getClass() != types[i])) { 176 return false; 177 } 178 } 179 return true; 180 } 181 182 public static boolean overloadedGuard_1(Class<?> t1, Class<?> t2, Object receiver, Object arg) { 183 return receiver.getClass() == t1 && 184 (arg == null || arg.getClass() == t2); 185 } 186 187 public static boolean overloadedGuard_2(Class<?> t1, Class<?> t2, Class<?> t3, Object receiver, Object arg1, Object arg2) { 188 return receiver.getClass() == t1 && 189 (arg1 == null || arg1.getClass() == t2) && 190 (arg2 == null || arg2.getClass() == t3); 191 } 192 193 public static boolean overloadedGuard_3(Class<?> t1, Class<?> t2, Class<?> t3, Class<?> t4, Object receiver, Object arg1, Object arg2, Object arg3) { 194 return receiver.getClass() == t1 && 195 (arg1 == null || arg1.getClass() == t2) && 196 (arg2 == null || arg2.getClass() == t3) && 197 (arg3 == null || arg3.getClass() == t4); 198 } 199 200 public static boolean overloadedGuard_4(Class<?> t1, Class<?> t2, Class<?> t3, Class<?> t4, Class<?> t5, Object receiver, Object arg1, Object arg2, Object arg3, Object arg4) { 201 return receiver.getClass() == t1 && 202 (arg1 == null || arg1.getClass() == t2) && 203 (arg2 == null || arg2.getClass() == t3) && 204 (arg3 == null || arg3.getClass() == t4) && 205 (arg4 == null || arg4.getClass() == t5); 206 } 207 208 public static MethodHandle vtableLookup(InlineCache inlineCache, Object[] args) { 209 Class<?> receiverClass = args[0].getClass(); 210 MethodHandle target = inlineCache.vtable.get(receiverClass); 211 if (target == null) { 212 target = lookupTarget(receiverClass, inlineCache, args); 213 inlineCache.vtable.put(receiverClass, target); 214 } 215 return target; 216 } 217 218 private static MethodHandle lookupTarget(Class<?> receiverClass, InlineCache inlineCache, Object[] args) { 219 MethodInvocation invocation = inlineCache.toMethodInvocation(args); 220 if (receiverClass.isArray()) { 221 return new ArrayMethodFinder(invocation, inlineCache.callerLookup).find(); 222 } 223 if (isCallOnDynamicObject(inlineCache, args[0])) { 224 DynamicObject dynamicObject = (DynamicObject) args[0]; 225 return dynamicObject.invoker(inlineCache.name, inlineCache.type()); 226 } else { 227 return findTarget(invocation, inlineCache.callerLookup, inlineCache); 228 } 229 } 230 231 public static Object resetFallback(InlineCache inlineCache, Object[] args) throws Throwable { 232 inlineCache.depth = 0; 233 return fallback(inlineCache, args); 234 } 235 236 public static Object fallback(InlineCache inlineCache, Object[] args) throws Throwable { 237 238 if (inlineCache.isMegaMorphic()) { 239 return installVTableDispatch(inlineCache, args); 240 } 241 242 if (args[0] == null) { 243 if (shouldReturnNull(inlineCache, args[0])) { 244 return null; 245 } else { 246 throw new NullPointerException("On method: " 247 + inlineCache.name + " " + inlineCache.type().dropParameterTypes(0, 1)); 248 } 249 } 250 251 Class<?> receiverClass = args[0].getClass(); 252 MethodHandle target = lookupTarget(receiverClass, inlineCache, args); 253 254 if (target == null) { 255 // TODO: extract method to look for a `fallback` method on the receiver 256 InlineCache fallbackCallSite = new InlineCache( 257 inlineCache.callerLookup, 258 "fallback", 259 methodType(Object.class, Object.class, Object.class, Object[].class), 260 false); 261 Object[] fallbackArgs = new Object[]{ 262 args[0], 263 inlineCache.name, 264 Arrays.copyOfRange(args, 1, args.length) 265 }; 266 target = lookupTarget(receiverClass, fallbackCallSite, fallbackArgs); 267 if (target != null) { 268 return fallback(fallbackCallSite, fallbackArgs); 269 } else { 270 throw new NoSuchMethodError(receiverClass + "::" + inlineCache.name); 271 } 272 } 273 274 MethodHandle guard = CLASS_GUARD.bindTo(receiverClass); 275 MethodHandle fallback = inlineCache.getTarget(); 276 MethodHandle root = guardWithTest(guard, target, fallback); 277 if (inlineCache.nullSafeGuarded) { 278 root = makeNullSafeGuarded(root); 279 } 280 inlineCache.setTarget(root); 281 inlineCache.depth = inlineCache.depth + 1; 282 return target.invokeWithArguments(args); 283 } 284 285 private static MethodHandle makeNullSafeGuarded(MethodHandle root) { 286 MethodHandle catchThenNull = dropArguments(constant(Object.class, null), 0, NullPointerException.class); 287 root = catchException(root, NullPointerException.class, catchThenNull); 288 return root; 289 } 290 291 private static boolean shouldReturnNull(InlineCache inlineCache, Object arg) { 292 return (arg == null) && inlineCache.nullSafeGuarded; 293 } 294 295 private static Object installVTableDispatch(InlineCache inlineCache, Object[] args) throws Throwable { 296 if (inlineCache.vtable == null) { 297 inlineCache.vtable = new WeakHashMap<>(); 298 } 299 MethodHandle lookup = VTABLE_LOOKUP 300 .bindTo(inlineCache) 301 .asCollector(Object[].class, args.length); 302 MethodHandle exactInvoker = exactInvoker(inlineCache.type()); 303 MethodHandle vtableTarget = foldArguments(exactInvoker, lookup); 304 if (inlineCache.nullSafeGuarded) { 305 vtableTarget = makeNullSafeGuarded(vtableTarget); 306 } 307 inlineCache.setTarget(vtableTarget); 308 if (shouldReturnNull(inlineCache, args[0])) { 309 return null; 310 } 311 return vtableTarget.invokeWithArguments(args); 312 } 313 314 private static boolean isCallOnDynamicObject(InlineCache inlineCache, Object arg) { 315 return (arg instanceof DynamicObject) 316 && !DYNAMIC_OBJECT_RESERVED_METHOD_NAMES.contains(inlineCache.name) 317 && (!"toString".equals(inlineCache.name) || ((DynamicObject) arg).hasMethod("toString")); 318 } 319 320 private static MethodHandle findTarget(MethodInvocation invocation, Lookup lookup, InlineCache inlineCache) { 321 MethodHandle target; 322 323 target = new PropertyMethodFinder(invocation, lookup).find(); 324 if (target != null) { 325 return target; 326 } 327 328 RegularMethodFinder regularMethodFinder = new RegularMethodFinder(invocation, lookup); 329 target = regularMethodFinder.find(); 330 if (target != null) { 331 if (regularMethodFinder.isOverloaded()) { 332 Object[] args = invocation.arguments(); 333 Class[] types = new Class[args.length]; 334 for (int i = 0; i < types.length; i++) { 335 types[i] = (args[i] == null) ? Object.class : args[i].getClass(); 336 } 337 MethodHandle guard; 338 switch (args.length) { 339 case 2: 340 guard = insertArguments(OVERLOADED_GUARD_1, 0, types[0], types[1]); 341 break; 342 case 3: 343 guard = insertArguments(OVERLOADED_GUARD_2, 0, types[0], types[1], types[2]); 344 break; 345 case 4: 346 guard = insertArguments(OVERLOADED_GUARD_3, 0, types[0], types[1], types[2], types[3]); 347 break; 348 case 5: 349 guard = insertArguments(OVERLOADED_GUARD_4, 0, types[0], types[1], types[2], types[3], types[4]); 350 break; 351 default: 352 guard = OVERLOADED_GUARD_GENERIC.bindTo(types).asCollector(Object[].class, types.length); 353 } 354 return guardWithTest(guard, target, inlineCache.resetFallback); 355 } 356 return target; 357 } 358 359 target = new AugmentationMethodFinder(invocation, lookup).find(); 360 if (target != null) { 361 return target; 362 } 363 return null; 364 } 365}