瀏覽代碼

Make Painless static state more than 2x smaller saving 7M+ of heap at idle (#103326)

First off, deduplicate painless clasess more aggressively across
contexts. Also, save the memory for the static deduplication maps by
just passing local variables through the build process. This only runs a
single time anyway so there's no point in retaining these static maps,
we'll never use them again.

Before, unused idle painless:
![image](https://github.com/elastic/elasticsearch/assets/6490959/5e7bef79-c8a2-40fe-8436-aacdfb75cfc6)

After:

![image](https://github.com/elastic/elasticsearch/assets/6490959/596d5d91-f450-4cc3-b7ce-5de40da5d398)

12.4M of static state become 4.7M saving almost 8M on every ES node
(since painless is a module now)
Armin Braun 1 年之前
父節點
當前提交
6a4315aeeb

+ 4 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/PainlessScriptEngine.java

@@ -13,6 +13,7 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.painless.Compiler.Loader;
 import org.elasticsearch.painless.lookup.PainlessLookup;
 import org.elasticsearch.painless.lookup.PainlessLookupBuilder;
+import org.elasticsearch.painless.lookup.PainlessMethod;
 import org.elasticsearch.painless.spi.Whitelist;
 import org.elasticsearch.painless.symbol.ScriptScope;
 import org.elasticsearch.script.ScriptContext;
@@ -84,9 +85,11 @@ public final class PainlessScriptEngine implements ScriptEngine {
         Map<ScriptContext<?>, Compiler> mutableContextsToCompilers = new HashMap<>();
         Map<ScriptContext<?>, PainlessLookup> mutableContextsToLookups = new HashMap<>();
 
+        final Map<Object, Object> dedup = new HashMap<>();
+        final Map<PainlessMethod, PainlessMethod> filteredMethodCache = new HashMap<>();
         for (Map.Entry<ScriptContext<?>, List<Whitelist>> entry : contexts.entrySet()) {
             ScriptContext<?> context = entry.getKey();
-            PainlessLookup lookup = PainlessLookupBuilder.buildFromWhitelists(entry.getValue());
+            PainlessLookup lookup = PainlessLookupBuilder.buildFromWhitelists(entry.getValue(), dedup, filteredMethodCache);
             mutableContextsToCompilers.put(
                 context,
                 new Compiler(context.instanceClazz, context.factoryClazz, context.statefulFactoryClazz, lookup)

+ 1 - 1
modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessClass.java

@@ -49,7 +49,7 @@ public final class PainlessClass {
 
         this.getterMethodHandles = Map.copyOf(getterMethodHandles);
         this.setterMethodHandles = Map.copyOf(setterMethodHandles);
-        this.runtimeMethods = Map.copyOf(runtimeMethods);
+        this.runtimeMethods = runtimeMethods.equals(methods) ? this.methods : Map.copyOf(runtimeMethods);
     }
 
     @Override

+ 0 - 24
modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookup.java

@@ -163,18 +163,6 @@ public final class PainlessLookup {
         return lookupPainlessObject(targetClass, objectLookup);
     }
 
-    public List<PainlessMethod> lookupPainlessSubClassesMethod(String targetCanonicalClassName, String methodName, int methodArity) {
-        Objects.requireNonNull(targetCanonicalClassName);
-
-        Class<?> targetClass = canonicalTypeNameToType(targetCanonicalClassName);
-
-        if (targetClass == null) {
-            return null;
-        }
-
-        return lookupPainlessSubClassesMethod(targetClass, methodName, methodArity);
-    }
-
     public List<PainlessMethod> lookupPainlessSubClassesMethod(Class<?> targetClass, String methodName, int methodArity) {
         Objects.requireNonNull(targetClass);
         Objects.requireNonNull(methodName);
@@ -218,18 +206,6 @@ public final class PainlessLookup {
         return subMethods;
     }
 
-    public PainlessField lookupPainlessField(String targetCanonicalClassName, boolean isStatic, String fieldName) {
-        Objects.requireNonNull(targetCanonicalClassName);
-
-        Class<?> targetClass = canonicalTypeNameToType(targetCanonicalClassName);
-
-        if (targetClass == null) {
-            return null;
-        }
-
-        return lookupPainlessField(targetClass, isStatic, fieldName);
-    }
-
     public PainlessField lookupPainlessField(Class<?> targetClass, boolean isStatic, String fieldName) {
         Objects.requireNonNull(targetClass);
         Objects.requireNonNull(fieldName);

+ 83 - 61
modules/lang-painless/src/main/java/org/elasticsearch/painless/lookup/PainlessLookupBuilder.java

@@ -56,18 +56,14 @@ import static org.elasticsearch.painless.lookup.PainlessLookupUtility.typesToCan
 
 public final class PainlessLookupBuilder {
 
-    private static final Map<PainlessConstructor, PainlessConstructor> painlessConstructorCache = new HashMap<>();
-    private static final Map<PainlessMethod, PainlessMethod> painlessMethodCache = new HashMap<>();
-    private static final Map<PainlessField, PainlessField> painlessFieldCache = new HashMap<>();
-    private static final Map<PainlessClassBinding, PainlessClassBinding> painlessClassBindingCache = new HashMap<>();
-    private static final Map<PainlessInstanceBinding, PainlessInstanceBinding> painlessInstanceBindingCache = new HashMap<>();
-    private static final Map<PainlessMethod, PainlessMethod> painlessFilteredCache = new HashMap<>();
-
     private static final Pattern CLASS_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][._a-zA-Z0-9]*$");
-    private static final Pattern METHOD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
-    private static final Pattern FIELD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
+    private static final Pattern METHOD_AND_FIELD_NAME_PATTERN = Pattern.compile("^[_a-zA-Z][_a-zA-Z0-9]*$");
 
-    public static PainlessLookup buildFromWhitelists(List<Whitelist> whitelists) {
+    public static PainlessLookup buildFromWhitelists(
+        List<Whitelist> whitelists,
+        Map<Object, Object> dedup,
+        Map<PainlessMethod, PainlessMethod> filteredMethodCache
+    ) {
         PainlessLookupBuilder painlessLookupBuilder = new PainlessLookupBuilder();
         String origin = "internal error";
 
@@ -92,7 +88,8 @@ public final class PainlessLookupBuilder {
                         painlessLookupBuilder.addPainlessConstructor(
                             targetCanonicalClassName,
                             whitelistConstructor.canonicalTypeNameParameters,
-                            whitelistConstructor.painlessAnnotations
+                            whitelistConstructor.painlessAnnotations,
+                            dedup
                         );
                     }
 
@@ -105,7 +102,8 @@ public final class PainlessLookupBuilder {
                             whitelistMethod.methodName,
                             whitelistMethod.returnCanonicalTypeName,
                             whitelistMethod.canonicalTypeNameParameters,
-                            whitelistMethod.painlessAnnotations
+                            whitelistMethod.painlessAnnotations,
+                            dedup
                         );
                     }
 
@@ -116,7 +114,8 @@ public final class PainlessLookupBuilder {
                             targetCanonicalClassName,
                             whitelistField.fieldName,
                             whitelistField.canonicalTypeNameParameter,
-                            whitelistField.painlessAnnotations
+                            whitelistField.painlessAnnotations,
+                            dedup
                         );
                     }
                 }
@@ -129,7 +128,8 @@ public final class PainlessLookupBuilder {
                         whitelistStatic.methodName,
                         whitelistStatic.returnCanonicalTypeName,
                         whitelistStatic.canonicalTypeNameParameters,
-                        whitelistStatic.painlessAnnotations
+                        whitelistStatic.painlessAnnotations,
+                        dedup
                     );
                 }
 
@@ -141,7 +141,8 @@ public final class PainlessLookupBuilder {
                         whitelistClassBinding.methodName,
                         whitelistClassBinding.returnCanonicalTypeName,
                         whitelistClassBinding.canonicalTypeNameParameters,
-                        whitelistClassBinding.painlessAnnotations
+                        whitelistClassBinding.painlessAnnotations,
+                        dedup
                     );
                 }
 
@@ -152,7 +153,8 @@ public final class PainlessLookupBuilder {
                         whitelistInstanceBinding.methodName,
                         whitelistInstanceBinding.returnCanonicalTypeName,
                         whitelistInstanceBinding.canonicalTypeNameParameters,
-                        whitelistInstanceBinding.painlessAnnotations
+                        whitelistInstanceBinding.painlessAnnotations,
+                        dedup
                     );
                 }
             }
@@ -160,7 +162,7 @@ public final class PainlessLookupBuilder {
             throw new IllegalArgumentException("error loading whitelist(s) " + origin, exception);
         }
 
-        return painlessLookupBuilder.build();
+        return painlessLookupBuilder.build(dedup, filteredMethodCache);
     }
 
     // javaClassNamesToClasses is all the classes that need to be available to the custom classloader
@@ -269,7 +271,7 @@ public final class PainlessLookupBuilder {
         return new IllegalArgumentException(Strings.format(formatText, args), cause);
     }
 
-    public void addPainlessClass(Class<?> clazz, Map<Class<?>, Object> annotations) {
+    private void addPainlessClass(Class<?> clazz, Map<Class<?>, Object> annotations) {
         Objects.requireNonNull(clazz);
         Objects.requireNonNull(annotations);
 
@@ -355,10 +357,11 @@ public final class PainlessLookupBuilder {
         }
     }
 
-    public void addPainlessConstructor(
+    private void addPainlessConstructor(
         String targetCanonicalClassName,
         List<String> canonicalTypeNameParameters,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
         Objects.requireNonNull(targetCanonicalClassName);
         Objects.requireNonNull(canonicalTypeNameParameters);
@@ -391,10 +394,15 @@ public final class PainlessLookupBuilder {
             typeParameters.add(typeParameter);
         }
 
-        addPainlessConstructor(targetClass, typeParameters, annotations);
+        addPainlessConstructor(targetClass, typeParameters, annotations, dedup);
     }
 
-    public void addPainlessConstructor(Class<?> targetClass, List<Class<?>> typeParameters, Map<Class<?>, Object> annotations) {
+    private void addPainlessConstructor(
+        Class<?> targetClass,
+        List<Class<?>> typeParameters,
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
+    ) {
         Objects.requireNonNull(targetClass);
         Objects.requireNonNull(typeParameters);
 
@@ -473,7 +481,7 @@ public final class PainlessLookupBuilder {
         );
 
         if (existingPainlessConstructor == null) {
-            newPainlessConstructor = painlessConstructorCache.computeIfAbsent(newPainlessConstructor, Function.identity());
+            newPainlessConstructor = (PainlessConstructor) dedup.computeIfAbsent(newPainlessConstructor, Function.identity());
             painlessClassBuilder.constructors.put(painlessConstructorKey.intern(), newPainlessConstructor);
         } else if (newPainlessConstructor.equals(existingPainlessConstructor) == false) {
             throw lookupException(
@@ -486,14 +494,15 @@ public final class PainlessLookupBuilder {
         }
     }
 
-    public void addPainlessMethod(
+    private void addPainlessMethod(
         ClassLoader classLoader,
         String targetCanonicalClassName,
         String augmentedCanonicalClassName,
         String methodName,
         String returnCanonicalTypeName,
         List<String> canonicalTypeNameParameters,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
 
         Objects.requireNonNull(classLoader);
@@ -561,7 +570,7 @@ public final class PainlessLookupBuilder {
             );
         }
 
-        addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters, annotations);
+        addPainlessMethod(targetClass, augmentedClass, methodName, returnType, typeParameters, annotations, dedup);
     }
 
     public void addPainlessMethod(
@@ -570,7 +579,8 @@ public final class PainlessLookupBuilder {
         String methodName,
         Class<?> returnType,
         List<Class<?>> typeParameters,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
 
         Objects.requireNonNull(targetClass);
@@ -585,7 +595,7 @@ public final class PainlessLookupBuilder {
 
         String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
 
-        if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
+        if (METHOD_AND_FIELD_NAME_PATTERN.matcher(methodName).matches() == false) {
             throw new IllegalArgumentException(
                 "invalid method name [" + methodName + "] for target class [" + targetCanonicalClassName + "]."
             );
@@ -748,7 +758,7 @@ public final class PainlessLookupBuilder {
         );
 
         if (existingPainlessMethod == null) {
-            newPainlessMethod = painlessMethodCache.computeIfAbsent(newPainlessMethod, key -> key);
+            newPainlessMethod = (PainlessMethod) dedup.computeIfAbsent(newPainlessMethod, Function.identity());
 
             if (isStatic) {
                 painlessClassBuilder.staticMethods.put(painlessMethodKey.intern(), newPainlessMethod);
@@ -771,12 +781,13 @@ public final class PainlessLookupBuilder {
         }
     }
 
-    public void addPainlessField(
+    private void addPainlessField(
         ClassLoader classLoader,
         String targetCanonicalClassName,
         String fieldName,
         String canonicalTypeNameParameter,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
 
         Objects.requireNonNull(classLoader);
@@ -827,15 +838,16 @@ public final class PainlessLookupBuilder {
             );
         }
 
-        addPainlessField(targetClass, augmentedClass, fieldName, typeParameter, annotations);
+        addPainlessField(targetClass, augmentedClass, fieldName, typeParameter, annotations, dedup);
     }
 
-    public void addPainlessField(
+    private void addPainlessField(
         Class<?> targetClass,
         Class<?> augmentedClass,
         String fieldName,
         Class<?> typeParameter,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
 
         Objects.requireNonNull(targetClass);
@@ -849,7 +861,7 @@ public final class PainlessLookupBuilder {
 
         String targetCanonicalClassName = typeToCanonicalTypeName(targetClass);
 
-        if (FIELD_NAME_PATTERN.matcher(fieldName).matches() == false) {
+        if (METHOD_AND_FIELD_NAME_PATTERN.matcher(fieldName).matches() == false) {
             throw new IllegalArgumentException(
                 "invalid field name [" + fieldName + "] for target class [" + targetCanonicalClassName + "]."
             );
@@ -946,7 +958,7 @@ public final class PainlessLookupBuilder {
             PainlessField newPainlessField = new PainlessField(javaField, typeParameter, annotations, methodHandleGetter, null);
 
             if (existingPainlessField == null) {
-                newPainlessField = painlessFieldCache.computeIfAbsent(newPainlessField, Function.identity());
+                newPainlessField = (PainlessField) dedup.computeIfAbsent(newPainlessField, Function.identity());
                 painlessClassBuilder.staticFields.put(painlessFieldKey.intern(), newPainlessField);
             } else if (newPainlessField.equals(existingPainlessField) == false) {
                 throw lookupException(
@@ -981,7 +993,7 @@ public final class PainlessLookupBuilder {
             );
 
             if (existingPainlessField == null) {
-                newPainlessField = painlessFieldCache.computeIfAbsent(newPainlessField, key -> key);
+                newPainlessField = (PainlessField) dedup.computeIfAbsent(newPainlessField, Function.identity());
                 painlessClassBuilder.fields.put(painlessFieldKey.intern(), newPainlessField);
             } else if (newPainlessField.equals(existingPainlessField) == false) {
                 throw lookupException(
@@ -1004,7 +1016,8 @@ public final class PainlessLookupBuilder {
         String methodName,
         String returnCanonicalTypeName,
         List<String> canonicalTypeNameParameters,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
 
         Objects.requireNonNull(classLoader);
@@ -1046,7 +1059,7 @@ public final class PainlessLookupBuilder {
             );
         }
 
-        addImportedPainlessMethod(targetClass, methodName, returnType, typeParameters, annotations);
+        addImportedPainlessMethod(targetClass, methodName, returnType, typeParameters, annotations, dedup);
     }
 
     public void addImportedPainlessMethod(
@@ -1054,7 +1067,8 @@ public final class PainlessLookupBuilder {
         String methodName,
         Class<?> returnType,
         List<Class<?>> typeParameters,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
         Objects.requireNonNull(targetClass);
         Objects.requireNonNull(methodName);
@@ -1077,7 +1091,7 @@ public final class PainlessLookupBuilder {
             );
         }
 
-        if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
+        if (METHOD_AND_FIELD_NAME_PATTERN.matcher(methodName).matches() == false) {
             throw new IllegalArgumentException(
                 "invalid imported method name [" + methodName + "] for target class [" + targetCanonicalClassName + "]."
             );
@@ -1182,7 +1196,7 @@ public final class PainlessLookupBuilder {
         );
 
         if (existingImportedPainlessMethod == null) {
-            newImportedPainlessMethod = painlessMethodCache.computeIfAbsent(newImportedPainlessMethod, key -> key);
+            newImportedPainlessMethod = (PainlessMethod) dedup.computeIfAbsent(newImportedPainlessMethod, Function.identity());
             painlessMethodKeysToImportedPainlessMethods.put(painlessMethodKey.intern(), newImportedPainlessMethod);
         } else if (newImportedPainlessMethod.equals(existingImportedPainlessMethod) == false) {
             throw lookupException(
@@ -1206,7 +1220,8 @@ public final class PainlessLookupBuilder {
         String methodName,
         String returnCanonicalTypeName,
         List<String> canonicalTypeNameParameters,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
 
         Objects.requireNonNull(classLoader);
@@ -1247,15 +1262,16 @@ public final class PainlessLookupBuilder {
             );
         }
 
-        addPainlessClassBinding(targetClass, methodName, returnType, typeParameters, annotations);
+        addPainlessClassBinding(targetClass, methodName, returnType, typeParameters, annotations, dedup);
     }
 
-    public void addPainlessClassBinding(
+    private void addPainlessClassBinding(
         Class<?> targetClass,
         String methodName,
         Class<?> returnType,
         List<Class<?>> typeParameters,
-        Map<Class<?>, Object> annotations
+        Map<Class<?>, Object> annotations,
+        Map<Object, Object> dedup
     ) {
         Objects.requireNonNull(targetClass);
         Objects.requireNonNull(methodName);
@@ -1333,7 +1349,7 @@ public final class PainlessLookupBuilder {
             }
         }
 
-        if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
+        if (METHOD_AND_FIELD_NAME_PATTERN.matcher(methodName).matches() == false) {
             throw new IllegalArgumentException(
                 "invalid method name [" + methodName + "] for class binding [" + targetCanonicalClassName + "]."
             );
@@ -1446,7 +1462,7 @@ public final class PainlessLookupBuilder {
         );
 
         if (existingPainlessClassBinding == null) {
-            newPainlessClassBinding = painlessClassBindingCache.computeIfAbsent(newPainlessClassBinding, Function.identity());
+            newPainlessClassBinding = (PainlessClassBinding) dedup.computeIfAbsent(newPainlessClassBinding, Function.identity());
             painlessMethodKeysToPainlessClassBindings.put(painlessMethodKey.intern(), newPainlessClassBinding);
         } else if (newPainlessClassBinding.equals(existingPainlessClassBinding) == false) {
             throw lookupException(
@@ -1469,7 +1485,8 @@ public final class PainlessLookupBuilder {
         String methodName,
         String returnCanonicalTypeName,
         List<String> canonicalTypeNameParameters,
-        Map<Class<?>, Object> painlessAnnotations
+        Map<Class<?>, Object> painlessAnnotations,
+        Map<Object, Object> dedup
     ) {
 
         Objects.requireNonNull(targetInstance);
@@ -1509,7 +1526,7 @@ public final class PainlessLookupBuilder {
             );
         }
 
-        addPainlessInstanceBinding(targetInstance, methodName, returnType, typeParameters, painlessAnnotations);
+        addPainlessInstanceBinding(targetInstance, methodName, returnType, typeParameters, painlessAnnotations, dedup);
     }
 
     public void addPainlessInstanceBinding(
@@ -1517,7 +1534,8 @@ public final class PainlessLookupBuilder {
         String methodName,
         Class<?> returnType,
         List<Class<?>> typeParameters,
-        Map<Class<?>, Object> painlessAnnotations
+        Map<Class<?>, Object> painlessAnnotations,
+        Map<Object, Object> dedup
     ) {
         Objects.requireNonNull(targetInstance);
         Objects.requireNonNull(methodName);
@@ -1542,7 +1560,7 @@ public final class PainlessLookupBuilder {
             );
         }
 
-        if (METHOD_NAME_PATTERN.matcher(methodName).matches() == false) {
+        if (METHOD_AND_FIELD_NAME_PATTERN.matcher(methodName).matches() == false) {
             throw new IllegalArgumentException(
                 "invalid method name [" + methodName + "] for instance binding [" + targetCanonicalClassName + "]."
             );
@@ -1629,7 +1647,7 @@ public final class PainlessLookupBuilder {
         );
 
         if (existingPainlessInstanceBinding == null) {
-            newPainlessInstanceBinding = painlessInstanceBindingCache.computeIfAbsent(newPainlessInstanceBinding, key -> key);
+            newPainlessInstanceBinding = (PainlessInstanceBinding) dedup.computeIfAbsent(newPainlessInstanceBinding, Function.identity());
             painlessMethodKeysToPainlessInstanceBindings.put(painlessMethodKey.intern(), newPainlessInstanceBinding);
         } else if (newPainlessInstanceBinding.equals(existingPainlessInstanceBinding) == false) {
             throw lookupException(
@@ -1649,16 +1667,19 @@ public final class PainlessLookupBuilder {
         }
     }
 
-    public PainlessLookup build() {
+    public PainlessLookup build(Map<Object, Object> dedup, Map<PainlessMethod, PainlessMethod> filteredMethodCache) {
         buildPainlessClassHierarchy();
         setFunctionalInterfaceMethods();
-        generateRuntimeMethods();
+        generateRuntimeMethods(filteredMethodCache);
         cacheRuntimeHandles();
 
         Map<Class<?>, PainlessClass> classesToPainlessClasses = Maps.newMapWithExpectedSize(classesToPainlessClassBuilders.size());
 
         for (Map.Entry<Class<?>, PainlessClassBuilder> painlessClassBuilderEntry : classesToPainlessClassBuilders.entrySet()) {
-            classesToPainlessClasses.put(painlessClassBuilderEntry.getKey(), painlessClassBuilderEntry.getValue().build());
+            classesToPainlessClasses.put(
+                painlessClassBuilderEntry.getKey(),
+                (PainlessClass) dedup.computeIfAbsent(painlessClassBuilderEntry.getValue().build(), Function.identity())
+            );
         }
 
         if (javaClassNamesToClasses.values().containsAll(canonicalClassNamesToClasses.values()) == false) {
@@ -1817,7 +1838,7 @@ public final class PainlessLookupBuilder {
      * {@link Map}. The {@link PainlessClass#runtimeMethods} {@link Map} is used exclusively to look up methods at
      * run-time resulting from calls with a def type value target.
      */
-    private void generateRuntimeMethods() {
+    private void generateRuntimeMethods(Map<PainlessMethod, PainlessMethod> filteredMethodCache) {
         for (Map.Entry<Class<?>, PainlessClassBuilder> painlessClassBuilderEntry : classesToPainlessClassBuilders.entrySet()) {
             Class<?> targetClass = painlessClassBuilderEntry.getKey();
             PainlessClassBuilder painlessClassBuilder = painlessClassBuilderEntry.getValue();
@@ -1832,7 +1853,7 @@ public final class PainlessLookupBuilder {
                         || typeParameter == Long.class
                         || typeParameter == Float.class
                         || typeParameter == Double.class) {
-                        generateFilteredMethod(targetClass, painlessClassBuilder, painlessMethod);
+                        generateFilteredMethod(targetClass, painlessClassBuilder, painlessMethod, filteredMethodCache);
                     }
                 }
             }
@@ -1842,10 +1863,11 @@ public final class PainlessLookupBuilder {
     private static void generateFilteredMethod(
         Class<?> targetClass,
         PainlessClassBuilder painlessClassBuilder,
-        PainlessMethod painlessMethod
+        PainlessMethod painlessMethod,
+        Map<PainlessMethod, PainlessMethod> filteredMethodCache
     ) {
         String painlessMethodKey = buildPainlessMethodKey(painlessMethod.javaMethod().getName(), painlessMethod.typeParameters().size());
-        PainlessMethod filteredPainlessMethod = painlessFilteredCache.get(painlessMethod);
+        PainlessMethod filteredPainlessMethod = filteredMethodCache.get(painlessMethod);
 
         if (filteredPainlessMethod == null) {
             Method javaMethod = painlessMethod.javaMethod();
@@ -1899,7 +1921,7 @@ public final class PainlessLookupBuilder {
                     Map.of()
                 );
                 painlessClassBuilder.runtimeMethods.put(painlessMethodKey.intern(), filteredPainlessMethod);
-                painlessFilteredCache.put(painlessMethod, filteredPainlessMethod);
+                filteredMethodCache.put(painlessMethod, filteredPainlessMethod);
             } catch (Exception exception) {
                 throw new IllegalStateException(
                     "internal error occurred attempting to generate a runtime method [" + painlessMethodKey + "]",

+ 3 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/AliasTests.java

@@ -34,7 +34,9 @@ public class AliasTests extends ScriptTestCase {
         IllegalArgumentException err = expectThrows(
             IllegalArgumentException.class,
             () -> PainlessLookupBuilder.buildFromWhitelists(
-                List.of(WhitelistLoader.loadFromResourceFiles(PainlessPlugin.class, "org.elasticsearch.painless.alias-shadow"))
+                List.of(WhitelistLoader.loadFromResourceFiles(PainlessPlugin.class, "org.elasticsearch.painless.alias-shadow")),
+                new HashMap<>(),
+                new HashMap<>()
             )
         );
         assertEquals(

+ 6 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/DebugTests.java

@@ -16,6 +16,7 @@ import org.elasticsearch.painless.lookup.PainlessLookupBuilder;
 import org.elasticsearch.script.ScriptException;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.Map;
 
 import static java.util.Collections.singletonList;
@@ -26,7 +27,11 @@ import static org.hamcrest.Matchers.hasKey;
 import static org.hamcrest.Matchers.not;
 
 public class DebugTests extends ScriptTestCase {
-    private final PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(PainlessPlugin.BASE_WHITELISTS);
+    private final PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(
+        PainlessPlugin.BASE_WHITELISTS,
+        new HashMap<>(),
+        new HashMap<>()
+    );
 
     public void testExplain() {
         // Debug.explain can explain an object

+ 5 - 15
modules/lang-painless/src/test/java/org/elasticsearch/painless/Debugger.java

@@ -19,6 +19,7 @@ import org.objectweb.asm.util.Textifier;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.HashMap;
 import java.util.List;
 
 /** quick and dirty tools for debugging */
@@ -35,12 +36,8 @@ final class Debugger {
         PrintWriter outputWriter = new PrintWriter(output);
         Textifier textifier = new Textifier();
         try {
-            new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(whitelists)).compile(
-                "<debugging>",
-                source,
-                settings,
-                textifier
-            );
+            new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(whitelists, new HashMap<>(), new HashMap<>()))
+                .compile("<debugging>", source, settings, textifier);
         } catch (RuntimeException e) {
             textifier.print(outputWriter);
             e.addSuppressed(new Exception("current bytecode: \n" + output));
@@ -65,15 +62,8 @@ final class Debugger {
         PrintWriter outputWriter = new PrintWriter(output);
         Textifier textifier = new Textifier();
         try {
-            new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(whitelists)).compile(
-                "<debugging>",
-                source,
-                settings,
-                textifier,
-                semanticPhaseVisitor,
-                irPhaseVisitor,
-                asmPhaseVisitor
-            );
+            new Compiler(iface, null, null, PainlessLookupBuilder.buildFromWhitelists(whitelists, new HashMap<>(), new HashMap<>()))
+                .compile("<debugging>", source, settings, textifier, semanticPhaseVisitor, irPhaseVisitor, asmPhaseVisitor);
         } catch (RuntimeException e) {
             textifier.print(outputWriter);
             e.addSuppressed(new Exception("current bytecode: \n" + output));

+ 5 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/DefBootstrapTests.java

@@ -22,7 +22,11 @@ import java.util.Collections;
 import java.util.HashMap;
 
 public class DefBootstrapTests extends ESTestCase {
-    private final PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(PainlessPlugin.BASE_WHITELISTS);
+    private final PainlessLookup painlessLookup = PainlessLookupBuilder.buildFromWhitelists(
+        PainlessPlugin.BASE_WHITELISTS,
+        new HashMap<>(),
+        new HashMap<>()
+    );
 
     /** calls toString() on integers, twice */
     public void testOneType() throws Throwable {

+ 4 - 1
modules/lang-painless/src/test/java/org/elasticsearch/painless/LookupTests.java

@@ -16,6 +16,7 @@ import org.elasticsearch.test.ESTestCase;
 import org.junit.Before;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Set;
 
@@ -26,7 +27,9 @@ public class LookupTests extends ESTestCase {
     @Before
     public void setup() {
         painlessLookup = PainlessLookupBuilder.buildFromWhitelists(
-            Collections.singletonList(WhitelistLoader.loadFromResourceFiles(PainlessPlugin.class, "org.elasticsearch.painless.lookup"))
+            Collections.singletonList(WhitelistLoader.loadFromResourceFiles(PainlessPlugin.class, "org.elasticsearch.painless.lookup")),
+            new HashMap<>(),
+            new HashMap<>()
         );
     }