Skip to content

Commit

Permalink
WIP save MethodHandles directly. Doesn't work.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
prdoyle committed Dec 24, 2023
1 parent 1c51725 commit 8ba4d52
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 103 deletions.
135 changes: 34 additions & 101 deletions bosk-core/src/main/java/io/vena/bosk/bytecode/ClassBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -70,7 +66,7 @@ public final class ClassBuilder<T> {
private ClassWriter classWriter = null;
private MethodBuilder currentMethod = null;

private final List<CurriedField> curriedFields = new ArrayList<>();
private final List<CurriedValue> curriedValues = new ArrayList<>();

/**
* @param className The simple name of the generated class;
Expand Down Expand Up @@ -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, "<init>", "(" + ctorParameterDescriptor + ")V", null, null);
MethodVisitor ctor = classVisitor.visitMethod(ACC_PUBLIC, "<init>", "()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, "<init>", "()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();
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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<Long, List<Object>> CONST_MAP = new ConcurrentHashMap<>();
private static final AtomicLong METHOD_HANDLE_COUNT = new AtomicLong(0);
private static final Map<String, MethodHandle> METHOD_HANDLES_BY_NAME = new ConcurrentHashMap<>();

/**
* @param index is 1-based
*/
public static Object fetchConstant(long constMapKey, int index) {
List<Object> 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),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 8ba4d52

Please sign in to comment.