1 /* 2 * Copyright (c) 2012, 2024, 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 26 package java.lang.invoke; 27 28 import sun.invoke.util.VerifyAccess; 29 import sun.invoke.util.VerifyType; 30 import sun.invoke.util.Wrapper; 31 32 import java.lang.classfile.*; 33 import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; 34 import java.lang.classfile.attribute.SourceFileAttribute; 35 import java.lang.classfile.constantpool.ClassEntry; 36 import java.lang.classfile.constantpool.ConstantPoolBuilder; 37 import java.lang.classfile.constantpool.FieldRefEntry; 38 import java.lang.classfile.instruction.SwitchCase; 39 import java.lang.constant.ClassDesc; 40 import java.lang.constant.ConstantDesc; 41 import java.lang.constant.MethodTypeDesc; 42 import java.lang.invoke.LambdaForm.BasicType; 43 import java.lang.invoke.LambdaForm.Name; 44 import java.lang.invoke.LambdaForm.NamedFunction; 45 import java.lang.reflect.Modifier; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.HashMap; 49 import java.util.List; 50 import java.util.function.Consumer; 51 import java.util.stream.Stream; 52 import jdk.internal.constant.MethodTypeDescImpl; 53 import jdk.internal.constant.ReferenceClassDescImpl; 54 55 import static java.lang.classfile.ClassFile.*; 56 import static java.lang.constant.ConstantDescs.*; 57 import static java.lang.invoke.LambdaForm.*; 58 import static java.lang.invoke.LambdaForm.BasicType.*; 59 import static java.lang.invoke.MethodHandleNatives.Constants.*; 60 import static java.lang.invoke.MethodHandleStatics.*; 61 import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; 62 import static jdk.internal.constant.ConstantUtils.concat; 63 import static jdk.internal.constant.ConstantUtils.validateInternalClassName; 64 65 /** 66 * Code generation backend for LambdaForm. 67 * <p> 68 * @author John Rose, JSR 292 EG 69 */ 70 class InvokerBytecodeGenerator { 71 /** Define class names for convenience. */ 72 private static final ClassDesc CD_CasesHolder = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$CasesHolder;"); 73 private static final ClassDesc CD_DirectMethodHandle = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/DirectMethodHandle;"); 74 private static final ClassDesc CD_MemberName = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MemberName;"); 75 private static final ClassDesc CD_MethodHandleImpl = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl;"); 76 private static final ClassDesc CD_LambdaForm = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm;"); 77 private static final ClassDesc CD_LambdaForm_Name = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Name;"); 78 private static final ClassDesc CD_LoopClauses = ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/MethodHandleImpl$LoopClauses;"); 79 private static final ClassDesc CD_Object_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/Object;"); 80 private static final ClassDesc CD_MethodHandle_array = ReferenceClassDescImpl.ofValidated("[Ljava/lang/invoke/MethodHandle;"); 81 private static final ClassDesc CD_MethodHandle_array2 = ReferenceClassDescImpl.ofValidated("[[Ljava/lang/invoke/MethodHandle;"); 82 83 private static final MethodTypeDesc MTD_boolean_Object = MethodTypeDescImpl.ofValidated(CD_boolean, CD_Object); 84 private static final MethodTypeDesc MTD_Object_int = MethodTypeDescImpl.ofValidated(CD_Object, CD_int); 85 private static final MethodTypeDesc MTD_Object_Class = MethodTypeDescImpl.ofValidated(CD_Object, CD_Class); 86 private static final MethodTypeDesc MTD_Object_Object = MethodTypeDescImpl.ofValidated(CD_Object, CD_Object); 87 88 private static final String CLASS_PREFIX = "java/lang/invoke/LambdaForm$"; 89 private static final String SOURCE_PREFIX = "LambdaForm$"; 90 91 /** Name of its super class*/ 92 static final ClassDesc INVOKER_SUPER_DESC = CD_Object; 93 94 /** Name of new class */ 95 private final String name; 96 private final String className; 97 private final ConstantPoolBuilder pool = ConstantPoolBuilder.of(); 98 private final ClassEntry classEntry; 99 100 private final LambdaForm lambdaForm; 101 private final String invokerName; 102 private final MethodType invokerType; 103 104 /** Info about local variables in compiled lambda form */ 105 private int[] localsMap; // index 106 private Class<?>[] localClasses; // type 107 108 private final List<ClassData> classData = new ArrayList<>(); 109 110 private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory(); 111 private static final Class<?> HOST_CLASS = LambdaForm.class; 112 private static final MethodHandles.Lookup LOOKUP = lookup(); 113 114 private static MethodHandles.Lookup lookup() { 115 try { 116 return MethodHandles.privateLookupIn(HOST_CLASS, IMPL_LOOKUP); 117 } catch (IllegalAccessException e) { 118 throw newInternalError(e); 119 } 120 } 121 122 /** Main constructor; other constructors delegate to this one. */ 123 private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize, 124 String name, String invokerName, MethodType invokerType) { 125 int p = invokerName.indexOf('.'); 126 if (p > -1) { 127 name = invokerName.substring(0, p); 128 invokerName = invokerName.substring(p + 1); 129 } 130 if (dumper().isEnabled()) { 131 name = makeDumpableClassName(name); 132 } 133 this.name = name; 134 this.className = CLASS_PREFIX.concat(name); 135 validateInternalClassName(name); 136 this.classEntry = pool.classEntry(ReferenceClassDescImpl.ofValidated(concat("L", className, ";"))); 137 this.lambdaForm = lambdaForm; 138 this.invokerName = invokerName; 139 this.invokerType = invokerType; 140 this.localsMap = new int[localsMapSize+1]; // last entry of localsMap is count of allocated local slots 141 this.localClasses = new Class<?>[localsMapSize+1]; 142 } 143 144 /** For generating LambdaForm interpreter entry points. */ 145 private InvokerBytecodeGenerator(String name, String invokerName, MethodType invokerType) { 146 this(null, invokerType.parameterCount(), 147 name, invokerName, invokerType); 148 MethodType mt = invokerType.erase(); 149 // Create an array to map name indexes to locals indexes. 150 localsMap[0] = 0; // localsMap has at least one element 151 for (int i = 1, index = 0; i < localsMap.length; i++) { 152 Class<?> cl = mt.parameterType(i - 1); 153 index += (cl == long.class || cl == double.class) ? 2 : 1; 154 localsMap[i] = index; 155 } 156 } 157 158 /** For generating customized code for a single LambdaForm. */ 159 private InvokerBytecodeGenerator(String name, LambdaForm form, MethodType invokerType) { 160 this(name, form.lambdaName(), form, invokerType); 161 } 162 163 /** For generating customized code for a single LambdaForm. */ 164 InvokerBytecodeGenerator(String name, String invokerName, 165 LambdaForm form, MethodType invokerType) { 166 this(form, form.names.length, 167 name, invokerName, invokerType); 168 // Create an array to map name indexes to locals indexes. 169 Name[] names = form.names; 170 for (int i = 0, index = 0; i < localsMap.length; i++) { 171 localsMap[i] = index; 172 if (i < names.length) { 173 BasicType type = names[i].type(); 174 index += type.basicTypeSlots(); 175 } 176 } 177 } 178 179 /** instance counters for dumped classes */ 180 private static final HashMap<String,Integer> DUMP_CLASS_FILES_COUNTERS = 181 dumper().isEnabled() ? new HashMap<>(): null; 182 183 private static String makeDumpableClassName(String className) { 184 Integer ctr; 185 synchronized (DUMP_CLASS_FILES_COUNTERS) { 186 ctr = DUMP_CLASS_FILES_COUNTERS.get(className); 187 if (ctr == null) ctr = 0; 188 DUMP_CLASS_FILES_COUNTERS.put(className, ctr+1); 189 } 190 191 var buf = new StringBuilder(className.length() + 3).append(className); 192 int ctrVal = ctr; 193 if (ctrVal < 10) { 194 buf.repeat('0', 2); 195 } else if (ctrVal < 100) { 196 buf.append('0'); 197 } 198 buf.append(ctrVal); 199 return buf.toString(); 200 } 201 202 record ClassData(FieldRefEntry field, Object value) {} 203 204 FieldRefEntry classData(ClassFileBuilder<?, ?> cfb, Object arg, ClassDesc desc) { 205 // unique static variable name 206 String name; 207 List<ClassData> classData = this.classData; 208 if (dumper().isEnabled()) { 209 Class<?> c = arg.getClass(); 210 while (c.isArray()) { 211 c = c.getComponentType(); 212 } 213 name = "_DATA_" + c.getSimpleName() + "_" + classData.size(); 214 } else { 215 name = "_D_" + classData.size(); 216 } 217 var field = pool.fieldRefEntry(classEntry, pool.nameAndTypeEntry(name, desc)); 218 classData.add(new ClassData(field, arg)); 219 return field; 220 } 221 222 /** 223 * Extract the MemberName of a newly-defined method. 224 */ 225 private MemberName loadMethod(byte[] classFile) { 226 Class<?> invokerClass = LOOKUP.makeHiddenClassDefiner(className, classFile, dumper()) 227 .defineClass(true, classDataValues()); 228 return resolveInvokerMember(invokerClass, invokerName, invokerType); 229 } 230 231 private static MemberName resolveInvokerMember(Class<?> invokerClass, String name, MethodType type) { 232 MemberName member = new MemberName(invokerClass, name, type, REF_invokeStatic); 233 try { 234 member = MEMBERNAME_FACTORY.resolveOrFail(REF_invokeStatic, member, 235 HOST_CLASS, LM_TRUSTED, 236 ReflectiveOperationException.class); 237 } catch (ReflectiveOperationException e) { 238 throw newInternalError(e); 239 } 240 return member; 241 } 242 243 /** 244 * Set up class file generation. 245 */ 246 private byte[] classFileSetup(Consumer<? super ClassBuilder> config) { 247 try { 248 return ClassFile.of().build(classEntry, pool, new Consumer<>() { 249 @Override 250 public void accept(ClassBuilder clb) { 251 clb.withFlags(ACC_ABSTRACT | ACC_SUPER) 252 .withSuperclass(INVOKER_SUPER_DESC) 253 .with(SourceFileAttribute.of(clb.constantPool().utf8Entry(SOURCE_PREFIX + name))); 254 config.accept(clb); 255 } 256 }); 257 } catch (RuntimeException e) { 258 throw new BytecodeGenerationException(e); 259 } 260 } 261 262 private void methodSetup(ClassBuilder clb, Consumer<? super MethodBuilder> config) { 263 var invokerDesc = methodDesc(invokerType); 264 clb.withMethod(invokerName, invokerDesc, ACC_STATIC, config); 265 } 266 267 /** 268 * Returns the class data object that will be passed to `Lookup.defineHiddenClassWithClassData`. 269 * The classData is loaded in the <clinit> method of the generated class. 270 * If the class data contains only one single object, this method returns that single object. 271 * If the class data contains more than one objects, this method returns a List. 272 * 273 * This method returns null if no class data. 274 */ 275 private Object classDataValues() { 276 final List<ClassData> cd = classData; 277 int size = cd.size(); 278 return switch (size) { 279 case 0 -> null; // special case (classData is not used by <clinit>) 280 case 1 -> cd.get(0).value; // special case (single object) 281 case 2 -> List.of(cd.get(0).value, cd.get(1).value); 282 case 3 -> List.of(cd.get(0).value, cd.get(1).value, cd.get(2).value); 283 case 4 -> List.of(cd.get(0).value, cd.get(1).value, cd.get(2).value, cd.get(3).value); 284 default -> { 285 Object[] data = new Object[size]; 286 for (int i = 0; i < size; i++) { 287 data[i] = classData.get(i).value; 288 } 289 yield List.of(data); 290 } 291 }; 292 } 293 294 /* 295 * <clinit> to initialize the static final fields with the live class data 296 * LambdaForms can't use condy due to bootstrapping issue. 297 */ 298 static void clinit(ClassBuilder clb, ClassEntry classEntry, List<ClassData> classData) { 299 if (classData.isEmpty()) 300 return; 301 302 clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, new Consumer<>() { 303 @Override 304 public void accept(CodeBuilder cob) { 305 cob.ldc(classEntry) 306 .invokestatic(CD_MethodHandles, "classData", MTD_Object_Class); 307 int size = classData.size(); 308 if (size == 1) { 309 var field = classData.getFirst().field; 310 // add the static field 311 clb.withField(field.name(), field.type(), ACC_STATIC | ACC_FINAL); 312 313 var ft = field.typeSymbol(); 314 if (ft != CD_Object) 315 cob.checkcast(ft); 316 cob.putstatic(field); 317 } else { 318 cob.checkcast(CD_List) 319 .astore(0); 320 int index = 0; 321 var listGet = cob.constantPool().interfaceMethodRefEntry(CD_List, "get", MTD_Object_int); 322 for (int i = 0; i < size; i++) { 323 var field = classData.get(i).field; 324 // add the static field 325 clb.withField(field.name(), field.type(), ACC_STATIC | ACC_FINAL); 326 // initialize the static field 327 cob.aload(0) 328 .loadConstant(index++) 329 .invokeinterface(listGet); 330 var ft = field.typeSymbol(); 331 if (ft != CD_Object) 332 cob.checkcast(ft); 333 cob.putstatic(field); 334 } 335 } 336 cob.return_(); 337 } 338 }); 339 } 340 341 private void emitLoadInsn(CodeBuilder cob, TypeKind type, int index) { 342 cob.loadLocal(type, localsMap[index]); 343 } 344 345 private void emitStoreInsn(CodeBuilder cob, TypeKind type, int index) { 346 cob.storeLocal(type, localsMap[index]); 347 } 348 349 /** 350 * Emit a boxing call. 351 */ 352 private void emitBoxing(CodeBuilder cob, TypeKind tk) { 353 TypeConvertingMethodAdapter.box(cob, tk); 354 } 355 356 /** 357 * Emit an unboxing call (plus preceding checkcast). 358 */ 359 private void emitUnboxing(CodeBuilder cob, TypeKind target) { 360 switch (target) { 361 case BOOLEAN -> emitReferenceCast(cob, Boolean.class, null); 362 case CHAR -> emitReferenceCast(cob, Character.class, null); 363 case BYTE, DOUBLE, FLOAT, INT, LONG, SHORT -> 364 emitReferenceCast(cob, Number.class, null); 365 default -> {} 366 } 367 TypeConvertingMethodAdapter.unbox(cob, target); 368 } 369 370 /** 371 * Emit an implicit conversion for an argument which must be of the given pclass. 372 * This is usually a no-op, except when pclass is a subword type or a reference other than Object or an interface. 373 * 374 * @param ptype type of value present on stack 375 * @param pclass type of value required on stack 376 * @param arg compile-time representation of value on stack (Node, constant) or null if none 377 */ 378 private void emitImplicitConversion(CodeBuilder cob, BasicType ptype, Class<?> pclass, Object arg) { 379 assert(basicType(pclass) == ptype); // boxing/unboxing handled by caller 380 if (pclass == ptype.basicTypeClass() && ptype != L_TYPE) 381 return; // nothing to do 382 switch (ptype) { 383 case L_TYPE: 384 if (VerifyType.isNullConversion(Object.class, pclass, false)) { 385 if (PROFILE_LEVEL > 0) 386 emitReferenceCast(cob, Object.class, arg); 387 return; 388 } 389 emitReferenceCast(cob, pclass, arg); 390 return; 391 case I_TYPE: 392 if (!VerifyType.isNullConversion(int.class, pclass, false)) 393 emitPrimCast(cob, ptype.basicTypeKind(), TypeKind.from(pclass)); 394 return; 395 } 396 throw newInternalError("bad implicit conversion: tc="+ptype+": "+pclass); 397 } 398 399 /** Update localClasses type map. Return true if the information is already present. */ 400 private boolean assertStaticType(Class<?> cls, Name n) { 401 int local = n.index(); 402 Class<?> aclass = localClasses[local]; 403 if (aclass != null && (aclass == cls || cls.isAssignableFrom(aclass))) { 404 return true; // type info is already present 405 } else if (aclass == null || aclass.isAssignableFrom(cls)) { 406 localClasses[local] = cls; // type info can be improved 407 } 408 return false; 409 } 410 411 private void emitReferenceCast(CodeBuilder cob, Class<?> cls, Object arg) { 412 Name writeBack = null; // local to write back result 413 if (arg instanceof Name n) { 414 if (lambdaForm.useCount(n) > 1) { 415 // This guy gets used more than once. 416 writeBack = n; 417 if (assertStaticType(cls, n)) { 418 return; // this cast was already performed 419 } 420 } 421 } 422 if (isStaticallyNameable(cls)) { 423 ClassDesc sig = classDesc(cls); 424 cob.checkcast(sig); 425 } else { 426 cob.getstatic(classData(cob, cls, CD_Class)) 427 .swap() 428 .invokevirtual(CD_Class, "cast", MTD_Object_Object); 429 if (Object[].class.isAssignableFrom(cls)) 430 cob.checkcast(CD_Object_array); 431 else if (PROFILE_LEVEL > 0) 432 cob.checkcast(CD_Object); 433 } 434 if (writeBack != null) { 435 cob.dup(); 436 emitStoreInsn(cob, TypeKind.REFERENCE, writeBack.index()); 437 } 438 } 439 440 private static MemberName resolveFrom(String name, MethodType type, Class<?> holder) { 441 assert(!UNSAFE.shouldBeInitialized(holder)) : holder + "not initialized"; 442 MemberName member = new MemberName(holder, name, type, REF_invokeStatic); 443 MemberName resolvedMember = MemberName.getFactory().resolveOrNull(REF_invokeStatic, member, holder, LM_TRUSTED); 444 traceLambdaForm(name, type, holder, resolvedMember); 445 return resolvedMember; 446 } 447 448 private static MemberName lookupPregenerated(LambdaForm form, MethodType invokerType) { 449 if (form.customized != null) { 450 // No pre-generated version for customized LF 451 return null; 452 } 453 String name = form.kind.methodName; 454 switch (form.kind) { 455 case BOUND_REINVOKER: { 456 name = name + "_" + BoundMethodHandle.speciesDataFor(form).key(); 457 return resolveFrom(name, invokerType, DelegatingMethodHandle.Holder.class); 458 } 459 case DELEGATE: return resolveFrom(name, invokerType, DelegatingMethodHandle.Holder.class); 460 case ZERO: // fall-through 461 case IDENTITY: { 462 name = name + "_" + form.returnType().basicTypeChar(); 463 return resolveFrom(name, invokerType, LambdaForm.Holder.class); 464 } 465 case EXACT_INVOKER: // fall-through 466 case EXACT_LINKER: // fall-through 467 case LINK_TO_CALL_SITE: // fall-through 468 case LINK_TO_TARGET_METHOD: // fall-through 469 case GENERIC_INVOKER: // fall-through 470 case GENERIC_LINKER: return resolveFrom(name, invokerType, Invokers.Holder.class); 471 case GET_REFERENCE: // fall-through 472 case GET_BOOLEAN: // fall-through 473 case GET_BYTE: // fall-through 474 case GET_CHAR: // fall-through 475 case GET_SHORT: // fall-through 476 case GET_INT: // fall-through 477 case GET_LONG: // fall-through 478 case GET_FLOAT: // fall-through 479 case GET_DOUBLE: // fall-through 480 case PUT_REFERENCE: // fall-through 481 case PUT_BOOLEAN: // fall-through 482 case PUT_BYTE: // fall-through 483 case PUT_CHAR: // fall-through 484 case PUT_SHORT: // fall-through 485 case PUT_INT: // fall-through 486 case PUT_LONG: // fall-through 487 case PUT_FLOAT: // fall-through 488 case PUT_DOUBLE: // fall-through 489 case DIRECT_NEW_INVOKE_SPECIAL: // fall-through 490 case DIRECT_INVOKE_INTERFACE: // fall-through 491 case DIRECT_INVOKE_SPECIAL: // fall-through 492 case DIRECT_INVOKE_SPECIAL_IFC: // fall-through 493 case DIRECT_INVOKE_STATIC: // fall-through 494 case DIRECT_INVOKE_STATIC_INIT: // fall-through 495 case DIRECT_INVOKE_VIRTUAL: return resolveFrom(name, invokerType, DirectMethodHandle.Holder.class); 496 } 497 return null; 498 } 499 500 /** 501 * Generate customized bytecode for a given LambdaForm. 502 */ 503 static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType) { 504 MemberName pregenerated = lookupPregenerated(form, invokerType); 505 if (pregenerated != null) return pregenerated; // pre-generated bytecode 506 507 InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("MH", form, invokerType); 508 return g.loadMethod(g.generateCustomizedCodeBytes()); 509 } 510 511 /** Generates code to check that actual receiver and LambdaForm matches */ 512 private boolean checkActualReceiver(CodeBuilder cob) { 513 // Expects MethodHandle on the stack and actual receiver MethodHandle in slot #0 514 cob.dup() 515 .aload(0) 516 .invokestatic(CD_MethodHandleImpl, "assertSame", MethodTypeDescImpl.ofValidated(CD_void, CD_Object, CD_Object)); 517 return true; 518 } 519 520 static final Annotation DONTINLINE = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/DontInline;")); 521 static final Annotation FORCEINLINE = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/ForceInline;")); 522 static final Annotation HIDDEN = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljdk/internal/vm/annotation/Hidden;")); 523 static final Annotation INJECTEDPROFILE = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/InjectedProfile;")); 524 static final Annotation LF_COMPILED = Annotation.of(ReferenceClassDescImpl.ofValidated("Ljava/lang/invoke/LambdaForm$Compiled;")); 525 526 // Suppress method in backtraces displayed to the user, mark this method as 527 // a compiled LambdaForm, then either force or prohibit inlining. 528 public static final RuntimeVisibleAnnotationsAttribute LF_DONTINLINE_ANNOTATIONS = RuntimeVisibleAnnotationsAttribute.of(HIDDEN, LF_COMPILED, DONTINLINE); 529 public static final RuntimeVisibleAnnotationsAttribute LF_FORCEINLINE_ANNOTATIONS = RuntimeVisibleAnnotationsAttribute.of(HIDDEN, LF_COMPILED, FORCEINLINE); 530 531 /** 532 * Generate an invoker method for the passed {@link LambdaForm}. 533 */ 534 private byte[] generateCustomizedCodeBytes() { 535 final byte[] classFile = classFileSetup(new Consumer<>() { 536 @Override 537 public void accept(ClassBuilder clb) { 538 addMethod(clb, true); 539 clinit(clb, classEntry, classData); 540 bogusMethod(clb, lambdaForm); 541 } 542 }); 543 return classFile; 544 } 545 546 void addMethod(ClassBuilder clb, boolean alive) { 547 methodSetup(clb, new Consumer<>() { 548 @Override 549 public void accept(MethodBuilder mb) { 550 551 if (lambdaForm.forceInline) { 552 mb.accept(LF_FORCEINLINE_ANNOTATIONS); 553 } else { 554 mb.accept(LF_DONTINLINE_ANNOTATIONS); 555 } 556 557 if (alive) { 558 classData(mb, lambdaForm, CD_LambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled. 559 } 560 561 mb.withCode(new Consumer<>() { 562 @Override 563 public void accept(CodeBuilder cob) { 564 if (lambdaForm.customized != null) { 565 // Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute 566 // receiver MethodHandle (at slot #0) with an embedded constant and use it instead. 567 // It enables more efficient code generation in some situations, since embedded constants 568 // are compile-time constants for JIT compiler. 569 cob.getstatic(classData(cob, lambdaForm.customized, CD_MethodHandle)); 570 assert(checkActualReceiver(cob)); // expects MethodHandle on top of the stack 571 cob.astore(0); 572 } 573 574 // iterate over the form's names, generating bytecode instructions for each 575 // start iterating at the first name following the arguments 576 Name onStack = null; 577 for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { 578 Name name = lambdaForm.names[i]; 579 580 emitStoreResult(cob, onStack); 581 onStack = name; // unless otherwise modified below 582 MethodHandleImpl.Intrinsic intr = name.function.intrinsicName(); 583 switch (intr) { 584 case SELECT_ALTERNATIVE: 585 assert lambdaForm.isSelectAlternative(i); 586 if (PROFILE_GWT) { 587 assert(name.arguments[0] instanceof Name n && 588 n.refersTo(MethodHandleImpl.class, "profileBoolean")); 589 mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of(INJECTEDPROFILE))); 590 } 591 onStack = emitSelectAlternative(cob, name, lambdaForm.names[i+1]); 592 i++; // skip MH.invokeBasic of the selectAlternative result 593 continue; 594 case GUARD_WITH_CATCH: 595 assert lambdaForm.isGuardWithCatch(i); 596 onStack = emitGuardWithCatch(cob, i); 597 i += 2; // jump to the end of GWC idiom 598 continue; 599 case TRY_FINALLY: 600 assert lambdaForm.isTryFinally(i); 601 onStack = emitTryFinally(cob, i); 602 i += 2; // jump to the end of the TF idiom 603 continue; 604 case TABLE_SWITCH: 605 assert lambdaForm.isTableSwitch(i); 606 int numCases = (Integer) name.function.intrinsicData(); 607 onStack = emitTableSwitch(cob, i, numCases); 608 i += 2; // jump to the end of the TS idiom 609 continue; 610 case LOOP: 611 assert lambdaForm.isLoop(i); 612 onStack = emitLoop(cob, i); 613 i += 2; // jump to the end of the LOOP idiom 614 continue; 615 case ARRAY_LOAD: 616 emitArrayLoad(cob, name); 617 continue; 618 case ARRAY_STORE: 619 emitArrayStore(cob, name); 620 continue; 621 case ARRAY_LENGTH: 622 emitArrayLength(cob, name); 623 continue; 624 case IDENTITY: 625 assert(name.arguments.length == 1); 626 emitPushArguments(cob, name, 0); 627 continue; 628 case ZERO: 629 assert(name.arguments.length == 0); 630 cob.loadConstant((ConstantDesc)name.type.basicTypeWrapper().zero()); 631 continue; 632 case NONE: 633 // no intrinsic associated 634 break; 635 default: 636 throw newInternalError("Unknown intrinsic: "+intr); 637 } 638 639 MemberName member = name.function.member(); 640 if (isStaticallyInvocable(member)) { 641 emitStaticInvoke(cob, member, name); 642 } else { 643 emitInvoke(cob, name); 644 } 645 } 646 647 // return statement 648 emitReturn(cob, onStack); 649 } 650 }); 651 } 652 }); 653 } 654 655 /** 656 * The BytecodeGenerationException. 657 */ 658 @SuppressWarnings("serial") 659 static final class BytecodeGenerationException extends RuntimeException { 660 BytecodeGenerationException(Exception cause) { 661 super(cause); 662 } 663 } 664 665 void emitArrayLoad(CodeBuilder cob, Name name) { 666 Class<?> elementType = name.function.methodType().parameterType(0).getComponentType(); 667 assert elementType != null; 668 emitPushArguments(cob, name, 0); 669 if (elementType.isPrimitive()) { 670 cob.arrayLoad(TypeKind.from(elementType)); 671 } else { 672 cob.aaload(); 673 } 674 } 675 676 void emitArrayStore(CodeBuilder cob, Name name) { 677 Class<?> elementType = name.function.methodType().parameterType(0).getComponentType(); 678 assert elementType != null; 679 emitPushArguments(cob, name, 0); 680 if (elementType.isPrimitive()) { 681 cob.arrayStore(TypeKind.from(elementType)); 682 } else { 683 cob.aastore(); 684 } 685 } 686 687 void emitArrayLength(CodeBuilder cob, Name name) { 688 assert name.function.methodType().parameterType(0).isArray(); 689 emitPushArguments(cob, name, 0); 690 cob.arraylength(); 691 } 692 693 /** 694 * Emit an invoke for the given name. 695 */ 696 void emitInvoke(CodeBuilder cob, Name name) { 697 assert(!name.isLinkerMethodInvoke()); // should use the static path for these 698 if (true) { 699 // push receiver 700 MethodHandle target = name.function.resolvedHandle(); 701 assert(target != null) : name.exprString(); 702 cob.getstatic(classData(cob, target, CD_MethodHandle)); 703 emitReferenceCast(cob, MethodHandle.class, target); 704 } else { 705 // load receiver 706 cob.aload(0); 707 emitReferenceCast(cob, MethodHandle.class, null); 708 cob.getfield(CD_MethodHandle, "form", CD_LambdaForm) 709 .getfield(CD_LambdaForm, "names", CD_LambdaForm_Name); 710 // TODO more to come 711 } 712 713 // push arguments 714 emitPushArguments(cob, name, 0); 715 716 // invocation 717 MethodType type = name.function.methodType(); 718 cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type.basicType())); 719 } 720 721 private static final Class<?>[] STATICALLY_INVOCABLE_PACKAGES = { 722 // Sample classes from each package we are willing to bind to statically: 723 java.lang.Object.class, 724 java.util.Arrays.class, 725 jdk.internal.misc.Unsafe.class 726 //MethodHandle.class already covered 727 }; 728 729 static boolean isStaticallyInvocable(NamedFunction ... functions) { 730 for (NamedFunction nf : functions) { 731 if (!isStaticallyInvocable(nf.member())) { 732 return false; 733 } 734 } 735 return true; 736 } 737 738 static boolean isStaticallyInvocable(Name name) { 739 return isStaticallyInvocable(name.function.member()); 740 } 741 742 static boolean isStaticallyInvocable(MemberName member) { 743 if (member == null) return false; 744 if (member.isConstructor()) return false; 745 Class<?> cls = member.getDeclaringClass(); 746 // Fast-path non-private members declared by MethodHandles, which is a common 747 // case 748 if (MethodHandle.class.isAssignableFrom(cls) && !member.isPrivate()) { 749 assert(isStaticallyInvocableType(member.getMethodOrFieldType())); 750 return true; 751 } 752 if (cls.isArray() || cls.isPrimitive()) 753 return false; // FIXME 754 if (cls.isAnonymousClass() || cls.isLocalClass()) 755 return false; // inner class of some sort 756 if (cls.getClassLoader() != MethodHandle.class.getClassLoader()) 757 return false; // not on BCP 758 if (cls.isHidden()) 759 return false; 760 if (!isStaticallyInvocableType(member.getMethodOrFieldType())) 761 return false; 762 if (!member.isPrivate() && VerifyAccess.isSamePackage(MethodHandle.class, cls)) 763 return true; // in java.lang.invoke package 764 if (member.isPublic() && isStaticallyNameable(cls)) 765 return true; 766 return false; 767 } 768 769 private static boolean isStaticallyInvocableType(MethodType mtype) { 770 if (!isStaticallyNameable(mtype.returnType())) 771 return false; 772 for (Class<?> ptype : mtype.ptypes()) 773 if (!isStaticallyNameable(ptype)) 774 return false; 775 return true; 776 } 777 778 static boolean isStaticallyNameable(Class<?> cls) { 779 if (cls == Object.class) 780 return true; 781 if (MethodHandle.class.isAssignableFrom(cls)) { 782 assert(!cls.isHidden()); 783 return true; 784 } 785 while (cls.isArray()) 786 cls = cls.getComponentType(); 787 if (cls.isPrimitive()) 788 return true; // int[].class, for example 789 if (cls.isHidden()) 790 return false; 791 // could use VerifyAccess.isClassAccessible but the following is a safe approximation 792 if (cls.getClassLoader() != Object.class.getClassLoader()) 793 return false; 794 if (VerifyAccess.isSamePackage(MethodHandle.class, cls)) 795 return true; 796 if (!Modifier.isPublic(cls.getModifiers())) 797 return false; 798 for (Class<?> pkgcls : STATICALLY_INVOCABLE_PACKAGES) { 799 if (VerifyAccess.isSamePackage(pkgcls, cls)) 800 return true; 801 } 802 return false; 803 } 804 805 void emitStaticInvoke(CodeBuilder cob, Name name) { 806 emitStaticInvoke(cob, name.function.member(), name); 807 } 808 809 /** 810 * Emit an invoke for the given name, using the MemberName directly. 811 */ 812 void emitStaticInvoke(CodeBuilder cob, MemberName member, Name name) { 813 assert(member.equals(name.function.member())); 814 Class<?> defc = member.getDeclaringClass(); 815 ClassDesc cdesc = classDesc(defc); 816 String mname = member.getName(); 817 byte refKind = member.getReferenceKind(); 818 if (refKind == REF_invokeSpecial) { 819 // in order to pass the verifier, we need to convert this to invokevirtual in all cases 820 assert(member.canBeStaticallyBound()) : member; 821 refKind = REF_invokeVirtual; 822 } 823 824 assert(!(member.getDeclaringClass().isInterface() && refKind == REF_invokeVirtual)); 825 826 // push arguments 827 emitPushArguments(cob, name, 0); 828 829 // invocation 830 if (member.isMethod()) { 831 var methodTypeDesc = methodDesc(member.getMethodType()); 832 cob.invoke(refKindOpcode(refKind), cdesc, mname, methodTypeDesc, 833 member.getDeclaringClass().isInterface()); 834 } else { 835 var fieldTypeDesc = classDesc(member.getFieldType()); 836 cob.fieldAccess(refKindOpcode(refKind), cdesc, mname, fieldTypeDesc); 837 } 838 // Issue a type assertion for the result, so we can avoid casts later. 839 if (name.type == L_TYPE) { 840 Class<?> rtype = member.getInvocationType().returnType(); 841 assert(!rtype.isPrimitive()); 842 if (rtype != Object.class && !rtype.isInterface()) { 843 assertStaticType(rtype, name); 844 } 845 } 846 } 847 848 Opcode refKindOpcode(byte refKind) { 849 switch (refKind) { 850 case REF_invokeVirtual: return Opcode.INVOKEVIRTUAL; 851 case REF_invokeStatic: return Opcode.INVOKESTATIC; 852 case REF_invokeSpecial: return Opcode.INVOKESPECIAL; 853 case REF_invokeInterface: return Opcode.INVOKEINTERFACE; 854 case REF_getField: return Opcode.GETFIELD; 855 case REF_putField: return Opcode.PUTFIELD; 856 case REF_getStatic: return Opcode.GETSTATIC; 857 case REF_putStatic: return Opcode.PUTSTATIC; 858 } 859 throw new InternalError("refKind="+refKind); 860 } 861 862 /** 863 * Emit bytecode for the selectAlternative idiom. 864 * 865 * The pattern looks like (Cf. MethodHandleImpl.makeGuardWithTest): 866 * <blockquote><pre>{@code 867 * Lambda(a0:L,a1:I)=>{ 868 * t2:I=foo.test(a1:I); 869 * t3:L=MethodHandleImpl.selectAlternative(t2:I,(MethodHandle(int)int),(MethodHandle(int)int)); 870 * t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I} 871 * }</pre></blockquote> 872 */ 873 private Name emitSelectAlternative(CodeBuilder cob, Name selectAlternativeName, Name invokeBasicName) { 874 assert isStaticallyInvocable(invokeBasicName); 875 876 Name receiver = (Name) invokeBasicName.arguments[0]; 877 878 Label L_fallback = cob.newLabel(); 879 Label L_done = cob.newLabel(); 880 881 // load test result 882 emitPushArgument(cob, selectAlternativeName, 0); 883 884 // if_icmpne L_fallback 885 cob.ifeq(L_fallback); 886 887 // invoke selectAlternativeName.arguments[1] 888 Class<?>[] preForkClasses = localClasses.clone(); 889 emitPushArgument(cob, selectAlternativeName, 1); // get 2nd argument of selectAlternative 890 emitStoreInsn(cob, TypeKind.REFERENCE, receiver.index()); // store the MH in the receiver slot 891 emitStaticInvoke(cob, invokeBasicName); 892 893 // goto L_done 894 cob.goto_w(L_done) 895 // L_fallback: 896 .labelBinding(L_fallback); 897 898 // invoke selectAlternativeName.arguments[2] 899 System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); 900 emitPushArgument(cob, selectAlternativeName, 2); // get 3rd argument of selectAlternative 901 emitStoreInsn(cob, TypeKind.REFERENCE, receiver.index()); // store the MH in the receiver slot 902 emitStaticInvoke(cob, invokeBasicName); 903 904 // L_done: 905 cob.labelBinding(L_done); 906 // for now do not bother to merge typestate; just reset to the dominator state 907 System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); 908 909 return invokeBasicName; // return what's on stack 910 } 911 912 /** 913 * Emit bytecode for the guardWithCatch idiom. 914 * 915 * The pattern looks like (Cf. MethodHandleImpl.makeGuardWithCatch): 916 * <blockquote><pre>{@code 917 * guardWithCatch=Lambda(a0:L,a1:L,a2:L,a3:L,a4:L,a5:L,a6:L,a7:L)=>{ 918 * t8:L=MethodHandle.invokeBasic(a4:L,a6:L,a7:L); 919 * t9:L=MethodHandleImpl.guardWithCatch(a1:L,a2:L,a3:L,t8:L); 920 * t10:I=MethodHandle.invokeBasic(a5:L,t9:L);t10:I} 921 * }</pre></blockquote> 922 * 923 * It is compiled into bytecode equivalent of the following code: 924 * <blockquote><pre>{@code 925 * try { 926 * return a1.invokeBasic(a6, a7); 927 * } catch (Throwable e) { 928 * if (!a2.isInstance(e)) throw e; 929 * return a3.invokeBasic(ex, a6, a7); 930 * }}</pre></blockquote> 931 */ 932 private Name emitGuardWithCatch(CodeBuilder cob, int pos) { 933 Name args = lambdaForm.names[pos]; 934 Name invoker = lambdaForm.names[pos+1]; 935 Name result = lambdaForm.names[pos+2]; 936 937 Label L_startBlock = cob.newLabel(); 938 Label L_endBlock = cob.newLabel(); 939 Label L_handler = cob.newLabel(); 940 Label L_done = cob.newLabel(); 941 942 Class<?> returnType = result.function.resolvedHandle().type().returnType(); 943 MethodType type = args.function.resolvedHandle().type() 944 .dropParameterTypes(0,1) 945 .changeReturnType(returnType); 946 947 cob.exceptionCatch(L_startBlock, L_endBlock, L_handler, CD_Throwable) 948 // Normal case 949 .labelBinding(L_startBlock); 950 // load target 951 emitPushArgument(cob, invoker, 0); 952 emitPushArguments(cob, args, 1); // skip 1st argument: method handle 953 cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type.basicType())) 954 .labelBinding(L_endBlock) 955 .goto_w(L_done) 956 // Exceptional case 957 .labelBinding(L_handler) 958 // Check exception's type 959 .dup(); 960 // load exception class 961 emitPushArgument(cob, invoker, 1); 962 cob.swap() 963 .invokevirtual(CD_Class, "isInstance", MTD_boolean_Object); 964 Label L_rethrow = cob.newLabel(); 965 cob.ifeq(L_rethrow); 966 967 // Invoke catcher 968 // load catcher 969 emitPushArgument(cob, invoker, 2); 970 cob.swap(); 971 emitPushArguments(cob, args, 1); // skip 1st argument: method handle 972 MethodType catcherType = type.insertParameterTypes(0, Throwable.class); 973 cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(catcherType.basicType())) 974 .goto_w(L_done) 975 .labelBinding(L_rethrow) 976 .athrow() 977 .labelBinding(L_done); 978 979 return result; 980 } 981 982 /** 983 * Emit bytecode for the tryFinally idiom. 984 * <p> 985 * The pattern looks like (Cf. MethodHandleImpl.makeTryFinally): 986 * <blockquote><pre>{@code 987 * // a0: BMH 988 * // a1: target, a2: cleanup 989 * // a3: box, a4: unbox 990 * // a5 (and following): arguments 991 * tryFinally=Lambda(a0:L,a1:L,a2:L,a3:L,a4:L,a5:L)=>{ 992 * t6:L=MethodHandle.invokeBasic(a3:L,a5:L); // box the arguments into an Object[] 993 * t7:L=MethodHandleImpl.tryFinally(a1:L,a2:L,t6:L); // call the tryFinally executor 994 * t8:L=MethodHandle.invokeBasic(a4:L,t7:L);t8:L} // unbox the result; return the result 995 * }</pre></blockquote> 996 * <p> 997 * It is compiled into bytecode equivalent to the following code: 998 * <blockquote><pre>{@code 999 * Throwable t; 1000 * Object r; 1001 * try { 1002 * r = a1.invokeBasic(a5); 1003 * } catch (Throwable thrown) { 1004 * t = thrown; 1005 * throw t; 1006 * } finally { 1007 * r = a2.invokeBasic(t, r, a5); 1008 * } 1009 * return r; 1010 * }</pre></blockquote> 1011 * <p> 1012 * Specifically, the bytecode will have the following form (the stack effects are given for the beginnings of 1013 * blocks, and for the situations after executing the given instruction - the code will have a slightly different 1014 * shape if the return type is {@code void}): 1015 * <blockquote><pre>{@code 1016 * TRY: (--) 1017 * load target (-- target) 1018 * load args (-- args... target) 1019 * INVOKEVIRTUAL MethodHandle.invokeBasic (depends) 1020 * FINALLY_NORMAL: (-- r_2nd* r) 1021 * store returned value (--) 1022 * load cleanup (-- cleanup) 1023 * ACONST_NULL (-- t cleanup) 1024 * load returned value (-- r_2nd* r t cleanup) 1025 * load args (-- args... r_2nd* r t cleanup) 1026 * INVOKEVIRTUAL MethodHandle.invokeBasic (-- r_2nd* r) 1027 * GOTO DONE 1028 * CATCH: (-- t) 1029 * DUP (-- t t) 1030 * FINALLY_EXCEPTIONAL: (-- t t) 1031 * load cleanup (-- cleanup t t) 1032 * SWAP (-- t cleanup t) 1033 * load default for r (-- r_2nd* r t cleanup t) 1034 * load args (-- args... r_2nd* r t cleanup t) 1035 * INVOKEVIRTUAL MethodHandle.invokeBasic (-- r_2nd* r t) 1036 * POP/POP2* (-- t) 1037 * ATHROW 1038 * DONE: (-- r) 1039 * }</pre></blockquote> 1040 * * = depends on whether the return type takes up 2 stack slots. 1041 */ 1042 private Name emitTryFinally(CodeBuilder cob, int pos) { 1043 Name args = lambdaForm.names[pos]; 1044 Name invoker = lambdaForm.names[pos+1]; 1045 Name result = lambdaForm.names[pos+2]; 1046 1047 Label lFrom = cob.newLabel(); 1048 Label lTo = cob.newLabel(); 1049 Label lCatch = cob.newLabel(); 1050 Label lDone = cob.newLabel(); 1051 1052 Class<?> returnType = result.function.resolvedHandle().type().returnType(); 1053 BasicType basicReturnType = BasicType.basicType(returnType); 1054 boolean isNonVoid = returnType != void.class; 1055 1056 MethodType type = args.function.resolvedHandle().type() 1057 .dropParameterTypes(0,1) 1058 .changeReturnType(returnType); 1059 MethodType cleanupType = type.insertParameterTypes(0, Throwable.class); 1060 if (isNonVoid) { 1061 cleanupType = cleanupType.insertParameterTypes(1, returnType); 1062 } 1063 MethodTypeDesc cleanupDesc = methodDesc(cleanupType.basicType()); 1064 1065 // exception handler table 1066 cob.exceptionCatch(lFrom, lTo, lCatch, CD_Throwable); 1067 1068 // TRY: 1069 cob.labelBinding(lFrom); 1070 emitPushArgument(cob, invoker, 0); // load target 1071 emitPushArguments(cob, args, 1); // load args (skip 0: method handle) 1072 cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type.basicType())) 1073 .labelBinding(lTo); 1074 1075 // FINALLY_NORMAL: 1076 int index = extendLocalsMap(new Class<?>[]{ returnType }); 1077 if (isNonVoid) { 1078 emitStoreInsn(cob, basicReturnType.basicTypeKind(), index); 1079 } 1080 emitPushArgument(cob, invoker, 1); // load cleanup 1081 cob.aconst_null(); 1082 if (isNonVoid) { 1083 emitLoadInsn(cob, basicReturnType.basicTypeKind(), index); 1084 } 1085 emitPushArguments(cob, args, 1); // load args (skip 0: method handle) 1086 cob.invokevirtual(CD_MethodHandle, "invokeBasic", cleanupDesc) 1087 .goto_w(lDone) 1088 // CATCH: 1089 .labelBinding(lCatch) 1090 .dup(); 1091 1092 // FINALLY_EXCEPTIONAL: 1093 emitPushArgument(cob, invoker, 1); // load cleanup 1094 cob.swap(); 1095 if (isNonVoid) { 1096 emitZero(cob, BasicType.basicType(returnType)); // load default for result 1097 } 1098 emitPushArguments(cob, args, 1); // load args (skip 0: method handle) 1099 cob.invokevirtual(CD_MethodHandle, "invokeBasic", cleanupDesc); 1100 if (isNonVoid) { 1101 emitPopInsn(cob, basicReturnType); 1102 } 1103 cob.athrow() 1104 // DONE: 1105 .labelBinding(lDone); 1106 1107 return result; 1108 } 1109 1110 private void emitPopInsn(CodeBuilder cob, BasicType type) { 1111 switch (type) { 1112 case I_TYPE, F_TYPE, L_TYPE -> cob.pop(); 1113 case J_TYPE, D_TYPE -> cob.pop2(); 1114 default -> throw new InternalError("unknown type: " + type); 1115 } 1116 } 1117 1118 private Name emitTableSwitch(CodeBuilder cob, int pos, int numCases) { 1119 Name args = lambdaForm.names[pos]; 1120 Name invoker = lambdaForm.names[pos + 1]; 1121 Name result = lambdaForm.names[pos + 2]; 1122 1123 Class<?> returnType = result.function.resolvedHandle().type().returnType(); 1124 MethodType caseType = args.function.resolvedHandle().type() 1125 .dropParameterTypes(0, 1) // drop collector 1126 .changeReturnType(returnType); 1127 MethodTypeDesc caseDescriptor = methodDesc(caseType.basicType()); 1128 1129 emitPushArgument(cob, invoker, 2); // push cases 1130 cob.getfield(CD_CasesHolder, "cases", CD_MethodHandle_array); 1131 int casesLocal = extendLocalsMap(new Class<?>[] { MethodHandle[].class }); 1132 emitStoreInsn(cob, TypeKind.REFERENCE, casesLocal); 1133 1134 Label endLabel = cob.newLabel(); 1135 Label defaultLabel = cob.newLabel(); 1136 List<SwitchCase> cases = new ArrayList<>(numCases); 1137 for (int i = 0; i < numCases; i++) { 1138 cases.add(SwitchCase.of(i, cob.newLabel())); 1139 } 1140 1141 emitPushArgument(cob, invoker, 0); // push switch input 1142 cob.tableswitch(0, numCases - 1, defaultLabel, cases) 1143 .labelBinding(defaultLabel); 1144 emitPushArgument(cob, invoker, 1); // push default handle 1145 emitPushArguments(cob, args, 1); // again, skip collector 1146 cob.invokevirtual(CD_MethodHandle, "invokeBasic", caseDescriptor) 1147 .goto_(endLabel); 1148 1149 for (int i = 0; i < numCases; i++) { 1150 cob.labelBinding(cases.get(i).target()); 1151 // Load the particular case: 1152 emitLoadInsn(cob, TypeKind.REFERENCE, casesLocal); 1153 cob.loadConstant(i) 1154 .aaload(); 1155 1156 // invoke it: 1157 emitPushArguments(cob, args, 1); // again, skip collector 1158 cob.invokevirtual(CD_MethodHandle, "invokeBasic", caseDescriptor) 1159 .goto_(endLabel); 1160 } 1161 1162 cob.labelBinding(endLabel); 1163 1164 return result; 1165 } 1166 1167 /** 1168 * Emit bytecode for the loop idiom. 1169 * <p> 1170 * The pattern looks like (Cf. MethodHandleImpl.loop): 1171 * <blockquote><pre>{@code 1172 * // a0: BMH 1173 * // a1: LoopClauses (containing an array of arrays: inits, steps, preds, finis) 1174 * // a2: box, a3: unbox 1175 * // a4 (and following): arguments 1176 * loop=Lambda(a0:L,a1:L,a2:L,a3:L,a4:L)=>{ 1177 * t5:L=MethodHandle.invokeBasic(a2:L,a4:L); // box the arguments into an Object[] 1178 * t6:L=MethodHandleImpl.loop(bt:L,a1:L,t5:L); // call the loop executor (with supplied types in bt) 1179 * t7:L=MethodHandle.invokeBasic(a3:L,t6:L);t7:L} // unbox the result; return the result 1180 * }</pre></blockquote> 1181 * <p> 1182 * It is compiled into bytecode equivalent to the code seen in {@link MethodHandleImpl#loop(BasicType[], 1183 * MethodHandleImpl.LoopClauses, Object...)}, with the difference that no arrays 1184 * will be used for local state storage. Instead, the local state will be mapped to actual stack slots. 1185 * <p> 1186 * Bytecode generation applies an unrolling scheme to enable better bytecode generation regarding local state type 1187 * handling. The generated bytecode will have the following form ({@code void} types are ignored for convenience). 1188 * Assume there are {@code C} clauses in the loop. 1189 * <blockquote><pre>{@code 1190 * PREINIT: ALOAD_1 1191 * CHECKCAST LoopClauses 1192 * GETFIELD LoopClauses.clauses 1193 * ASTORE clauseDataIndex // place the clauses 2-dimensional array on the stack 1194 * INIT: (INIT_SEQ for clause 1) 1195 * ... 1196 * (INIT_SEQ for clause C) 1197 * LOOP: (LOOP_SEQ for clause 1) 1198 * ... 1199 * (LOOP_SEQ for clause C) 1200 * GOTO LOOP 1201 * DONE: ... 1202 * }</pre></blockquote> 1203 * <p> 1204 * The {@code INIT_SEQ_x} sequence for clause {@code x} (with {@code x} ranging from {@code 0} to {@code C-1}) has 1205 * the following shape. Assume slot {@code vx} is used to hold the state for clause {@code x}. 1206 * <blockquote><pre>{@code 1207 * INIT_SEQ_x: ALOAD clauseDataIndex 1208 * ICONST_0 1209 * AALOAD // load the inits array 1210 * ICONST x 1211 * AALOAD // load the init handle for clause x 1212 * load args 1213 * INVOKEVIRTUAL MethodHandle.invokeBasic 1214 * store vx 1215 * }</pre></blockquote> 1216 * <p> 1217 * The {@code LOOP_SEQ_x} sequence for clause {@code x} (with {@code x} ranging from {@code 0} to {@code C-1}) has 1218 * the following shape. Again, assume slot {@code vx} is used to hold the state for clause {@code x}. 1219 * <blockquote><pre>{@code 1220 * LOOP_SEQ_x: ALOAD clauseDataIndex 1221 * ICONST_1 1222 * AALOAD // load the steps array 1223 * ICONST x 1224 * AALOAD // load the step handle for clause x 1225 * load locals 1226 * load args 1227 * INVOKEVIRTUAL MethodHandle.invokeBasic 1228 * store vx 1229 * ALOAD clauseDataIndex 1230 * ICONST_2 1231 * AALOAD // load the preds array 1232 * ICONST x 1233 * AALOAD // load the pred handle for clause x 1234 * load locals 1235 * load args 1236 * INVOKEVIRTUAL MethodHandle.invokeBasic 1237 * IFNE LOOP_SEQ_x+1 // predicate returned false -> jump to next clause 1238 * ALOAD clauseDataIndex 1239 * ICONST_3 1240 * AALOAD // load the finis array 1241 * ICONST x 1242 * AALOAD // load the fini handle for clause x 1243 * load locals 1244 * load args 1245 * INVOKEVIRTUAL MethodHandle.invokeBasic 1246 * GOTO DONE // jump beyond end of clauses to return from loop 1247 * }</pre></blockquote> 1248 */ 1249 private Name emitLoop(CodeBuilder cob, int pos) { 1250 Name args = lambdaForm.names[pos]; 1251 Name invoker = lambdaForm.names[pos+1]; 1252 Name result = lambdaForm.names[pos+2]; 1253 1254 // extract clause and loop-local state types 1255 // find the type info in the loop invocation 1256 BasicType[] loopClauseTypes = (BasicType[]) invoker.arguments[0]; 1257 Class<?>[] loopLocalStateTypes = Stream.of(loopClauseTypes) 1258 .filter(bt -> bt != BasicType.V_TYPE) 1259 .map(BasicType::basicTypeClass).toArray(Class<?>[]::new); 1260 Class<?>[] localTypes = new Class<?>[loopLocalStateTypes.length + 1]; 1261 localTypes[0] = MethodHandleImpl.LoopClauses.class; 1262 System.arraycopy(loopLocalStateTypes, 0, localTypes, 1, loopLocalStateTypes.length); 1263 1264 final int clauseDataIndex = extendLocalsMap(localTypes); 1265 final int firstLoopStateIndex = clauseDataIndex + 1; 1266 1267 Class<?> returnType = result.function.resolvedHandle().type().returnType(); 1268 MethodType loopType = args.function.resolvedHandle().type() 1269 .dropParameterTypes(0,1) 1270 .changeReturnType(returnType); 1271 MethodType loopHandleType = loopType.insertParameterTypes(0, loopLocalStateTypes); 1272 MethodType predType = loopHandleType.changeReturnType(boolean.class); 1273 MethodType finiType = loopHandleType; 1274 1275 final int nClauses = loopClauseTypes.length; 1276 1277 // indices to invoker arguments to load method handle arrays 1278 final int inits = 1; 1279 final int steps = 2; 1280 final int preds = 3; 1281 final int finis = 4; 1282 1283 Label lLoop = cob.newLabel(); 1284 Label lDone = cob.newLabel(); 1285 Label lNext; 1286 1287 // PREINIT: 1288 emitPushArgument(cob, MethodHandleImpl.LoopClauses.class, invoker.arguments[1]); 1289 cob.getfield(CD_LoopClauses, "clauses", CD_MethodHandle_array2); 1290 emitStoreInsn(cob, TypeKind.REFERENCE, clauseDataIndex); 1291 1292 // INIT: 1293 for (int c = 0, state = 0; c < nClauses; ++c) { 1294 MethodType cInitType = loopType.changeReturnType(loopClauseTypes[c].basicTypeClass()); 1295 emitLoopHandleInvoke(cob, invoker, inits, c, args, false, cInitType, loopLocalStateTypes, clauseDataIndex, 1296 firstLoopStateIndex); 1297 if (cInitType.returnType() != void.class) { 1298 emitStoreInsn(cob, BasicType.basicType(cInitType.returnType()).basicTypeKind(), firstLoopStateIndex + state); 1299 ++state; 1300 } 1301 } 1302 1303 // LOOP: 1304 cob.labelBinding(lLoop); 1305 1306 for (int c = 0, state = 0; c < nClauses; ++c) { 1307 lNext = cob.newLabel(); 1308 1309 MethodType stepType = loopHandleType.changeReturnType(loopClauseTypes[c].basicTypeClass()); 1310 boolean isVoid = stepType.returnType() == void.class; 1311 1312 // invoke loop step 1313 emitLoopHandleInvoke(cob, invoker, steps, c, args, true, stepType, loopLocalStateTypes, clauseDataIndex, 1314 firstLoopStateIndex); 1315 if (!isVoid) { 1316 emitStoreInsn(cob, BasicType.basicType(stepType.returnType()).basicTypeKind(), firstLoopStateIndex + state); 1317 ++state; 1318 } 1319 1320 // invoke loop predicate 1321 emitLoopHandleInvoke(cob, invoker, preds, c, args, true, predType, loopLocalStateTypes, clauseDataIndex, 1322 firstLoopStateIndex); 1323 cob.ifne(lNext); 1324 1325 // invoke fini 1326 emitLoopHandleInvoke(cob, invoker, finis, c, args, true, finiType, loopLocalStateTypes, clauseDataIndex, 1327 firstLoopStateIndex); 1328 cob.goto_w(lDone) 1329 // this is the beginning of the next loop clause 1330 .labelBinding(lNext); 1331 } 1332 1333 cob.goto_w(lLoop) 1334 // DONE: 1335 .labelBinding(lDone); 1336 1337 return result; 1338 } 1339 1340 private int extendLocalsMap(Class<?>[] types) { 1341 int firstSlot = localsMap.length - 1; 1342 localsMap = Arrays.copyOf(localsMap, localsMap.length + types.length); 1343 localClasses = Arrays.copyOf(localClasses, localClasses.length + types.length); 1344 System.arraycopy(types, 0, localClasses, firstSlot, types.length); 1345 int index = localsMap[firstSlot - 1] + 1; 1346 int lastSlots = 0; 1347 for (int i = 0; i < types.length; ++i) { 1348 localsMap[firstSlot + i] = index; 1349 lastSlots = BasicType.basicType(localClasses[firstSlot + i]).basicTypeSlots(); 1350 index += lastSlots; 1351 } 1352 localsMap[localsMap.length - 1] = index - lastSlots; 1353 return firstSlot; 1354 } 1355 1356 private void emitLoopHandleInvoke(CodeBuilder cob, Name holder, int handles, int clause, Name args, boolean pushLocalState, 1357 MethodType type, Class<?>[] loopLocalStateTypes, int clauseDataSlot, 1358 int firstLoopStateSlot) { 1359 // load handle for clause 1360 emitPushClauseArray(cob, clauseDataSlot, handles); 1361 cob.loadConstant(clause) 1362 .aaload(); 1363 // load loop state (preceding the other arguments) 1364 if (pushLocalState) { 1365 for (int s = 0; s < loopLocalStateTypes.length; ++s) { 1366 emitLoadInsn(cob, BasicType.basicType(loopLocalStateTypes[s]).basicTypeKind(), firstLoopStateSlot + s); 1367 } 1368 } 1369 // load loop args (skip 0: method handle) 1370 emitPushArguments(cob, args, 1); 1371 cob.invokevirtual(CD_MethodHandle, "invokeBasic", methodDesc(type)); 1372 } 1373 1374 private void emitPushClauseArray(CodeBuilder cob, int clauseDataSlot, int which) { 1375 emitLoadInsn(cob, TypeKind.REFERENCE, clauseDataSlot); 1376 cob.loadConstant(which - 1) 1377 .aaload(); 1378 } 1379 1380 private void emitZero(CodeBuilder cob, BasicType type) { 1381 switch (type) { 1382 case I_TYPE -> cob.iconst_0(); 1383 case J_TYPE -> cob.lconst_0(); 1384 case F_TYPE -> cob.fconst_0(); 1385 case D_TYPE -> cob.dconst_0(); 1386 case L_TYPE -> cob.aconst_null(); 1387 default -> throw new InternalError("unknown type: " + type); 1388 }; 1389 } 1390 1391 private void emitPushArguments(CodeBuilder cob, Name args, int start) { 1392 MethodType type = args.function.methodType(); 1393 for (int i = start; i < args.arguments.length; i++) { 1394 emitPushArgument(cob, type.parameterType(i), args.arguments[i]); 1395 } 1396 } 1397 1398 private void emitPushArgument(CodeBuilder cob, Name name, int paramIndex) { 1399 Object arg = name.arguments[paramIndex]; 1400 Class<?> ptype = name.function.methodType().parameterType(paramIndex); 1401 emitPushArgument(cob, ptype, arg); 1402 } 1403 1404 private void emitPushArgument(CodeBuilder cob, Class<?> ptype, Object arg) { 1405 BasicType bptype = basicType(ptype); 1406 if (arg instanceof Name n) { 1407 emitLoadInsn(cob, n.type.basicTypeKind(), n.index()); 1408 emitImplicitConversion(cob, n.type, ptype, n); 1409 } else if ((arg == null || arg instanceof String) && bptype == L_TYPE) { 1410 cob.loadConstant((ConstantDesc)arg); 1411 } else { 1412 if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) { 1413 cob.loadConstant((ConstantDesc)arg); 1414 } else { 1415 cob.getstatic(classData(cob, arg, CD_Object)); 1416 emitImplicitConversion(cob, L_TYPE, ptype, arg); 1417 } 1418 } 1419 } 1420 1421 /** 1422 * Store the name to its local, if necessary. 1423 */ 1424 private void emitStoreResult(CodeBuilder cob, Name name) { 1425 if (name != null && name.type != V_TYPE) { 1426 // non-void: actually assign 1427 emitStoreInsn(cob, name.type.basicTypeKind(), name.index()); 1428 } 1429 } 1430 1431 /** 1432 * Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type. 1433 */ 1434 private void emitReturn(CodeBuilder cob, Name onStack) { 1435 // return statement 1436 Class<?> rclass = invokerType.returnType(); 1437 BasicType rtype = lambdaForm.returnType(); 1438 assert(rtype == basicType(rclass)); // must agree 1439 if (rtype == V_TYPE) { 1440 // void 1441 cob.return_(); 1442 // it doesn't matter what rclass is; the JVM will discard any value 1443 } else { 1444 LambdaForm.Name rn = lambdaForm.names[lambdaForm.result]; 1445 1446 // put return value on the stack if it is not already there 1447 if (rn != onStack) { 1448 emitLoadInsn(cob, rtype.basicTypeKind(), lambdaForm.result); 1449 } 1450 1451 emitImplicitConversion(cob, rtype, rclass, rn); 1452 1453 // generate actual return statement 1454 cob.return_(rtype.basicTypeKind()); 1455 } 1456 } 1457 1458 /** 1459 * Emit a type conversion bytecode casting from "from" to "to". 1460 */ 1461 private void emitPrimCast(CodeBuilder cob, TypeKind from, TypeKind to) { 1462 // Here's how. 1463 // - indicates forbidden 1464 // <-> indicates implicit 1465 // to ----> boolean byte short char int long float double 1466 // from boolean <-> - - - - - - - 1467 // byte - <-> i2s i2c <-> i2l i2f i2d 1468 // short - i2b <-> i2c <-> i2l i2f i2d 1469 // char - i2b i2s <-> <-> i2l i2f i2d 1470 // int - i2b i2s i2c <-> i2l i2f i2d 1471 // long - l2i,i2b l2i,i2s l2i,i2c l2i <-> l2f l2d 1472 // float - f2i,i2b f2i,i2s f2i,i2c f2i f2l <-> f2d 1473 // double - d2i,i2b d2i,i2s d2i,i2c d2i d2l d2f <-> 1474 if (from != to && from != TypeKind.BOOLEAN) try { 1475 cob.conversion(from, to); 1476 } catch (IllegalArgumentException e) { 1477 throw new IllegalStateException("unhandled prim cast: " + from + "2" + to); 1478 } 1479 } 1480 1481 /** 1482 * Generate bytecode for a LambdaForm.vmentry which calls interpretWithArguments. 1483 */ 1484 static MemberName generateLambdaFormInterpreterEntryPoint(MethodType mt) { 1485 assert(isValidSignature(basicTypeSignature(mt))); 1486 String name = "interpret_"+basicTypeChar(mt.returnType()); 1487 MethodType type = mt; // includes leading argument 1488 type = type.changeParameterType(0, MethodHandle.class); 1489 InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("LFI", name, type); 1490 return g.loadMethod(g.generateLambdaFormInterpreterEntryPointBytes()); 1491 } 1492 1493 private byte[] generateLambdaFormInterpreterEntryPointBytes() { 1494 final byte[] classFile = classFileSetup(new Consumer<>() { 1495 @Override 1496 public void accept(ClassBuilder clb) { 1497 methodSetup(clb, new Consumer<>() { 1498 @Override 1499 public void accept(MethodBuilder mb) { 1500 1501 mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of( 1502 HIDDEN, // Suppress this method in backtraces displayed to the user. 1503 DONTINLINE // Don't inline the interpreter entry. 1504 ))); 1505 1506 mb.withCode(new Consumer<>() { 1507 @Override 1508 public void accept(CodeBuilder cob) { 1509 // create parameter array 1510 cob.loadConstant(invokerType.parameterCount()) 1511 .anewarray(CD_Object); 1512 1513 // fill parameter array 1514 for (int i = 0; i < invokerType.parameterCount(); i++) { 1515 Class<?> ptype = invokerType.parameterType(i); 1516 cob.dup() 1517 .loadConstant(i); 1518 emitLoadInsn(cob, basicType(ptype).basicTypeKind(), i); 1519 // box if primitive type 1520 if (ptype.isPrimitive()) { 1521 emitBoxing(cob, TypeKind.from(ptype)); 1522 } 1523 cob.aastore(); 1524 } 1525 // invoke 1526 cob.aload(0) 1527 .getfield(CD_MethodHandle, "form", CD_LambdaForm) 1528 .swap() // swap form and array; avoid local variable 1529 .invokevirtual(CD_LambdaForm, "interpretWithArguments", MethodTypeDescImpl.ofValidated(CD_Object, CD_Object_array)); 1530 1531 // maybe unbox 1532 Class<?> rtype = invokerType.returnType(); 1533 TypeKind rtypeK = TypeKind.from(rtype); 1534 if (rtype.isPrimitive() && rtype != void.class) { 1535 emitUnboxing(cob, rtypeK); 1536 } 1537 1538 // return statement 1539 cob.return_(rtypeK); 1540 } 1541 }); 1542 } 1543 }); 1544 clinit(clb, classEntry, classData); 1545 bogusMethod(clb, invokerType); 1546 } 1547 }); 1548 return classFile; 1549 } 1550 1551 /** 1552 * Generate bytecode for a NamedFunction invoker. 1553 */ 1554 static MemberName generateNamedFunctionInvoker(MethodTypeForm typeForm) { 1555 MethodType invokerType = NamedFunction.INVOKER_METHOD_TYPE; 1556 String invokerName = "invoke_" + shortenSignature(basicTypeSignature(typeForm.erasedType())); 1557 InvokerBytecodeGenerator g = new InvokerBytecodeGenerator("NFI", invokerName, invokerType); 1558 return g.loadMethod(g.generateNamedFunctionInvokerImpl(typeForm)); 1559 } 1560 1561 private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) { 1562 MethodType dstType = typeForm.erasedType(); 1563 final byte[] classFile = classFileSetup(new Consumer<>() { 1564 @Override 1565 public void accept(ClassBuilder clb) { 1566 methodSetup(clb, new Consumer<>() { 1567 @Override 1568 public void accept(MethodBuilder mb) { 1569 1570 mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of( 1571 HIDDEN, // Suppress this method in backtraces displayed to the user. 1572 FORCEINLINE // Force inlining of this invoker method. 1573 ))); 1574 1575 mb.withCode(new Consumer<>() { 1576 @Override 1577 public void accept(CodeBuilder cob) { 1578 // Load receiver 1579 cob.aload(0); 1580 1581 // Load arguments from array 1582 for (int i = 0; i < dstType.parameterCount(); i++) { 1583 cob.aload(1) 1584 .loadConstant(i) 1585 .aaload(); 1586 1587 // Maybe unbox 1588 Class<?> dptype = dstType.parameterType(i); 1589 if (dptype.isPrimitive()) { 1590 TypeKind dstTK = TypeKind.from(dptype); 1591 TypeKind srcTK = dstTK.asLoadable(); 1592 emitUnboxing(cob, srcTK); 1593 emitPrimCast(cob, srcTK, dstTK); 1594 } 1595 } 1596 1597 // Invoke 1598 MethodTypeDesc targetDesc = methodDesc(dstType.basicType()); 1599 cob.invokevirtual(CD_MethodHandle, "invokeBasic", targetDesc); 1600 1601 // Box primitive types 1602 Class<?> rtype = dstType.returnType(); 1603 if (rtype != void.class && rtype.isPrimitive()) { 1604 TypeKind srcTK = TypeKind.from(rtype); 1605 TypeKind dstTK = srcTK.asLoadable(); 1606 // boolean casts not allowed 1607 emitPrimCast(cob, srcTK, dstTK); 1608 emitBoxing(cob, dstTK); 1609 } 1610 1611 // If the return type is void we return a null reference. 1612 if (rtype == void.class) { 1613 cob.aconst_null(); 1614 } 1615 cob.areturn(); // NOTE: NamedFunction invokers always return a reference value. 1616 } 1617 }); 1618 } 1619 }); 1620 clinit(clb, classEntry, classData); 1621 bogusMethod(clb, dstType); 1622 } 1623 }); 1624 return classFile; 1625 } 1626 1627 /** 1628 * Emit a bogus method that just loads some string constants. This is to get the constants into the constant pool 1629 * for debugging purposes. 1630 */ 1631 private void bogusMethod(ClassBuilder clb, Object os) { 1632 if (dumper().isEnabled()) { 1633 clb.withMethodBody("dummy", MTD_void, ACC_STATIC, new Consumer<>() { 1634 @Override 1635 public void accept(CodeBuilder cob) { 1636 cob.ldc(os.toString()) 1637 .pop() 1638 .return_(); 1639 } 1640 }); 1641 } 1642 } 1643 1644 static ClassDesc classDesc(Class<?> cls) { 1645 // assert(VerifyAccess.isTypeVisible(cls, Object.class)) : cls.getName(); 1646 return cls == MethodHandle.class ? CD_MethodHandle 1647 : cls == DirectMethodHandle.class ? CD_DirectMethodHandle 1648 : cls == Object.class ? CD_Object 1649 : cls == MemberName.class ? CD_MemberName 1650 : cls == MethodType.class ? CD_MethodType 1651 : cls.isPrimitive() ? Wrapper.forPrimitiveType(cls).basicClassDescriptor() 1652 : ReferenceClassDescImpl.ofValidated(cls.descriptorString()); 1653 } 1654 1655 static MethodTypeDesc methodDesc(MethodType mt) { 1656 var params = new ClassDesc[mt.parameterCount()]; 1657 for (int i = 0; i < params.length; i++) { 1658 params[i] = classDesc(mt.parameterType(i)); 1659 } 1660 return MethodTypeDescImpl.ofValidated(classDesc(mt.returnType()), params); 1661 } 1662 }