1 /* 2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package java.lang.invoke; 26 27 import sun.invoke.util.Wrapper; 28 29 import java.lang.invoke.AbstractConstantGroup.BSCIWithCache; 30 import java.util.Arrays; 31 32 import static java.lang.invoke.BootstrapCallInfo.makeBootstrapCallInfo; 33 import static java.lang.invoke.ConstantGroup.makeConstantGroup; 34 import static java.lang.invoke.MethodHandleNatives.*; 35 import static java.lang.invoke.MethodHandleStatics.TRACE_METHOD_LINKAGE; 36 import static java.lang.invoke.MethodHandles.Lookup; 37 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; 38 39 final class BootstrapMethodInvoker { 40 /** 41 * Factored code for invoking a bootstrap method for invokedynamic 42 * or a dynamic constant. 43 * @param resultType the expected return type (either CallSite or a constant type) 44 * @param bootstrapMethod the BSM to call 45 * @param name the method name or constant name 46 * @param type the method type or constant type 47 * @param info information passed up from the JVM, to derive static arguments 48 * @param callerClass the class containing the resolved method call or constant load 49 * @param <T> the expected return type 50 * @return the expected value, either a CallSite or a constant value 51 */ 52 static <T> T invoke(Class<T> resultType, 53 MethodHandle bootstrapMethod, 54 // Callee information: 55 String name, Object type, 56 // Extra arguments for BSM, if any: 57 Object info, 58 // Caller information: 59 Class<?> callerClass) { 60 MethodHandles.Lookup caller = IMPL_LOOKUP.in(callerClass); 61 Object result; 62 boolean pullMode = isPullModeBSM(bootstrapMethod); // default value is false 63 boolean vmIsPushing = !staticArgumentsPulled(info); // default value is true 64 MethodHandle pullModeBSM; 65 // match the VM with the BSM 66 if (vmIsPushing) { 67 // VM is pushing arguments at us 68 pullModeBSM = null; 69 if (pullMode) { 70 bootstrapMethod = Adapters.pushMePullYou(bootstrapMethod, true); 71 } 72 } else { 73 // VM wants us to pull args from it 74 pullModeBSM = pullMode ? bootstrapMethod : 75 Adapters.pushMePullYou(bootstrapMethod, false); 76 bootstrapMethod = null; 77 } 78 try { 79 info = maybeReBox(info); 80 if (info == null) { 81 // VM is allowed to pass up a null meaning no BSM args 82 result = bootstrapMethod.invoke(caller, name, type); 83 } 84 else if (!info.getClass().isArray()) { 85 // VM is allowed to pass up a single BSM arg directly 86 result = bootstrapMethod.invoke(caller, name, type, info); 87 } 88 else if (info.getClass() == int[].class) { 89 // VM is allowed to pass up a pair {argc, index} 90 // referring to 'argc' BSM args at some place 'index' 91 // in the guts of the VM (associated with callerClass). 92 // The format of this index pair is private to the 93 // handshake between the VM and this class only. 94 // This supports "pulling" of arguments. 95 // The VM is allowed to do this for any reason. 96 // The code in this method makes up for any mismatches. 97 BootstrapCallInfo<Object> bsci 98 = new VM_BSCI<>(bootstrapMethod, name, type, caller, (int[])info); 99 // Pull-mode API is (Lookup, BootstrapCallInfo) -> Object 100 result = pullModeBSM.invoke(caller, bsci); 101 } 102 else { 103 // VM is allowed to pass up a full array of resolved BSM args 104 Object[] argv = (Object[]) info; 105 maybeReBoxElements(argv); 106 switch (argv.length) { 107 case 0: 108 result = bootstrapMethod.invoke(caller, name, type); 109 break; 110 case 1: 111 result = bootstrapMethod.invoke(caller, name, type, 112 argv[0]); 113 break; 114 case 2: 115 result = bootstrapMethod.invoke(caller, name, type, 116 argv[0], argv[1]); 117 break; 118 case 3: 119 result = bootstrapMethod.invoke(caller, name, type, 120 argv[0], argv[1], argv[2]); 121 break; 122 case 4: 123 result = bootstrapMethod.invoke(caller, name, type, 124 argv[0], argv[1], argv[2], argv[3]); 125 break; 126 case 5: 127 result = bootstrapMethod.invoke(caller, name, type, 128 argv[0], argv[1], argv[2], argv[3], argv[4]); 129 break; 130 case 6: 131 result = bootstrapMethod.invoke(caller, name, type, 132 argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); 133 break; 134 default: 135 final int NON_SPREAD_ARG_COUNT = 3; // (caller, name, type) 136 final int MAX_SAFE_SIZE = MethodType.MAX_MH_ARITY / 2 - NON_SPREAD_ARG_COUNT; 137 if (argv.length >= MAX_SAFE_SIZE) { 138 // to be on the safe side, use invokeWithArguments which handles jumbo lists 139 Object[] newargv = new Object[NON_SPREAD_ARG_COUNT + argv.length]; 140 newargv[0] = caller; 141 newargv[1] = name; 142 newargv[2] = type; 143 System.arraycopy(argv, 0, newargv, NON_SPREAD_ARG_COUNT, argv.length); 144 result = bootstrapMethod.invokeWithArguments(newargv); 145 break; 146 } 147 MethodType invocationType = MethodType.genericMethodType(NON_SPREAD_ARG_COUNT + argv.length); 148 MethodHandle typedBSM = bootstrapMethod.asType(invocationType); 149 MethodHandle spreader = invocationType.invokers().spreadInvoker(NON_SPREAD_ARG_COUNT); 150 result = spreader.invokeExact(typedBSM, (Object) caller, (Object) name, type, argv); 151 } 152 } 153 if (resultType.isPrimitive()) { 154 // Non-reference conversions are more than just plain casts. 155 // By pushing the value through a funnel of the form (T x)->x, 156 // the boxed result can be widened as needed. See MH::asType. 157 MethodHandle funnel = MethodHandles.identity(resultType); 158 result = funnel.invoke(result); 159 // Now it is the wrapper type for resultType. 160 resultType = Wrapper.asWrapperType(resultType); 161 } 162 return resultType.cast(result); 163 } 164 catch (Error e) { 165 // Pass through an Error, including BootstrapMethodError, any other 166 // form of linkage error, such as IllegalAccessError if the bootstrap 167 // method is inaccessible, or say ThreadDeath/OutOfMemoryError 168 // See the "Linking Exceptions" section for the invokedynamic 169 // instruction in JVMS 6.5. 170 throw e; 171 } 172 catch (Throwable ex) { 173 // Wrap anything else in BootstrapMethodError 174 throw new BootstrapMethodError("bootstrap method initialization exception", ex); 175 } 176 } 177 178 /** The JVM produces java.lang.Integer values to box 179 * CONSTANT_Integer boxes but does not intern them. 180 * Let's intern them. This is slightly wrong for 181 * a {@code CONSTANT_Dynamic} which produces an 182 * un-interned integer (e.g., {@code new Integer(0)}). 183 */ 184 private static Object maybeReBox(Object x) { 185 if (x instanceof Integer) { 186 int xi = (int) x; 187 if (xi == (byte) xi) 188 x = xi; // must rebox; see JLS 5.1.7 189 } 190 return x; 191 } 192 193 private static void maybeReBoxElements(Object[] xa) { 194 for (int i = 0; i < xa.length; i++) { 195 xa[i] = maybeReBox(xa[i]); 196 } 197 } 198 199 /** Canonical VM-aware implementation of BootstrapCallInfo. 200 * Knows how to dig into the JVM for lazily resolved (pull-mode) constants. 201 */ 202 private static final class VM_BSCI<T> extends BSCIWithCache<T> { 203 private final int[] indexInfo; 204 private final Class<?> caller; // for index resolution only 205 206 VM_BSCI(MethodHandle bsm, String name, T type, 207 Lookup lookup, int[] indexInfo) { 208 super(bsm, name, type, indexInfo[0]); 209 if (!lookup.hasPrivateAccess()) //D.I.D. 210 throw new AssertionError("bad Lookup object"); 211 this.caller = lookup.lookupClass(); 212 this.indexInfo = indexInfo; 213 // scoop up all the easy stuff right away: 214 prefetchIntoCache(0, size()); 215 } 216 217 @Override Object fillCache(int i) { 218 Object[] buf = { null }; 219 copyConstants(i, i+1, buf, 0); 220 Object res = wrapNull(buf[0]); 221 cache[i] = res; 222 int next = i + 1; 223 if (next < cache.length && cache[next] == null) 224 maybePrefetchIntoCache(next, false); // try to prefetch 225 return res; 226 } 227 228 @Override public int copyConstants(int start, int end, 229 Object[] buf, int pos) { 230 int i = start, bufi = pos; 231 while (i < end) { 232 Object x = cache[i]; 233 if (x == null) break; 234 buf[bufi++] = unwrapNull(x); 235 i++; 236 } 237 // give up at first null and grab the rest in one big block 238 if (i >= end) return i; 239 Object[] temp = new Object[end - i]; 240 if (TRACE_METHOD_LINKAGE) 241 System.out.println("resolving more BSM arguments: "+ 242 Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, end)); 243 copyOutBootstrapArguments(caller, indexInfo, 244 i, end, temp, 0, 245 true, null); 246 for (Object x : temp) { 247 x = maybeReBox(x); 248 buf[bufi++] = x; 249 cache[i++] = wrapNull(x); 250 } 251 if (end < cache.length && cache[end] == null) 252 maybePrefetchIntoCache(end, true); // try to prefetch 253 return i; 254 } 255 256 private static final int MIN_PF = 4; 257 private void maybePrefetchIntoCache(int i, boolean bulk) { 258 int len = cache.length; 259 assert(0 <= i && i <= len); 260 int pfLimit = i; 261 if (bulk) pfLimit += i; // exponential prefetch expansion 262 // try to prefetch at least MIN_PF elements 263 if (pfLimit < i + MIN_PF) pfLimit = i + MIN_PF; 264 if (pfLimit > len || pfLimit < 0) pfLimit = len; 265 // stop prefetching where cache is more full than empty 266 int empty = 0, nonEmpty = 0, lastEmpty = i; 267 for (int j = i; j < pfLimit; j++) { 268 if (cache[j] == null) { 269 empty++; 270 lastEmpty = j; 271 } else { 272 nonEmpty++; 273 if (nonEmpty > empty) { 274 pfLimit = lastEmpty + 1; 275 break; 276 } 277 if (pfLimit < len) pfLimit++; 278 } 279 } 280 if (bulk && empty < MIN_PF && pfLimit < len) 281 return; // not worth the effort 282 prefetchIntoCache(i, pfLimit); 283 } 284 285 private void prefetchIntoCache(int i, int pfLimit) { 286 if (pfLimit <= i) return; // corner case 287 Object[] temp = new Object[pfLimit - i]; 288 if (TRACE_METHOD_LINKAGE) 289 System.out.println("prefetching BSM arguments: "+ 290 Arrays.asList(caller.getSimpleName(), Arrays.toString(indexInfo), i, pfLimit)); 291 copyOutBootstrapArguments(caller, indexInfo, 292 i, pfLimit, temp, 0, 293 false, NOT_PRESENT); 294 for (Object x : temp) { 295 if (x != NOT_PRESENT && cache[i] == null) { 296 cache[i] = wrapNull(maybeReBox(x)); 297 } 298 i++; 299 } 300 } 301 } 302 303 /*non-public*/ static final 304 class Adapters { 305 // skeleton for push-mode BSM which wraps a pull-mode BSM: 306 static Object pushToBootstrapMethod(MethodHandle pullModeBSM, 307 MethodHandles.Lookup lookup, String name, Object type, 308 Object... arguments) throws Throwable { 309 ConstantGroup cons = makeConstantGroup(Arrays.asList(arguments)); 310 BootstrapCallInfo<?> bsci = makeBootstrapCallInfo(pullModeBSM, name, type, cons); 311 if (TRACE_METHOD_LINKAGE) 312 System.out.println("pull-mode BSM gets pushed arguments from fake BSCI"); 313 return pullModeBSM.invoke(lookup, bsci); 314 } 315 316 // skeleton for pull-mode BSM which wraps a push-mode BSM: 317 static Object pullFromBootstrapMethod(MethodHandle pushModeBSM, 318 MethodHandles.Lookup lookup, BootstrapCallInfo<?> bsci) 319 throws Throwable { 320 int argc = bsci.size(); 321 Object arguments[] = new Object[3 + argc]; 322 arguments[0] = lookup; 323 arguments[1] = bsci.invocationName(); 324 arguments[2] = bsci.invocationType(); 325 bsci.copyConstants(0, argc, arguments, 3); 326 if (TRACE_METHOD_LINKAGE) 327 System.out.println("pulled arguments from VM for push-mode BSM"); 328 return pushModeBSM.invokeWithArguments(arguments); 329 } 330 static final MethodHandle MH_pushToBootstrapMethod; 331 static final MethodHandle MH_pullFromBootstrapMethod; 332 static { 333 final Class<?> THIS_CLASS = Adapters.class; 334 try { 335 MH_pushToBootstrapMethod = IMPL_LOOKUP 336 .findStatic(THIS_CLASS, "pushToBootstrapMethod", 337 MethodType.methodType(Object.class, MethodHandle.class, 338 Lookup.class, String.class, Object.class, Object[].class)); 339 MH_pullFromBootstrapMethod = IMPL_LOOKUP 340 .findStatic(THIS_CLASS, "pullFromBootstrapMethod", 341 MethodType.methodType(Object.class, MethodHandle.class, 342 Lookup.class, BootstrapCallInfo.class)); 343 } catch (Throwable ex) { 344 throw new InternalError(ex); 345 } 346 } 347 348 /** Given a push-mode BSM (taking one argument) convert it to a 349 * pull-mode BSM (taking N pre-resolved arguments). 350 * This method is used when, in fact, the JVM is passing up 351 * pre-resolved arguments, but the BSM is expecting lazy stuff. 352 * Or, when goToPushMode is true, do the reverse transform. 353 * (The two transforms are exactly inverse.) 354 */ 355 static MethodHandle pushMePullYou(MethodHandle bsm, boolean goToPushMode) { 356 if (TRACE_METHOD_LINKAGE) 357 System.out.println("converting BSM to "+(goToPushMode ? "push mode" : "pull mode")); 358 assert(isPullModeBSM(bsm) == goToPushMode); //there must be a change 359 if (goToPushMode) { 360 return Adapters.MH_pushToBootstrapMethod.bindTo(bsm).withVarargs(true); 361 } else { 362 return Adapters.MH_pullFromBootstrapMethod.bindTo(bsm).withVarargs(false); 363 } 364 } 365 } 366 }