Skip to content

Commit 6ca24e1

Browse files
authored
Painless: Use LocalMethod Map For Lookup at Runtime (#32599)
This modifies Def to use a Map<String, LocalMethod> to look up user-defined methods at runtime instead of writing constant methodhandles to do the reverse lookup. This creates a consistency between how LocalMethods are looked up at compile-time and run-time. This consistency will allow this code to be more maintainable moving forward. This will also allow FunctionReference to be cleaned up in a follow up PR.
1 parent 1e4751e commit 6ca24e1

File tree

12 files changed

+77
-79
lines changed

12 files changed

+77
-79
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.painless;
2121

2222
import org.elasticsearch.bootstrap.BootstrapInfo;
23+
import org.elasticsearch.painless.Locals.LocalMethod;
2324
import org.elasticsearch.painless.antlr.Walker;
2425
import org.elasticsearch.painless.lookup.PainlessLookup;
2526
import org.elasticsearch.painless.node.SSource;
@@ -32,6 +33,7 @@
3233
import java.security.CodeSource;
3334
import java.security.SecureClassLoader;
3435
import java.security.cert.Certificate;
36+
import java.util.Map;
3537
import java.util.concurrent.atomic.AtomicInteger;
3638

3739
import static org.elasticsearch.painless.WriterConstants.CLASS_NAME;
@@ -200,7 +202,7 @@ Constructor<?> compile(Loader loader, MainMethodReserved reserved, String name,
200202
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
201203
SSource root = Walker.buildPainlessTree(scriptClassInfo, reserved, name, source, settings, painlessLookup,
202204
null);
203-
root.analyze(painlessLookup);
205+
Map<String, LocalMethod> localMethods = root.analyze(painlessLookup);
204206
root.write();
205207

206208
try {
@@ -209,6 +211,7 @@ Constructor<?> compile(Loader loader, MainMethodReserved reserved, String name,
209211
clazz.getField("$SOURCE").set(null, source);
210212
clazz.getField("$STATEMENTS").set(null, root.getStatements());
211213
clazz.getField("$DEFINITION").set(null, painlessLookup);
214+
clazz.getField("$LOCALS").set(null, localMethods);
212215

213216
return clazz.getConstructors()[0];
214217
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.

modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java

+16-22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.painless;
2121

22+
import org.elasticsearch.painless.Locals.LocalMethod;
2223
import org.elasticsearch.painless.lookup.PainlessClass;
2324
import org.elasticsearch.painless.lookup.PainlessLookup;
2425
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
@@ -232,8 +233,10 @@ static PainlessMethod lookupMethodInternal(PainlessLookup painlessLookup, Class<
232233
* @throws IllegalArgumentException if no matching whitelisted method was found.
233234
* @throws Throwable if a method reference cannot be converted to an functional interface
234235
*/
235-
static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, MethodType callSiteType,
236-
Class<?> receiverClass, String name, Object args[]) throws Throwable {
236+
static MethodHandle lookupMethod(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
237+
MethodHandles.Lookup methodHandlesLookup, MethodType callSiteType, Class<?> receiverClass, String name, Object args[])
238+
throws Throwable {
239+
237240
String recipeString = (String) args[0];
238241
int numArguments = callSiteType.parameterCount();
239242
// simple case: no lambdas
@@ -286,6 +289,7 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lo
286289
// the implementation is strongly typed, now that we know the interface type,
287290
// we have everything.
288291
filter = lookupReferenceInternal(painlessLookup,
292+
localMethods,
289293
methodHandlesLookup,
290294
interfaceType,
291295
type,
@@ -297,6 +301,7 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lo
297301
// this cache). It won't blow up since we never nest here (just references)
298302
MethodType nestedType = MethodType.methodType(interfaceType, captures);
299303
CallSite nested = DefBootstrap.bootstrap(painlessLookup,
304+
localMethods,
300305
methodHandlesLookup,
301306
call,
302307
nestedType,
@@ -324,24 +329,23 @@ static MethodHandle lookupMethod(PainlessLookup painlessLookup, MethodHandles.Lo
324329
* This is just like LambdaMetaFactory, only with a dynamic type. The interface type is known,
325330
* so we simply need to lookup the matching implementation method based on receiver type.
326331
*/
327-
static MethodHandle lookupReference(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, String interfaceClass,
328-
Class<?> receiverClass, String name) throws Throwable {
332+
static MethodHandle lookupReference(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
333+
MethodHandles.Lookup methodHandlesLookup, String interfaceClass, Class<?> receiverClass, String name) throws Throwable {
329334
Class<?> interfaceType = painlessLookup.canonicalTypeNameToType(interfaceClass);
330335
PainlessMethod interfaceMethod = painlessLookup.lookupPainlessClass(interfaceType).functionalMethod;
331336
if (interfaceMethod == null) {
332337
throw new IllegalArgumentException("Class [" + interfaceClass + "] is not a functional interface");
333338
}
334339
int arity = interfaceMethod.typeParameters.size();
335340
PainlessMethod implMethod = lookupMethodInternal(painlessLookup, receiverClass, name, arity);
336-
return lookupReferenceInternal(painlessLookup, methodHandlesLookup, interfaceType,
337-
PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
341+
return lookupReferenceInternal(painlessLookup, localMethods, methodHandlesLookup,
342+
interfaceType, PainlessLookupUtility.typeToCanonicalTypeName(implMethod.targetClass),
338343
implMethod.javaMethod.getName(), receiverClass);
339344
}
340345

341346
/** Returns a method handle to an implementation of clazz, given method reference signature. */
342-
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup,
343-
Class<?> clazz, String type, String call, Class<?>... captures)
344-
throws Throwable {
347+
private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
348+
MethodHandles.Lookup methodHandlesLookup, Class<?> clazz, String type, String call, Class<?>... captures) throws Throwable {
345349
final FunctionRef ref;
346350
if ("this".equals(type)) {
347351
// user written method
@@ -351,13 +355,8 @@ private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLooku
351355
"to [" + PainlessLookupUtility.typeToCanonicalTypeName(clazz) + "], not a functional interface");
352356
}
353357
int arity = interfaceMethod.typeParameters.size() + captures.length;
354-
final MethodHandle handle;
355-
try {
356-
MethodHandle accessor = methodHandlesLookup.findStaticGetter(methodHandlesLookup.lookupClass(),
357-
getUserFunctionHandleFieldName(call, arity),
358-
MethodHandle.class);
359-
handle = (MethodHandle)accessor.invokeExact();
360-
} catch (NoSuchFieldException | IllegalAccessException e) {
358+
LocalMethod localMethod = localMethods.get(Locals.buildLocalMethodKey(call, arity));
359+
if (localMethod == null) {
361360
// is it a synthetic method? If we generated the method ourselves, be more helpful. It can only fail
362361
// because the arity does not match the expected interface type.
363362
if (call.contains("$")) {
@@ -366,7 +365,7 @@ private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLooku
366365
}
367366
throw new IllegalArgumentException("Unknown call [" + call + "] with [" + arity + "] arguments.");
368367
}
369-
ref = new FunctionRef(clazz, interfaceMethod, call, handle.type(), captures.length);
368+
ref = new FunctionRef(clazz, interfaceMethod, call, localMethod.methodType, captures.length);
370369
} else {
371370
// whitelist lookup
372371
ref = FunctionRef.resolveFromLookup(painlessLookup, clazz, type, call, captures.length);
@@ -385,11 +384,6 @@ private static MethodHandle lookupReferenceInternal(PainlessLookup painlessLooku
385384
return callSite.dynamicInvoker().asType(MethodType.methodType(clazz, captures));
386385
}
387386

388-
/** gets the field name used to lookup up the MethodHandle for a function. */
389-
public static String getUserFunctionHandleFieldName(String name, int arity) {
390-
return "handle$" + name + "$" + arity;
391-
}
392-
393387
/**
394388
* Looks up handle for a dynamic field getter (field load)
395389
* <p>

modules/lang-painless/src/main/java/org/elasticsearch/painless/DefBootstrap.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package org.elasticsearch.painless;
2121

2222
import org.elasticsearch.common.SuppressForbidden;
23+
import org.elasticsearch.painless.Locals.LocalMethod;
2324
import org.elasticsearch.painless.lookup.PainlessLookup;
2425

2526
import java.lang.invoke.CallSite;
@@ -28,6 +29,7 @@
2829
import java.lang.invoke.MethodType;
2930
import java.lang.invoke.MutableCallSite;
3031
import java.lang.invoke.WrongMethodTypeException;
32+
import java.util.Map;
3133

3234
/**
3335
* Painless invokedynamic bootstrap for the call site.
@@ -105,19 +107,21 @@ static final class PIC extends MutableCallSite {
105107
static final int MAX_DEPTH = 5;
106108

107109
private final PainlessLookup painlessLookup;
110+
private final Map<String, LocalMethod> localMethods;
108111
private final MethodHandles.Lookup methodHandlesLookup;
109112
private final String name;
110113
private final int flavor;
111114
private final Object[] args;
112115
int depth; // pkg-protected for testing
113116

114-
PIC(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup,
115-
String name, MethodType type, int initialDepth, int flavor, Object[] args) {
117+
PIC(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
118+
MethodHandles.Lookup methodHandlesLookup, String name, MethodType type, int initialDepth, int flavor, Object[] args) {
116119
super(type);
117120
if (type.parameterType(0) != Object.class) {
118121
throw new BootstrapMethodError("The receiver type (1st arg) of invokedynamic descriptor must be Object.");
119122
}
120123
this.painlessLookup = painlessLookup;
124+
this.localMethods = localMethods;
121125
this.methodHandlesLookup = methodHandlesLookup;
122126
this.name = name;
123127
this.flavor = flavor;
@@ -145,7 +149,7 @@ static boolean checkClass(Class<?> clazz, Object receiver) {
145149
private MethodHandle lookup(int flavor, String name, Class<?> receiver) throws Throwable {
146150
switch(flavor) {
147151
case METHOD_CALL:
148-
return Def.lookupMethod(painlessLookup, methodHandlesLookup, type(), receiver, name, args);
152+
return Def.lookupMethod(painlessLookup, localMethods, methodHandlesLookup, type(), receiver, name, args);
149153
case LOAD:
150154
return Def.lookupGetter(painlessLookup, receiver, name);
151155
case STORE:
@@ -157,7 +161,7 @@ private MethodHandle lookup(int flavor, String name, Class<?> receiver) throws T
157161
case ITERATOR:
158162
return Def.lookupIterator(receiver);
159163
case REFERENCE:
160-
return Def.lookupReference(painlessLookup, methodHandlesLookup, (String) args[0], receiver, name);
164+
return Def.lookupReference(painlessLookup, localMethods, methodHandlesLookup, (String) args[0], receiver, name);
161165
case INDEX_NORMALIZE:
162166
return Def.lookupIndexNormalize(receiver);
163167
default: throw new AssertionError();
@@ -432,8 +436,9 @@ static boolean checkBoth(Class<?> left, Class<?> right, Object leftObject, Objec
432436
* <p>
433437
* see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokedynamic
434438
*/
435-
public static CallSite bootstrap(PainlessLookup painlessLookup, MethodHandles.Lookup methodHandlesLookup, String name,
436-
MethodType type, int initialDepth, int flavor, Object... args) {
439+
@SuppressWarnings("unchecked")
440+
public static CallSite bootstrap(PainlessLookup painlessLookup, Map<String, LocalMethod> localMethods,
441+
MethodHandles.Lookup methodHandlesLookup, String name, MethodType type, int initialDepth, int flavor, Object... args) {
437442
// validate arguments
438443
switch(flavor) {
439444
// "function-call" like things get a polymorphic cache
@@ -452,7 +457,7 @@ public static CallSite bootstrap(PainlessLookup painlessLookup, MethodHandles.Lo
452457
if (args.length != numLambdas + 1) {
453458
throw new BootstrapMethodError("Illegal number of parameters: expected " + numLambdas + " references");
454459
}
455-
return new PIC(painlessLookup, methodHandlesLookup, name, type, initialDepth, flavor, args);
460+
return new PIC(painlessLookup, localMethods, methodHandlesLookup, name, type, initialDepth, flavor, args);
456461
case LOAD:
457462
case STORE:
458463
case ARRAY_LOAD:
@@ -462,15 +467,15 @@ public static CallSite bootstrap(PainlessLookup painlessLookup, MethodHandles.Lo
462467
if (args.length > 0) {
463468
throw new BootstrapMethodError("Illegal static bootstrap parameters for flavor: " + flavor);
464469
}
465-
return new PIC(painlessLookup, methodHandlesLookup, name, type, initialDepth, flavor, args);
470+
return new PIC(painlessLookup, localMethods, methodHandlesLookup, name, type, initialDepth, flavor, args);
466471
case REFERENCE:
467472
if (args.length != 1) {
468473
throw new BootstrapMethodError("Invalid number of parameters for reference call");
469474
}
470475
if (args[0] instanceof String == false) {
471476
throw new BootstrapMethodError("Illegal parameter for reference call: " + args[0]);
472477
}
473-
return new PIC(painlessLookup, methodHandlesLookup, name, type, initialDepth, flavor, args);
478+
return new PIC(painlessLookup, localMethods, methodHandlesLookup, name, type, initialDepth, flavor, args);
474479

475480
// operators get monomorphic cache, with a generic impl for a fallback
476481
case UNARY_OPERATOR:

modules/lang-painless/src/main/java/org/elasticsearch/painless/Locals.java

+19-23
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
import java.util.List;
3333
import java.util.Map;
3434
import java.util.Set;
35+
import java.util.stream.Collectors;
36+
37+
import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typeToJavaType;
3538

3639
/**
3740
* Tracks user defined methods and variables across compilation phases.
@@ -74,17 +77,24 @@ public LocalMethod(String name, Class<?> returnType, List<Class<?>> typeParamete
7477

7578
/** Creates a new local variable scope (e.g. loop) inside the current scope */
7679
public static Locals newLocalScope(Locals currentScope) {
77-
return new Locals(currentScope);
80+
Locals locals = new Locals(currentScope);
81+
locals.methods = currentScope.methods;
82+
83+
return locals;
7884
}
7985

8086
/**
8187
* Creates a new lambda scope inside the current scope
8288
* <p>
8389
* This is just like {@link #newFunctionScope}, except the captured parameters are made read-only.
8490
*/
85-
public static Locals newLambdaScope(Locals programScope, Class<?> returnType, List<Parameter> parameters,
91+
public static Locals newLambdaScope(Locals programScope, String name, Class<?> returnType, List<Parameter> parameters,
8692
int captureCount, int maxLoopCounter) {
8793
Locals locals = new Locals(programScope, programScope.painlessLookup, returnType, KEYWORDS);
94+
locals.methods = programScope.methods;
95+
List<Class<?>> typeParameters = parameters.stream().map(parameter -> typeToJavaType(parameter.clazz)).collect(Collectors.toList());
96+
locals.methods.put(buildLocalMethodKey(name, parameters.size()), new LocalMethod(name, returnType, typeParameters,
97+
MethodType.methodType(typeToJavaType(returnType), typeParameters)));
8898
for (int i = 0; i < parameters.size(); i++) {
8999
Parameter parameter = parameters.get(i);
90100
// TODO: allow non-captures to be r/w:
@@ -104,6 +114,7 @@ public static Locals newLambdaScope(Locals programScope, Class<?> returnType, Li
104114
/** Creates a new function scope inside the current scope */
105115
public static Locals newFunctionScope(Locals programScope, Class<?> returnType, List<Parameter> parameters, int maxLoopCounter) {
106116
Locals locals = new Locals(programScope, programScope.painlessLookup, returnType, KEYWORDS);
117+
locals.methods = programScope.methods;
107118
for (Parameter parameter : parameters) {
108119
locals.addVariable(parameter.location, parameter.clazz, parameter.name, false);
109120
}
@@ -118,6 +129,7 @@ public static Locals newFunctionScope(Locals programScope, Class<?> returnType,
118129
public static Locals newMainMethodScope(ScriptClassInfo scriptClassInfo, Locals programScope, int maxLoopCounter) {
119130
Locals locals = new Locals(
120131
programScope, programScope.painlessLookup, scriptClassInfo.getExecuteMethodReturnType(), KEYWORDS);
132+
locals.methods = programScope.methods;
121133
// This reference. Internal use only.
122134
locals.defineVariable(null, Object.class, THIS, true);
123135

@@ -136,6 +148,7 @@ public static Locals newMainMethodScope(ScriptClassInfo scriptClassInfo, Locals
136148
/** Creates a new program scope: the list of methods. It is the parent for all methods */
137149
public static Locals newProgramScope(PainlessLookup painlessLookup, Collection<LocalMethod> methods) {
138150
Locals locals = new Locals(null, painlessLookup, null, null);
151+
locals.methods = new HashMap<>();
139152
for (LocalMethod method : methods) {
140153
locals.addMethod(method);
141154
}
@@ -167,15 +180,8 @@ public Variable getVariable(Location location, String name) {
167180
}
168181

169182
/** Looks up a method. Returns null if the method does not exist. */
170-
public LocalMethod getMethod(String key) {
171-
LocalMethod method = lookupMethod(key);
172-
if (method != null) {
173-
return method;
174-
}
175-
if (parent != null) {
176-
return parent.getMethod(key);
177-
}
178-
return null;
183+
public LocalMethod getMethod(String methodName, int methodArity) {
184+
return methods.get(buildLocalMethodKey(methodName, methodArity));
179185
}
180186

181187
/** Creates a new variable. Throws IAE if the variable has already been defined (even in a parent) or reserved. */
@@ -260,15 +266,10 @@ private Variable lookupVariable(Location location, String name) {
260266
return variables.get(name);
261267
}
262268

263-
/** Looks up a method at this scope only. Returns null if the method does not exist. */
264-
private LocalMethod lookupMethod(String key) {
265-
if (methods == null) {
266-
return null;
267-
}
268-
return methods.get(key);
269+
public Map<String, LocalMethod> getMethods() {
270+
return Collections.unmodifiableMap(methods);
269271
}
270272

271-
272273
/** Defines a variable at this scope internally. */
273274
private Variable defineVariable(Location location, Class<?> type, String name, boolean readonly) {
274275
if (variables == null) {
@@ -281,14 +282,9 @@ private Variable defineVariable(Location location, Class<?> type, String name, b
281282
}
282283

283284
private void addMethod(LocalMethod method) {
284-
if (methods == null) {
285-
methods = new HashMap<>();
286-
}
287285
methods.put(buildLocalMethodKey(method.name, method.typeParameters.size()), method);
288-
// TODO: check result
289286
}
290287

291-
292288
private int getNextSlot() {
293289
return nextSlotNumber;
294290
}

0 commit comments

Comments
 (0)