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