Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 69 additions & 5 deletions src/main/java/org/codehaus/groovy/classgen/asm/ClosureWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,19 @@ protected interface UseExistingReference {
// off the emitted-bytecode surface.
private static final String DISPATCHERS_GETTER = "$getPackedDispatchers$";
private static final String DISPATCHERS_GETTER_DESC = "()Ljava/lang/Object;";
// The factory emitted into the hosting class that adapts its three tables to their functional
// interfaces through bytecode-level LambdaMetafactory sites (see writeDispatchersFactory).
private static final String DISPATCHERS_FACTORY = "$packedDispatchersFactory$";
private static final String BUNDLE_TYPE = "org/codehaus/groovy/runtime/GeneratedDispatcher$Bundle";
private static final String DISPATCHER_TYPE = "org/codehaus/groovy/runtime/GeneratedDispatcher";
private static final String ARITY1_TYPE = "org/codehaus/groovy/runtime/GeneratedDispatcher$Arity1";
private static final String ARITY2_TYPE = "org/codehaus/groovy/runtime/GeneratedDispatcher$Arity2";
private static final Handle LMF_BOOTSTRAP = new Handle(
H_INVOKESTATIC, "java/lang/invoke/LambdaMetafactory", "metafactory",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;"
+ "Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)"
+ "Ljava/lang/invoke/CallSite;",
false);
// Max tableswitch cases per dispatch method (power of two: the two-level entry method selects a
// chunk with a shift); sized so a full chunk stays well under the JIT's 325-byte inlining budget.
private static final int DISPATCH_CHUNK = 8;
Expand Down Expand Up @@ -1082,20 +1095,29 @@ public void writePackedDispatcher() {
org.objectweb.asm.ClassVisitor cv = controller.getClassVisitor();

// the accessor: return INDY packedDispatchers()Object — IndyInterface.packedDispatchers
// (delegating to GeneratedDispatcher.bootstrap) links the class's three dispatch tables
// (through LambdaMetafactory, with this class's lookup) once, on first adapter creation,
// and every later call returns the constant bundle, so the accessor is also the cache
// (delegating to GeneratedDispatcher.bootstrap) invokes this class's emitted factory
// once, on first adapter creation, and every later call returns the constant bundle,
// so the accessor is also the cache
MethodVisitor mv = cv.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, DISPATCHERS_GETTER, DISPATCHERS_GETTER_DESC, null, null);
mv.visitCode();
// The bundle is built by a factory emitted into this class (see writeDispatchersFactory)
// and reached as a constant bootstrap argument, so the bootstrap needs neither a runtime
// Lookup.findStatic nor a programmatic LambdaMetafactory call — an undeclared reflective
// lookup and a run-time class definition, the two operations ahead-of-time runtimes
// restrict; GraalVM native image is the verified case (GROOVY-12227).
Handle bootstrap = new Handle(
H_INVOKESTATIC, INDY_INTERFACE_TYPE, "packedDispatchers",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;"
+ "Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;",
false);
mv.visitInvokeDynamicInsn("packedDispatchers", DISPATCHERS_GETTER_DESC, bootstrap);
mv.visitInvokeDynamicInsn("packedDispatchers", DISPATCHERS_GETTER_DESC, bootstrap,
new Handle(H_INVOKESTATIC, internal, DISPATCHERS_FACTORY, DISPATCHERS_GETTER_DESC, false));
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();

writeDispatchersFactory(cv, internal);

// the array-free per-arity tables, over the targets whose captured-plus-argument count
// matches (membership is sparse over the id space, so cases use lookupswitch); routing is
// the adapter's responsibility, so any other id landing here is a compiler bug
Expand Down Expand Up @@ -1199,6 +1221,48 @@ private static void writeDispatchSwitch(final MethodVisitor mv, final String int
* switches over its id-range's members (at most {@code DISPATCH_CHUNK}, since a range spans
* {@code DISPATCH_CHUNK} consecutive ids).
*/
/**
* Emits the hosting class's dispatcher factory: three <em>bytecode-level</em>
* {@code LambdaMetafactory} sites adapting its private static tables to their functional
* interfaces, wrapped in one {@code Bundle}.
* <p>
* Emitting the linkage here rather than calling {@code LambdaMetafactory} programmatically
* from the bootstrap matters twice over. The sites are ordinary {@code invokedynamic},
* visible in the class file, so an ahead-of-time compiler can pre-process them at build
* time (GraalVM native image, the verified case, does) — no class is defined at run time,
* and the JVM path is unchanged (the VM spins the same hidden class when it links the site).
* And because the factory lives in the hosting class, its method references reach that
* class's own private tables directly, so no {@code Lookup.findStatic} — and hence no
* per-class reflection metadata — is needed either (GROOVY-12227).
*/
private static void writeDispatchersFactory(final org.objectweb.asm.ClassVisitor cv, final String internal) {
MethodVisitor mv = cv.visitMethod(ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC, DISPATCHERS_FACTORY, DISPATCHERS_GETTER_DESC, null, null);
mv.visitCode();
mv.visitTypeInsn(NEW, BUNDLE_TYPE);
mv.visitInsn(DUP);
emitLambda(mv, internal, "dispatch", DISPATCHER_TYPE, DISPATCH_METHOD, DISPATCH_DESC);
emitLambda(mv, internal, "dispatch1", ARITY1_TYPE, DISPATCH1_METHOD, DISPATCH1_DESC);
emitLambda(mv, internal, "dispatch2", ARITY2_TYPE, DISPATCH2_METHOD, DISPATCH2_DESC);
mv.visitMethodInsn(INVOKESPECIAL, BUNDLE_TYPE, "<init>",
"(L" + DISPATCHER_TYPE + ";L" + ARITY1_TYPE + ";L" + ARITY2_TYPE + ";)V", false);
mv.visitInsn(ARETURN);
mv.visitMaxs(0, 0);
mv.visitEnd();
}

/**
* Emits one {@code invokedynamic} adapting {@code tableMethod} to the single abstract method
* {@code samName} of {@code ifaceType}. The table's descriptor is both the erased and the
* instantiated signature, so the metafactory inserts no adaptation.
*/
private static void emitLambda(final MethodVisitor mv, final String internal, final String samName,
final String ifaceType, final String tableMethod, final String tableDesc) {
mv.visitInvokeDynamicInsn(samName, "()L" + ifaceType + ";", LMF_BOOTSTRAP,
org.objectweb.asm.Type.getMethodType(tableDesc),
new Handle(H_INVOKESTATIC, internal, tableMethod, tableDesc, false),
org.objectweb.asm.Type.getMethodType(tableDesc));
}

private static void writeArityTable(final org.objectweb.asm.ClassVisitor cv, final String internal,
final ClassNode enclosing, final List<MethodNode> targets, final int paramCount,
final String tableMethod, final String tableDesc) {
Expand Down
42 changes: 36 additions & 6 deletions src/main/java/org/codehaus/groovy/runtime/GeneratedDispatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import java.lang.invoke.CallSite;
import java.lang.invoke.ConstantCallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;


/**
* A per-class table of compiler-generated dispatch targets, reached by a compact
* integer id instead of a {@link java.lang.invoke.MethodHandle}.
Expand Down Expand Up @@ -115,7 +117,11 @@ final class Bundle {
final Arity1 arity1;
final Arity2 arity2;

Bundle(final GeneratedDispatcher dispatcher, final Arity1 arity1, final Arity2 arity2) {
/**
* Public because the hosting class's compiler-emitted factory constructs it directly
* (see {@code ClosureWriter#writeDispatchersFactory}); not API for hand-written code.
*/
public Bundle(final GeneratedDispatcher dispatcher, final Arity1 arity1, final Arity2 arity2) {
this.dispatcher = dispatcher;
this.arity1 = arity1;
this.arity2 = arity2;
Expand Down Expand Up @@ -150,11 +156,12 @@ static Class<?>[] paramTypes(final MethodHandles.Lookup caller, final String nam
}

/**
* Invokedynamic bootstrap for the hosting class's dispatcher accessor: adapts the class's
* three private static dispatch tables to their functional interfaces (one hidden class
* each, via {@code LambdaMetafactory} with the caller's full-privilege lookup) and returns
* them as one constant {@link Bundle}. Linked once per class, on first adapter creation.
* Emitted bytecode reaches this through
* Legacy invokedynamic bootstrap for the dispatcher accessor, kept for class files emitted
* by earlier 6.0 pre-releases: adapts the class's three private static dispatch tables to
* their functional interfaces (one hidden class each, via {@code LambdaMetafactory} with the
* caller's full-privilege lookup) and returns them as one constant {@link Bundle}. Current
* class files link through the one-{@code MethodHandle} overload instead. Emitted bytecode
* reaches this through
* {@code org.codehaus.groovy.vmplugin.v8.IndyInterface#packedDispatchers} — the central
* bytecode-facing bootstrap surface — which delegates here.
*
Expand Down Expand Up @@ -182,4 +189,27 @@ static CallSite bootstrap(final MethodHandles.Lookup caller, final String name,
twoType, caller.findStatic(host, TABLE2_METHOD, twoType), twoType).getTarget().invokeExact();
return new ConstantCallSite(MethodHandles.constant(type.returnType(), new Bundle(dispatcher, arity1, arity2)));
}

/**
* Invokedynamic bootstrap for the dispatcher accessor: the hosting class supplies (as a
* constant bootstrap argument) a compiler-emitted factory that builds the bundle from its
* own <em>bytecode-level</em> {@code LambdaMetafactory} sites, so linking is one call and
* this method neither looks anything up nor defines any class — fit for any ahead-of-time
* runtime that restricts run-time reflection or class definition. Under GraalVM native
* image, the verified case, those sites are pre-processed at image build time and the
* factory's method references reach the class's own private tables without reflection
* metadata (GROOVY-12227). Emitted bytecode reaches this through
* {@code org.codehaus.groovy.vmplugin.v8.IndyInterface#packedDispatchers}.
*
* @param caller the hosting class's lookup (supplied by the JVM, unused)
* @param name the invoked name (unused)
* @param type the accessor's type (see the three-argument overload)
* @param factory the hosting class's {@code $packedDispatchersFactory$}, {@code () -> Bundle}
* @return a constant call site producing the bundle
* @throws Throwable if the factory fails (a compiler bug)
*/
static CallSite bootstrap(final MethodHandles.Lookup caller, final String name, final MethodType type,
final MethodHandle factory) throws Throwable {
return new ConstantCallSite(MethodHandles.constant(type.returnType(), factory.invoke()));
}
}
24 changes: 20 additions & 4 deletions src/main/java/org/codehaus/groovy/vmplugin/v8/IndyInterface.java
Original file line number Diff line number Diff line change
Expand Up @@ -640,17 +640,33 @@ public static CallSite staticArrayAccess(MethodHandles.Lookup lookup, String nam
}

/**
* Invokedynamic bootstrap for a class's packed-closure dispatcher accessor (GROOVY-12151):
* links the class's generated dispatch tables into one constant bundle, lazily on first
* adapter creation. Delegates to {@link GeneratedDispatcher#bootstrap}; hosted here so
* emitted bytecode references only this central bootstrap surface.
* Legacy invokedynamic bootstrap for a class's packed-closure dispatcher accessor
* (GROOVY-12151), kept for class files emitted by earlier 6.0 pre-releases: links the
* class's generated dispatch tables into one constant bundle, lazily on first adapter
* creation. Delegates to {@link GeneratedDispatcher#bootstrap}; hosted here so emitted
* bytecode references only this central bootstrap surface.
*
* @since 6.0.0
*/
public static CallSite packedDispatchers(MethodHandles.Lookup caller, String name, MethodType type) throws Throwable {
return GeneratedDispatcher.bootstrap(caller, name, type);
}

/**
* Invokedynamic bootstrap for a class's packed-closure dispatcher accessor (GROOVY-12151):
* the hosting class supplies a factory that builds the bundle from its own bytecode-level
* {@code LambdaMetafactory} sites. Nothing is looked up and no class is defined at link
* time, so this links unchanged in ahead-of-time environments that restrict either —
* GraalVM native image being the verified case (GROOVY-12227). The
* three-argument form remains for class files emitted by earlier 6.0 pre-releases.
*
* @since 6.0.0
*/
public static CallSite packedDispatchers(MethodHandles.Lookup caller, String name, MethodType type,
MethodHandle factory) throws Throwable {
return GeneratedDispatcher.bootstrap(caller, name, type, factory);
}

/**
* Constant-dynamic bootstrap for a packed closure literal's declared parameter types
* (GROOVY-12151): decodes a method descriptor into a {@code Class[]} resolved once per
Expand Down
Loading
Loading