From 2cb98befe57235abbabdb6551b0bf2c0a9bf1365 Mon Sep 17 00:00:00 2001 From: Adam Sotona Date: Wed, 13 Dec 2023 17:19:21 +0100 Subject: [PATCH] 8294960: Convert java.base/java.lang.invoke package to use the Classfile API to generate lambdas and method handlers --- .../java/lang/classfile/ClassBuilder.java | 23 +- .../java/lang/invoke/ClassSpecializer.java | 447 +++--- .../lang/invoke/GenerateJLIClassesHelper.java | 27 +- .../invoke/InnerClassLambdaMetafactory.java | 519 +++--- .../lang/invoke/InvokerBytecodeGenerator.java | 1410 ++++++++--------- .../classes/java/lang/invoke/LambdaForm.java | 20 +- .../java/lang/invoke/MethodHandleImpl.java | 61 +- .../java/lang/invoke/MethodHandles.java | 26 +- .../invoke/TypeConvertingMethodAdapter.java | 362 ++--- 9 files changed, 1298 insertions(+), 1597 deletions(-) diff --git a/src/java.base/share/classes/java/lang/classfile/ClassBuilder.java b/src/java.base/share/classes/java/lang/classfile/ClassBuilder.java index f1b8bb13d27e5..1df4c4d28a96d 100644 --- a/src/java.base/share/classes/java/lang/classfile/ClassBuilder.java +++ b/src/java.base/share/classes/java/lang/classfile/ClassBuilder.java @@ -170,7 +170,12 @@ ClassBuilder withField(Utf8Entry name, default ClassBuilder withField(Utf8Entry name, Utf8Entry descriptor, int flags) { - return withField(name, descriptor, fb -> fb.withFlags(flags)); + return withField(name, descriptor, new Consumer() { + @Override + public void accept(FieldBuilder fb) { + fb.withFlags(flags); + } + }); } /** @@ -199,7 +204,13 @@ default ClassBuilder withField(String name, default ClassBuilder withField(String name, ClassDesc descriptor, int flags) { - return withField(name, descriptor, fb -> fb.withFlags(flags)); + return withField(name, descriptor, new Consumer() { + //cannot use lambda here + @Override + public void accept(FieldBuilder fb) { + fb.withFlags(flags); + } + }); } /** @@ -246,7 +257,13 @@ default ClassBuilder withMethodBody(Utf8Entry name, Utf8Entry descriptor, int methodFlags, Consumer handler) { - return withMethod(name, descriptor, methodFlags, mb -> mb.withCode(handler)); + return withMethod(name, descriptor, methodFlags, new Consumer() { + //cannot use lambda here + @Override + public void accept(MethodBuilder mb) { + mb.withCode(handler); + } + }); } /** diff --git a/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java b/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java index a5eb681fb8823..63020a383d998 100644 --- a/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java +++ b/src/java.base/share/classes/java/lang/invoke/ClassSpecializer.java @@ -25,10 +25,13 @@ package java.lang.invoke; +import java.lang.classfile.*; +import java.lang.classfile.attribute.ExceptionsAttribute; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDescs; +import java.lang.constant.MethodTypeDesc; import jdk.internal.loader.BootLoader; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.FieldVisitor; -import jdk.internal.org.objectweb.asm.MethodVisitor; import jdk.internal.vm.annotation.Stable; import sun.invoke.util.BytecodeName; @@ -47,7 +50,7 @@ import static java.lang.invoke.MethodHandleNatives.Constants.REF_putStatic; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; -import static jdk.internal.org.objectweb.asm.Opcodes.*; +import static java.lang.classfile.ClassFile.*; /** * Class specialization code. @@ -572,8 +575,7 @@ Class generateConcreteSpeciesCode(String className, ClassSpecialize } // These are named like constants because there is only one per specialization scheme: - private final String SPECIES_DATA = classBCName(metaType); - private final String SPECIES_DATA_SIG = classSig(SPECIES_DATA); + private final ClassDesc SPECIES_DATA_DESC = metaType.describeConstable().get(); private final String SPECIES_DATA_NAME = sdAccessor.getName(); private final int SPECIES_DATA_MODS = sdAccessor.getModifiers(); private final List TRANSFORM_NAMES; // derived from transformMethods @@ -599,264 +601,210 @@ Class generateConcreteSpeciesCode(String className, ClassSpecialize /*non-public*/ byte[] generateConcreteSpeciesCodeFile(String className0, ClassSpecializer.SpeciesData speciesData) { - final String className = classBCName(className0); - final String superClassName = classBCName(speciesData.deriveSuperClass()); - - final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); + final ClassDesc classDesc = ClassDesc.of(className0); + final ClassDesc superClassDesc = speciesData.deriveSuperClass().describeConstable().get(); final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC - cw.visit(CLASSFILE_VERSION, NOT_ACC_PUBLIC + ACC_FINAL + ACC_SUPER, className, null, superClassName, null); - - final String sourceFile = className.substring(className.lastIndexOf('.')+1); - cw.visitSource(sourceFile, null); - - // emit static types and BMH_SPECIES fields - FieldVisitor fw = cw.visitField(NOT_ACC_PUBLIC + ACC_STATIC, sdFieldName, SPECIES_DATA_SIG, null, null); - fw.visitAnnotation(STABLE_SIG, true); - fw.visitEnd(); - - // handy holder for dealing with groups of typed values (ctor arguments and fields) - class Var { - final int index; - final String name; - final Class type; - final String desc; - final BasicType basicType; - final int slotIndex; - Var(int index, int slotIndex) { - this.index = index; - this.slotIndex = slotIndex; - name = null; type = null; desc = null; - basicType = BasicType.V_TYPE; - } - Var(String name, Class type, Var prev) { - int slotIndex = prev.nextSlotIndex(); - int index = prev.nextIndex(); - if (name == null) name = "x"; - if (name.endsWith("#")) - name = name.substring(0, name.length()-1) + index; - assert(!type.equals(void.class)); - String desc = classSig(type); - BasicType basicType = BasicType.basicType(type); - this.index = index; - this.name = name; - this.type = type; - this.desc = desc; - this.basicType = basicType; - this.slotIndex = slotIndex; - } - Var lastOf(List vars) { - int n = vars.size(); - return (n == 0 ? this : vars.get(n-1)); - } - List fromTypes(List types) { - Var prev = this; - ArrayList result = new ArrayList<>(types.size()); - int i = 0; - for (X x : types) { - String vn = name; - Class vt; - if (x instanceof Class cl) { - vt = cl; - // make the names friendlier if debugging - assert((vn = vn + "_" + (i++)) != null); - } else { - @SuppressWarnings("unchecked") - Var v = (Var) x; - vn = v.name; - vt = v.type; + return ClassFile.of().build(classDesc, clb -> { + clb.withFlags(NOT_ACC_PUBLIC + ACC_FINAL + ACC_SUPER); + clb.withSuperclass(superClassDesc); + + clb.with(SourceFileAttribute.of(classDesc.displayName())); + + // emit static types and BMH_SPECIES fields + clb.withField(sdFieldName, SPECIES_DATA_DESC, NOT_ACC_PUBLIC + ACC_STATIC); + + // handy holder for dealing with groups of typed values (ctor arguments and fields) + class Var { + final int index; + final String name; + final Class type; + final String desc; + final BasicType basicType; + final int slotIndex; + Var(int index, int slotIndex) { + this.index = index; + this.slotIndex = slotIndex; + name = null; type = null; desc = null; + basicType = BasicType.V_TYPE; + } + Var(String name, Class type, Var prev) { + int slotIndex = prev.nextSlotIndex(); + int index = prev.nextIndex(); + if (name == null) name = "x"; + if (name.endsWith("#")) + name = name.substring(0, name.length()-1) + index; + assert(!type.equals(void.class)); + String desc = classSig(type); + BasicType basicType = BasicType.basicType(type); + this.index = index; + this.name = name; + this.type = type; + this.desc = desc; + this.basicType = basicType; + this.slotIndex = slotIndex; + } + Var lastOf(List vars) { + int n = vars.size(); + return (n == 0 ? this : vars.get(n-1)); + } + List fromTypes(List types) { + Var prev = this; + ArrayList result = new ArrayList<>(types.size()); + int i = 0; + for (X x : types) { + String vn = name; + Class vt; + if (x instanceof Class cl) { + vt = cl; + // make the names friendlier if debugging + assert((vn = vn + "_" + (i++)) != null); + } else { + @SuppressWarnings("unchecked") + Var v = (Var) x; + vn = v.name; + vt = v.type; + } + prev = new Var(vn, vt, prev); + result.add(prev); } - prev = new Var(vn, vt, prev); - result.add(prev); + return result; } - return result; - } - int slotSize() { return basicType.basicTypeSlots(); } - int nextIndex() { return index + (slotSize() == 0 ? 0 : 1); } - int nextSlotIndex() { return slotIndex >= 0 ? slotIndex + slotSize() : slotIndex; } - boolean isInHeap() { return slotIndex < 0; } - void emitVarInstruction(int asmop, MethodVisitor mv) { - if (asmop == ALOAD) - asmop = typeLoadOp(basicType.basicTypeChar()); - else - throw new AssertionError("bad op="+asmop+" for desc="+desc); - mv.visitVarInsn(asmop, slotIndex); - } - public void emitFieldInsn(int asmop, MethodVisitor mv) { - mv.visitFieldInsn(asmop, className, name, desc); - } - } - - final Var NO_THIS = new Var(0, 0), - AFTER_THIS = new Var(0, 1), - IN_HEAP = new Var(0, -1); - - // figure out the field types - final List> fieldTypes = speciesData.fieldTypes(); - final List fields = new ArrayList<>(fieldTypes.size()); - { - Var nextF = IN_HEAP; - for (Class ft : fieldTypes) { - String fn = chooseFieldName(ft, nextF.nextIndex()); - nextF = new Var(fn, ft, nextF); - fields.add(nextF); + int slotSize() { return basicType.basicTypeSlots(); } + int nextIndex() { return index + (slotSize() == 0 ? 0 : 1); } + int nextSlotIndex() { return slotIndex >= 0 ? slotIndex + slotSize() : slotIndex; } + boolean isInHeap() { return slotIndex < 0; } + void emitLoadInstruction(CodeBuilder cob) { + cob.loadInstruction(basicType.btKind, slotIndex); + } } - } - - // emit bound argument fields - for (Var field : fields) { - cw.visitField(ACC_FINAL, field.name, field.desc, null, null).visitEnd(); - } - MethodVisitor mv; - - // emit implementation of speciesData() - mv = cw.visitMethod((SPECIES_DATA_MODS & ACC_PPP) + ACC_FINAL, - SPECIES_DATA_NAME, "()" + SPECIES_DATA_SIG, null, null); - mv.visitCode(); - mv.visitFieldInsn(GETSTATIC, className, sdFieldName, SPECIES_DATA_SIG); - mv.visitInsn(ARETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - - // figure out the constructor arguments - MethodType superCtorType = ClassSpecializer.this.baseConstructorType(); - MethodType thisCtorType = superCtorType.appendParameterTypes(fieldTypes); - - // emit constructor - { - mv = cw.visitMethod(ACC_PRIVATE, - "", methodSig(thisCtorType), null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); // this - - final List ctorArgs = AFTER_THIS.fromTypes(superCtorType.parameterList()); - for (Var ca : ctorArgs) { - ca.emitVarInstruction(ALOAD, mv); + final Var NO_THIS = new Var(0, 0), + AFTER_THIS = new Var(0, 1), + IN_HEAP = new Var(0, -1); + + // figure out the field types + final List> fieldTypes = speciesData.fieldTypes(); + final List fields = new ArrayList<>(fieldTypes.size()); + { + Var nextF = IN_HEAP; + for (Class ft : fieldTypes) { + String fn = chooseFieldName(ft, nextF.nextIndex()); + nextF = new Var(fn, ft, nextF); + fields.add(nextF); + } } - // super(ca...) - mv.visitMethodInsn(INVOKESPECIAL, superClassName, - "", methodSig(superCtorType), false); - - // store down fields - Var lastFV = AFTER_THIS.lastOf(ctorArgs); - for (Var f : fields) { - // this.argL1 = argL1 - mv.visitVarInsn(ALOAD, 0); // this - lastFV = new Var(f.name, f.type, lastFV); - lastFV.emitVarInstruction(ALOAD, mv); - f.emitFieldInsn(PUTFIELD, mv); + // emit bound argument fields + for (Var field : fields) { + clb.withField(field.name, ClassDesc.ofDescriptor(field.desc), ACC_FINAL); } - mv.visitInsn(RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } + // emit implementation of speciesData() + clb.withMethodBody(SPECIES_DATA_NAME, MethodTypeDesc.of(SPECIES_DATA_DESC), (SPECIES_DATA_MODS & ACC_PPP) + ACC_FINAL, + cob -> cob.getstatic(classDesc, sdFieldName, SPECIES_DATA_DESC) + .areturn()); - // emit make() ...factory method wrapping constructor - { - MethodType ftryType = thisCtorType.changeReturnType(topClass()); - mv = cw.visitMethod(NOT_ACC_PUBLIC + ACC_STATIC, - "make", methodSig(ftryType), null, null); - mv.visitCode(); - // make instance - mv.visitTypeInsn(NEW, className); - mv.visitInsn(DUP); - // load factory method arguments: ctarg... and arg... - for (Var v : NO_THIS.fromTypes(ftryType.parameterList())) { - v.emitVarInstruction(ALOAD, mv); - } + // figure out the constructor arguments + MethodType superCtorType = ClassSpecializer.this.baseConstructorType(); + MethodType thisCtorType = superCtorType.appendParameterTypes(fieldTypes); - // finally, invoke the constructor and return - mv.visitMethodInsn(INVOKESPECIAL, className, - "", methodSig(thisCtorType), false); - mv.visitInsn(ARETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); - } + // emit constructor + clb.withMethodBody(ConstantDescs.INIT_NAME, methodSig(thisCtorType), ACC_PRIVATE, cob -> { + cob.aload(0); // this - // For each transform, emit the customized override of the transform method. - // This method mixes together some incoming arguments (from the transform's - // static type signature) with the field types themselves, and passes - // the resulting mish-mosh of values to a method handle produced by - // the species itself. (Typically this method handle is the factory - // method of this species or a related one.) - for (int whichtm = 0; whichtm < TRANSFORM_NAMES.size(); whichtm++) { - final String TNAME = TRANSFORM_NAMES.get(whichtm); - final MethodType TTYPE = TRANSFORM_TYPES.get(whichtm); - final int TMODS = TRANSFORM_MODS.get(whichtm); - mv = cw.visitMethod((TMODS & ACC_PPP) | ACC_FINAL, - TNAME, TTYPE.toMethodDescriptorString(), null, E_THROWABLE); - mv.visitCode(); - // return a call to the corresponding "transform helper", something like this: - // MY_SPECIES.transformHelper(whichtm).invokeBasic(ctarg, ..., argL0, ..., xarg) - mv.visitFieldInsn(GETSTATIC, className, - sdFieldName, SPECIES_DATA_SIG); - emitIntConstant(whichtm, mv); - mv.visitMethodInsn(INVOKEVIRTUAL, SPECIES_DATA, - "transformHelper", "(I)" + MH_SIG, false); - - List targs = AFTER_THIS.fromTypes(TTYPE.parameterList()); - List tfields = new ArrayList<>(fields); - // mix them up and load them for the transform helper: - List helperArgs = speciesData.deriveTransformHelperArguments(transformMethods.get(whichtm), whichtm, targs, tfields); - List> helperTypes = new ArrayList<>(helperArgs.size()); - for (Var ha : helperArgs) { - helperTypes.add(ha.basicType.basicTypeClass()); - if (ha.isInHeap()) { - assert(tfields.contains(ha)); - mv.visitVarInsn(ALOAD, 0); - ha.emitFieldInsn(GETFIELD, mv); - } else { - assert(targs.contains(ha)); - ha.emitVarInstruction(ALOAD, mv); + final List ctorArgs = AFTER_THIS.fromTypes(superCtorType.parameterList()); + for (Var ca : ctorArgs) { + ca.emitLoadInstruction(cob); } - } - - // jump into the helper (which is probably a factory method) - final Class rtype = TTYPE.returnType(); - final BasicType rbt = BasicType.basicType(rtype); - MethodType invokeBasicType = MethodType.methodType(rbt.basicTypeClass(), helperTypes); - mv.visitMethodInsn(INVOKEVIRTUAL, MH, - "invokeBasic", methodSig(invokeBasicType), false); - if (rbt == BasicType.L_TYPE) { - mv.visitTypeInsn(CHECKCAST, classBCName(rtype)); - mv.visitInsn(ARETURN); - } else { - throw newInternalError("NYI: transform of type "+rtype); - } - mv.visitMaxs(0, 0); - mv.visitEnd(); - } - cw.visitEnd(); - - return cw.toByteArray(); - } + // super(ca...) + cob.invokespecial(superClassDesc, ConstantDescs.INIT_NAME, methodSig(superCtorType)); + + // store down fields + Var lastFV = AFTER_THIS.lastOf(ctorArgs); + for (Var f : fields) { + // this.argL1 = argL1 + cob.aload(0); // this + lastFV = new Var(f.name, f.type, lastFV); + lastFV.emitLoadInstruction(cob); + cob.putfield(classDesc, f.name, ClassDesc.ofDescriptor(f.desc)); + } - private int typeLoadOp(char t) { - return switch (t) { - case 'L' -> ALOAD; - case 'I' -> ILOAD; - case 'J' -> LLOAD; - case 'F' -> FLOAD; - case 'D' -> DLOAD; - default -> throw newInternalError("unrecognized type " + t); - }; - } + cob.return_(); + }); - private void emitIntConstant(int con, MethodVisitor mv) { - if (ICONST_M1 - ICONST_0 <= con && con <= ICONST_5 - ICONST_0) - mv.visitInsn(ICONST_0 + con); - else if (con == (byte) con) - mv.visitIntInsn(BIPUSH, con); - else if (con == (short) con) - mv.visitIntInsn(SIPUSH, con); - else { - mv.visitLdcInsn(con); - } + // emit make() ...factory method wrapping constructor + MethodType ftryType = thisCtorType.changeReturnType(topClass()); + clb.withMethodBody("make", methodSig(ftryType), NOT_ACC_PUBLIC + ACC_STATIC, cob -> { + // make instance + cob.new_(classDesc) + .dup(); + // load factory method arguments: ctarg... and arg... + for (Var v : NO_THIS.fromTypes(ftryType.parameterList())) { + v.emitLoadInstruction(cob); + } + // finally, invoke the constructor and return + cob.invokespecial(classDesc, ConstantDescs.INIT_NAME, methodSig(thisCtorType)) + .areturn(); + }); + + // For each transform, emit the customized override of the transform method. + // This method mixes together some incoming arguments (from the transform's + // static type signature) with the field types themselves, and passes + // the resulting mish-mosh of values to a method handle produced by + // the species itself. (Typically this method handle is the factory + // method of this species or a related one.) + for (int i = 0; i < TRANSFORM_NAMES.size(); i++) { + final int whichtm = i; + final String TNAME = TRANSFORM_NAMES.get(whichtm); + final MethodType TTYPE = TRANSFORM_TYPES.get(whichtm); + final int TMODS = TRANSFORM_MODS.get(whichtm); + clb.withMethod(TNAME, methodSig(TTYPE), (TMODS & ACC_PPP) | ACC_FINAL, mb -> { + mb.withFlags((TMODS & ACC_PPP) | ACC_FINAL) + .with(ExceptionsAttribute.of(mb.constantPool().classEntry(ConstantDescs.CD_Throwable))) + .withCode(cob -> { + // return a call to the corresponding "transform helper", something like this: + // MY_SPECIES.transformHelper(whichtm).invokeBasic(ctarg, ..., argL0, ..., xarg) + cob.getstatic(classDesc, sdFieldName, SPECIES_DATA_DESC) + .constantInstruction(whichtm) + .invokevirtual(SPECIES_DATA_DESC, "transformHelper", MethodTypeDesc.ofDescriptor("(I)" + MH_SIG)); + + List targs = AFTER_THIS.fromTypes(TTYPE.parameterList()); + List tfields = new ArrayList<>(fields); + // mix them up and load them for the transform helper: + List helperArgs = speciesData.deriveTransformHelperArguments(transformMethods.get(whichtm), whichtm, targs, tfields); + List> helperTypes = new ArrayList<>(helperArgs.size()); + for (Var ha : helperArgs) { + helperTypes.add(ha.basicType.basicTypeClass()); + if (ha.isInHeap()) { + assert(tfields.contains(ha)); + cob.aload(0); + cob.getfield(classDesc, ha.name, ClassDesc.ofDescriptor(ha.desc)); + } else { + assert(targs.contains(ha)); + ha.emitLoadInstruction(cob); + } + } + + // jump into the helper (which is probably a factory method) + final Class rtype = TTYPE.returnType(); + final BasicType rbt = BasicType.basicType(rtype); + MethodType invokeBasicType = MethodType.methodType(rbt.basicTypeClass(), helperTypes); + cob.invokevirtual(ConstantDescs.CD_MethodHandle, + "invokeBasic", + MethodTypeDesc.ofDescriptor(invokeBasicType.toMethodDescriptorString())); + if (rbt == BasicType.L_TYPE) { + cob.checkcast(rtype.describeConstable().get()) + .areturn(); + } else { + throw newInternalError("NYI: transform of type "+rtype); + } + }); + }); + } + }); } // @@ -992,16 +940,13 @@ protected Factory makeFactory() { // Other misc helpers: private static final String MH = "java/lang/invoke/MethodHandle"; private static final String MH_SIG = "L" + MH + ";"; - private static final String STABLE = "jdk/internal/vm/annotation/Stable"; - private static final String STABLE_SIG = "L" + STABLE + ";"; - private static final String[] E_THROWABLE = new String[] { "java/lang/Throwable" }; static { assert(MH_SIG.equals(classSig(MethodHandle.class))); assert(MH.equals(classBCName(MethodHandle.class))); } - static String methodSig(MethodType mt) { - return mt.toMethodDescriptorString(); + static MethodTypeDesc methodSig(MethodType mt) { + return mt.describeConstable().get(); } static String classSig(Class cls) { if (cls.isPrimitive() || cls.isArray()) diff --git a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java index ca652d8d29d14..c23da0828ead2 100644 --- a/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java +++ b/src/java.base/share/classes/java/lang/invoke/GenerateJLIClassesHelper.java @@ -25,8 +25,9 @@ package java.lang.invoke; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.Opcodes; +import java.lang.classfile.ClassFile; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.constant.ClassDesc; import sun.invoke.util.Wrapper; import java.util.ArrayList; @@ -38,6 +39,7 @@ import java.util.TreeSet; import java.util.stream.Stream; +import static java.lang.classfile.ClassFile.*; import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION; import static java.lang.invoke.MethodTypeForm.*; @@ -504,19 +506,14 @@ static byte[] generateInvokersHolderClassBytes(String className, * a class with a specified name. */ private static byte[] generateCodeBytesForLFs(String className, String[] names, LambdaForm[] forms) { - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); - cw.visit(CLASSFILE_VERSION, Opcodes.ACC_PRIVATE + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, - className, null, InvokerBytecodeGenerator.INVOKER_SUPER_NAME, null); - cw.visitSource(className.substring(className.lastIndexOf('/') + 1), null); - - for (int i = 0; i < forms.length; i++) { - InvokerBytecodeGenerator g - = new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()); - g.setClassWriter(cw); - g.addMethod(); - } - - return cw.toByteArray(); + return ClassFile.of().build(ClassDesc.ofInternalName(className), clb -> { + clb.withFlags(ACC_PRIVATE | ACC_FINAL | ACC_SUPER) + .withSuperclass(InvokerBytecodeGenerator.INVOKER_SUPER_DESC) + .with(SourceFileAttribute.of(clb.constantPool().utf8Entry(className.substring(className.lastIndexOf('/') + 1)))); + for (int i = 0; i < forms.length; i++) { + new InvokerBytecodeGenerator(className, names[i], forms[i], forms[i].methodType()).addMethod(clb); + } + }); } private static LambdaForm makeReinvokerFor(MethodType type) { diff --git a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java index 593c66fab7047..ac03f8dd4e11d 100644 --- a/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java +++ b/src/java.base/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java @@ -26,23 +26,31 @@ package java.lang.invoke; import jdk.internal.misc.CDS; -import jdk.internal.org.objectweb.asm.*; import jdk.internal.util.ClassFileDumper; -import sun.invoke.util.BytecodeDescriptor; import sun.invoke.util.VerifyAccess; import sun.security.action.GetBooleanAction; import java.io.Serializable; -import java.lang.constant.ConstantDescs; +import java.lang.classfile.ClassBuilder; +import java.lang.classfile.ClassFile; +import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; +import java.lang.constant.ClassDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.DynamicConstantDesc; +import java.lang.constant.MethodTypeDesc; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.function.Consumer; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; -import static java.lang.invoke.MethodHandleStatics.CLASSFILE_VERSION; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.STRONG; import static java.lang.invoke.MethodType.methodType; -import static jdk.internal.org.objectweb.asm.Opcodes.*; /** * Lambda metafactory implementation which dynamically creates an @@ -51,33 +59,38 @@ * @see LambdaMetafactory */ /* package */ final class InnerClassLambdaMetafactory extends AbstractValidatingLambdaMetafactory { - private static final String METHOD_DESCRIPTOR_VOID = Type.getMethodDescriptor(Type.VOID_TYPE); - private static final String JAVA_LANG_OBJECT = "java/lang/Object"; - private static final String NAME_CTOR = ""; private static final String LAMBDA_INSTANCE_FIELD = "LAMBDA_INSTANCE$"; //Serialization support - private static final String NAME_SERIALIZED_LAMBDA = "java/lang/invoke/SerializedLambda"; - private static final String NAME_NOT_SERIALIZABLE_EXCEPTION = "java/io/NotSerializableException"; - private static final String DESCR_METHOD_WRITE_REPLACE = "()Ljava/lang/Object;"; - private static final String DESCR_METHOD_WRITE_OBJECT = "(Ljava/io/ObjectOutputStream;)V"; - private static final String DESCR_METHOD_READ_OBJECT = "(Ljava/io/ObjectInputStream;)V"; + private static final ClassDesc CD_SERIALIZED_LAMBDA = ClassDesc.ofInternalName("java/lang/invoke/SerializedLambda"); + private static final ClassDesc CD_NOT_SERIALIZABLE_EXCEPTION = ClassDesc.ofInternalName("java/io/NotSerializableException"); + private static final ClassDesc CD_OBJECTOUTPUTSTREAM = ClassDesc.ofInternalName("java/io/ObjectOutputStream"); + private static final ClassDesc CD_OBJECTINPUTSTREAM = ClassDesc.ofInternalName("java/io/ObjectInputStream"); + private static final MethodTypeDesc MTD_METHOD_WRITE_REPLACE = MethodTypeDesc.of(CD_Object); + private static final MethodTypeDesc MTD_METHOD_WRITE_OBJECT = MethodTypeDesc.of(CD_void, CD_OBJECTOUTPUTSTREAM); + private static final MethodTypeDesc MTD_METHOD_READ_OBJECT = MethodTypeDesc.of(CD_void, CD_OBJECTINPUTSTREAM); private static final String NAME_METHOD_WRITE_REPLACE = "writeReplace"; private static final String NAME_METHOD_READ_OBJECT = "readObject"; private static final String NAME_METHOD_WRITE_OBJECT = "writeObject"; - private static final String DESCR_CLASS = "Ljava/lang/Class;"; - private static final String DESCR_STRING = "Ljava/lang/String;"; - private static final String DESCR_OBJECT = "Ljava/lang/Object;"; - private static final String DESCR_CTOR_SERIALIZED_LAMBDA - = "(" + DESCR_CLASS + DESCR_STRING + DESCR_STRING + DESCR_STRING + "I" - + DESCR_STRING + DESCR_STRING + DESCR_STRING + DESCR_STRING + "[" + DESCR_OBJECT + ")V"; + private static final MethodTypeDesc MTD_CTOR_SERIALIZED_LAMBDA = MethodTypeDesc.of(CD_void, + CD_Class, + CD_String, + CD_String, + CD_String, + CD_int, + CD_String, + CD_String, + CD_String, + CD_String, + CD_Object.arrayType()); - private static final String DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION = "(Ljava/lang/String;)V"; - private static final String[] SER_HOSTILE_EXCEPTIONS = new String[] {NAME_NOT_SERIALIZABLE_EXCEPTION}; + private static final MethodTypeDesc MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION = MethodTypeDesc.of(CD_void, CD_String); private static final String[] EMPTY_STRING_ARRAY = new String[0]; + private static final ClassDesc[] EMPTY_CLASSDESC_ARRAY = new ClassDesc[0]; + // For dumping generated classes to disk, for debugging purposes private static final ClassFileDumper lambdaProxyClassFileDumper; @@ -85,7 +98,7 @@ private static final boolean disableEagerInitialization; // condy to load implMethod from class data - private static final ConstantDynamic implMethodCondy; + private static final DynamicConstantDesc implMethodCondy; static { // To dump the lambda proxy classes, set this system property: @@ -98,21 +111,19 @@ disableEagerInitialization = GetBooleanAction.privilegedGetProperty(disableEagerInitializationKey); // condy to load implMethod from class data - MethodType classDataMType = methodType(Object.class, MethodHandles.Lookup.class, String.class, Class.class); - Handle classDataBsm = new Handle(H_INVOKESTATIC, Type.getInternalName(MethodHandles.class), "classData", - classDataMType.descriptorString(), false); - implMethodCondy = new ConstantDynamic(ConstantDescs.DEFAULT_NAME, MethodHandle.class.descriptorString(), classDataBsm); + implMethodCondy = DynamicConstantDesc.ofNamed(BSM_CLASS_DATA, DEFAULT_NAME, CD_MethodHandle); } // See context values in AbstractValidatingLambdaMetafactory - private final String implMethodClassName; // Name of type containing implementation "CC" + private final ClassDesc implMethodClassDesc; // Name of type containing implementation "CC" private final String implMethodName; // Name of implementation method "impl" - private final String implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" + private final MethodTypeDesc implMethodDesc; // Type descriptor for implementation methods "(I)Ljava/lang/String;" private final MethodType constructorType; // Generated class constructor type "(CC)void" - private final ClassWriter cw; // ASM class writer + private final MethodTypeDesc constructorTypeDesc;// Type descriptor for the generated class constructor type "(CC)void" private final String[] argNames; // Generated names for the constructor arguments - private final String[] argDescs; // Type descriptors for the constructor arguments - private final String lambdaClassName; // Generated name for the generated class "X$$Lambda" + private final ClassDesc[] argDescs; // Type descriptors for the constructor arguments + private final String lambdaClassName; // Generated name for the generated class "X$$Lambda$1" + private final ClassDesc lambdaClassDesc; // Type descriptor for the generated class "X$$Lambda$1" private final boolean useImplMethodHandle; // use MethodHandle invocation instead of symbolic bytecode invocation /** @@ -168,11 +179,13 @@ public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, super(caller, factoryType, interfaceMethodName, interfaceMethodType, implementation, dynamicMethodType, isSerializable, altInterfaces, altMethods); - implMethodClassName = implClass.getName().replace('.', '/'); + implMethodClassDesc = ClassDesc.ofDescriptor(implClass.descriptorString()); implMethodName = implInfo.getName(); - implMethodDesc = implInfo.getMethodType().toMethodDescriptorString(); + implMethodDesc = MethodTypeDesc.ofDescriptor(implInfo.getMethodType().descriptorString()); constructorType = factoryType.changeReturnType(Void.TYPE); + constructorTypeDesc = MethodTypeDesc.ofDescriptor(constructorType.descriptorString()); lambdaClassName = lambdaClassName(targetClass); + lambdaClassDesc = ClassDesc.ofInternalName(lambdaClassName); // If the target class invokes a protected method inherited from a // superclass in a different package, or does 'invokespecial', the // lambda class has no access to the resolved method. Instead, we need @@ -181,18 +194,18 @@ public InnerClassLambdaMetafactory(MethodHandles.Lookup caller, // generating bridges in the target class) useImplMethodHandle = (Modifier.isProtected(implInfo.getModifiers()) && !VerifyAccess.isSamePackage(targetClass, implInfo.getDeclaringClass())) || - implKind == H_INVOKESPECIAL; - cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + implKind == MethodHandleInfo.REF_invokeSpecial; int parameterCount = factoryType.parameterCount(); if (parameterCount > 0) { argNames = new String[parameterCount]; - argDescs = new String[parameterCount]; + argDescs = new ClassDesc[parameterCount]; for (int i = 0; i < parameterCount; i++) { argNames[i] = "arg$" + (i + 1); - argDescs[i] = BytecodeDescriptor.unparse(factoryType.parameterType(i)); + argDescs[i] = factoryType.parameterType(i).describeConstable().orElseThrow(); } } else { - argNames = argDescs = EMPTY_STRING_ARRAY; + argNames = EMPTY_STRING_ARRAY; + argDescs = EMPTY_CLASSDESC_ARRAY; } } @@ -298,65 +311,63 @@ private Class spinInnerClass() throws LambdaConversionException { * is not found */ private Class generateInnerClass() throws LambdaConversionException { - String[] interfaceNames; - String interfaceName = interfaceClass.getName().replace('.', '/'); + List interfaces; + ClassDesc interfaceDesc = ClassDesc.ofDescriptor(interfaceClass.descriptorString()); boolean accidentallySerializable = !isSerializable && Serializable.class.isAssignableFrom(interfaceClass); if (altInterfaces.length == 0) { - interfaceNames = new String[]{interfaceName}; + interfaces = List.of(interfaceDesc); } else { // Assure no duplicate interfaces (ClassFormatError) - Set itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1); - itfs.add(interfaceName); + Set itfs = LinkedHashSet.newLinkedHashSet(altInterfaces.length + 1); + itfs.add(interfaceDesc); for (Class i : altInterfaces) { - itfs.add(i.getName().replace('.', '/')); + itfs.add(ClassDesc.ofDescriptor(i.descriptorString())); accidentallySerializable |= !isSerializable && Serializable.class.isAssignableFrom(i); } - interfaceNames = itfs.toArray(new String[itfs.size()]); + interfaces = new ArrayList<>(itfs); } + final boolean finalAccidentallySerializable = accidentallySerializable; + final byte[] classBytes = ClassFile.of().build(lambdaClassDesc, new Consumer() { + @Override + public void accept(ClassBuilder clb) { + clb.withFlags(ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC); + clb.withInterfaceSymbols(interfaces); + // Generate final fields to be filled in by constructor + for (int i = 0; i < argDescs.length; i++) { + clb.withField(argNames[i], argDescs[i], ACC_PRIVATE + ACC_FINAL); + } - cw.visit(CLASSFILE_VERSION, ACC_SUPER + ACC_FINAL + ACC_SYNTHETIC, - lambdaClassName, null, - JAVA_LANG_OBJECT, interfaceNames); - - // Generate final fields to be filled in by constructor - for (int i = 0; i < argDescs.length; i++) { - FieldVisitor fv = cw.visitField(ACC_PRIVATE + ACC_FINAL, - argNames[i], - argDescs[i], - null, null); - fv.visitEnd(); - } + generateConstructor(clb); - generateConstructor(); + if (factoryType.parameterCount() == 0 && disableEagerInitialization) { + generateClassInitializer(clb); + } - if (factoryType.parameterCount() == 0 && disableEagerInitialization) { - generateClassInitializer(); - } + // Forward the SAM method + clb.withMethodBody(interfaceMethodName, + MethodTypeDesc.ofDescriptor(interfaceMethodType.toMethodDescriptorString()), + ACC_PUBLIC, + forwardingMethod(interfaceMethodType)); + + // Forward the bridges + if (altMethods != null) { + for (MethodType mt : altMethods) { + clb.withMethodBody(interfaceMethodName, + MethodTypeDesc.ofDescriptor(mt.toMethodDescriptorString()), + ACC_PUBLIC|ACC_BRIDGE, + forwardingMethod(mt)); + } + } - // Forward the SAM method - MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName, - interfaceMethodType.toMethodDescriptorString(), null, null); - new ForwardingMethodGenerator(mv).generate(interfaceMethodType); - - // Forward the altMethods - if (altMethods != null) { - for (MethodType mt : altMethods) { - mv = cw.visitMethod(ACC_PUBLIC, interfaceMethodName, - mt.toMethodDescriptorString(), null, null); - new ForwardingMethodGenerator(mv).generate(mt); + if (isSerializable) + generateSerializationFriendlyMethods(clb); + else if (finalAccidentallySerializable) + generateSerializationHostileMethods(clb); } - } - - if (isSerializable) - generateSerializationFriendlyMethods(); - else if (accidentallySerializable) - generateSerializationHostileMethods(); - - cw.visitEnd(); + }); // Define the generated class in this VM. - final byte[] classBytes = cw.toByteArray(); try { // this class is linked at the indy callsite; so define a hidden nestmate var classdata = useImplMethodHandle? implementation : null; @@ -371,237 +382,187 @@ else if (accidentallySerializable) /** * Generate a static field and a static initializer that sets this field to an instance of the lambda */ - private void generateClassInitializer() { - String lambdaTypeDescriptor = factoryType.returnType().descriptorString(); + private void generateClassInitializer(ClassBuilder clb) { + ClassDesc lambdaTypeDescriptor = ClassDesc.ofDescriptor(factoryType.returnType().descriptorString()); // Generate the static final field that holds the lambda singleton - FieldVisitor fv = cw.visitField(ACC_PRIVATE | ACC_STATIC | ACC_FINAL, - LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, null, null); - fv.visitEnd(); + clb.withField(LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor, ACC_PRIVATE | ACC_STATIC | ACC_FINAL); // Instantiate the lambda and store it to the static final field - MethodVisitor clinit = cw.visitMethod(ACC_STATIC, "", "()V", null, null); - clinit.visitCode(); - - clinit.visitTypeInsn(NEW, lambdaClassName); - clinit.visitInsn(Opcodes.DUP); - assert factoryType.parameterCount() == 0; - clinit.visitMethodInsn(INVOKESPECIAL, lambdaClassName, NAME_CTOR, constructorType.toMethodDescriptorString(), false); - clinit.visitFieldInsn(PUTSTATIC, lambdaClassName, LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor); - - clinit.visitInsn(RETURN); - clinit.visitMaxs(-1, -1); - clinit.visitEnd(); + clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.new_(lambdaClassDesc) + .dup(); + assert factoryType.parameterCount() == 0; + cob.invokespecial(lambdaClassDesc, INIT_NAME, constructorTypeDesc) + .putstatic(lambdaClassDesc, LAMBDA_INSTANCE_FIELD, lambdaTypeDescriptor) + .return_(); + } + }); } /** * Generate the constructor for the class */ - private void generateConstructor() { + private void generateConstructor(ClassBuilder clb) { // Generate constructor - MethodVisitor ctor = cw.visitMethod(ACC_PRIVATE, NAME_CTOR, - constructorType.toMethodDescriptorString(), null, null); - ctor.visitCode(); - ctor.visitVarInsn(ALOAD, 0); - ctor.visitMethodInsn(INVOKESPECIAL, JAVA_LANG_OBJECT, NAME_CTOR, - METHOD_DESCRIPTOR_VOID, false); - int parameterCount = factoryType.parameterCount(); - for (int i = 0, lvIndex = 0; i < parameterCount; i++) { - ctor.visitVarInsn(ALOAD, 0); - Class argType = factoryType.parameterType(i); - ctor.visitVarInsn(getLoadOpcode(argType), lvIndex + 1); - lvIndex += getParameterSize(argType); - ctor.visitFieldInsn(PUTFIELD, lambdaClassName, argNames[i], argDescs[i]); - } - ctor.visitInsn(RETURN); - // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored - ctor.visitMaxs(-1, -1); - ctor.visitEnd(); + clb.withMethodBody(INIT_NAME, constructorTypeDesc, ACC_PRIVATE, + new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.aload(0); + cob.invokespecial(CD_Object, INIT_NAME, + MethodTypeDesc.of(CD_void)); + int parameterCount = factoryType.parameterCount(); + for (int i = 0; i < parameterCount; i++) { + cob.aload(0); + Class argType = factoryType.parameterType(i); + cob.loadInstruction(TypeKind.from(argType), cob.parameterSlot(i)); + cob.putfield(lambdaClassDesc, argNames[i], argDescs[i]); + } + cob.return_(); + } + }); } /** * Generate a writeReplace method that supports serialization */ - private void generateSerializationFriendlyMethods() { - TypeConvertingMethodAdapter mv - = new TypeConvertingMethodAdapter( - cw.visitMethod(ACC_PRIVATE + ACC_FINAL, - NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, - null, null)); - - mv.visitCode(); - mv.visitTypeInsn(NEW, NAME_SERIALIZED_LAMBDA); - mv.visitInsn(DUP); - mv.visitLdcInsn(Type.getType(targetClass)); - mv.visitLdcInsn(factoryType.returnType().getName().replace('.', '/')); - mv.visitLdcInsn(interfaceMethodName); - mv.visitLdcInsn(interfaceMethodType.toMethodDescriptorString()); - mv.visitLdcInsn(implInfo.getReferenceKind()); - mv.visitLdcInsn(implInfo.getDeclaringClass().getName().replace('.', '/')); - mv.visitLdcInsn(implInfo.getName()); - mv.visitLdcInsn(implInfo.getMethodType().toMethodDescriptorString()); - mv.visitLdcInsn(dynamicMethodType.toMethodDescriptorString()); - mv.iconst(argDescs.length); - mv.visitTypeInsn(ANEWARRAY, JAVA_LANG_OBJECT); - for (int i = 0; i < argDescs.length; i++) { - mv.visitInsn(DUP); - mv.iconst(i); - mv.visitVarInsn(ALOAD, 0); - mv.visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); - mv.boxIfTypePrimitive(Type.getType(argDescs[i])); - mv.visitInsn(AASTORE); - } - mv.visitMethodInsn(INVOKESPECIAL, NAME_SERIALIZED_LAMBDA, NAME_CTOR, - DESCR_CTOR_SERIALIZED_LAMBDA, false); - mv.visitInsn(ARETURN); - // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored - mv.visitMaxs(-1, -1); - mv.visitEnd(); + private void generateSerializationFriendlyMethods(ClassBuilder clb) { + clb.withMethodBody(NAME_METHOD_WRITE_REPLACE, MTD_METHOD_WRITE_REPLACE, ACC_PRIVATE + ACC_FINAL, + new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.new_(CD_SERIALIZED_LAMBDA) + .dup() + .constantInstruction(Opcode.LDC, targetClass.describeConstable().get()) + .constantInstruction(Opcode.LDC, factoryType.returnType().getName().replace('.', '/')) + .constantInstruction(Opcode.LDC, interfaceMethodName) + .constantInstruction(Opcode.LDC, interfaceMethodType.toMethodDescriptorString()) + .constantInstruction(Opcode.LDC, implInfo.getReferenceKind()) + .constantInstruction(Opcode.LDC, implInfo.getDeclaringClass().getName().replace('.', '/')) + .constantInstruction(Opcode.LDC, implInfo.getName()) + .constantInstruction(Opcode.LDC, implInfo.getMethodType().toMethodDescriptorString()) + .constantInstruction(Opcode.LDC, dynamicMethodType.toMethodDescriptorString()) + .constantInstruction(argDescs.length); + + cob.anewarray(CD_Object); + for (int i = 0; i < argDescs.length; i++) { + cob.dup(); + cob.constantInstruction(i); + cob.aload(0); + cob.getfield(lambdaClassDesc, argNames[i], argDescs[i]); + TypeConvertingMethodAdapter.boxIfTypePrimitive(cob, TypeKind.from(argDescs[i])); + cob.aastore(); + } + cob.invokespecial(CD_SERIALIZED_LAMBDA, INIT_NAME, + MTD_CTOR_SERIALIZED_LAMBDA, false); + cob.areturn(); + } + }); } /** * Generate a readObject/writeObject method that is hostile to serialization */ - private void generateSerializationHostileMethods() { - MethodVisitor mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, - NAME_METHOD_WRITE_OBJECT, DESCR_METHOD_WRITE_OBJECT, - null, SER_HOSTILE_EXCEPTIONS); - mv.visitCode(); - mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); - mv.visitInsn(DUP); - mv.visitLdcInsn("Non-serializable lambda"); - mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, - DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION, false); - mv.visitInsn(ATHROW); - mv.visitMaxs(-1, -1); - mv.visitEnd(); - - mv = cw.visitMethod(ACC_PRIVATE + ACC_FINAL, - NAME_METHOD_READ_OBJECT, DESCR_METHOD_READ_OBJECT, - null, SER_HOSTILE_EXCEPTIONS); - mv.visitCode(); - mv.visitTypeInsn(NEW, NAME_NOT_SERIALIZABLE_EXCEPTION); - mv.visitInsn(DUP); - mv.visitLdcInsn("Non-serializable lambda"); - mv.visitMethodInsn(INVOKESPECIAL, NAME_NOT_SERIALIZABLE_EXCEPTION, NAME_CTOR, - DESCR_CTOR_NOT_SERIALIZABLE_EXCEPTION, false); - mv.visitInsn(ATHROW); - mv.visitMaxs(-1, -1); - mv.visitEnd(); + private void generateSerializationHostileMethods(ClassBuilder clb) { + clb.withMethodBody(NAME_METHOD_WRITE_OBJECT, MTD_METHOD_WRITE_OBJECT, ACC_PRIVATE + ACC_FINAL, + new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.new_(CD_NOT_SERIALIZABLE_EXCEPTION) + .dup() + .constantInstruction(Opcode.LDC, "Non-serializable lambda") + .invokespecial(CD_NOT_SERIALIZABLE_EXCEPTION, INIT_NAME, + MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION, false) + .throwInstruction(); + } + }); + clb.withMethodBody(NAME_METHOD_READ_OBJECT, MTD_METHOD_READ_OBJECT, ACC_PRIVATE + ACC_FINAL, + new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.new_(CD_NOT_SERIALIZABLE_EXCEPTION) + .dup() + .constantInstruction(Opcode.LDC, "Non-serializable lambda") + .invokespecial(CD_NOT_SERIALIZABLE_EXCEPTION, INIT_NAME, + MTD_CTOR_NOT_SERIALIZABLE_EXCEPTION, false) + .throwInstruction(); + } + }); } /** - * This class generates a method body which calls the lambda implementation + * This method generates a method body which calls the lambda implementation * method, converting arguments, as needed. */ - private class ForwardingMethodGenerator extends TypeConvertingMethodAdapter { - - ForwardingMethodGenerator(MethodVisitor mv) { - super(mv); - } - - void generate(MethodType methodType) { - visitCode(); - - if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { - visitTypeInsn(NEW, implMethodClassName); - visitInsn(DUP); - } - if (useImplMethodHandle) { - visitLdcInsn(implMethodCondy); - } - for (int i = 0; i < argNames.length; i++) { - visitVarInsn(ALOAD, 0); - visitFieldInsn(GETFIELD, lambdaClassName, argNames[i], argDescs[i]); - } + Consumer forwardingMethod(MethodType methodType) { + return new Consumer() { + @Override + public void accept(CodeBuilder cob) { + if (implKind == MethodHandleInfo.REF_newInvokeSpecial) { + cob.new_(implMethodClassDesc) + .dup(); + } + if (useImplMethodHandle) { + cob.constantInstruction(Opcode.LDC, implMethodCondy); + } + for (int i = 0; i < argNames.length; i++) { + cob.aload(0) + .getfield(lambdaClassDesc, argNames[i], argDescs[i]); + } - convertArgumentTypes(methodType); + convertArgumentTypes(cob, methodType); - if (useImplMethodHandle) { - MethodType mtype = implInfo.getMethodType(); - if (implKind != MethodHandleInfo.REF_invokeStatic) { - mtype = mtype.insertParameterTypes(0, implClass); + if (useImplMethodHandle) { + MethodType mtype = implInfo.getMethodType(); + if (implKind != MethodHandleInfo.REF_invokeStatic) { + mtype = mtype.insertParameterTypes(0, implClass); + } + cob.invokevirtual(CD_MethodHandle, "invokeExact", + MethodTypeDesc.ofDescriptor(mtype.descriptorString())); + } else { + // Invoke the method we want to forward to + cob.invokeInstruction(invocationOpcode(), implMethodClassDesc, + implMethodName, implMethodDesc, + implClass.isInterface()); } - visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", - "invokeExact", mtype.descriptorString(), false); - } else { - // Invoke the method we want to forward to - visitMethodInsn(invocationOpcode(), implMethodClassName, - implMethodName, implMethodDesc, - implClass.isInterface()); + // Convert the return value (if any) and return it + // Note: if adapting from non-void to void, the 'return' + // instruction will pop the unneeded result + Class implReturnClass = implMethodType.returnType(); + Class samReturnClass = methodType.returnType(); + TypeConvertingMethodAdapter.convertType(cob, implReturnClass, samReturnClass, samReturnClass); + cob.returnInstruction(TypeKind.from(samReturnClass)); } - // Convert the return value (if any) and return it - // Note: if adapting from non-void to void, the 'return' - // instruction will pop the unneeded result - Class implReturnClass = implMethodType.returnType(); - Class samReturnClass = methodType.returnType(); - convertType(implReturnClass, samReturnClass, samReturnClass); - visitInsn(getReturnOpcode(samReturnClass)); - // Maxs computed by ClassWriter.COMPUTE_MAXS,these arguments ignored - visitMaxs(-1, -1); - visitEnd(); - } - - private void convertArgumentTypes(MethodType samType) { - int lvIndex = 0; - int samParametersLength = samType.parameterCount(); - int captureArity = factoryType.parameterCount(); - for (int i = 0; i < samParametersLength; i++) { - Class argType = samType.parameterType(i); - visitVarInsn(getLoadOpcode(argType), lvIndex + 1); - lvIndex += getParameterSize(argType); - convertType(argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i)); - } - } - - private int invocationOpcode() throws InternalError { - return switch (implKind) { - case MethodHandleInfo.REF_invokeStatic -> INVOKESTATIC; - case MethodHandleInfo.REF_newInvokeSpecial -> INVOKESPECIAL; - case MethodHandleInfo.REF_invokeVirtual -> INVOKEVIRTUAL; - case MethodHandleInfo.REF_invokeInterface -> INVOKEINTERFACE; - case MethodHandleInfo.REF_invokeSpecial -> INVOKESPECIAL; - default -> throw new InternalError("Unexpected invocation kind: " + implKind); - }; - } + }; } - static int getParameterSize(Class c) { - if (c == Void.TYPE) { - return 0; - } else if (c == Long.TYPE || c == Double.TYPE) { - return 2; + private void convertArgumentTypes(CodeBuilder cob, MethodType samType) { + int samParametersLength = samType.parameterCount(); + int captureArity = factoryType.parameterCount(); + for (int i = 0; i < samParametersLength; i++) { + Class argType = samType.parameterType(i); + cob.loadInstruction(TypeKind.from(argType), cob.parameterSlot(i)); + TypeConvertingMethodAdapter.convertType(cob, argType, implMethodType.parameterType(captureArity + i), dynamicMethodType.parameterType(i)); } - return 1; } - static int getLoadOpcode(Class c) { - if(c == Void.TYPE) { - throw new InternalError("Unexpected void type of load opcode"); - } - return ILOAD + getOpcodeOffset(c); - } - - static int getReturnOpcode(Class c) { - if(c == Void.TYPE) { - return RETURN; - } - return IRETURN + getOpcodeOffset(c); - } - - private static int getOpcodeOffset(Class c) { - if (c.isPrimitive()) { - if (c == Long.TYPE) { - return 1; - } else if (c == Float.TYPE) { - return 2; - } else if (c == Double.TYPE) { - return 3; - } - return 0; - } else { - return 4; - } + private Opcode invocationOpcode() throws InternalError { + return switch (implKind) { + case MethodHandleInfo.REF_invokeStatic -> + Opcode.INVOKESTATIC; + case MethodHandleInfo.REF_newInvokeSpecial -> + Opcode.INVOKESPECIAL; + case MethodHandleInfo.REF_invokeVirtual -> + Opcode.INVOKEVIRTUAL; + case MethodHandleInfo.REF_invokeInterface -> + Opcode.INVOKEINTERFACE; + case MethodHandleInfo.REF_invokeSpecial -> + Opcode.INVOKESPECIAL; + default -> + throw new InternalError("Unexpected invocation kind: " + implKind); + }; } - } diff --git a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java index 207d1e345ae94..ce90092c23d4e 100644 --- a/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java +++ b/src/java.base/share/classes/java/lang/invoke/InvokerBytecodeGenerator.java @@ -25,30 +25,36 @@ package java.lang.invoke; -import jdk.internal.org.objectweb.asm.ClassWriter; -import jdk.internal.org.objectweb.asm.FieldVisitor; -import jdk.internal.org.objectweb.asm.Label; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; +import jdk.internal.vm.annotation.DontInline; +import jdk.internal.vm.annotation.ForceInline; +import jdk.internal.vm.annotation.Hidden; import sun.invoke.util.VerifyAccess; import sun.invoke.util.VerifyType; import sun.invoke.util.Wrapper; +import java.lang.classfile.*; +import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute; +import java.lang.classfile.attribute.SourceFileAttribute; +import java.lang.classfile.instruction.SwitchCase; +import java.lang.constant.ClassDesc; +import java.lang.constant.ConstantDesc; +import static java.lang.constant.ConstantDescs.*; +import java.lang.constant.MethodTypeDesc; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Set; +import java.util.function.Consumer; import java.util.stream.Stream; -import static java.lang.invoke.LambdaForm.BasicType; -import static java.lang.invoke.LambdaForm.BasicType.*; +import static java.lang.classfile.ClassFile.*; import static java.lang.invoke.LambdaForm.*; +import static java.lang.invoke.LambdaForm.BasicType.*; import static java.lang.invoke.MethodHandleNatives.Constants.*; import static java.lang.invoke.MethodHandleStatics.*; -import static java.lang.invoke.MethodHandles.Lookup.*; +import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; /** * Code generation backend for LambdaForm. @@ -57,32 +63,29 @@ */ class InvokerBytecodeGenerator { /** Define class names for convenience. */ - private static final String MH = "java/lang/invoke/MethodHandle"; - private static final String MHI = "java/lang/invoke/MethodHandleImpl"; - private static final String LF = "java/lang/invoke/LambdaForm"; - private static final String LFN = "java/lang/invoke/LambdaForm$Name"; - private static final String CLS = "java/lang/Class"; - private static final String OBJ = "java/lang/Object"; - private static final String OBJARY = "[Ljava/lang/Object;"; - - private static final String LOOP_CLAUSES = MHI + "$LoopClauses"; - private static final String MHARY2 = "[[L" + MH + ";"; - private static final String MH_SIG = "L" + MH + ";"; - - - private static final String LF_SIG = "L" + LF + ";"; - private static final String LFN_SIG = "L" + LFN + ";"; - private static final String LL_SIG = "(L" + OBJ + ";)L" + OBJ + ";"; - private static final String LLV_SIG = "(L" + OBJ + ";L" + OBJ + ";)V"; - private static final String CLASS_PREFIX = LF + "$"; + private static final ClassDesc CD_MHI = ClassDesc.ofInternalName("java/lang/invoke/MethodHandleImpl"); + private static final ClassDesc CD_LF = ClassDesc.ofInternalName("java/lang/invoke/LambdaForm"); + private static final ClassDesc CD_LFN = ClassDesc.ofInternalName("java/lang/invoke/LambdaForm$Name"); + private static final ClassDesc CD_OBJARY = CD_Object.arrayType(); + + private static final ClassDesc CD_LOOP_CLAUSES = ClassDesc.ofInternalName("java/lang/invoke/MethodHandleImpl$LoopClauses"); + + private static final ClassDesc CD_MHARY2 = CD_MethodHandle.arrayType(2); + + + private static final MethodTypeDesc MTD_LL_SIG = MethodTypeDesc.of(CD_Object, CD_Object); + private static final MethodTypeDesc MTD_LLV_SIG = MethodTypeDesc.of(CD_void, CD_Object, CD_Object); + private static final MethodTypeDesc MTD_Object_int = MethodTypeDesc.of(CD_Object, CD_int); + private static final String CLASS_PREFIX = "java/lang/invoke/LambdaForm$"; private static final String SOURCE_PREFIX = "LambdaForm$"; /** Name of its super class*/ - static final String INVOKER_SUPER_NAME = OBJ; + static final ClassDesc INVOKER_SUPER_DESC = CD_Object; /** Name of new class */ private final String name; private final String className; + private final ClassDesc classDesc; private final LambdaForm lambdaForm; private final String invokerName; @@ -92,14 +95,11 @@ class InvokerBytecodeGenerator { private int[] localsMap; // index private Class[] localClasses; // type - /** ASM bytecode generation. */ - private ClassWriter cw; - private MethodVisitor mv; private final List classData = new ArrayList<>(); - /** Single element internal class name lookup cache. */ + /** Single element class descriptor lookup cache. */ private Class lastClass; - private String lastInternalName; + private ClassDesc lastClassDesc; private static final MemberName.Factory MEMBERNAME_FACTORY = MemberName.getFactory(); private static final Class HOST_CLASS = LambdaForm.class; @@ -126,6 +126,7 @@ private InvokerBytecodeGenerator(LambdaForm lambdaForm, int localsMapSize, } this.name = name; this.className = CLASS_PREFIX + name; + this.classDesc = ClassDesc.ofInternalName(className); this.lambdaForm = lambdaForm; this.invokerName = invokerName; this.invokerType = invokerType; @@ -188,10 +189,10 @@ private static String makeDumpableClassName(String className) { static class ClassData { final String name; - final String desc; + final ClassDesc desc; final Object value; - ClassData(String name, String desc, Object value) { + ClassData(String name, ClassDesc desc, Object value) { this.name = name; this.desc = desc; this.value = value; @@ -204,15 +205,15 @@ public String toString() { } String classData(Object arg) { - String desc; + ClassDesc desc; if (arg instanceof Class) { - desc = "Ljava/lang/Class;"; + desc = CD_Class; } else if (arg instanceof MethodHandle) { - desc = MH_SIG; + desc = CD_MethodHandle; } else if (arg instanceof LambdaForm) { - desc = LF_SIG; + desc = CD_LF; } else { - desc = "Ljava/lang/Object;"; + desc = CD_Object; } // unique static variable name @@ -265,27 +266,27 @@ private static MemberName resolveInvokerMember(Class invokerClass, String nam /** * Set up class file generation. */ - private ClassWriter classFilePrologue() { + private byte[] classFileSetup(Consumer config) { final int NOT_ACC_PUBLIC = 0; // not ACC_PUBLIC - ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES); - setClassWriter(cw); - cw.visit(CLASSFILE_VERSION, NOT_ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER, - className, null, INVOKER_SUPER_NAME, null); - cw.visitSource(SOURCE_PREFIX + name, null); - return cw; - } - - private void methodPrologue() { - String invokerDesc = invokerType.toMethodDescriptorString(); - mv = cw.visitMethod(Opcodes.ACC_STATIC, invokerName, invokerDesc, null, null); + try { + return ClassFile.of().build(classDesc, new Consumer<>() { + @Override + public void accept(ClassBuilder clb) { + clb.withFlags(NOT_ACC_PUBLIC + ACC_FINAL + ACC_SUPER); + clb.withSuperclass(INVOKER_SUPER_DESC); + clb.withVersion(CLASSFILE_VERSION, 0); + clb.with(SourceFileAttribute.of(clb.constantPool().utf8Entry(SOURCE_PREFIX + name))); + config.accept(clb); + } + }); + } catch (RuntimeException e) { + throw new BytecodeGenerationException(e); + } } - /** - * Tear down class file generation. - */ - private void methodEpilogue() { - mv.visitMaxs(0, 0); - mv.visitEnd(); + private void methodSetup(ClassBuilder clb, Consumer config) { + var invokerDesc = MethodTypeDesc.ofDescriptor(invokerType.toMethodDescriptorString()); + clb.withMethod(invokerName, invokerDesc, ACC_STATIC, config); } /** @@ -318,202 +319,40 @@ private Object classDataValues() { * to initialize the static final fields with the live class data * LambdaForms can't use condy due to bootstrapping issue. */ - static void clinit(ClassWriter cw, String className, List classData) { + static void clinit(ClassBuilder clb, ClassDesc classDesc, List classData) { if (classData.isEmpty()) return; for (ClassData p : classData) { // add the static field - FieldVisitor fv = cw.visitField(Opcodes.ACC_STATIC|Opcodes.ACC_FINAL, p.name, p.desc, null, null); - fv.visitEnd(); - } - - MethodVisitor mv = cw.visitMethod(Opcodes.ACC_STATIC, "", "()V", null, null); - mv.visitCode(); - mv.visitLdcInsn(Type.getType("L" + className + ";")); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/invoke/MethodHandles", - "classData", "(Ljava/lang/Class;)Ljava/lang/Object;", false); - if (classData.size() == 1) { - ClassData p = classData.get(0); - mv.visitTypeInsn(Opcodes.CHECKCAST, p.desc.substring(1, p.desc.length()-1)); - mv.visitFieldInsn(Opcodes.PUTSTATIC, className, p.name, p.desc); - } else { - mv.visitTypeInsn(Opcodes.CHECKCAST, "java/util/List"); - mv.visitVarInsn(Opcodes.ASTORE, 0); - int index = 0; - for (ClassData p : classData) { - // initialize the static field - mv.visitVarInsn(Opcodes.ALOAD, 0); - emitIconstInsn(mv, index++); - mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, "java/util/List", - "get", "(I)Ljava/lang/Object;", true); - mv.visitTypeInsn(Opcodes.CHECKCAST, p.desc.substring(1, p.desc.length()-1)); - mv.visitFieldInsn(Opcodes.PUTSTATIC, className, p.name, p.desc); - } - } - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(2, 1); - mv.visitEnd(); - } - - /* - * Low-level emit helpers. - */ - private void emitConst(Object con) { - if (con == null) { - mv.visitInsn(Opcodes.ACONST_NULL); - return; - } - if (con instanceof Integer) { - emitIconstInsn((int) con); - return; - } - if (con instanceof Byte) { - emitIconstInsn((byte)con); - return; - } - if (con instanceof Short) { - emitIconstInsn((short)con); - return; - } - if (con instanceof Character) { - emitIconstInsn((char)con); - return; - } - if (con instanceof Long) { - long x = (long) con; - short sx = (short)x; - if (x == sx) { - if (sx >= 0 && sx <= 1) { - mv.visitInsn(Opcodes.LCONST_0 + (int) sx); - } else { - emitIconstInsn((int) x); - mv.visitInsn(Opcodes.I2L); - } - return; - } - } - if (con instanceof Float) { - float x = (float) con; - short sx = (short)x; - if (x == sx) { - if (sx >= 0 && sx <= 2) { - mv.visitInsn(Opcodes.FCONST_0 + (int) sx); + clb.withField(p.name, p.desc, ACC_STATIC|ACC_FINAL); + } + + clb.withMethodBody(CLASS_INIT_NAME, MTD_void, ACC_STATIC, new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.constantInstruction(classDesc) + .invokestatic(CD_MethodHandles, "classData", MethodTypeDesc.of(CD_Object, CD_Class)); + if (classData.size() == 1) { + ClassData p = classData.get(0); + cob.checkcast(p.desc) + .putstatic(classDesc, p.name, p.desc); } else { - emitIconstInsn((int) x); - mv.visitInsn(Opcodes.I2F); - } - return; - } - } - if (con instanceof Double) { - double x = (double) con; - short sx = (short)x; - if (x == sx) { - if (sx >= 0 && sx <= 1) { - mv.visitInsn(Opcodes.DCONST_0 + (int) sx); - } else { - emitIconstInsn((int) x); - mv.visitInsn(Opcodes.I2D); + cob.checkcast(CD_List) + .astore(0); + int index = 0; + for (ClassData p : classData) { + // initialize the static field + cob.aload(0) + .constantInstruction(index++) + .invokeinterface(CD_List, "get", MTD_Object_int) + .checkcast(p.desc) + .putstatic(classDesc, p.name, p.desc); + } } - return; + cob.return_(); } - } - if (con instanceof Boolean) { - emitIconstInsn((boolean) con ? 1 : 0); - return; - } - // fall through: - mv.visitLdcInsn(con); - } - - private void emitIconstInsn(final int cst) { - emitIconstInsn(mv, cst); - } - - private static void emitIconstInsn(MethodVisitor mv, int cst) { - if (cst >= -1 && cst <= 5) { - mv.visitInsn(Opcodes.ICONST_0 + cst); - } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { - mv.visitIntInsn(Opcodes.BIPUSH, cst); - } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { - mv.visitIntInsn(Opcodes.SIPUSH, cst); - } else { - mv.visitLdcInsn(cst); - } - } - - /* - * NOTE: These load/store methods use the localsMap to find the correct index! - */ - private void emitLoadInsn(BasicType type, int index) { - int opcode = loadInsnOpcode(type); - mv.visitVarInsn(opcode, localsMap[index]); - } - - private int loadInsnOpcode(BasicType type) throws InternalError { - return switch (type) { - case I_TYPE -> Opcodes.ILOAD; - case J_TYPE -> Opcodes.LLOAD; - case F_TYPE -> Opcodes.FLOAD; - case D_TYPE -> Opcodes.DLOAD; - case L_TYPE -> Opcodes.ALOAD; - default -> throw new InternalError("unknown type: " + type); - }; - } - private void emitAloadInsn(int index) { - emitLoadInsn(L_TYPE, index); - } - - private void emitStoreInsn(BasicType type, int index) { - int opcode = storeInsnOpcode(type); - mv.visitVarInsn(opcode, localsMap[index]); - } - - private int storeInsnOpcode(BasicType type) throws InternalError { - return switch (type) { - case I_TYPE -> Opcodes.ISTORE; - case J_TYPE -> Opcodes.LSTORE; - case F_TYPE -> Opcodes.FSTORE; - case D_TYPE -> Opcodes.DSTORE; - case L_TYPE -> Opcodes.ASTORE; - default -> throw new InternalError("unknown type: " + type); - }; - } - private void emitAstoreInsn(int index) { - emitStoreInsn(L_TYPE, index); - } - - private byte arrayTypeCode(Wrapper elementType) { - return (byte) switch (elementType) { - case BOOLEAN -> Opcodes.T_BOOLEAN; - case BYTE -> Opcodes.T_BYTE; - case CHAR -> Opcodes.T_CHAR; - case SHORT -> Opcodes.T_SHORT; - case INT -> Opcodes.T_INT; - case LONG -> Opcodes.T_LONG; - case FLOAT -> Opcodes.T_FLOAT; - case DOUBLE -> Opcodes.T_DOUBLE; - case OBJECT -> 0; // in place of Opcodes.T_OBJECT - default -> throw new InternalError(); - }; - } - - private int arrayInsnOpcode(byte tcode, int aaop) throws InternalError { - assert(aaop == Opcodes.AASTORE || aaop == Opcodes.AALOAD); - int xas = switch (tcode) { - case Opcodes.T_BOOLEAN -> Opcodes.BASTORE; - case Opcodes.T_BYTE -> Opcodes.BASTORE; - case Opcodes.T_CHAR -> Opcodes.CASTORE; - case Opcodes.T_SHORT -> Opcodes.SASTORE; - case Opcodes.T_INT -> Opcodes.IASTORE; - case Opcodes.T_LONG -> Opcodes.LASTORE; - case Opcodes.T_FLOAT -> Opcodes.FASTORE; - case Opcodes.T_DOUBLE -> Opcodes.DASTORE; - case 0 -> Opcodes.AASTORE; - default -> throw new InternalError(); - }; - return xas - Opcodes.AASTORE + aaop; + }); } /** @@ -521,11 +360,11 @@ private int arrayInsnOpcode(byte tcode, int aaop) throws InternalError { * * @param wrapper primitive type class to box. */ - private void emitBoxing(Wrapper wrapper) { - String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); + private void emitBoxing(CodeBuilder cob, Wrapper wrapper) { + ClassDesc owner = ClassDesc.ofDescriptor(wrapper.wrapperType().descriptorString()); String name = "valueOf"; - String desc = "(" + wrapper.basicTypeChar() + ")L" + owner + ";"; - mv.visitMethodInsn(Opcodes.INVOKESTATIC, owner, name, desc, false); + MethodTypeDesc desc = MethodTypeDesc.of(owner, ClassDesc.ofDescriptor(wrapper.basicTypeString())); + cob.invokestatic(owner, name, desc); } /** @@ -533,12 +372,12 @@ private void emitBoxing(Wrapper wrapper) { * * @param wrapper wrapper type class to unbox. */ - private void emitUnboxing(Wrapper wrapper) { - String owner = "java/lang/" + wrapper.wrapperType().getSimpleName(); + private void emitUnboxing(CodeBuilder cob, Wrapper wrapper) { + ClassDesc owner = ClassDesc.ofDescriptor(wrapper.wrapperType().descriptorString()); String name = wrapper.primitiveSimpleName() + "Value"; - String desc = "()" + wrapper.basicTypeChar(); - emitReferenceCast(wrapper.wrapperType(), null); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, owner, name, desc, false); + MethodTypeDesc desc = MethodTypeDesc.of(ClassDesc.ofDescriptor(wrapper.basicTypeString())); + emitReferenceCast(cob, wrapper.wrapperType(), null); + cob.invokevirtual(owner, name, desc); } /** @@ -549,7 +388,7 @@ private void emitUnboxing(Wrapper wrapper) { * @param pclass type of value required on stack * @param arg compile-time representation of value on stack (Node, constant) or null if none */ - private void emitImplicitConversion(BasicType ptype, Class pclass, Object arg) { + private void emitImplicitConversion(CodeBuilder cob, BasicType ptype, Class pclass, Object arg) { assert(basicType(pclass) == ptype); // boxing/unboxing handled by caller if (pclass == ptype.basicTypeClass() && ptype != L_TYPE) return; // nothing to do @@ -557,14 +396,14 @@ private void emitImplicitConversion(BasicType ptype, Class pclass, Object arg case L_TYPE: if (VerifyType.isNullConversion(Object.class, pclass, false)) { if (PROFILE_LEVEL > 0) - emitReferenceCast(Object.class, arg); + emitReferenceCast(cob, Object.class, arg); return; } - emitReferenceCast(pclass, arg); + emitReferenceCast(cob, pclass, arg); return; case I_TYPE: if (!VerifyType.isNullConversion(int.class, pclass, false)) - emitPrimCast(ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass)); + emitPrimCast(cob, ptype.basicTypeWrapper(), Wrapper.forPrimitiveType(pclass)); return; } throw newInternalError("bad implicit conversion: tc="+ptype+": "+pclass); @@ -582,7 +421,7 @@ private boolean assertStaticType(Class cls, Name n) { return false; } - private void emitReferenceCast(Class cls, Object arg) { + private void emitReferenceCast(CodeBuilder cob, Class cls, Object arg) { Name writeBack = null; // local to write back result if (arg instanceof Name n) { if (lambdaForm.useCount(n) > 1) { @@ -594,51 +433,35 @@ private void emitReferenceCast(Class cls, Object arg) { } } if (isStaticallyNameable(cls)) { - String sig = getInternalName(cls); - mv.visitTypeInsn(Opcodes.CHECKCAST, sig); + ClassDesc sig = classDescCached(cls); + cob.checkcast(sig); } else { - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(cls), "Ljava/lang/Class;"); - mv.visitInsn(Opcodes.SWAP); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, CLS, "cast", LL_SIG, false); + cob.getstatic(classDesc, classData(cls), CD_Class) + .swap() + .invokevirtual(CD_Class, "cast", MTD_LL_SIG); if (Object[].class.isAssignableFrom(cls)) - mv.visitTypeInsn(Opcodes.CHECKCAST, OBJARY); + cob.checkcast(CD_OBJARY); else if (PROFILE_LEVEL > 0) - mv.visitTypeInsn(Opcodes.CHECKCAST, OBJ); + cob.checkcast(CD_Object); } if (writeBack != null) { - mv.visitInsn(Opcodes.DUP); - emitAstoreInsn(writeBack.index()); + cob.dup(); + cob.astore(localsMap[writeBack.index()]); } } - /** - * Emits an actual return instruction conforming to the given return type. - */ - private void emitReturnInsn(BasicType type) { - int opcode = switch (type) { - case I_TYPE -> Opcodes.IRETURN; - case J_TYPE -> Opcodes.LRETURN; - case F_TYPE -> Opcodes.FRETURN; - case D_TYPE -> Opcodes.DRETURN; - case L_TYPE -> Opcodes.ARETURN; - case V_TYPE -> Opcodes.RETURN; - default -> throw new InternalError("unknown return type: " + type); - }; - mv.visitInsn(opcode); - } - - private String getInternalName(Class c) { - if (c == Object.class) return OBJ; - else if (c == Object[].class) return OBJARY; - else if (c == Class.class) return CLS; - else if (c == MethodHandle.class) return MH; + private ClassDesc classDescCached(Class c) { + if (c == Object.class) return CD_Object; + else if (c == Object[].class) return CD_OBJARY; + else if (c == Class.class) return CD_Class; + else if (c == MethodHandle.class) return CD_MethodHandle; assert(VerifyAccess.isTypeVisible(c, Object.class)) : c.getName(); if (c == lastClass) { - return lastInternalName; + return lastClassDesc; } lastClass = c; - return lastInternalName = c.getName().replace('.', '/'); + return lastClassDesc = ClassDesc.ofDescriptor(c.descriptorString()); } private static MemberName resolveFrom(String name, MethodType type, Class holder) { @@ -713,173 +536,154 @@ static MemberName generateCustomizedCode(LambdaForm form, MethodType invokerType } /** Generates code to check that actual receiver and LambdaForm matches */ - private boolean checkActualReceiver() { + private boolean checkActualReceiver(CodeBuilder cob) { // Expects MethodHandle on the stack and actual receiver MethodHandle in slot #0 - mv.visitInsn(Opcodes.DUP); - mv.visitVarInsn(Opcodes.ALOAD, localsMap[0]); - mv.visitMethodInsn(Opcodes.INVOKESTATIC, MHI, "assertSame", LLV_SIG, false); + cob.dup() + .aload(0) + .invokestatic(CD_MHI, "assertSame", MTD_LLV_SIG); return true; } - static String className(String cn) { - assert checkClassName(cn): "Class not found: " + cn; - return cn; + private static Annotation annotation(Class cls) { + return Annotation.of(cls.describeConstable().orElseThrow()); } - static boolean checkClassName(String cn) { - Type tp = Type.getType(cn); - // additional sanity so only valid "L;" descriptors work - if (tp.getSort() != Type.OBJECT) { - return false; - } - try { - Class c = Class.forName(tp.getClassName(), false, null); - return true; - } catch (ClassNotFoundException e) { - return false; - } - } - - static final String DONTINLINE_SIG = className("Ljdk/internal/vm/annotation/DontInline;"); - static final String FORCEINLINE_SIG = className("Ljdk/internal/vm/annotation/ForceInline;"); - static final String HIDDEN_SIG = className("Ljdk/internal/vm/annotation/Hidden;"); - static final String INJECTEDPROFILE_SIG = className("Ljava/lang/invoke/InjectedProfile;"); - static final String LF_COMPILED_SIG = className("Ljava/lang/invoke/LambdaForm$Compiled;"); - + static final Annotation DONTINLINE = annotation(DontInline.class); + static final Annotation FORCEINLINE = annotation(ForceInline.class); + static final Annotation HIDDEN = annotation(Hidden.class); + static final Annotation INJECTEDPROFILE = annotation(InjectedProfile.class); + static final Annotation LF_COMPILED = annotation(LambdaForm.Compiled.class); /** * Generate an invoker method for the passed {@link LambdaForm}. */ private byte[] generateCustomizedCodeBytes() { - classFilePrologue(); - addMethod(); - clinit(cw, className, classData); - bogusMethod(lambdaForm); - - return toByteArray(); + final byte[] classFile = classFileSetup(new Consumer() { + @Override + public void accept(ClassBuilder clb) { + addMethod(clb); + clinit(clb, classDesc, classData); + bogusMethod(clb, lambdaForm); + } + }); + return classFile; } - void setClassWriter(ClassWriter cw) { - this.cw = cw; - } + void addMethod(ClassBuilder clb) { + methodSetup(clb, new Consumer() { + @Override + public void accept(MethodBuilder mb) { - void addMethod() { - methodPrologue(); + List annotations = new ArrayList<>(3); - // Suppress this method in backtraces displayed to the user. - mv.visitAnnotation(HIDDEN_SIG, true); + // Suppress this method in backtraces displayed to the user. + annotations.add(HIDDEN); - // Mark this method as a compiled LambdaForm - mv.visitAnnotation(LF_COMPILED_SIG, true); + // Mark this method as a compiled LambdaForm + annotations.add(LF_COMPILED); - if (lambdaForm.forceInline) { - // Force inlining of this invoker method. - mv.visitAnnotation(FORCEINLINE_SIG, true); - } else { - mv.visitAnnotation(DONTINLINE_SIG, true); - } + if (lambdaForm.forceInline) { + // Force inlining of this invoker method. + annotations.add(FORCEINLINE); + } else { + annotations.add(DONTINLINE); + } + mb.accept(RuntimeVisibleAnnotationsAttribute.of(annotations)); + + classData(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled. + + mb.withCode(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + if (lambdaForm.customized != null) { + // Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute + // receiver MethodHandle (at slot #0) with an embedded constant and use it instead. + // It enables more efficient code generation in some situations, since embedded constants + // are compile-time constants for JIT compiler. + cob.getstatic(classDesc, classData(lambdaForm.customized), CD_MethodHandle) + .checkcast(CD_MethodHandle); + assert(checkActualReceiver(cob)); // expects MethodHandle on top of the stack + cob.astore(0); + } - classData(lambdaForm); // keep LambdaForm instance & its compiled form lifetime tightly coupled. - - if (lambdaForm.customized != null) { - // Since LambdaForm is customized for a particular MethodHandle, it's safe to substitute - // receiver MethodHandle (at slot #0) with an embedded constant and use it instead. - // It enables more efficient code generation in some situations, since embedded constants - // are compile-time constants for JIT compiler. - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(lambdaForm.customized), MH_SIG); - mv.visitTypeInsn(Opcodes.CHECKCAST, MH); - assert(checkActualReceiver()); // expects MethodHandle on top of the stack - mv.visitVarInsn(Opcodes.ASTORE, localsMap[0]); - } + // iterate over the form's names, generating bytecode instructions for each + // start iterating at the first name following the arguments + Name onStack = null; + for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { + Name name = lambdaForm.names[i]; + + emitStoreResult(cob, onStack); + onStack = name; // unless otherwise modified below + MethodHandleImpl.Intrinsic intr = name.function.intrinsicName(); + switch (intr) { + case SELECT_ALTERNATIVE: + assert lambdaForm.isSelectAlternative(i); + if (PROFILE_GWT) { + assert(name.arguments[0] instanceof Name n && + n.refersTo(MethodHandleImpl.class, "profileBoolean")); + mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of(INJECTEDPROFILE))); + } + onStack = emitSelectAlternative(cob, name, lambdaForm.names[i+1]); + i++; // skip MH.invokeBasic of the selectAlternative result + continue; + case GUARD_WITH_CATCH: + assert lambdaForm.isGuardWithCatch(i); + onStack = emitGuardWithCatch(cob, i); + i += 2; // jump to the end of GWC idiom + continue; + case TRY_FINALLY: + assert lambdaForm.isTryFinally(i); + onStack = emitTryFinally(cob, i); + i += 2; // jump to the end of the TF idiom + continue; + case TABLE_SWITCH: + assert lambdaForm.isTableSwitch(i); + int numCases = (Integer) name.function.intrinsicData(); + onStack = emitTableSwitch(cob, i, numCases); + i += 2; // jump to the end of the TS idiom + continue; + case LOOP: + assert lambdaForm.isLoop(i); + onStack = emitLoop(cob, i); + i += 2; // jump to the end of the LOOP idiom + continue; + case ARRAY_LOAD: + emitArrayLoad(cob, name); + continue; + case ARRAY_STORE: + emitArrayStore(cob, name); + continue; + case ARRAY_LENGTH: + emitArrayLength(cob, name); + continue; + case IDENTITY: + assert(name.arguments.length == 1); + emitPushArguments(cob, name, 0); + continue; + case ZERO: + assert(name.arguments.length == 0); + cob.constantInstruction((ConstantDesc)name.type.basicTypeWrapper().zero()); + continue; + case NONE: + // no intrinsic associated + break; + default: + throw newInternalError("Unknown intrinsic: "+intr); + } + + MemberName member = name.function.member(); + if (isStaticallyInvocable(member)) { + emitStaticInvoke(cob, member, name); + } else { + emitInvoke(cob, name); + } + } - // iterate over the form's names, generating bytecode instructions for each - // start iterating at the first name following the arguments - Name onStack = null; - for (int i = lambdaForm.arity; i < lambdaForm.names.length; i++) { - Name name = lambdaForm.names[i]; - - emitStoreResult(onStack); - onStack = name; // unless otherwise modified below - MethodHandleImpl.Intrinsic intr = name.function.intrinsicName(); - switch (intr) { - case SELECT_ALTERNATIVE: - assert lambdaForm.isSelectAlternative(i); - if (PROFILE_GWT) { - assert(name.arguments[0] instanceof Name n && - n.refersTo(MethodHandleImpl.class, "profileBoolean")); - mv.visitAnnotation(INJECTEDPROFILE_SIG, true); + // return statement + emitReturn(cob, onStack); } - onStack = emitSelectAlternative(name, lambdaForm.names[i+1]); - i++; // skip MH.invokeBasic of the selectAlternative result - continue; - case GUARD_WITH_CATCH: - assert lambdaForm.isGuardWithCatch(i); - onStack = emitGuardWithCatch(i); - i += 2; // jump to the end of GWC idiom - continue; - case TRY_FINALLY: - assert lambdaForm.isTryFinally(i); - onStack = emitTryFinally(i); - i += 2; // jump to the end of the TF idiom - continue; - case TABLE_SWITCH: - assert lambdaForm.isTableSwitch(i); - int numCases = (Integer) name.function.intrinsicData(); - onStack = emitTableSwitch(i, numCases); - i += 2; // jump to the end of the TS idiom - continue; - case LOOP: - assert lambdaForm.isLoop(i); - onStack = emitLoop(i); - i += 2; // jump to the end of the LOOP idiom - continue; - case ARRAY_LOAD: - emitArrayLoad(name); - continue; - case ARRAY_STORE: - emitArrayStore(name); - continue; - case ARRAY_LENGTH: - emitArrayLength(name); - continue; - case IDENTITY: - assert(name.arguments.length == 1); - emitPushArguments(name, 0); - continue; - case ZERO: - assert(name.arguments.length == 0); - emitConst(name.type.basicTypeWrapper().zero()); - continue; - case NONE: - // no intrinsic associated - break; - default: - throw newInternalError("Unknown intrinsic: "+intr); + }); } - - MemberName member = name.function.member(); - if (isStaticallyInvocable(member)) { - emitStaticInvoke(member, name); - } else { - emitInvoke(name); - } - } - - // return statement - emitReturn(onStack); - - methodEpilogue(); - } - - /* - * @throws BytecodeGenerationException if something goes wrong when - * generating the byte code - */ - private byte[] toByteArray() { - try { - return cw.toByteArray(); - } catch (RuntimeException e) { - throw new BytecodeGenerationException(e); - } + }); } /** @@ -892,48 +696,63 @@ static final class BytecodeGenerationException extends RuntimeException { } } - void emitArrayLoad(Name name) { emitArrayOp(name, Opcodes.AALOAD); } - void emitArrayStore(Name name) { emitArrayOp(name, Opcodes.AASTORE); } - void emitArrayLength(Name name) { emitArrayOp(name, Opcodes.ARRAYLENGTH); } + void emitArrayLoad(CodeBuilder cob, Name name) { + Class elementType = name.function.methodType().parameterType(0).getComponentType(); + assert elementType != null; + emitPushArguments(cob, name, 0); + if (elementType.isPrimitive()) { + Wrapper w = Wrapper.forPrimitiveType(elementType); + cob.arrayLoadInstruction(TypeKind.fromDescriptor(w.basicTypeString())); + } else { + cob.aaload(); + } + } - void emitArrayOp(Name name, int arrayOpcode) { - assert arrayOpcode == Opcodes.AALOAD || arrayOpcode == Opcodes.AASTORE || arrayOpcode == Opcodes.ARRAYLENGTH; + void emitArrayStore(CodeBuilder cob, Name name) { Class elementType = name.function.methodType().parameterType(0).getComponentType(); assert elementType != null; - emitPushArguments(name, 0); - if (arrayOpcode != Opcodes.ARRAYLENGTH && elementType.isPrimitive()) { + emitPushArguments(cob, name, 0); + if (elementType.isPrimitive()) { Wrapper w = Wrapper.forPrimitiveType(elementType); - arrayOpcode = arrayInsnOpcode(arrayTypeCode(w), arrayOpcode); + cob.arrayStoreInstruction(TypeKind.fromDescriptor(w.basicTypeString())); + } else { + cob.aastore(); } - mv.visitInsn(arrayOpcode); + } + + void emitArrayLength(CodeBuilder cob, Name name) { + Class elementType = name.function.methodType().parameterType(0).getComponentType(); + assert elementType != null; + emitPushArguments(cob, name, 0); + cob.arraylength(); } /** * Emit an invoke for the given name. */ - void emitInvoke(Name name) { + void emitInvoke(CodeBuilder cob, Name name) { assert(!name.isLinkerMethodInvoke()); // should use the static path for these if (true) { // push receiver MethodHandle target = name.function.resolvedHandle(); assert(target != null) : name.exprString(); - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(target), MH_SIG); - emitReferenceCast(MethodHandle.class, target); + cob.getstatic(classDesc, classData(target), CD_MethodHandle); + emitReferenceCast(cob, MethodHandle.class, target); } else { // load receiver - emitAloadInsn(0); - emitReferenceCast(MethodHandle.class, null); - mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", LF_SIG); - mv.visitFieldInsn(Opcodes.GETFIELD, LF, "names", LFN_SIG); + cob.aload(0); + emitReferenceCast(cob, MethodHandle.class, null); + cob.getfield(CD_MethodHandle, "form", CD_LF) + .getfield(CD_LF, "names", CD_LFN); // TODO more to come } // push arguments - emitPushArguments(name, 0); + emitPushArguments(cob, name, 0); // invocation MethodType type = name.function.methodType(); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", MethodTypeDesc.ofDescriptor(type.basicType().toMethodDescriptorString())); } private static final Class[] STATICALLY_INVOCABLE_PACKAGES = { @@ -1020,19 +839,18 @@ static boolean isStaticallyNameable(Class cls) { return false; } - void emitStaticInvoke(Name name) { - emitStaticInvoke(name.function.member(), name); + void emitStaticInvoke(CodeBuilder cob, Name name) { + emitStaticInvoke(cob, name.function.member(), name); } /** * Emit an invoke for the given name, using the MemberName directly. */ - void emitStaticInvoke(MemberName member, Name name) { + void emitStaticInvoke(CodeBuilder cob, MemberName member, Name name) { assert(member.equals(name.function.member())); Class defc = member.getDeclaringClass(); - String cname = getInternalName(defc); + ClassDesc cdesc = classDescCached(defc); String mname = member.getName(); - String mtype; byte refKind = member.getReferenceKind(); if (refKind == REF_invokeSpecial) { // in order to pass the verifier, we need to convert this to invokevirtual in all cases @@ -1043,16 +861,16 @@ void emitStaticInvoke(MemberName member, Name name) { assert(!(member.getDeclaringClass().isInterface() && refKind == REF_invokeVirtual)); // push arguments - emitPushArguments(name, 0); + emitPushArguments(cob, name, 0); // invocation if (member.isMethod()) { - mtype = member.getMethodType().toMethodDescriptorString(); - mv.visitMethodInsn(refKindOpcode(refKind), cname, mname, mtype, - member.getDeclaringClass().isInterface()); + var methodTypeDesc = MethodTypeDesc.ofDescriptor(member.getMethodType().descriptorString()); + cob.invokeInstruction(refKindOpcode(refKind), cdesc, mname, methodTypeDesc, + member.getDeclaringClass().isInterface()); } else { - mtype = MethodType.toFieldDescriptorString(member.getFieldType()); - mv.visitFieldInsn(refKindOpcode(refKind), cname, mname, mtype); + var fieldTypeDesc = ClassDesc.ofDescriptor(member.getFieldType().descriptorString()); + cob.fieldInstruction(refKindOpcode(refKind), cdesc, mname, fieldTypeDesc); } // Issue a type assertion for the result, so we can avoid casts later. if (name.type == L_TYPE) { @@ -1064,16 +882,16 @@ void emitStaticInvoke(MemberName member, Name name) { } } - int refKindOpcode(byte refKind) { + Opcode refKindOpcode(byte refKind) { switch (refKind) { - case REF_invokeVirtual: return Opcodes.INVOKEVIRTUAL; - case REF_invokeStatic: return Opcodes.INVOKESTATIC; - case REF_invokeSpecial: return Opcodes.INVOKESPECIAL; - case REF_invokeInterface: return Opcodes.INVOKEINTERFACE; - case REF_getField: return Opcodes.GETFIELD; - case REF_putField: return Opcodes.PUTFIELD; - case REF_getStatic: return Opcodes.GETSTATIC; - case REF_putStatic: return Opcodes.PUTSTATIC; + case REF_invokeVirtual: return Opcode.INVOKEVIRTUAL; + case REF_invokeStatic: return Opcode.INVOKESTATIC; + case REF_invokeSpecial: return Opcode.INVOKESPECIAL; + case REF_invokeInterface: return Opcode.INVOKEINTERFACE; + case REF_getField: return Opcode.GETFIELD; + case REF_putField: return Opcode.PUTFIELD; + case REF_getStatic: return Opcode.GETSTATIC; + case REF_putStatic: return Opcode.PUTSTATIC; } throw new InternalError("refKind="+refKind); } @@ -1089,40 +907,40 @@ int refKindOpcode(byte refKind) { * t4:I=MethodHandle.invokeBasic(t3:L,a1:I);t4:I} * } */ - private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicName) { + private Name emitSelectAlternative(CodeBuilder cob, Name selectAlternativeName, Name invokeBasicName) { assert isStaticallyInvocable(invokeBasicName); Name receiver = (Name) invokeBasicName.arguments[0]; - Label L_fallback = new Label(); - Label L_done = new Label(); + Label L_fallback = cob.newLabel(); + Label L_done = cob.newLabel(); // load test result - emitPushArgument(selectAlternativeName, 0); + emitPushArgument(cob, selectAlternativeName, 0); // if_icmpne L_fallback - mv.visitJumpInsn(Opcodes.IFEQ, L_fallback); + cob.ifeq(L_fallback); // invoke selectAlternativeName.arguments[1] Class[] preForkClasses = localClasses.clone(); - emitPushArgument(selectAlternativeName, 1); // get 2nd argument of selectAlternative - emitAstoreInsn(receiver.index()); // store the MH in the receiver slot - emitStaticInvoke(invokeBasicName); + emitPushArgument(cob, selectAlternativeName, 1); // get 2nd argument of selectAlternative + cob.astore(localsMap[receiver.index()]); // store the MH in the receiver slot + emitStaticInvoke(cob, invokeBasicName); // goto L_done - mv.visitJumpInsn(Opcodes.GOTO, L_done); + cob.goto_w(L_done); // L_fallback: - mv.visitLabel(L_fallback); + cob.labelBinding(L_fallback); // invoke selectAlternativeName.arguments[2] System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); - emitPushArgument(selectAlternativeName, 2); // get 3rd argument of selectAlternative - emitAstoreInsn(receiver.index()); // store the MH in the receiver slot - emitStaticInvoke(invokeBasicName); + emitPushArgument(cob, selectAlternativeName, 2); // get 3rd argument of selectAlternative + cob.astore(localsMap[receiver.index()]); // store the MH in the receiver slot + emitStaticInvoke(cob, invokeBasicName); // L_done: - mv.visitLabel(L_done); + cob.labelBinding(L_done); // for now do not bother to merge typestate; just reset to the dominator state System.arraycopy(preForkClasses, 0, localClasses, 0, preForkClasses.length); @@ -1149,57 +967,57 @@ private Name emitSelectAlternative(Name selectAlternativeName, Name invokeBasicN * return a3.invokeBasic(ex, a6, a7); * }} */ - private Name emitGuardWithCatch(int pos) { + private Name emitGuardWithCatch(CodeBuilder cob, int pos) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos+1]; Name result = lambdaForm.names[pos+2]; - Label L_startBlock = new Label(); - Label L_endBlock = new Label(); - Label L_handler = new Label(); - Label L_done = new Label(); + Label L_startBlock = cob.newLabel(); + Label L_endBlock = cob.newLabel(); + Label L_handler = cob.newLabel(); + Label L_done = cob.newLabel(); Class returnType = result.function.resolvedHandle().type().returnType(); MethodType type = args.function.resolvedHandle().type() .dropParameterTypes(0,1) .changeReturnType(returnType); - mv.visitTryCatchBlock(L_startBlock, L_endBlock, L_handler, "java/lang/Throwable"); + cob.exceptionCatch(L_startBlock, L_endBlock, L_handler, CD_Throwable); // Normal case - mv.visitLabel(L_startBlock); + cob.labelBinding(L_startBlock); // load target - emitPushArgument(invoker, 0); - emitPushArguments(args, 1); // skip 1st argument: method handle - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false); - mv.visitLabel(L_endBlock); - mv.visitJumpInsn(Opcodes.GOTO, L_done); + emitPushArgument(cob, invoker, 0); + emitPushArguments(cob, args, 1); // skip 1st argument: method handle + cob.invokevirtual(CD_MethodHandle, "invokeBasic", MethodTypeDesc.ofDescriptor(type.basicType().toMethodDescriptorString())); + cob.labelBinding(L_endBlock); + cob.goto_w(L_done); // Exceptional case - mv.visitLabel(L_handler); + cob.labelBinding(L_handler); // Check exception's type - mv.visitInsn(Opcodes.DUP); + cob.dup(); // load exception class - emitPushArgument(invoker, 1); - mv.visitInsn(Opcodes.SWAP); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "isInstance", "(Ljava/lang/Object;)Z", false); - Label L_rethrow = new Label(); - mv.visitJumpInsn(Opcodes.IFEQ, L_rethrow); + emitPushArgument(cob, invoker, 1); + cob.swap(); + cob.invokevirtual(CD_Class, "isInstance", MethodTypeDesc.of(CD_boolean, CD_Object)); + Label L_rethrow = cob.newLabel(); + cob.branchInstruction(Opcode.IFEQ, L_rethrow); // Invoke catcher // load catcher - emitPushArgument(invoker, 2); - mv.visitInsn(Opcodes.SWAP); - emitPushArguments(args, 1); // skip 1st argument: method handle + emitPushArgument(cob, invoker, 2); + cob.swap(); + emitPushArguments(cob, args, 1); // skip 1st argument: method handle MethodType catcherType = type.insertParameterTypes(0, Throwable.class); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", catcherType.basicType().toMethodDescriptorString(), false); - mv.visitJumpInsn(Opcodes.GOTO, L_done); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", MethodTypeDesc.ofDescriptor(catcherType.basicType().toMethodDescriptorString())); + cob.goto_w(L_done); - mv.visitLabel(L_rethrow); - mv.visitInsn(Opcodes.ATHROW); + cob.labelBinding(L_rethrow); + cob.throwInstruction(); - mv.visitLabel(L_done); + cob.labelBinding(L_done); return result; } @@ -1264,15 +1082,15 @@ private Name emitGuardWithCatch(int pos) { * } * * = depends on whether the return type takes up 2 stack slots. */ - private Name emitTryFinally(int pos) { + private Name emitTryFinally(CodeBuilder cob, int pos) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos+1]; Name result = lambdaForm.names[pos+2]; - Label lFrom = new Label(); - Label lTo = new Label(); - Label lCatch = new Label(); - Label lDone = new Label(); + Label lFrom = cob.newLabel(); + Label lTo = cob.newLabel(); + Label lCatch = cob.newLabel(); + Label lDone = cob.newLabel(); Class returnType = result.function.resolvedHandle().type().returnType(); BasicType basicReturnType = BasicType.basicType(returnType); @@ -1285,68 +1103,74 @@ private Name emitTryFinally(int pos) { if (isNonVoid) { cleanupType = cleanupType.insertParameterTypes(1, returnType); } - String cleanupDesc = cleanupType.basicType().toMethodDescriptorString(); + MethodTypeDesc cleanupDesc = MethodTypeDesc.ofDescriptor(cleanupType.basicType().toMethodDescriptorString()); // exception handler table - mv.visitTryCatchBlock(lFrom, lTo, lCatch, "java/lang/Throwable"); + cob.exceptionCatch(lFrom, lTo, lCatch, CD_Throwable); // TRY: - mv.visitLabel(lFrom); - emitPushArgument(invoker, 0); // load target - emitPushArguments(args, 1); // load args (skip 0: method handle) - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.basicType().toMethodDescriptorString(), false); - mv.visitLabel(lTo); + cob.labelBinding(lFrom); + emitPushArgument(cob, invoker, 0); // load target + emitPushArguments(cob, args, 1); // load args (skip 0: method handle) + cob.invokevirtual(CD_MethodHandle, "invokeBasic", MethodTypeDesc.ofDescriptor(type.basicType().toMethodDescriptorString())); + cob.labelBinding(lTo); // FINALLY_NORMAL: int index = extendLocalsMap(new Class[]{ returnType }); if (isNonVoid) { - emitStoreInsn(basicReturnType, index); + cob.storeInstruction(basicReturnType.basicTypeKind(), localsMap[index]); } - emitPushArgument(invoker, 1); // load cleanup - mv.visitInsn(Opcodes.ACONST_NULL); + emitPushArgument(cob, invoker, 1); // load cleanup + cob.constantInstruction(null); if (isNonVoid) { - emitLoadInsn(basicReturnType, index); + cob.loadInstruction(basicReturnType.basicTypeKind(), localsMap[index]); } - emitPushArguments(args, 1); // load args (skip 0: method handle) - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", cleanupDesc, false); - mv.visitJumpInsn(Opcodes.GOTO, lDone); + emitPushArguments(cob, args, 1); // load args (skip 0: method handle) + cob.invokevirtual(CD_MethodHandle, "invokeBasic", cleanupDesc); + cob.goto_w(lDone); // CATCH: - mv.visitLabel(lCatch); - mv.visitInsn(Opcodes.DUP); + cob.labelBinding(lCatch); + cob.dup(); // FINALLY_EXCEPTIONAL: - emitPushArgument(invoker, 1); // load cleanup - mv.visitInsn(Opcodes.SWAP); + emitPushArgument(cob, invoker, 1); // load cleanup + cob.swap(); if (isNonVoid) { - emitZero(BasicType.basicType(returnType)); // load default for result + emitZero(cob, BasicType.basicType(returnType)); // load default for result } - emitPushArguments(args, 1); // load args (skip 0: method handle) - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", cleanupDesc, false); + emitPushArguments(cob, args, 1); // load args (skip 0: method handle) + cob.invokevirtual(CD_MethodHandle, "invokeBasic", cleanupDesc); if (isNonVoid) { - emitPopInsn(basicReturnType); + emitPopInsn(cob, basicReturnType); } - mv.visitInsn(Opcodes.ATHROW); + cob.throwInstruction(); // DONE: - mv.visitLabel(lDone); + cob.labelBinding(lDone); return result; } - private void emitPopInsn(BasicType type) { - mv.visitInsn(popInsnOpcode(type)); + private void emitPopInsn(CodeBuilder cob, BasicType type) { + cob.stackInstruction(popInsnOpcode(type)); } - private static int popInsnOpcode(BasicType type) { - return switch (type) { - case I_TYPE, F_TYPE, L_TYPE -> Opcodes.POP; - case J_TYPE, D_TYPE -> Opcodes.POP2; - default -> throw new InternalError("unknown type: " + type); - }; + private static Opcode popInsnOpcode(BasicType type) { + switch (type) { + case I_TYPE: + case F_TYPE: + case L_TYPE: + return Opcode.POP; + case J_TYPE: + case D_TYPE: + return Opcode.POP2; + default: + throw new InternalError("unknown type: " + type); + } } - private Name emitTableSwitch(int pos, int numCases) { + private Name emitTableSwitch(CodeBuilder cob, int pos, int numCases) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos + 1]; Name result = lambdaForm.names[pos + 2]; @@ -1355,45 +1179,45 @@ private Name emitTableSwitch(int pos, int numCases) { MethodType caseType = args.function.resolvedHandle().type() .dropParameterTypes(0, 1) // drop collector .changeReturnType(returnType); - String caseDescriptor = caseType.basicType().toMethodDescriptorString(); + MethodTypeDesc caseDescriptor = MethodTypeDesc.ofDescriptor(caseType.basicType().toMethodDescriptorString()); - emitPushArgument(invoker, 2); // push cases - mv.visitFieldInsn(Opcodes.GETFIELD, "java/lang/invoke/MethodHandleImpl$CasesHolder", "cases", - "[Ljava/lang/invoke/MethodHandle;"); + emitPushArgument(cob, invoker, 2); // push cases + cob.getfield(ClassDesc.ofInternalName("java/lang/invoke/MethodHandleImpl$CasesHolder"), "cases", + CD_MethodHandle.arrayType()); int casesLocal = extendLocalsMap(new Class[] { MethodHandle[].class }); - emitStoreInsn(L_TYPE, casesLocal); + cob.astore(localsMap[casesLocal]); - Label endLabel = new Label(); - Label defaultLabel = new Label(); - Label[] caseLabels = new Label[numCases]; - for (int i = 0; i < caseLabels.length; i++) { - caseLabels[i] = new Label(); + Label endLabel = cob.newLabel(); + Label defaultLabel = cob.newLabel(); + List cases = new ArrayList<>(numCases); + for (int i = 0; i < numCases; i++) { + cases.add(SwitchCase.of(i, cob.newLabel())); } - emitPushArgument(invoker, 0); // push switch input - mv.visitTableSwitchInsn(0, numCases - 1, defaultLabel, caseLabels); + emitPushArgument(cob, invoker, 0); // push switch input + cob.tableSwitchInstruction(0, numCases - 1, defaultLabel, cases); - mv.visitLabel(defaultLabel); - emitPushArgument(invoker, 1); // push default handle - emitPushArguments(args, 1); // again, skip collector - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", caseDescriptor, false); - mv.visitJumpInsn(Opcodes.GOTO, endLabel); + cob.labelBinding(defaultLabel); + emitPushArgument(cob, invoker, 1); // push default handle + emitPushArguments(cob, args, 1); // again, skip collector + cob.invokevirtual(CD_MethodHandle, "invokeBasic", caseDescriptor); + cob.goto_(endLabel); for (int i = 0; i < numCases; i++) { - mv.visitLabel(caseLabels[i]); + cob.labelBinding(cases.get(i).target()); // Load the particular case: - emitLoadInsn(L_TYPE, casesLocal); - emitIconstInsn(i); - mv.visitInsn(Opcodes.AALOAD); + cob.aload(localsMap[casesLocal]); + cob.constantInstruction(i); + cob.aaload(); // invoke it: - emitPushArguments(args, 1); // again, skip collector - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", caseDescriptor, false); + emitPushArguments(cob, args, 1); // again, skip collector + cob.invokevirtual(CD_MethodHandle, "invokeBasic", caseDescriptor); - mv.visitJumpInsn(Opcodes.GOTO, endLabel); + cob.goto_(endLabel); } - mv.visitLabel(endLabel); + cob.labelBinding(endLabel); return result; } @@ -1480,7 +1304,7 @@ private Name emitTableSwitch(int pos, int numCases) { * GOTO DONE // jump beyond end of clauses to return from loop * } */ - private Name emitLoop(int pos) { + private Name emitLoop(CodeBuilder cob, int pos) { Name args = lambdaForm.names[pos]; Name invoker = lambdaForm.names[pos+1]; Name result = lambdaForm.names[pos+2]; @@ -1488,8 +1312,9 @@ private Name emitLoop(int pos) { // extract clause and loop-local state types // find the type info in the loop invocation BasicType[] loopClauseTypes = (BasicType[]) invoker.arguments[0]; - Class[] loopLocalStateTypes = Stream.of(loopClauseTypes). - filter(bt -> bt != BasicType.V_TYPE).map(BasicType::basicTypeClass).toArray(Class[]::new); + Class[] loopLocalStateTypes = Stream.of(loopClauseTypes) + .filter(bt -> bt != BasicType.V_TYPE) + .map(BasicType::basicTypeClass).toArray(Class[]::new); Class[] localTypes = new Class[loopLocalStateTypes.length + 1]; localTypes[0] = MethodHandleImpl.LoopClauses.class; System.arraycopy(loopLocalStateTypes, 0, localTypes, 1, loopLocalStateTypes.length); @@ -1513,61 +1338,61 @@ private Name emitLoop(int pos) { final int preds = 3; final int finis = 4; - Label lLoop = new Label(); - Label lDone = new Label(); + Label lLoop = cob.newLabel(); + Label lDone = cob.newLabel(); Label lNext; // PREINIT: - emitPushArgument(MethodHandleImpl.LoopClauses.class, invoker.arguments[1]); - mv.visitFieldInsn(Opcodes.GETFIELD, LOOP_CLAUSES, "clauses", MHARY2); - emitAstoreInsn(clauseDataIndex); + emitPushArgument(cob, MethodHandleImpl.LoopClauses.class, invoker.arguments[1]); + cob.getfield(CD_LOOP_CLAUSES, "clauses", CD_MHARY2); + cob.astore(localsMap[clauseDataIndex]); // INIT: for (int c = 0, state = 0; c < nClauses; ++c) { MethodType cInitType = loopType.changeReturnType(loopClauseTypes[c].basicTypeClass()); - emitLoopHandleInvoke(invoker, inits, c, args, false, cInitType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, inits, c, args, false, cInitType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); if (cInitType.returnType() != void.class) { - emitStoreInsn(BasicType.basicType(cInitType.returnType()), firstLoopStateIndex + state); + cob.storeInstruction(BasicType.basicType(cInitType.returnType()).basicTypeKind(), localsMap[firstLoopStateIndex + state]); ++state; } } // LOOP: - mv.visitLabel(lLoop); + cob.labelBinding(lLoop); for (int c = 0, state = 0; c < nClauses; ++c) { - lNext = new Label(); + lNext = cob.newLabel(); MethodType stepType = loopHandleType.changeReturnType(loopClauseTypes[c].basicTypeClass()); boolean isVoid = stepType.returnType() == void.class; // invoke loop step - emitLoopHandleInvoke(invoker, steps, c, args, true, stepType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, steps, c, args, true, stepType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); if (!isVoid) { - emitStoreInsn(BasicType.basicType(stepType.returnType()), firstLoopStateIndex + state); + cob.storeInstruction(BasicType.basicType(stepType.returnType()).basicTypeKind(), localsMap[firstLoopStateIndex + state]); ++state; } // invoke loop predicate - emitLoopHandleInvoke(invoker, preds, c, args, true, predType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, preds, c, args, true, predType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); - mv.visitJumpInsn(Opcodes.IFNE, lNext); + cob.ifne(lNext); // invoke fini - emitLoopHandleInvoke(invoker, finis, c, args, true, finiType, loopLocalStateTypes, clauseDataIndex, + emitLoopHandleInvoke(cob, invoker, finis, c, args, true, finiType, loopLocalStateTypes, clauseDataIndex, firstLoopStateIndex); - mv.visitJumpInsn(Opcodes.GOTO, lDone); + cob.goto_w(lDone); // this is the beginning of the next loop clause - mv.visitLabel(lNext); + cob.labelBinding(lNext); } - mv.visitJumpInsn(Opcodes.GOTO, lLoop); + cob.goto_w(lLoop); // DONE: - mv.visitLabel(lDone); + cob.labelBinding(lDone); return result; } @@ -1588,69 +1413,67 @@ private int extendLocalsMap(Class[] types) { return firstSlot; } - private void emitLoopHandleInvoke(Name holder, int handles, int clause, Name args, boolean pushLocalState, + private void emitLoopHandleInvoke(CodeBuilder cob, Name holder, int handles, int clause, Name args, boolean pushLocalState, MethodType type, Class[] loopLocalStateTypes, int clauseDataSlot, int firstLoopStateSlot) { // load handle for clause - emitPushClauseArray(clauseDataSlot, handles); - emitIconstInsn(clause); - mv.visitInsn(Opcodes.AALOAD); + emitPushClauseArray(cob, clauseDataSlot, handles); + cob.constantInstruction(clause); + cob.aaload(); // load loop state (preceding the other arguments) if (pushLocalState) { for (int s = 0; s < loopLocalStateTypes.length; ++s) { - emitLoadInsn(BasicType.basicType(loopLocalStateTypes[s]), firstLoopStateSlot + s); + cob.loadInstruction(BasicType.basicType(loopLocalStateTypes[s]).basicTypeKind(), localsMap[firstLoopStateSlot + s]); } } // load loop args (skip 0: method handle) - emitPushArguments(args, 1); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", type.toMethodDescriptorString(), false); + emitPushArguments(cob, args, 1); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", MethodTypeDesc.ofDescriptor(type.toMethodDescriptorString())); } - private void emitPushClauseArray(int clauseDataSlot, int which) { - emitAloadInsn(clauseDataSlot); - emitIconstInsn(which - 1); - mv.visitInsn(Opcodes.AALOAD); + private void emitPushClauseArray(CodeBuilder cob, int clauseDataSlot, int which) { + cob.aload(localsMap[clauseDataSlot]); + cob.constantInstruction(which - 1); + cob.aaload(); } - private void emitZero(BasicType type) { - mv.visitInsn(switch (type) { - case I_TYPE -> Opcodes.ICONST_0; - case J_TYPE -> Opcodes.LCONST_0; - case F_TYPE -> Opcodes.FCONST_0; - case D_TYPE -> Opcodes.DCONST_0; - case L_TYPE -> Opcodes.ACONST_NULL; + private void emitZero(CodeBuilder cob, BasicType type) { + switch (type) { + case I_TYPE -> cob.iconst_0(); + case J_TYPE -> cob.lconst_0(); + case F_TYPE -> cob.fconst_0(); + case D_TYPE -> cob.dconst_0(); + case L_TYPE -> cob.aconst_null(); default -> throw new InternalError("unknown type: " + type); - }); + }; } - private void emitPushArguments(Name args, int start) { + private void emitPushArguments(CodeBuilder cob, Name args, int start) { MethodType type = args.function.methodType(); for (int i = start; i < args.arguments.length; i++) { - emitPushArgument(type.parameterType(i), args.arguments[i]); + emitPushArgument(cob, type.parameterType(i), args.arguments[i]); } } - private void emitPushArgument(Name name, int paramIndex) { + private void emitPushArgument(CodeBuilder cob, Name name, int paramIndex) { Object arg = name.arguments[paramIndex]; Class ptype = name.function.methodType().parameterType(paramIndex); - emitPushArgument(ptype, arg); + emitPushArgument(cob, ptype, arg); } - private void emitPushArgument(Class ptype, Object arg) { + private void emitPushArgument(CodeBuilder cob, Class ptype, Object arg) { BasicType bptype = basicType(ptype); if (arg instanceof Name n) { - emitLoadInsn(n.type, n.index()); - emitImplicitConversion(n.type, ptype, n); - } else if (arg == null && bptype == L_TYPE) { - mv.visitInsn(Opcodes.ACONST_NULL); - } else if (arg instanceof String && bptype == L_TYPE) { - mv.visitLdcInsn(arg); + cob.loadInstruction(n.type.basicTypeKind(), localsMap[n.index()]); + emitImplicitConversion(cob, n.type, ptype, n); + } else if ((arg == null || arg instanceof String) && bptype == L_TYPE) { + cob.constantInstruction((ConstantDesc)arg); } else { if (Wrapper.isWrapperType(arg.getClass()) && bptype != L_TYPE) { - emitConst(arg); + cob.constantInstruction((ConstantDesc)arg); } else { - mv.visitFieldInsn(Opcodes.GETSTATIC, className, classData(arg), "Ljava/lang/Object;"); - emitImplicitConversion(L_TYPE, ptype, arg); + cob.getstatic(classDesc, classData(arg), CD_Object); + emitImplicitConversion(cob, L_TYPE, ptype, arg); } } } @@ -1658,44 +1481,44 @@ private void emitPushArgument(Class ptype, Object arg) { /** * Store the name to its local, if necessary. */ - private void emitStoreResult(Name name) { + private void emitStoreResult(CodeBuilder cob, Name name) { if (name != null && name.type != V_TYPE) { // non-void: actually assign - emitStoreInsn(name.type, name.index()); + cob.storeInstruction(name.type.basicTypeKind(), localsMap[name.index()]); } } /** * Emits a return statement from a LF invoker. If required, the result type is cast to the correct return type. */ - private void emitReturn(Name onStack) { + private void emitReturn(CodeBuilder cob, Name onStack) { // return statement Class rclass = invokerType.returnType(); BasicType rtype = lambdaForm.returnType(); assert(rtype == basicType(rclass)); // must agree if (rtype == V_TYPE) { // void - mv.visitInsn(Opcodes.RETURN); + cob.return_(); // it doesn't matter what rclass is; the JVM will discard any value } else { LambdaForm.Name rn = lambdaForm.names[lambdaForm.result]; // put return value on the stack if it is not already there if (rn != onStack) { - emitLoadInsn(rtype, lambdaForm.result); + cob.loadInstruction(rtype.basicTypeKind(), localsMap[lambdaForm.result]); } - emitImplicitConversion(rtype, rclass, rn); + emitImplicitConversion(cob, rtype, rclass, rn); // generate actual return statement - emitReturnInsn(rtype); + cob.returnInstruction(rtype.basicTypeKind()); } } /** * Emit a type conversion bytecode casting from "from" to "to". */ - private void emitPrimCast(Wrapper from, Wrapper to) { + private void emitPrimCast(CodeBuilder cob, Wrapper from, Wrapper to) { // Here's how. // - indicates forbidden // <-> indicates implicit @@ -1714,15 +1537,15 @@ private void emitPrimCast(Wrapper from, Wrapper to) { } if (from.isSubwordOrInt()) { // cast from {byte,short,char,int} to anything - emitI2X(to); + emitI2X(cob, to); } else { // cast from {long,float,double} to anything if (to.isSubwordOrInt()) { // cast to {byte,short,char,int} - emitX2I(from); + emitX2I(cob, from); if (to.bitWidth() < 32) { // targets other than int require another conversion - emitI2X(to); + emitI2X(cob, to); } } else { // cast to {long,float,double} - this is verbose @@ -1730,23 +1553,23 @@ private void emitPrimCast(Wrapper from, Wrapper to) { switch (from) { case LONG -> { switch (to) { - case FLOAT -> mv.visitInsn(Opcodes.L2F); - case DOUBLE -> mv.visitInsn(Opcodes.L2D); - default -> error = true; + case FLOAT -> cob.l2f(); + case DOUBLE -> cob.l2d(); + default -> error = true; } } case FLOAT -> { switch (to) { - case LONG -> mv.visitInsn(Opcodes.F2L); - case DOUBLE -> mv.visitInsn(Opcodes.F2D); - default -> error = true; + case LONG -> cob.f2l(); + case DOUBLE -> cob.f2d(); + default -> error = true; } } case DOUBLE -> { switch (to) { - case LONG -> mv.visitInsn(Opcodes.D2L); - case FLOAT -> mv.visitInsn(Opcodes.D2F); - default -> error = true; + case LONG -> cob.d2l(); + case FLOAT -> cob.d2f(); + default -> error = true; } } default -> error = true; @@ -1758,30 +1581,43 @@ private void emitPrimCast(Wrapper from, Wrapper to) { } } - private void emitI2X(Wrapper type) { + private void emitI2X(CodeBuilder cob, Wrapper type) { switch (type) { - case BYTE: mv.visitInsn(Opcodes.I2B); break; - case SHORT: mv.visitInsn(Opcodes.I2S); break; - case CHAR: mv.visitInsn(Opcodes.I2C); break; - case INT: /* naught */ break; - case LONG: mv.visitInsn(Opcodes.I2L); break; - case FLOAT: mv.visitInsn(Opcodes.I2F); break; - case DOUBLE: mv.visitInsn(Opcodes.I2D); break; - case BOOLEAN: - // For compatibility with ValueConversions and explicitCastArguments: - mv.visitInsn(Opcodes.ICONST_1); - mv.visitInsn(Opcodes.IAND); - break; - default: throw new InternalError("unknown type: " + type); + case BYTE -> + cob.i2b(); + case SHORT -> + cob.i2s(); + case CHAR -> + cob.i2c(); + case INT -> { + } + /* naught */ + case LONG -> + cob.i2l(); + case FLOAT -> + cob.i2f(); + case DOUBLE -> + cob.i2d(); + case BOOLEAN -> { + // For compatibility with ValueConversions and explicitCastArguments: + cob.constantInstruction(1); + cob.iand(); + } + default -> + throw new InternalError("unknown type: " + type); } } - private void emitX2I(Wrapper type) { + private void emitX2I(CodeBuilder cob, Wrapper type) { switch (type) { - case LONG -> mv.visitInsn(Opcodes.L2I); - case FLOAT -> mv.visitInsn(Opcodes.F2I); - case DOUBLE -> mv.visitInsn(Opcodes.D2I); - default -> throw new InternalError("unknown type: " + type); + case LONG -> + cob.l2i(); + case FLOAT -> + cob.f2i(); + case DOUBLE -> + cob.d2i(); + default -> + throw new InternalError("unknown type: " + type); } } @@ -1798,51 +1634,60 @@ static MemberName generateLambdaFormInterpreterEntryPoint(MethodType mt) { } private byte[] generateLambdaFormInterpreterEntryPointBytes() { - classFilePrologue(); - methodPrologue(); - - // Suppress this method in backtraces displayed to the user. - mv.visitAnnotation(HIDDEN_SIG, true); - - // Don't inline the interpreter entry. - mv.visitAnnotation(DONTINLINE_SIG, true); - - // create parameter array - emitIconstInsn(invokerType.parameterCount()); - mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); - - // fill parameter array - for (int i = 0; i < invokerType.parameterCount(); i++) { - Class ptype = invokerType.parameterType(i); - mv.visitInsn(Opcodes.DUP); - emitIconstInsn(i); - emitLoadInsn(basicType(ptype), i); - // box if primitive type - if (ptype.isPrimitive()) { - emitBoxing(Wrapper.forPrimitiveType(ptype)); + final byte[] classFile = classFileSetup(new Consumer() { + @Override + public void accept(ClassBuilder clb) { + methodSetup(clb, new Consumer() { + @Override + public void accept(MethodBuilder mb) { + + mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of( + HIDDEN, // Suppress this method in backtraces displayed to the user. + DONTINLINE // Don't inline the interpreter entry. + ))); + + mb.withCode(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + // create parameter array + cob.constantInstruction(invokerType.parameterCount()); + cob.anewarray(CD_Object); + + // fill parameter array + for (int i = 0; i < invokerType.parameterCount(); i++) { + Class ptype = invokerType.parameterType(i); + cob.dup(); + cob.constantInstruction(i); + cob.loadInstruction(basicType(ptype).basicTypeKind(), localsMap[i]); + // box if primitive type + if (ptype.isPrimitive()) { + emitBoxing(cob, Wrapper.forPrimitiveType(ptype)); + } + cob.aastore(); + } + // invoke + cob.aload(0); + cob.getfield(CD_MethodHandle, "form", CD_LF); + cob.swap(); // swap form and array; avoid local variable + cob.invokevirtual(CD_LF, "interpretWithArguments", MethodTypeDesc.of(CD_Object, CD_Object.arrayType())); + + // maybe unbox + Class rtype = invokerType.returnType(); + if (rtype.isPrimitive() && rtype != void.class) { + emitUnboxing(cob, Wrapper.forPrimitiveType(rtype)); + } + + // return statement + cob.returnInstruction(TypeKind.from(rtype)); + } + }); + } + }); + clinit(clb, classDesc, classData); + bogusMethod(clb, invokerType); } - mv.visitInsn(Opcodes.AASTORE); - } - // invoke - emitAloadInsn(0); - mv.visitFieldInsn(Opcodes.GETFIELD, MH, "form", "Ljava/lang/invoke/LambdaForm;"); - mv.visitInsn(Opcodes.SWAP); // swap form and array; avoid local variable - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, LF, "interpretWithArguments", "([Ljava/lang/Object;)Ljava/lang/Object;", false); - - // maybe unbox - Class rtype = invokerType.returnType(); - if (rtype.isPrimitive() && rtype != void.class) { - emitUnboxing(Wrapper.forPrimitiveType(rtype)); - } - - // return statement - emitReturnInsn(basicType(rtype)); - - methodEpilogue(); - clinit(cw, className, classData); - bogusMethod(invokerType); - - return cw.toByteArray(); + }); + return classFile; } /** @@ -1857,73 +1702,84 @@ static MemberName generateNamedFunctionInvoker(MethodTypeForm typeForm) { private byte[] generateNamedFunctionInvokerImpl(MethodTypeForm typeForm) { MethodType dstType = typeForm.erasedType(); - classFilePrologue(); - methodPrologue(); - - // Suppress this method in backtraces displayed to the user. - mv.visitAnnotation(HIDDEN_SIG, true); - - // Force inlining of this invoker method. - mv.visitAnnotation(FORCEINLINE_SIG, true); - - // Load receiver - emitAloadInsn(0); - - // Load arguments from array - for (int i = 0; i < dstType.parameterCount(); i++) { - emitAloadInsn(1); - emitIconstInsn(i); - mv.visitInsn(Opcodes.AALOAD); - - // Maybe unbox - Class dptype = dstType.parameterType(i); - if (dptype.isPrimitive()) { - Wrapper dstWrapper = Wrapper.forBasicType(dptype); - Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int - emitUnboxing(srcWrapper); - emitPrimCast(srcWrapper, dstWrapper); + final byte[] classFile = classFileSetup(new Consumer() { + @Override + public void accept(ClassBuilder clb) { + methodSetup(clb, new Consumer() { + @Override + public void accept(MethodBuilder mb) { + + mb.with(RuntimeVisibleAnnotationsAttribute.of(List.of( + HIDDEN, // Suppress this method in backtraces displayed to the user. + FORCEINLINE // Force inlining of this invoker method. + ))); + + mb.withCode(new Consumer() { + @Override + public void accept(CodeBuilder cob) { + // Load receiver + cob.aload(0); + + // Load arguments from array + for (int i = 0; i < dstType.parameterCount(); i++) { + cob.aload(1); + cob.constantInstruction(i); + cob.aaload(); + + // Maybe unbox + Class dptype = dstType.parameterType(i); + if (dptype.isPrimitive()) { + Wrapper dstWrapper = Wrapper.forBasicType(dptype); + Wrapper srcWrapper = dstWrapper.isSubwordOrInt() ? Wrapper.INT : dstWrapper; // narrow subword from int + emitUnboxing(cob, srcWrapper); + emitPrimCast(cob, srcWrapper, dstWrapper); + } + } + + // Invoke + MethodTypeDesc targetDesc = MethodTypeDesc.ofDescriptor(dstType.basicType().toMethodDescriptorString()); + cob.invokevirtual(CD_MethodHandle, "invokeBasic", targetDesc); + + // Box primitive types + Class rtype = dstType.returnType(); + if (rtype != void.class && rtype.isPrimitive()) { + Wrapper srcWrapper = Wrapper.forBasicType(rtype); + Wrapper dstWrapper = srcWrapper.isSubwordOrInt() ? Wrapper.INT : srcWrapper; // widen subword to int + // boolean casts not allowed + emitPrimCast(cob, srcWrapper, dstWrapper); + emitBoxing(cob, dstWrapper); + } + + // If the return type is void we return a null reference. + if (rtype == void.class) { + cob.aconst_null(); + } + cob.areturn(); // NOTE: NamedFunction invokers always return a reference value. + } + }); + } + }); + clinit(clb, classDesc, classData); + bogusMethod(clb, dstType); } - } - - // Invoke - String targetDesc = dstType.basicType().toMethodDescriptorString(); - mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, MH, "invokeBasic", targetDesc, false); - - // Box primitive types - Class rtype = dstType.returnType(); - if (rtype != void.class && rtype.isPrimitive()) { - Wrapper srcWrapper = Wrapper.forBasicType(rtype); - Wrapper dstWrapper = srcWrapper.isSubwordOrInt() ? Wrapper.INT : srcWrapper; // widen subword to int - // boolean casts not allowed - emitPrimCast(srcWrapper, dstWrapper); - emitBoxing(dstWrapper); - } - - // If the return type is void we return a null reference. - if (rtype == void.class) { - mv.visitInsn(Opcodes.ACONST_NULL); - } - emitReturnInsn(L_TYPE); // NOTE: NamedFunction invokers always return a reference value. - - methodEpilogue(); - clinit(cw, className, classData); - bogusMethod(dstType); - - return cw.toByteArray(); + }); + return classFile; } /** * Emit a bogus method that just loads some string constants. This is to get the constants into the constant pool * for debugging purposes. */ - private void bogusMethod(Object os) { + private void bogusMethod(ClassBuilder clb, Object os) { if (dumper().isEnabled()) { - mv = cw.visitMethod(Opcodes.ACC_STATIC, "dummy", "()V", null, null); - mv.visitLdcInsn(os.toString()); - mv.visitInsn(Opcodes.POP); - mv.visitInsn(Opcodes.RETURN); - mv.visitMaxs(0, 0); - mv.visitEnd(); + clb.withMethodBody("dummy", MethodTypeDesc.of(CD_void), ACC_STATIC, new Consumer() { + @Override + public void accept(CodeBuilder cob) { + cob.constantInstruction(os.toString()); + cob.pop(); + cob.return_(); + } + }); } } } diff --git a/src/java.base/share/classes/java/lang/invoke/LambdaForm.java b/src/java.base/share/classes/java/lang/invoke/LambdaForm.java index 8b75d7510fe27..4088fc3d15223 100644 --- a/src/java.base/share/classes/java/lang/invoke/LambdaForm.java +++ b/src/java.base/share/classes/java/lang/invoke/LambdaForm.java @@ -25,6 +25,7 @@ package java.lang.invoke; +import java.lang.classfile.TypeKind; import jdk.internal.perf.PerfCounter; import jdk.internal.vm.annotation.DontInline; import jdk.internal.vm.annotation.Hidden; @@ -137,12 +138,12 @@ class LambdaForm { public static final int VOID_RESULT = -1, LAST_RESULT = -2; enum BasicType { - L_TYPE('L', Object.class, Wrapper.OBJECT), // all reference types - I_TYPE('I', int.class, Wrapper.INT), - J_TYPE('J', long.class, Wrapper.LONG), - F_TYPE('F', float.class, Wrapper.FLOAT), - D_TYPE('D', double.class, Wrapper.DOUBLE), // all primitive types - V_TYPE('V', void.class, Wrapper.VOID); // not valid in all contexts + L_TYPE('L', Object.class, Wrapper.OBJECT, TypeKind.ReferenceType), // all reference types + I_TYPE('I', int.class, Wrapper.INT, TypeKind.IntType), + J_TYPE('J', long.class, Wrapper.LONG, TypeKind.LongType), + F_TYPE('F', float.class, Wrapper.FLOAT, TypeKind.FloatType), + D_TYPE('D', double.class, Wrapper.DOUBLE, TypeKind.DoubleType), // all primitive types + V_TYPE('V', void.class, Wrapper.VOID, TypeKind.VoidType); // not valid in all contexts static final @Stable BasicType[] ALL_TYPES = BasicType.values(); static final @Stable BasicType[] ARG_TYPES = Arrays.copyOf(ALL_TYPES, ALL_TYPES.length-1); @@ -153,11 +154,13 @@ enum BasicType { final char btChar; final Class btClass; final Wrapper btWrapper; + final TypeKind btKind; - private BasicType(char btChar, Class btClass, Wrapper wrapper) { + private BasicType(char btChar, Class btClass, Wrapper wrapper, TypeKind typeKind) { this.btChar = btChar; this.btClass = btClass; this.btWrapper = wrapper; + this.btKind = typeKind; } char basicTypeChar() { @@ -169,6 +172,9 @@ Class basicTypeClass() { Wrapper basicTypeWrapper() { return btWrapper; } + TypeKind basicTypeKind() { + return btKind; + } int basicTypeSlots() { return btWrapper.stackSlots(); } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java index e79c8463d30b2..759694ddd3ff3 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandleImpl.java @@ -25,10 +25,12 @@ package java.lang.invoke; +import java.lang.classfile.ClassFile; +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; import jdk.internal.access.JavaLangInvokeAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.foreign.abi.NativeEntryPoint; -import jdk.internal.org.objectweb.asm.ClassWriter; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; import jdk.internal.vm.annotation.ForceInline; @@ -56,13 +58,14 @@ import java.util.function.Function; import java.util.stream.Stream; +import static java.lang.classfile.ClassFile.*; +import static java.lang.constant.ConstantDescs.*; import static java.lang.invoke.LambdaForm.*; import static java.lang.invoke.MethodHandleNatives.Constants.MN_CALLER_SENSITIVE; import static java.lang.invoke.MethodHandleNatives.Constants.MN_HIDDEN_MEMBER; import static java.lang.invoke.MethodHandleStatics.*; import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP; import static java.lang.invoke.MethodHandles.Lookup.ClassOption.NESTMATE; -import static jdk.internal.org.objectweb.asm.Opcodes.*; /** * Trusted implementation code for MethodHandle. @@ -1250,8 +1253,6 @@ private static boolean checkCallerClass(Class expected) { /** Produces byte code for a class that is used as an injected invoker. */ private static byte[] generateInvokerTemplate() { - ClassWriter cw = new ClassWriter(0); - // private static class InjectedInvoker { // /* this is used to wrap DMH(s) of caller-sensitive methods */ // @Hidden @@ -1265,39 +1266,25 @@ private static byte[] generateInvokerTemplate() { // } // } // } - cw.visit(CLASSFILE_VERSION, ACC_PRIVATE | ACC_SUPER, "InjectedInvoker", null, "java/lang/Object", null); - { - var mv = cw.visitMethod(ACC_STATIC, "invoke_V", - "(Ljava/lang/invoke/MethodHandle;[Ljava/lang/Object;)Ljava/lang/Object;", - null, null); - - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 1); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", - "([Ljava/lang/Object;)Ljava/lang/Object;", false); - mv.visitInsn(ARETURN); - mv.visitMaxs(2, 2); - mv.visitEnd(); - - cw.visitEnd(); - } - - { - var mv = cw.visitMethod(ACC_STATIC, "reflect_invoke_V", - "(Ljava/lang/invoke/MethodHandle;Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", - null, null); - mv.visitCode(); - mv.visitVarInsn(ALOAD, 0); - mv.visitVarInsn(ALOAD, 1); - mv.visitVarInsn(ALOAD, 2); - mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", - "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;", false); - mv.visitInsn(ARETURN); - mv.visitMaxs(3, 3); - mv.visitEnd(); - } - return cw.toByteArray(); + return ClassFile.of().build(ClassDesc.of("InjectedInvoker"), clb -> clb + .withFlags(ACC_PRIVATE | ACC_SUPER) + .withMethodBody( + "invoke_V", + MethodTypeDesc.of(CD_Object, CD_MethodHandle, CD_Object.arrayType()), + ACC_STATIC, + cob -> cob.aload(0) + .aload(1) + .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDesc.of(CD_Object, CD_Object.arrayType())) + .areturn()) + .withMethodBody( + "reflect_invoke_V", + MethodTypeDesc.of(CD_Object, CD_MethodHandle, CD_Object, CD_Object.arrayType()), + ACC_STATIC, + cob -> cob.aload(0) + .aload(1) + .aload(2) + .invokevirtual(CD_MethodHandle, "invokeExact", MethodTypeDesc.of(CD_Object, CD_Object, CD_Object.arrayType())) + .areturn())); } } diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index da18a994a0138..1206775ec2e74 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -25,12 +25,11 @@ package java.lang.invoke; +import static java.lang.classfile.ClassFile.*; +import java.lang.classfile.ClassModel; import jdk.internal.access.SharedSecrets; import jdk.internal.misc.Unsafe; import jdk.internal.misc.VM; -import jdk.internal.org.objectweb.asm.ClassReader; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.CallerSensitiveAdapter; import jdk.internal.reflect.Reflection; @@ -2288,27 +2287,16 @@ private static ClassFile readClassFile(byte[] bytes) { String name; int accessFlags; try { - ClassReader reader = new ClassReader(bytes); - // ClassReader does not check if `this_class` is CONSTANT_Class_info - // workaround to read `this_class` using readConst and validate the value - int thisClass = reader.readUnsignedShort(reader.header + 2); - Object constant = reader.readConst(thisClass, new char[reader.getMaxStringLength()]); - if (!(constant instanceof Type type)) { - throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info"); - } - if (!type.getDescriptor().startsWith("L")) { - throw new ClassFormatError("this_class item: #" + thisClass + " not a CONSTANT_Class_info"); - } - name = type.getInternalName(); - accessFlags = reader.readUnsignedShort(reader.header); - } catch (RuntimeException e) { - // ASM exceptions are poorly specified + ClassModel cm = java.lang.classfile.ClassFile.of().parse(bytes); + name = cm.thisClass().asInternalName(); + accessFlags = cm.flags().flagsMask(); + } catch (IllegalArgumentException e) { ClassFormatError cfe = new ClassFormatError(); cfe.initCause(e); throw cfe; } // must be a class or interface - if ((accessFlags & Opcodes.ACC_MODULE) != 0) { + if ((accessFlags & ACC_MODULE) != 0) { throw newIllegalArgumentException("Not a class or interface: ACC_MODULE flag is set"); } return new ClassFile(name, accessFlags, bytes); diff --git a/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java b/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java index e49094073c3ca..582979f942d09 100644 --- a/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java +++ b/src/java.base/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java @@ -25,176 +25,139 @@ package java.lang.invoke; -import jdk.internal.org.objectweb.asm.MethodVisitor; -import jdk.internal.org.objectweb.asm.Opcodes; -import jdk.internal.org.objectweb.asm.Type; -import sun.invoke.util.BytecodeDescriptor; -import sun.invoke.util.Wrapper; -import static sun.invoke.util.Wrapper.*; - -class TypeConvertingMethodAdapter extends MethodVisitor { - - TypeConvertingMethodAdapter(MethodVisitor mv) { - super(Opcodes.ASM7, mv); - } - - private static final int NUM_WRAPPERS = Wrapper.COUNT; - - private static final String NAME_OBJECT = "java/lang/Object"; - private static final String WRAPPER_PREFIX = "Ljava/lang/"; - - // Same for all primitives; name of the boxing method - private static final String NAME_BOX_METHOD = "valueOf"; - - // Table of opcodes for widening primitive conversions; NOP = no conversion - private static final int[][] wideningOpcodes = new int[NUM_WRAPPERS][NUM_WRAPPERS]; - - private static final Wrapper[] FROM_WRAPPER_NAME = new Wrapper[16]; - - // Table of wrappers for primitives, indexed by ASM type sorts - private static final Wrapper[] FROM_TYPE_SORT = new Wrapper[12]; - - static { - for (Wrapper w : Wrapper.values()) { - if (w.basicTypeChar() != 'L') { - int wi = hashWrapperName(w.wrapperSimpleName()); - assert (FROM_WRAPPER_NAME[wi] == null); - FROM_WRAPPER_NAME[wi] = w; - } - } - - // wideningOpcodes[][] will be NOP-initialized by default - assert(Opcodes.NOP == 0); - - initWidening(LONG, Opcodes.I2L, BYTE, SHORT, INT, CHAR); - initWidening(LONG, Opcodes.F2L, FLOAT); - initWidening(FLOAT, Opcodes.I2F, BYTE, SHORT, INT, CHAR); - initWidening(FLOAT, Opcodes.L2F, LONG); - initWidening(DOUBLE, Opcodes.I2D, BYTE, SHORT, INT, CHAR); - initWidening(DOUBLE, Opcodes.F2D, FLOAT); - initWidening(DOUBLE, Opcodes.L2D, LONG); - - FROM_TYPE_SORT[Type.BYTE] = Wrapper.BYTE; - FROM_TYPE_SORT[Type.SHORT] = Wrapper.SHORT; - FROM_TYPE_SORT[Type.INT] = Wrapper.INT; - FROM_TYPE_SORT[Type.LONG] = Wrapper.LONG; - FROM_TYPE_SORT[Type.CHAR] = Wrapper.CHAR; - FROM_TYPE_SORT[Type.FLOAT] = Wrapper.FLOAT; - FROM_TYPE_SORT[Type.DOUBLE] = Wrapper.DOUBLE; - FROM_TYPE_SORT[Type.BOOLEAN] = Wrapper.BOOLEAN; - } - - private static void initWidening(Wrapper to, int opcode, Wrapper... from) { - for (Wrapper f : from) { - wideningOpcodes[f.ordinal()][to.ordinal()] = opcode; - } - } - - /** - * Class name to Wrapper hash, derived from Wrapper.hashWrap() - * @param xn - * @return The hash code 0-15 - */ - private static int hashWrapperName(String xn) { - if (xn.length() < 3) { - return 0; - } - return (3 * xn.charAt(1) + xn.charAt(2)) % 16; - } - - private Wrapper wrapperOrNullFromDescriptor(String desc) { - if (!desc.startsWith(WRAPPER_PREFIX)) { - // Not a class type (array or method), so not a boxed type - // or not in the right package - return null; - } - // Pare it down to the simple class name - String cname = desc.substring(WRAPPER_PREFIX.length(), desc.length() - 1); - // Hash to a Wrapper - Wrapper w = FROM_WRAPPER_NAME[hashWrapperName(cname)]; - if (w == null || w.wrapperSimpleName().equals(cname)) { - return w; - } else { - return null; - } - } - - private static String wrapperName(Wrapper w) { - return "java/lang/" + w.wrapperSimpleName(); - } - - private static String unboxMethod(Wrapper w) { - return w.primitiveSimpleName() + "Value"; - } - - private static String boxingDescriptor(Wrapper w) { - return "(" + w.basicTypeChar() + ")L" + wrapperName(w) + ";"; - } - - private static String unboxingDescriptor(Wrapper w) { - return "()" + w.basicTypeChar(); - } - - void boxIfTypePrimitive(Type t) { - Wrapper w = FROM_TYPE_SORT[t.getSort()]; - if (w != null) { - box(w); - } - } - - void widen(Wrapper ws, Wrapper wt) { +import java.lang.constant.ClassDesc; +import java.lang.constant.MethodTypeDesc; +import java.lang.classfile.CodeBuilder; +import java.lang.classfile.Opcode; +import java.lang.classfile.TypeKind; +import java.lang.classfile.constantpool.ClassEntry; +import java.lang.classfile.constantpool.ConstantPoolBuilder; +import java.lang.classfile.constantpool.MethodRefEntry; +import java.lang.classfile.constantpool.NameAndTypeEntry; +import static java.lang.constant.ConstantDescs.*; + +class TypeConvertingMethodAdapter { + + private static final ConstantPoolBuilder CP = ConstantPoolBuilder.of(); + + private static final ClassEntry CE_Boolean = CP.classEntry(CD_Boolean), + CE_Byte = CP.classEntry(CD_Byte), + CE_Short = CP.classEntry(CD_Short), + CE_Character = CP.classEntry(CD_Character), + CE_Integer = CP.classEntry(CD_Integer), + CE_Long = CP.classEntry(CD_Long), + CE_Float = CP.classEntry(CD_Float), + CE_Double = CP.classEntry(CD_Double), + CE_Number = CP.classEntry(CD_Number); + + private static MethodRefEntry box(ClassDesc primitive, ClassDesc target) { + return CP.methodRefEntry(target, "valueOf", MethodTypeDesc.of(target, primitive)); + } + + private static final MethodRefEntry BOX_BOOLEAN = box(CD_boolean, CD_Boolean), + BOX_BYTE = box(CD_byte, CD_Byte), + BOX_SHORT = box(CD_short, CD_Short), + BOX_CHAR = box(CD_char, CD_Character), + BOX_INT = box(CD_int, CD_Integer), + BOX_LONG = box(CD_long, CD_Long), + BOX_FLOAT = box(CD_float, CD_Float), + BOX_DOUBLE = box(CD_double, CD_Double); + + private static NameAndTypeEntry unboxTo(String methodName, ClassDesc primitiveTarget) { + return CP.nameAndTypeEntry(methodName, MethodTypeDesc.of(primitiveTarget)); + } + + private static final NameAndTypeEntry UNBOX_TO_BOOLEAN = unboxTo("booleanValue", CD_boolean), + UNBOX_TO_BYTE = unboxTo("byteValue", CD_byte), + UNBOX_TO_SHORT = unboxTo("shortValue", CD_short), + UNBOX_TO_CHAR = unboxTo("charValue", CD_char), + UNBOX_TO_INT = unboxTo("intValue", CD_int), + UNBOX_TO_LONG = unboxTo("longValue", CD_long), + UNBOX_TO_FLOAT = unboxTo("floatValue", CD_float), + UNBOX_TO_DOUBLE = unboxTo("doubleValue", CD_double); + + static private TypeKind typeKindFromClass(Class type) { + return type == int.class ? TypeKind.IntType + : type == long.class ? TypeKind.LongType + : type == short.class ? TypeKind.ShortType + : type == char.class ? TypeKind.CharType + : type == byte.class ? TypeKind.ByteType + : type == float.class ? TypeKind.FloatType + : type == double.class ? TypeKind.DoubleType + : type == boolean.class ? TypeKind.BooleanType + : type == void.class ? TypeKind.VoidType + : TypeKind.ReferenceType; + } + + static private TypeKind primitiveTypeKindFromClass(Class type) { + return type == Integer.class ? TypeKind.IntType + : type == Long.class ? TypeKind.LongType + : type == Short.class ? TypeKind.ShortType + : type == Character.class ? TypeKind.CharType + : type == Byte.class ? TypeKind.ByteType + : type == Float.class ? TypeKind.FloatType + : type == Double.class ? TypeKind.DoubleType + : type == Boolean.class ? TypeKind.BooleanType + : type == Void.class ? TypeKind.VoidType + : null; + } + + static private ClassEntry primitiveTypeToClassEntry(TypeKind tk) { + return switch (tk) { + case BooleanType -> CE_Boolean; + case ByteType -> CE_Byte; + case CharType -> CE_Character; + case DoubleType -> CE_Double; + case FloatType -> CE_Float; + case IntType -> CE_Integer; + case LongType -> CE_Long; + case ShortType -> CE_Short; + default -> null; + }; + } + + static void boxIfTypePrimitive(CodeBuilder cob, TypeKind tk) { + box(cob, tk); + } + + static void widen(CodeBuilder cob, TypeKind ws, TypeKind wt) { + ws = ws.asLoadable(); + wt = wt.asLoadable(); if (ws != wt) { - int opcode = wideningOpcodes[ws.ordinal()][wt.ordinal()]; - if (opcode != Opcodes.NOP) { - visitInsn(opcode); - } + cob.convertInstruction(ws, wt); } } - void box(Wrapper w) { - visitMethodInsn(Opcodes.INVOKESTATIC, - wrapperName(w), - NAME_BOX_METHOD, - boxingDescriptor(w), false); - } - - /** - * Convert types by unboxing. The source type is known to be a primitive wrapper. - * @param sname A primitive wrapper corresponding to wrapped reference source type - * @param wt A primitive wrapper being converted to - */ - void unbox(String sname, Wrapper wt) { - visitMethodInsn(Opcodes.INVOKEVIRTUAL, - sname, - unboxMethod(wt), - unboxingDescriptor(wt), false); - } - - private String descriptorToName(String desc) { - int last = desc.length() - 1; - if (desc.charAt(0) == 'L' && desc.charAt(last) == ';') { - // In descriptor form - return desc.substring(1, last); - } else { - // Already in internal name form - return desc; + static void box(CodeBuilder cob, TypeKind tk) { + switch (tk) { + case BooleanType -> cob.invokestatic(BOX_BOOLEAN); + case ByteType -> cob.invokestatic(BOX_BYTE); + case CharType -> cob.invokestatic(BOX_CHAR); + case DoubleType -> cob.invokestatic(BOX_DOUBLE); + case FloatType -> cob.invokestatic(BOX_FLOAT); + case IntType -> cob.invokestatic(BOX_INT); + case LongType -> cob.invokestatic(BOX_LONG); + case ShortType -> cob.invokestatic(BOX_SHORT); } } - void cast(String ds, String dt) { - String ns = descriptorToName(ds); - String nt = descriptorToName(dt); - if (!nt.equals(ns) && !nt.equals(NAME_OBJECT)) { - visitTypeInsn(Opcodes.CHECKCAST, nt); + static void unbox(CodeBuilder cob, ClassEntry src, TypeKind to) { + switch (to) { + case BooleanType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_BOOLEAN)); + case ByteType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_BYTE)); + case CharType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_CHAR)); + case DoubleType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_DOUBLE)); + case FloatType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_FLOAT)); + case IntType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_INT)); + case LongType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_LONG)); + case ShortType -> cob.invokevirtual(cob.constantPool().methodRefEntry(src, UNBOX_TO_SHORT)); } } - private Wrapper toWrapper(String desc) { - char first = desc.charAt(0); - if (first == '[' || first == '(') { - first = 'L'; + static void cast(CodeBuilder cob, ClassDesc dt) { + if (!dt.equals(CD_Object)) { + cob.typeCheckInstruction(Opcode.CHECKCAST, dt); } - return Wrapper.forBasicType(first); } /** @@ -204,7 +167,7 @@ private Wrapper toWrapper(String desc) { * @param target * @param functional */ - void convertType(Class arg, Class target, Class functional) { + static void convertType(CodeBuilder cob, Class arg, Class target, Class functional) { if (arg.equals(target) && arg.equals(functional)) { return; } @@ -212,84 +175,65 @@ void convertType(Class arg, Class target, Class functional) { return; } if (arg.isPrimitive()) { - Wrapper wArg = Wrapper.forPrimitiveType(arg); + TypeKind argTk = typeKindFromClass(arg); if (target.isPrimitive()) { // Both primitives: widening - widen(wArg, Wrapper.forPrimitiveType(target)); + widen(cob, argTk, typeKindFromClass(target)); } else { // Primitive argument to reference target - String dTarget = BytecodeDescriptor.unparse(target); - Wrapper wPrimTarget = wrapperOrNullFromDescriptor(dTarget); - if (wPrimTarget != null) { + TypeKind wPrimTk = primitiveTypeKindFromClass(target); + if (wPrimTk != null) { // The target is a boxed primitive type, widen to get there before boxing - widen(wArg, wPrimTarget); - box(wPrimTarget); + widen(cob, argTk, wPrimTk); + box(cob, wPrimTk); } else { // Otherwise, box and cast - box(wArg); - cast(wrapperName(wArg), dTarget); + box(cob, argTk); + cast(cob, target.describeConstable().orElseThrow()); } } } else { - String dArg = BytecodeDescriptor.unparse(arg); - String dSrc; - if (functional.isPrimitive()) { - dSrc = dArg; + Class src; + if (arg == functional || functional.isPrimitive()) { + src = arg; } else { // Cast to convert to possibly more specific type, and generate CCE for invalid arg - dSrc = BytecodeDescriptor.unparse(functional); - cast(dArg, dSrc); + src = functional; + cast(cob, functional.describeConstable().orElseThrow()); } - String dTarget = BytecodeDescriptor.unparse(target); if (target.isPrimitive()) { - Wrapper wTarget = toWrapper(dTarget); // Reference argument to primitive target - Wrapper wps = wrapperOrNullFromDescriptor(dSrc); + TypeKind wps = primitiveTypeKindFromClass(src); if (wps != null) { - if (wps.isSigned() || wps.isFloating()) { + if (wps != TypeKind.CharType && wps != TypeKind.BooleanType) { // Boxed number to primitive - unbox(wrapperName(wps), wTarget); + unbox(cob, primitiveTypeToClassEntry(wps), typeKindFromClass(target)); } else { // Character or Boolean - unbox(wrapperName(wps), wps); - widen(wps, wTarget); + unbox(cob, primitiveTypeToClassEntry(wps), wps); + widen(cob, wps, typeKindFromClass(target)); } } else { // Source type is reference type, but not boxed type, // assume it is super type of target type - String intermediate; - if (wTarget.isSigned() || wTarget.isFloating()) { - // Boxed number to primitive - intermediate = "java/lang/Number"; + ClassEntry intermediate; + if (target == char.class) { + intermediate = CE_Character; + } else if (target == boolean.class) { + intermediate = CE_Boolean; } else { - // Character or Boolean - intermediate = wrapperName(wTarget); + // Boxed number to primitive + intermediate = CE_Number; } - cast(dSrc, intermediate); - unbox(intermediate, wTarget); + cob.typeCheckInstruction(Opcode.CHECKCAST, intermediate); + unbox(cob, intermediate, typeKindFromClass(target)); } } else { // Both reference types: just case to target type - cast(dSrc, dTarget); + if (src != target) { + cast(cob, target.describeConstable().orElseThrow()); + } } } } - - /** - * The following method is copied from - * org.objectweb.asm.commons.InstructionAdapter. Part of ASM: a very small - * and fast Java bytecode manipulation framework. - * Copyright (c) 2000-2005 INRIA, France Telecom All rights reserved. - */ - void iconst(final int cst) { - if (cst >= -1 && cst <= 5) { - mv.visitInsn(Opcodes.ICONST_0 + cst); - } else if (cst >= Byte.MIN_VALUE && cst <= Byte.MAX_VALUE) { - mv.visitIntInsn(Opcodes.BIPUSH, cst); - } else if (cst >= Short.MIN_VALUE && cst <= Short.MAX_VALUE) { - mv.visitIntInsn(Opcodes.SIPUSH, cst); - } else { - mv.visitLdcInsn(cst); - } - } }