Browse Source

Use primitive types rather than boxing/unboxing for iterating over primitive arrays from defs (#92025)

This eliminates boxing in the cases where we're iterating over a primitive array from a def value
Simon Cooper 2 years ago
parent
commit
998f0b7984

+ 6 - 0
docs/changelog/92025.yaml

@@ -0,0 +1,6 @@
+pr: 92025
+summary: Use primitive types rather than boxing/unboxing for iterating over primitive
+  arrays from defs
+area: Infra/Scripting
+type: enhancement
+issues: []

File diff suppressed because it is too large
+ 437 - 223
modules/lang-painless/src/main/java/org/elasticsearch/painless/Def.java


+ 10 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/WriterConstants.java

@@ -8,6 +8,7 @@
 
 package org.elasticsearch.painless;
 
+import org.elasticsearch.painless.api.ValueIterator;
 import org.objectweb.asm.Handle;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
@@ -48,8 +49,17 @@ public final class WriterConstants {
     public static final MethodType NEEDS_PARAMETER_METHOD_TYPE = MethodType.methodType(boolean.class);
 
     public static final Type ITERATOR_TYPE = Type.getType(Iterator.class);
+    public static final Type VALUE_ITERATOR_TYPE = Type.getType(ValueIterator.class);
     public static final Method ITERATOR_HASNEXT = getAsmMethod(boolean.class, "hasNext");
     public static final Method ITERATOR_NEXT = getAsmMethod(Object.class, "next");
+    public static final Method VALUE_ITERATOR_NEXT_BOOLEAN = getAsmMethod(boolean.class, "nextBoolean");
+    public static final Method VALUE_ITERATOR_NEXT_BYTE = getAsmMethod(byte.class, "nextByte");
+    public static final Method VALUE_ITERATOR_NEXT_SHORT = getAsmMethod(short.class, "nextShort");
+    public static final Method VALUE_ITERATOR_NEXT_CHAR = getAsmMethod(char.class, "nextChar");
+    public static final Method VALUE_ITERATOR_NEXT_INT = getAsmMethod(int.class, "nextInt");
+    public static final Method VALUE_ITERATOR_NEXT_LONG = getAsmMethod(long.class, "nextLong");
+    public static final Method VALUE_ITERATOR_NEXT_FLOAT = getAsmMethod(float.class, "nextFloat");
+    public static final Method VALUE_ITERATOR_NEXT_DOUBLE = getAsmMethod(double.class, "nextDouble");
 
     public static final Type UTILITY_TYPE = Type.getType(Utility.class);
     public static final Method STRING_TO_CHAR = getAsmMethod(char.class, "StringTochar", String.class);

+ 32 - 0
modules/lang-painless/src/main/java/org/elasticsearch/painless/api/ValueIterator.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0 and the Server Side Public License, v 1; you may not use this file except
+ * in compliance with, at your election, the Elastic License 2.0 or the Server
+ * Side Public License, v 1.
+ */
+
+package org.elasticsearch.painless.api;
+
+import java.util.Iterator;
+
+/**
+ * An {@link Iterator} that can return primitive values
+ */
+public interface ValueIterator<T> extends Iterator<T> {
+    boolean nextBoolean();
+
+    byte nextByte();
+
+    short nextShort();
+
+    char nextChar();
+
+    int nextInt();
+
+    long nextLong();
+
+    float nextFloat();
+
+    double nextDouble();
+}

+ 29 - 6
modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultIRTreeToASMBytesPhase.java

@@ -17,6 +17,7 @@ import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.ScriptClassInfo;
 import org.elasticsearch.painless.WriterConstants;
 import org.elasticsearch.painless.api.Augmentation;
+import org.elasticsearch.painless.api.ValueIterator;
 import org.elasticsearch.painless.ir.BinaryImplNode;
 import org.elasticsearch.painless.ir.BinaryMathNode;
 import org.elasticsearch.painless.ir.BlockNode;
@@ -164,7 +165,6 @@ import java.lang.reflect.Modifier;
 import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.regex.Matcher;
 
@@ -175,6 +175,15 @@ import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
 import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
 import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
 import static org.elasticsearch.painless.WriterConstants.OBJECTS_TYPE;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_BOOLEAN;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_BYTE;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_CHAR;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_DOUBLE;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_FLOAT;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_INT;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_LONG;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_NEXT_SHORT;
+import static org.elasticsearch.painless.WriterConstants.VALUE_ITERATOR_TYPE;
 
 public class DefaultIRTreeToASMBytesPhase implements IRTreeVisitor<WriteScope> {
 
@@ -552,6 +561,8 @@ public class DefaultIRTreeToASMBytesPhase implements IRTreeVisitor<WriteScope> {
         MethodWriter methodWriter = writeScope.getMethodWriter();
         methodWriter.writeStatementOffset(irForEachSubIterableNode.getLocation());
 
+        PainlessMethod painlessMethod = irForEachSubIterableNode.getDecorationValue(IRDMethod.class);
+
         Variable variable = writeScope.defineVariable(
             irForEachSubIterableNode.getDecorationValue(IRDVariableType.class),
             irForEachSubIterableNode.getDecorationValue(IRDVariableName.class)
@@ -563,10 +574,8 @@ public class DefaultIRTreeToASMBytesPhase implements IRTreeVisitor<WriteScope> {
 
         visit(irForEachSubIterableNode.getConditionNode(), writeScope);
 
-        PainlessMethod painlessMethod = irForEachSubIterableNode.getDecorationValue(IRDMethod.class);
-
         if (painlessMethod == null) {
-            Type methodType = Type.getMethodType(Type.getType(Iterator.class), Type.getType(Object.class));
+            Type methodType = Type.getMethodType(Type.getType(ValueIterator.class), Type.getType(Object.class));
             methodWriter.invokeDefCall("iterator", methodType, DefBootstrap.ITERATOR);
         } else {
             methodWriter.invokeMethodCall(painlessMethod);
@@ -584,8 +593,22 @@ public class DefaultIRTreeToASMBytesPhase implements IRTreeVisitor<WriteScope> {
         methodWriter.ifZCmp(MethodWriter.EQ, end);
 
         methodWriter.visitVarInsn(iterator.getAsmType().getOpcode(Opcodes.ILOAD), iterator.getSlot());
-        methodWriter.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
-        methodWriter.writeCast(irForEachSubIterableNode.getDecorationValue(IRDCast.class));
+        if (painlessMethod != null || variable.getType().isPrimitive() == false) {
+            methodWriter.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
+            methodWriter.writeCast(irForEachSubIterableNode.getDecorationValue(IRDCast.class));
+        } else {
+            switch (variable.getAsmType().getSort()) {
+                case Type.BOOLEAN -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_BOOLEAN);
+                case Type.BYTE -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_BYTE);
+                case Type.SHORT -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_SHORT);
+                case Type.CHAR -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_CHAR);
+                case Type.INT -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_INT);
+                case Type.LONG -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_LONG);
+                case Type.FLOAT -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_FLOAT);
+                case Type.DOUBLE -> methodWriter.invokeInterface(VALUE_ITERATOR_TYPE, VALUE_ITERATOR_NEXT_DOUBLE);
+                default -> throw new IllegalArgumentException("Unknown primitive iteration variable type " + variable.getAsmType());
+            }
+        }
         methodWriter.visitVarInsn(variable.getAsmType().getOpcode(Opcodes.ISTORE), variable.getSlot());
 
         visit(irForEachSubIterableNode.getBlockNode(), writeScope.newLoopScope(begin, end));

+ 12 - 4
modules/lang-painless/src/main/java/org/elasticsearch/painless/phase/DefaultUserTreeToIRTreePhase.java

@@ -15,6 +15,7 @@ import org.elasticsearch.painless.Location;
 import org.elasticsearch.painless.MethodWriter;
 import org.elasticsearch.painless.Operation;
 import org.elasticsearch.painless.WriterConstants;
+import org.elasticsearch.painless.api.ValueIterator;
 import org.elasticsearch.painless.ir.BinaryImplNode;
 import org.elasticsearch.painless.ir.BinaryMathNode;
 import org.elasticsearch.painless.ir.BlockNode;
@@ -759,17 +760,24 @@ public class DefaultUserTreeToIRTreePhase implements UserTreeVisitor<ScriptScope
             irForEachSubIterableNode.setBlockNode(irBlockNode);
             irForEachSubIterableNode.attachDecoration(new IRDVariableType(variable.type()));
             irForEachSubIterableNode.attachDecoration(new IRDVariableName(variable.name()));
-            irForEachSubIterableNode.attachDecoration(new IRDIterableType(Iterator.class));
             irForEachSubIterableNode.attachDecoration(new IRDIterableName("#itr" + userEachNode.getLocation().getOffset()));
 
             if (iterableValueType != def.class) {
+                irForEachSubIterableNode.attachDecoration(new IRDIterableType(Iterator.class));
                 irForEachSubIterableNode.attachDecoration(
                     new IRDMethod(scriptScope.getDecoration(userEachNode, IterablePainlessMethod.class).iterablePainlessMethod())
                 );
-            }
 
-            if (painlessCast != null) {
-                irForEachSubIterableNode.attachDecoration(new IRDCast(painlessCast));
+                if (painlessCast != null) {
+                    irForEachSubIterableNode.attachDecoration(new IRDCast(painlessCast));
+                }
+            } else {
+                // use ValueIterator as we could be iterating over an array directly
+                irForEachSubIterableNode.attachDecoration(new IRDIterableType(ValueIterator.class));
+
+                if (painlessCast != null && variable.type().isPrimitive() == false) {
+                    irForEachSubIterableNode.attachDecoration(new IRDCast(painlessCast));
+                }
             }
 
             irConditionNode = irForEachSubIterableNode;

+ 125 - 0
modules/lang-painless/src/test/java/org/elasticsearch/painless/ArrayTests.java

@@ -79,4 +79,129 @@ public class ArrayTests extends ArrayLikeObjectTestCase {
     public void testDivideArray() {
         assertEquals(1, exec("def[] x = new def[1]; x[0] = 2; return x[0] / 2"));
     }
+
+    public void testPrimitiveIteration() {
+        assertEquals(true, exec("def x = new boolean[] { true, false }; boolean s = false; for (boolean l : x) s |= l; return s"));
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new boolean[] { true, false }; short s = 0; for (short l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new boolean[] { true, false }; char s = 0; for (char l : x) s = l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new boolean[] { true, false }; int s = 0; for (int l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new boolean[] { true, false }; long s = 0; for (long l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new boolean[] { true, false }; float s = 0; for (float l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new boolean[] { true, false }; double s = 0; for (double l : x) s += l; return s")
+        );
+        assertEquals(true, exec("def x = new boolean[] { true, false }; boolean s = false; for (def l : x) s |= l; return s"));
+
+        assertEquals((byte) 30, exec("def x = new byte[] { (byte)10, (byte)20 }; byte s = 0; for (byte l : x) s += l; return s"));
+        assertEquals((short) 30, exec("def x = new byte[] { (byte)10, (byte)20 }; short s = 0; for (short l : x) s += l; return s"));
+        assertEquals((char) 20, exec("def x = new byte[] { (byte)10, (byte)20 }; char s = 0; for (char l : x) s = l; return s"));
+        assertEquals(30, exec("def x = new byte[] { (byte)10, (byte)20 }; int s = 0; for (int l : x) s += l; return s"));
+        assertEquals(30L, exec("def x = new byte[] { (byte)10, (byte)20 }; long s = 0; for (long l : x) s += l; return s"));
+        assertEquals(30f, exec("def x = new byte[] { (byte)10, (byte)20 }; float s = 0; for (float l : x) s += l; return s"));
+        assertEquals(30d, exec("def x = new byte[] { (byte)10, (byte)20 }; double s = 0; for (double l : x) s += l; return s"));
+        assertEquals((byte) 30, exec("def x = new byte[] { (byte)10, (byte)20 }; byte s = 0; for (def l : x) s += l; return s"));
+
+        assertEquals((byte) 30, exec("def x = new short[] { (short)10, (short)20 }; byte s = 0; for (byte l : x) s += l; return s"));
+        assertEquals((short) 300, exec("def x = new short[] { (short)100, (short)200 }; short s = 0; for (short l : x) s += l; return s"));
+        assertEquals((char) 200, exec("def x = new short[] { (short)100, (short)200 }; char s = 0; for (char l : x) s = l; return s"));
+        assertEquals(300, exec("def x = new short[] { (short)100, (short)200 }; int s = 0; for (int l : x) s += l; return s"));
+        assertEquals(300L, exec("def x = new short[] { (short)100, (short)200 }; long s = 0; for (long l : x) s += l; return s"));
+        assertEquals(300f, exec("def x = new short[] { (short)100, (short)200 }; float s = 0; for (float l : x) s += l; return s"));
+        assertEquals(300d, exec("def x = new short[] { (short)100, (short)200 }; double s = 0; for (double l : x) s += l; return s"));
+        assertEquals((short) 300, exec("def x = new short[] { (short)100, (short)200 }; short s = 0; for (def l : x) s += l; return s"));
+
+        assertEquals((byte) 'b', exec("def x = new char[] { (char)'a', (char)'b' }; byte s = 0; for (byte l : x) s = l; return s"));
+        assertEquals((short) 'b', exec("def x = new char[] { (char)'a', (char)'b' }; short s = 0; for (short l : x) s = l; return s"));
+        assertEquals('b', exec("def x = new char[] { (char)'a', (char)'b' }; char s = 0; for (char l : x) s = l; return s"));
+        assertEquals((int) 'b', exec("def x = new char[] { (char)'a', (char)'b' }; int s = 0; for (int l : x) s = l; return s"));
+        assertEquals((long) 'b', exec("def x = new char[] { (char)'a', (char)'b' }; long s = 0; for (long l : x) s = l; return s"));
+        assertEquals((float) 'b', exec("def x = new char[] { (char)'a', (char)'b' }; float s = 0; for (float l : x) s = l; return s"));
+        assertEquals((double) 'b', exec("def x = new char[] { (char)'a', (char)'b' }; double s = 0; for (double l : x) s = l; return s"));
+        assertEquals('b', exec("def x = new char[] { (char)'a', (char)'b' }; char s = 0; for (def l : x) s = l; return s"));
+
+        assertEquals((byte) 30, exec("def x = new int[] { 10, 20 }; byte s = 0; for (byte l : x) s += l; return s"));
+        assertEquals((short) 300, exec("def x = new int[] { 100, 200 }; short s = 0; for (short l : x) s += l; return s"));
+        assertEquals((char) 200, exec("def x = new int[] { 100, 200 }; char s = 0; for (char l : x) s = l; return s"));
+        assertEquals(300, exec("def x = new int[] { 100, 200 }; int s = 0; for (int l : x) s += l; return s"));
+        assertEquals(300L, exec("def x = new int[] { 100, 200 }; long s = 0; for (long l : x) s += l; return s"));
+        assertEquals(300f, exec("def x = new int[] { 100, 200 }; float s = 0; for (float l : x) s += l; return s"));
+        assertEquals(300d, exec("def x = new int[] { 100, 200 }; double s = 0; for (double l : x) s += l; return s"));
+        assertEquals(300, exec("def x = new int[] { 100, 200 }; int s = 0; for (def l : x) s += l; return s"));
+
+        assertEquals((byte) 30, exec("def x = new long[] { 10, 20 }; byte s = 0; for (byte l : x) s += l; return s"));
+        assertEquals((short) 300, exec("def x = new long[] { 100, 200 }; short s = 0; for (short l : x) s += l; return s"));
+        assertEquals((char) 200, exec("def x = new long[] { 100, 200 }; char s = 0; for (char l : x) s = l; return s"));
+        assertEquals(300, exec("def x = new long[] { 100, 200 }; int s = 0; for (int l : x) s += l; return s"));
+        assertEquals(300L, exec("def x = new long[] { 100, 200 }; long s = 0; for (long l : x) s += l; return s"));
+        assertEquals(300f, exec("def x = new long[] { 100, 200 }; float s = 0; for (float l : x) s += l; return s"));
+        assertEquals(300d, exec("def x = new long[] { 100, 200 }; double s = 0; for (double l : x) s += l; return s"));
+        assertEquals(300L, exec("def x = new long[] { 100, 200 }; long s = 0; for (def l : x) s += l; return s"));
+
+        assertEquals((byte) 30, exec("def x = new float[] { 10, 20 }; byte s = 0; for (byte l : x) s += l; return s"));
+        assertEquals((short) 300, exec("def x = new float[] { 100, 200 }; short s = 0; for (short l : x) s += l; return s"));
+        assertEquals((char) 200, exec("def x = new float[] { 100, 200 }; char s = 0; for (char l : x) s = l; return s"));
+        assertEquals(300, exec("def x = new float[] { 100, 200 }; int s = 0; for (int l : x) s += l; return s"));
+        assertEquals(300L, exec("def x = new float[] { 100, 200 }; long s = 0; for (long l : x) s += l; return s"));
+        assertEquals(300f, exec("def x = new float[] { 100, 200 }; float s = 0; for (float l : x) s += l; return s"));
+        assertEquals(300d, exec("def x = new float[] { 100, 200 }; double s = 0; for (double l : x) s += l; return s"));
+        assertEquals(300f, exec("def x = new float[] { 100, 200 }; float s = 0; for (def l : x) s += l; return s"));
+
+        assertEquals((byte) 30, exec("def x = new double[] { 10, 20 }; byte s = 0; for (byte l : x) s += l; return s"));
+        assertEquals((short) 300, exec("def x = new double[] { 100, 200 }; short s = 0; for (short l : x) s += l; return s"));
+        assertEquals((char) 200, exec("def x = new double[] { 100, 200 }; char s = 0; for (char l : x) s = l; return s"));
+        assertEquals(300, exec("def x = new double[] { 100, 200 }; int s = 0; for (int l : x) s += l; return s"));
+        assertEquals(300L, exec("def x = new double[] { 100, 200 }; long s = 0; for (long l : x) s += l; return s"));
+        assertEquals(300f, exec("def x = new double[] { 100, 200 }; float s = 0; for (float l : x) s += l; return s"));
+        assertEquals(300d, exec("def x = new double[] { 100, 200 }; double s = 0; for (double l : x) s += l; return s"));
+        assertEquals(300d, exec("def x = new double[] { 100, 200 }; double s = 0; for (def l : x) s += l; return s"));
+
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; boolean s = false; for (boolean l : x) s |= l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; byte s = 0; for (byte l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; short s = 0; for (short l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; char s = 0; for (char l : x) s = l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; int s = 0; for (int l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; long s = 0; for (long l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; float s = 0; for (float l : x) s += l; return s")
+        );
+        expectScriptThrows(
+            ClassCastException.class,
+            () -> exec("def x = new String[] { 'foo', 'bar' }; double s = 0; for (double l : x) s += l; return s")
+        );
+    }
 }

+ 35 - 0
modules/lang-painless/src/test/java/org/elasticsearch/painless/DefOptimizationTests.java

@@ -416,4 +416,39 @@ public class DefOptimizationTests extends ScriptTestCase {
             "synthetic lambda$synthetic$0(D)D"
         );
     }
+
+    public void testPrimitiveArrayIteration() {
+        assertBytecodeExists(
+            "def x = new boolean[] { true, false }; boolean s = false; for (boolean l : x) s |= l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextBoolean ()Z"
+        );
+        assertBytecodeExists(
+            "def x = new byte[] { (byte)10, (byte)20 }; byte s = 0; for (byte l : x) s += l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextByte ()B"
+        );
+        assertBytecodeExists(
+            "def x = new short[] { (short)100, (short)200 }; short s = 0; for (short l : x) s += l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextShort ()S"
+        );
+        assertBytecodeExists(
+            "def x = new char[] { (char)'a', (char)'b' }; char s = 0; for (char l : x) s = l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextChar ()C"
+        );
+        assertBytecodeExists(
+            "def x = new int[] { 100, 200 }; int s = 0; for (int l : x) s += l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextInt ()I"
+        );
+        assertBytecodeExists(
+            "def x = new long[] { 100, 200 }; long s = 0; for (long l : x) s += l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextLong ()J"
+        );
+        assertBytecodeExists(
+            "def x = new float[] { 100, 200 }; float s = 0; for (float l : x) s += l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextFloat ()F"
+        );
+        assertBytecodeExists(
+            "def x = new double[] { 100, 200 }; double s = 0; for (double l : x) s += l; return s",
+            "INVOKEINTERFACE org/elasticsearch/painless/api/ValueIterator.nextDouble ()D"
+        );
+    }
 }

Some files were not shown because too many files changed in this diff