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