From 8ba4d52b669ec20be5a9b95dbb8b37fe76b04512 Mon Sep 17 00:00:00 2001 From: Patrick Doyle Date: Sun, 24 Dec 2023 10:52:56 -0500 Subject: [PATCH] WIP save MethodHandles directly. Doesn't work. Turns out every indy gets its own CallSite (I think I knew that once) and so you can't remove the MethodHandle when you create the CallSite in case you need two CallSites using the same MethodHandle. --- .../io/vena/bosk/bytecode/ClassBuilder.java | 135 +++++------------- .../{CurriedField.java => CurriedValue.java} | 3 +- 2 files changed, 35 insertions(+), 103 deletions(-) rename bosk-core/src/main/java/io/vena/bosk/bytecode/{CurriedField.java => CurriedValue.java} (52%) diff --git a/bosk-core/src/main/java/io/vena/bosk/bytecode/ClassBuilder.java b/bosk-core/src/main/java/io/vena/bosk/bytecode/ClassBuilder.java index 1ad97b99..19744fcc 100644 --- a/bosk-core/src/main/java/io/vena/bosk/bytecode/ClassBuilder.java +++ b/bosk-core/src/main/java/io/vena/bosk/bytecode/ClassBuilder.java @@ -2,6 +2,7 @@ import java.lang.invoke.CallSite; import java.lang.invoke.ConstantCallSite; +import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Constructor; @@ -25,17 +26,13 @@ import static io.vena.bosk.util.ReflectionHelpers.setAccessible; import static java.lang.reflect.Modifier.isStatic; -import static java.util.stream.Collectors.joining; -import static java.util.stream.Collectors.toList; import static org.objectweb.asm.ClassWriter.COMPUTE_FRAMES; import static org.objectweb.asm.Opcodes.ACC_FINAL; -import static org.objectweb.asm.Opcodes.ACC_PRIVATE; import static org.objectweb.asm.Opcodes.ACC_PUBLIC; import static org.objectweb.asm.Opcodes.ACC_SUPER; import static org.objectweb.asm.Opcodes.ALOAD; import static org.objectweb.asm.Opcodes.CHECKCAST; import static org.objectweb.asm.Opcodes.DUP; -import static org.objectweb.asm.Opcodes.GETFIELD; import static org.objectweb.asm.Opcodes.H_INVOKESTATIC; import static org.objectweb.asm.Opcodes.IFEQ; import static org.objectweb.asm.Opcodes.IFNE; @@ -47,7 +44,6 @@ import static org.objectweb.asm.Opcodes.ISTORE; import static org.objectweb.asm.Opcodes.NEW; import static org.objectweb.asm.Opcodes.POP; -import static org.objectweb.asm.Opcodes.PUTFIELD; import static org.objectweb.asm.Opcodes.RETURN; import static org.objectweb.asm.Opcodes.SWAP; import static org.objectweb.asm.Opcodes.V1_8; @@ -70,7 +66,7 @@ public final class ClassBuilder { private ClassWriter classWriter = null; private MethodBuilder currentMethod = null; - private final List curriedFields = new ArrayList<>(); + private final List curriedValues = new ArrayList<>(); /** * @param className The simple name of the generated class; @@ -109,28 +105,13 @@ public void beginClass() { } private void generateConstructor(StackTraceElement sourceFileOrigin) { - String ctorParameterDescriptor; - if (USE_FIELDS) { - ctorParameterDescriptor = curriedFields.stream() - .map(CurriedField::typeDescriptor) - .collect(joining()); - } else { - ctorParameterDescriptor = ""; - } - MethodVisitor ctor = classVisitor.visitMethod(ACC_PUBLIC, "", "(" + ctorParameterDescriptor + ")V", null, null); + MethodVisitor ctor = classVisitor.visitMethod(ACC_PUBLIC, "", "()V", null, null); ctor.visitCode(); Label label = new Label(); ctor.visitLabel(label); ctor.visitLineNumber(sourceFileOrigin.getLineNumber(), label); ctor.visitVarInsn(ALOAD, 0); ctor.visitMethodInsn(INVOKESPECIAL, superClassName, "", "()V", false); - if (USE_FIELDS) { - for (CurriedField field: curriedFields) { - ctor.visitVarInsn(ALOAD, 0); - ctor.visitVarInsn(ALOAD, field.slot()); - ctor.visitFieldInsn(PUTFIELD, slashyName, field.name(), field.typeDescriptor()); - } - } ctor.visitInsn(RETURN); ctor.visitMaxs(0, 0); // Computed automatically ctor.visitEnd(); @@ -206,55 +187,40 @@ public LocalVariable popToLocal(Type type) { * @param type */ public void pushObject(String name, Object object, Class type) { - CurriedField field = curry(name, object, type); - if (USE_FIELDS) { - beginPush(); - methodVisitor().visitVarInsn(ALOAD, 0); - methodVisitor().visitFieldInsn(GETFIELD, slashyName, field.name(), field.typeDescriptor()); - } else { - beginPush(); - /* - // Dynamic constants aren't supported in Java 8 - methodVisitor().visitLdcInsn(new ConstantDynamic( - field.name(), - field.typeDescriptor(), - FETCH_CONSTANT, constMapKey, field.slot() - )); - */ - methodVisitor().visitInvokeDynamicInsn( - field.name(), - "()" + field.typeDescriptor(), - CONSTANT_CALL_SITE, constMapKey, field.slot() - ); - } + CurriedValue field = curry(name, object, type); + beginPush(); + /* + // Dynamic constants aren't supported in Java 8 + methodVisitor().visitLdcInsn(new ConstantDynamic( + field.name(), + field.typeDescriptor(), + FETCH_CONSTANT, constMapKey, field.slot() + )); + */ + methodVisitor().visitInvokeDynamicInsn( + field.name(), + "()" + field.typeDescriptor(), + CONSTANT_CALL_SITE + ); } - private CurriedField curry(String name, Object object, Class type) { + private CurriedValue curry(String name, Object object, Class type) { type.cast(object); - for (CurriedField candidate: curriedFields) { + for (CurriedValue candidate: curriedValues) { if (candidate.value() == object) { return candidate; } } - int ctorParameterSlot = 1 + curriedFields.size(); - CurriedField result = new CurriedField( - ctorParameterSlot, - "CURRIED" + ctorParameterSlot + "_" + name, + String fullName = "CURRIED_" + METHOD_HANDLE_COUNT.incrementAndGet() + "_" + name; + CurriedValue result = new CurriedValue( + fullName, Type.getDescriptor(type), object); - curriedFields.add(result); - - if (USE_FIELDS) { - classVisitor.visitField( - ACC_PRIVATE | ACC_FINAL, - result.name(), - result.typeDescriptor(), - null, null - ).visitEnd(); - } + curriedValues.add(result); - LOGGER.debug("curry({}, {}) = {} {}", constMapKey, ctorParameterSlot, object.getClass().getSimpleName(), object); + METHOD_HANDLES_BY_NAME.put(fullName, MethodHandles.constant(type, object)); + LOGGER.warn("curry({}) = {} {}", fullName, type.getSimpleName(), object); return result; } @@ -360,21 +326,14 @@ private void branchAround(Runnable action, int opcode, int poppedSlots) { * @return A new instance of the class. */ public T buildInstance() { - CONST_MAP.put(this.constMapKey, curriedFields.stream().map(CurriedField::value).collect(toList())); generateConstructor(sourceFileOrigin); classVisitor.visitEnd(); Constructor ctor = new CustomClassLoader() .loadThemBytes(dottyName, classWriter.toByteArray()) .getConstructors()[0]; - Object[] args; - if (USE_FIELDS) { - args = curriedFields.stream().map(CurriedField::value).toArray(); - } else { - args = new Object[]{}; - } try { - return supertype.cast(ctor.newInstance(args)); + return supertype.cast(ctor.newInstance()); } catch (InstantiationException | IllegalAccessException | VerifyError | InvocationTargetException e) { throw new AssertionError("Should be able to instantiate the generated class", e); } @@ -435,56 +394,30 @@ public Class loadThemBytes(String dottyName, byte[] b) { public static final Type OBJECT_TYPE = Type.getType(Object.class); - private static final boolean USE_FIELDS = false; - - // Const map - - private final long constMapKey = CONST_COUNT.getAndIncrement(); + // MethodHandle map /* * These need to be static because they need to be accessible from static initializers * without having any object to start from. */ - private static final AtomicLong CONST_COUNT = new AtomicLong(0); - private static final Map> CONST_MAP = new ConcurrentHashMap<>(); + private static final AtomicLong METHOD_HANDLE_COUNT = new AtomicLong(0); + private static final Map METHOD_HANDLES_BY_NAME = new ConcurrentHashMap<>(); - /** - * @param index is 1-based - */ - public static Object fetchConstant(long constMapKey, int index) { - List list = CONST_MAP.get(constMapKey); - if (list == null) { - throw new IllegalStateException("Missing constants with key " + constMapKey); - } - Object result = list.get(index-1); - LOGGER.debug("fetchConstant({}, {}) returned {} {}", constMapKey, index, result.getClass().getSimpleName(), result); - return result; - } - - public static CallSite constantCallSite(MethodHandles.Lookup lookup, String name, MethodType methodType, long constMapKey, int index) { - Object value = fetchConstant(constMapKey, index); - // Doubly constant! An unchanging call site that returns an unchanging value - return new ConstantCallSite(MethodHandles.constant(Object.class, value).asType(methodType)); + public static CallSite constantCallSite(MethodHandles.Lookup lookup, String name, MethodType methodType) { + LOGGER.warn("constantCallSite({})", name); + return new ConstantCallSite(METHOD_HANDLES_BY_NAME.remove(name).asType(methodType)); } private static final Method CONSTANT_CALL_SITE_METHOD; static { try { - CONSTANT_CALL_SITE_METHOD = ClassBuilder.class.getDeclaredMethod("constantCallSite", MethodHandles.Lookup.class, String.class, MethodType.class, long.class, int.class); + CONSTANT_CALL_SITE_METHOD = ClassBuilder.class.getDeclaredMethod("constantCallSite", MethodHandles.Lookup.class, String.class, MethodType.class); } catch (NoSuchMethodException e) { throw new AssertionError(e); } } - private static final Handle FETCH_CONSTANT = new Handle( - H_INVOKESTATIC, - Type.getInternalName(ClassBuilder.class), - "fetchConstant", - "(JI)LObject;", - false - ); - private static final Handle CONSTANT_CALL_SITE = new Handle( H_INVOKESTATIC, Type.getInternalName(ClassBuilder.class), diff --git a/bosk-core/src/main/java/io/vena/bosk/bytecode/CurriedField.java b/bosk-core/src/main/java/io/vena/bosk/bytecode/CurriedValue.java similarity index 52% rename from bosk-core/src/main/java/io/vena/bosk/bytecode/CurriedField.java rename to bosk-core/src/main/java/io/vena/bosk/bytecode/CurriedValue.java index b98d2d5f..7eaace94 100644 --- a/bosk-core/src/main/java/io/vena/bosk/bytecode/CurriedField.java +++ b/bosk-core/src/main/java/io/vena/bosk/bytecode/CurriedValue.java @@ -3,8 +3,7 @@ import lombok.Value; @Value -public class CurriedField { - int slot; // The parameter slot in which this will arrive in the constructor +public class CurriedValue { String name; String typeDescriptor; Object value;