Merge pull request #18757 from jdconrad/each

Static For Each
This commit is contained in:
Jack Conradson 2016-06-06 18:29:27 -07:00
commit 3dfe2bbcb6
42 changed files with 970 additions and 380 deletions

View File

@ -32,6 +32,10 @@ LBRACE: '[';
RBRACE: ']';
LP: '(';
RP: ')';
// We switch modes after a dot to ensure there are not conflicts
// between shortcuts and decimal values. Without the mode switch
// shortcuts such as id.0.0 will fail because 0.0 will be interpreted
// as a decimal value instead of two individual list-style shortcuts.
DOT: '.' -> mode(AFTER_DOT);
COMMA: ',';
SEMICOLON: ';';

View File

@ -33,6 +33,7 @@ statement
| WHILE LP expression RP ( trailer | empty ) # while
| DO block WHILE LP expression RP delimiter # do
| FOR LP initializer? SEMICOLON expression? SEMICOLON afterthought? RP ( trailer | empty ) # for
| FOR LP decltype ID COLON expression RP trailer # each
| declaration delimiter # decl
| CONTINUE delimiter # continue
| BREAK delimiter # break

View File

@ -104,17 +104,16 @@ final class Compiler {
SSource root = Walker.buildPainlessTree(name, source, reserved, settings);
Variables variables = Analyzer.analyze(reserved, root);
BitSet expressions = new BitSet(source.length());
byte[] bytes = Writer.write(settings, name, source, variables, root, expressions);
try {
Class<? extends Executable> clazz = loader.define(CLASS_NAME, bytes);
java.lang.reflect.Constructor<? extends Executable> constructor =
java.lang.reflect.Constructor<? extends Executable> constructor =
clazz.getConstructor(String.class, String.class, BitSet.class);
return constructor.newInstance(name, source, expressions);
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally.
throw new IllegalStateException(
"An internal error occurred attempting to define the script [" + name + "].", exception);
throw new IllegalStateException("An internal error occurred attempting to define the script [" + name + "].", exception);
}
}

View File

@ -27,6 +27,7 @@ import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.invoke.MethodType;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@ -103,6 +104,8 @@ public final class Def {
private static final MethodHandle LIST_GET;
/** pointer to List.set(int,Object) */
private static final MethodHandle LIST_SET;
/** pointer to Iterable.iterator() */
private static final MethodHandle ITERATOR;
/** factory for arraylength MethodHandle (intrinsic) from Java 9 */
private static final MethodHandle JAVA9_ARRAY_LENGTH_MH_FACTORY;
@ -114,6 +117,7 @@ public final class Def {
MAP_PUT = lookup.findVirtual(Map.class , "put", MethodType.methodType(Object.class, Object.class, Object.class));
LIST_GET = lookup.findVirtual(List.class, "get", MethodType.methodType(Object.class, int.class));
LIST_SET = lookup.findVirtual(List.class, "set", MethodType.methodType(Object.class, int.class, Object.class));
ITERATOR = lookup.findVirtual(Iterable.class, "iterator", MethodType.methodType(Iterator.class));
} catch (final ReflectiveOperationException roe) {
throw new AssertionError(roe);
}
@ -374,6 +378,118 @@ public final class Def {
throw new IllegalArgumentException("Attempting to address a non-array type " +
"[" + receiverClass.getCanonicalName() + "] as an array.");
}
/** Helper class for isolating MethodHandles and methods to get iterators over arrays
* (to emulate "enhanced for loop" using MethodHandles). These cause boxing, and are not as efficient
* as they could be, but works.
*/
@SuppressWarnings("unused") // iterator() methods are are actually used, javac just does not know :)
private static final class ArrayIteratorHelper {
private static final Lookup PRIV_LOOKUP = MethodHandles.lookup();
private static final Map<Class<?>,MethodHandle> ARRAY_TYPE_MH_MAPPING = Collections.unmodifiableMap(
Stream.of(boolean[].class, byte[].class, short[].class, int[].class, long[].class,
char[].class, float[].class, double[].class, Object[].class)
.collect(Collectors.toMap(Function.identity(), type -> {
try {
return PRIV_LOOKUP.findStatic(PRIV_LOOKUP.lookupClass(), "iterator", MethodType.methodType(Iterator.class, type));
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
}))
);
private static final MethodHandle OBJECT_ARRAY_MH = ARRAY_TYPE_MH_MAPPING.get(Object[].class);
static Iterator<Boolean> iterator(final boolean[] array) {
return new Iterator<Boolean>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Boolean next() { return array[index++]; }
};
}
static Iterator<Byte> iterator(final byte[] array) {
return new Iterator<Byte>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Byte next() { return array[index++]; }
};
}
static Iterator<Short> iterator(final short[] array) {
return new Iterator<Short>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Short next() { return array[index++]; }
};
}
static Iterator<Integer> iterator(final int[] array) {
return new Iterator<Integer>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Integer next() { return array[index++]; }
};
}
static Iterator<Long> iterator(final long[] array) {
return new Iterator<Long>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Long next() { return array[index++]; }
};
}
static Iterator<Character> iterator(final char[] array) {
return new Iterator<Character>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Character next() { return array[index++]; }
};
}
static Iterator<Float> iterator(final float[] array) {
return new Iterator<Float>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Float next() { return array[index++]; }
};
}
static Iterator<Double> iterator(final double[] array) {
return new Iterator<Double>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Double next() { return array[index++]; }
};
}
static Iterator<Object> iterator(final Object[] array) {
return new Iterator<Object>() {
int index = 0;
@Override public boolean hasNext() { return index < array.length; }
@Override public Object next() { return array[index++]; }
};
}
static MethodHandle newIterator(Class<?> arrayType) {
if (!arrayType.isArray()) {
throw new IllegalArgumentException("type must be an array");
}
return (ARRAY_TYPE_MH_MAPPING.containsKey(arrayType)) ?
ARRAY_TYPE_MH_MAPPING.get(arrayType) :
OBJECT_ARRAY_MH.asType(OBJECT_ARRAY_MH.type().changeParameterType(0, arrayType));
}
private ArrayIteratorHelper() {}
}
/**
* Returns a method handle to do iteration (for enhanced for loop)
* @param receiverClass Class of the array to load the value from
* @return a MethodHandle that accepts the receiver as first argument, returns iterator
*/
static MethodHandle lookupIterator(Class<?> receiverClass) {
if (Iterable.class.isAssignableFrom(receiverClass)) {
return ITERATOR;
} else if (receiverClass.isArray()) {
return ArrayIteratorHelper.newIterator(receiverClass);
} else {
throw new IllegalArgumentException("Cannot iterate over [" + receiverClass.getCanonicalName() + "]");
}
}
// NOTE: Below methods are not cached, instead invoked directly because they are performant.
// We also check for Long values first when possible since the type is more

View File

@ -56,6 +56,8 @@ public final class DefBootstrap {
public static final int ARRAY_LOAD = 3;
/** static bootstrap parameter indicating a dynamic array store, e.g. foo[bar] = baz */
public static final int ARRAY_STORE = 4;
/** static bootstrap parameter indicating a dynamic iteration, e.g. for (x : y) */
public static final int ITERATOR = 5;
/**
* CallSite that implements the polymorphic inlining cache (PIC).
@ -103,6 +105,8 @@ public final class DefBootstrap {
return Def.lookupArrayLoad(clazz);
case ARRAY_STORE:
return Def.lookupArrayStore(clazz);
case ITERATOR:
return Def.lookupIterator(clazz);
default: throw new AssertionError();
}
}

View File

@ -123,7 +123,13 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
throw convertToScriptException(t);
}
}
/**
* Adds stack trace and other useful information to exceptiosn thrown
* from a Painless script.
* @param t The throwable to build an exception around.
* @return The generated ScriptException.
*/
private ScriptException convertToScriptException(Throwable t) {
// create a script stack: this is just the script portion
List<String> scriptStack = new ArrayList<>();
@ -169,7 +175,7 @@ final class ScriptImpl implements ExecutableScript, LeafSearchScript {
}
throw new ScriptException("runtime error", t, scriptStack, name, PainlessScriptEngineService.NAME);
}
/** returns true for methods that are part of the runtime */
private static boolean shouldFilter(StackTraceElement element) {
return element.getClassName().startsWith("org.elasticsearch.painless.") ||

View File

@ -138,15 +138,15 @@ public final class Variables {
public void decrementScope() {
int remove = scopes.pop();
while (remove > 0) {
Variable variable = variables.pop();
// TODO: is this working? the code reads backwards...
if (variable.read) {
throw variable.location.createError(new IllegalArgumentException("Variable [" + variable.name + "] never used."));
}
--remove;
}
}

View File

@ -30,6 +30,7 @@ import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.BitSet;
import java.util.Iterator;
import java.util.Map;
/**
@ -56,6 +57,10 @@ public final class WriterConstants {
public final static Type MAP_TYPE = Type.getType(Map.class);
public final static Method MAP_GET = getAsmMethod(Object.class, "get", Object.class);
public final static Type ITERATOR_TYPE = Type.getType(Iterator.class);
public final static Method ITERATOR_HASNEXT = getAsmMethod(boolean.class, "hasNext");
public final static Method ITERATOR_NEXT = getAsmMethod(Object.class, "next");
public final static Type UTILITY_TYPE = Type.getType(Utility.class);
public final static Method STRING_TO_CHAR = getAsmMethod(char.class, "StringTochar", String.class);
public final static Method CHAR_TO_STRING = getAsmMethod(String.class, "charToString", char.class);

View File

@ -46,6 +46,13 @@ class PainlessParserBaseVisitor<T> extends AbstractParseTreeVisitor<T> implement
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitFor(PainlessParser.ForContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*
* <p>The default implementation returns the result of calling
* {@link #visitChildren} on {@code ctx}.</p>
*/
@Override public T visitEach(PainlessParser.EachContext ctx) { return visitChildren(ctx); }
/**
* {@inheritDoc}
*

View File

@ -44,6 +44,13 @@ interface PainlessParserVisitor<T> extends ParseTreeVisitor<T> {
* @return the visitor result
*/
T visitFor(PainlessParser.ForContext ctx);
/**
* Visit a parse tree produced by the {@code each}
* labeled alternative in {@link PainlessParser#statement}.
* @param ctx the parse tree
* @return the visitor result
*/
T visitEach(PainlessParser.EachContext ctx);
/**
* Visit a parse tree produced by the {@code decl}
* labeled alternative in {@link PainlessParser#statement}.

View File

@ -53,6 +53,7 @@ import org.elasticsearch.painless.antlr.PainlessParser.DeclvarContext;
import org.elasticsearch.painless.antlr.PainlessParser.DelimiterContext;
import org.elasticsearch.painless.antlr.PainlessParser.DoContext;
import org.elasticsearch.painless.antlr.PainlessParser.DynamicContext;
import org.elasticsearch.painless.antlr.PainlessParser.EachContext;
import org.elasticsearch.painless.antlr.PainlessParser.EmptyContext;
import org.elasticsearch.painless.antlr.PainlessParser.ExprContext;
import org.elasticsearch.painless.antlr.PainlessParser.ExpressionContext;
@ -118,6 +119,7 @@ import org.elasticsearch.painless.node.SContinue;
import org.elasticsearch.painless.node.SDeclBlock;
import org.elasticsearch.painless.node.SDeclaration;
import org.elasticsearch.painless.node.SDo;
import org.elasticsearch.painless.node.SEach;
import org.elasticsearch.painless.node.SExpression;
import org.elasticsearch.painless.node.SFor;
import org.elasticsearch.painless.node.SIf;
@ -261,16 +263,28 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
if (ctx.trailer() != null) {
SBlock block = (SBlock)visit(ctx.trailer());
return new SFor(location(ctx),
settings.getMaxLoopCounter(), initializer, expression, afterthought, block);
return new SFor(location(ctx), settings.getMaxLoopCounter(), initializer, expression, afterthought, block);
} else if (ctx.empty() != null) {
return new SFor(location(ctx),
settings.getMaxLoopCounter(), initializer, expression, afterthought, null);
return new SFor(location(ctx), settings.getMaxLoopCounter(), initializer, expression, afterthought, null);
} else {
throw location(ctx).createError(new IllegalStateException("Illegal tree structure."));
}
}
@Override
public Object visitEach(EachContext ctx) {
if (settings.getMaxLoopCounter() > 0) {
reserved.usesLoop();
}
String type = ctx.decltype().getText();
String name = ctx.ID().getText();
AExpression expression = (AExpression)visitExpression(ctx.expression());
SBlock block = (SBlock)visit(ctx.trailer());
return new SEach(location(ctx), settings.getMaxLoopCounter(), type, name, expression, block);
}
@Override
public Object visitDecl(DeclContext ctx) {
return visit(ctx.declaration());

View File

@ -123,9 +123,19 @@ public abstract class AExpression extends ANode {
if (cast == null) {
if (constant == null || this instanceof EConstant) {
// For the case where a cast is not required and a constant is not set
// or the node is already an EConstant no changes are required to the tree.
return this;
} else {
final EConstant econstant = new EConstant(location, constant);
// For the case where a cast is not required but a
// constant is set, an EConstant replaces this node
// with the constant copied from this node. Note that
// for constants output data does not need to be copied
// from this node because the output data for the EConstant
// will already be the same.
EConstant econstant = new EConstant(location, constant);
econstant.analyze(variables);
if (!expected.equals(econstant.actual)) {
@ -136,7 +146,12 @@ public abstract class AExpression extends ANode {
}
} else {
if (constant == null) {
final ECast ecast = new ECast(location, this, cast);
// For the case where a cast is required and a constant is not set.
// Modify the tree to add an ECast between this node and its parent.
// The output data from this node is copied to the ECast for
// further reads done by the parent.
ECast ecast = new ECast(location, this, cast);
ecast.statement = statement;
ecast.actual = expected;
ecast.isNull = isNull;
@ -144,9 +159,17 @@ public abstract class AExpression extends ANode {
return ecast;
} else {
if (expected.sort.constant) {
// For the case where a cast is required, a constant is set,
// and the constant can be immediately cast to the expected type.
// An EConstant replaces this node with the constant cast appropriately
// from the constant value defined by this node. Note that
// for constants output data does not need to be copied
// from this node because the output data for the EConstant
// will already be the same.
constant = AnalyzerCaster.constCast(location, constant, cast);
final EConstant econstant = new EConstant(location, constant);
EConstant econstant = new EConstant(location, constant);
econstant.analyze(variables);
if (!expected.equals(econstant.actual)) {
@ -155,19 +178,36 @@ public abstract class AExpression extends ANode {
return econstant;
} else if (this instanceof EConstant) {
final ECast ecast = new ECast(location, this, cast);
// For the case where a cast is required, a constant is set,
// the constant cannot be immediately cast to the expected type,
// and this node is already an EConstant. Modify the tree to add
// an ECast between this node and its parent. Note that
// for constants output data does not need to be copied
// from this node because the output data for the EConstant
// will already be the same.
ECast ecast = new ECast(location, this, cast);
ecast.actual = expected;
return ecast;
} else {
final EConstant econstant = new EConstant(location, constant);
// For the case where a cast is required, a constant is set,
// the constant cannot be immediately cast to the expected type,
// and this node is not an EConstant. Replace this node with
// an Econstant node copying the constant from this node.
// Modify the tree to add an ECast between the EConstant node
// and its parent. Note that for constants output data does not
// need to be copied from this node because the output data for
// the EConstant will already be the same.
EConstant econstant = new EConstant(location, constant);
econstant.analyze(variables);
if (!actual.equals(econstant.actual)) {
throw createError(new IllegalStateException("Illegal tree structure."));
}
final ECast ecast = new ECast(location, econstant, cast);
ECast ecast = new ECast(location, econstant, cast);
ecast.actual = expected;
return ecast;

View File

@ -480,6 +480,7 @@ public final class EBinary extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(location);
if (actual.sort == Sort.STRING && operation == Operation.ADD) {
if (!cat) {
writer.writeNewStrings();

View File

@ -247,74 +247,115 @@ public final class EChain extends AExpression {
actual = last.after;
}
/**
* Handles writing byte code for variable/method chains for all given possibilities
* including String concatenation, compound assignment, regular assignment, and simple
* reads. Includes proper duplication for chained assignments and assignments that are
* also read from.
*
* Example given 'x[0] += 5;' where x is an array of shorts and x[0] is 1.
* Note this example has two links -- x (LVariable) and [0] (LBrace).
* The following steps occur:
* 1. call link{x}.write(...) -- no op [...]
* 2. call link{x}.load(...) -- loads the address of the x array onto the stack [..., address(x)]
* 3. call writer.dup(...) -- dup's the address of the x array onto the stack for later use with store [..., address(x), address(x)]
* 4. call link{[0]}.write(...) -- load the array index value of the constant int 0 onto the stack [..., address(x), address(x), int(0)]
* 5. call link{[0]}.load(...) -- load the short value from x[0] onto the stack [..., address(x), short(1)]
* 6. call writer.writeCast(there) -- casts the short on the stack to an int so it can be added with the rhs [..., address(x), int(1)]
* 7. call expression.write(...) -- puts the expression's value of the constant int 5 onto the stack [..., address(x), int(1), int(5)]
* 8. call writer.writeBinaryInstruction(operation) -- writes the int addition instruction [..., address(x), int(6)]
* 9. call writer.writeCast(back) -- convert the value on the stack back into a short [..., address(x), short(6)]
* 10. call link{[0]}.store(...) -- store the value on the stack into the 0th index of the array x [...]
*/
@Override
void write(MethodWriter writer) {
// can cause class cast exception among other things at runtime
writer.writeDebugInfo(location);
// For the case where the chain represents a String concatenation
// we must, depending on the Java version, write a StringBuilder or
// track types going onto the stack. This must be done before the
// links in the chain are read because we need the StringBuilder to
// be placed on the stack ahead of any potential concatenation arguments.
if (cat) {
writer.writeNewStrings();
}
ALink last = links.get(links.size() - 1);
// Go through all the links in the chain first calling write
// and then load, except for the final link which may be a store.
// See individual links for more information on what each of the
// write, load, and store methods do.
for (ALink link : links) {
link.write(writer);
link.write(writer); // call the write method on the link to prepare for a load/store operation
if (link == last && link.store) {
if (cat) {
writer.writeDup(link.size, 1);
link.load(writer);
writer.writeAppendStrings(link.after);
// Handle the case where we are doing a compound assignment
// representing a String concatenation.
expression.write(writer);
writer.writeDup(link.size, 1); // dup the StringBuilder
link.load(writer); // read the current link's value
writer.writeAppendStrings(link.after); // append the link's value using the StringBuilder
expression.write(writer); // write the bytecode for the rhs expression
if (!(expression instanceof EBinary) ||
((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
writer.writeAppendStrings(expression.actual);
writer.writeAppendStrings(expression.actual); // append the expression's value unless it's also a concatenation
}
writer.writeToStrings();
writer.writeCast(back);
writer.writeToStrings(); // put the value of the StringBuilder on the stack
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
if (link.load) {
writer.writeDup(link.after.sort.size, link.size);
writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack
}
link.store(writer);
link.store(writer); // store the link's value from the stack in its respective variable/field/array
} else if (operation != null) {
writer.writeDup(link.size, 0);
link.load(writer);
// Handle the case where we are doing a compound assignment that
// does not represent a String concatenation.
writer.writeDup(link.size, 0); // if necessary, dup the previous link's value to be both loaded from and stored to
link.load(writer); // load the current link's value
if (link.load && post) {
writer.writeDup(link.after.sort.size, link.size);
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also
// read from and is a post increment
}
writer.writeCast(there);
expression.write(writer);
writer.writeBinaryInstruction(location, promote, operation);
writer.writeCast(there); // if necessary cast the current link's value
// to the promotion type between the lhs and rhs types
expression.write(writer); // write the bytecode for the rhs expression
writer.writeBinaryInstruction(location, promote, operation); // write the operation instruction for compound assignment
writer.writeCast(back);
writer.writeCast(back); // if necessary cast the promotion type value back to the link's type
if (link.load && !post) {
writer.writeDup(link.after.sort.size, link.size);
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also
// read from and is not a post increment
}
link.store(writer);
link.store(writer); // store the link's value from the stack in its respective variable/field/array
} else {
expression.write(writer);
// Handle the case for a simple write.
expression.write(writer); // write the bytecode for the rhs expression
if (link.load) {
writer.writeDup(link.after.sort.size, link.size);
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also read from
}
link.store(writer);
link.store(writer); // store the link's value from the stack in its respective variable/field/array
}
} else {
link.load(writer);
// Handle the case for a simple read.
link.load(writer); // read the link's value onto the stack
}
}
writer.writeBranch(tru, fals);
writer.writeBranch(tru, fals); // if this is a branch node, write the bytecode to make an appropiate jump
}
}

View File

@ -401,6 +401,7 @@ public final class EComp extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(location);
boolean branch = tru != null || fals != null;
org.objectweb.asm.Type rtype = right.actual.type;
Sort rsort = right.actual.sort;

View File

@ -80,6 +80,7 @@ public final class EConditional extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(location);
Label localfals = new Label();
Label end = new Label();

View File

@ -167,6 +167,7 @@ public final class EUnary extends AExpression {
@Override
void write(MethodWriter writer) {
writer.writeDebugInfo(location);
if (operation == Operation.NOT) {
if (tru == null && fals == null) {
Label localfals = new Label();

View File

@ -47,7 +47,7 @@ public final class LBrace extends ALink {
throw createError(new IllegalArgumentException("Illegal array access made without target."));
}
final Sort sort = before.sort;
Sort sort = before.sort;
if (sort == Sort.ARRAY) {
index.expected = Definition.INT_TYPE;
@ -84,5 +84,4 @@ public final class LBrace extends ALink {
writer.writeDebugInfo(location);
writer.arrayStore(after.type);
}
}

View File

@ -93,6 +93,7 @@ public final class LCall extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
for (AExpression argument : arguments) {
argument.write(writer);
}

View File

@ -60,6 +60,7 @@ final class LDefArray extends ALink implements IDefLink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
writer.invokeDynamic("arrayLoad", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_LOAD);
}
@ -67,6 +68,7 @@ final class LDefArray extends ALink implements IDefLink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);
writer.invokeDynamic("arrayStore", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ARRAY_STORE);
}

View File

@ -69,6 +69,7 @@ final class LDefCall extends ALink implements IDefLink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
StringBuilder signature = new StringBuilder();
signature.append('(');

View File

@ -57,6 +57,7 @@ final class LDefField extends ALink implements IDefLink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.LOAD);
}
@ -64,6 +65,7 @@ final class LDefField extends ALink implements IDefLink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);
writer.invokeDynamic(value, desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.STORE);
}

View File

@ -107,6 +107,7 @@ public final class LField extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.getStatic(field.owner.type, field.javaName, field.type.type);
} else {
@ -117,6 +118,7 @@ public final class LField extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
writer.putStatic(field.owner.type, field.javaName, field.type.type);
} else {

View File

@ -81,6 +81,7 @@ final class LListShortcut extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
writer.invokeInterface(getter.owner.type, getter.method);
} else {
@ -95,6 +96,7 @@ final class LListShortcut extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
writer.invokeInterface(setter.owner.type, setter.method);
} else {

View File

@ -80,6 +80,7 @@ final class LMapShortcut extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
writer.invokeInterface(getter.owner.type, getter.method);
} else {
@ -94,6 +95,7 @@ final class LMapShortcut extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
writer.invokeInterface(setter.owner.type, setter.method);
} else {

View File

@ -81,6 +81,7 @@ public final class LNewArray extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
for (AExpression argument : arguments) {
argument.write(writer);
}

View File

@ -86,6 +86,7 @@ final class LShortcut extends ALink {
@Override
void load(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
writer.invokeInterface(getter.owner.type, getter.method);
} else {
@ -100,6 +101,7 @@ final class LShortcut extends ALink {
@Override
void store(MethodWriter writer) {
writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {
writer.invokeInterface(setter.owner.type, setter.method);
} else {

View File

@ -45,9 +45,11 @@ public final class SBlock extends AStatement {
throw createError(new IllegalArgumentException("A block must contain at least one statement."));
}
final AStatement last = statements.get(statements.size() - 1);
AStatement last = statements.get(statements.size() - 1);
for (AStatement statement : statements) {
// Note that we do not need to check after the last statement because
// there is no statement that can be unreachable after the last.
if (allEscape) {
throw createError(new IllegalArgumentException("Unreachable statement."));
}

View File

@ -86,6 +86,7 @@ public final class SCatch extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
Label jump = new Label();
writer.mark(jump);

View File

@ -50,7 +50,7 @@ public final class SDeclBlock extends AStatement {
@Override
void write(MethodWriter writer) {
for (SDeclaration declaration : declarations) {
for (AStatement declaration : declarations) {
declaration.write(writer);
}
}

View File

@ -68,6 +68,7 @@ public final class SDeclaration extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
if (expression == null) {
switch (variable.type.sort) {
case VOID: throw createError(new IllegalStateException("Illegal tree structure."));

View File

@ -88,6 +88,7 @@ public final class SDo extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
Label start = new Label();
Label begin = new Label();
Label end = new Label();

View File

@ -0,0 +1,228 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Variables;
import org.elasticsearch.painless.Variables.Variable;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_HASNEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_NEXT;
import static org.elasticsearch.painless.WriterConstants.ITERATOR_TYPE;
/**
* Represents a for-each loop shortcut for iterables. Defers to other S-nodes for non-iterable types.
*/
public class SEach extends AStatement {
final int maxLoopCounter;
final String type;
final String name;
AExpression expression;
final SBlock block;
// Members for all cases.
Variable variable = null;
Cast cast = null;
// Members for the array case.
Variable array = null;
Variable index = null;
Type indexed = null;
// Members for the iterable case.
Variable iterator = null;
Method method = null;
public SEach(Location location, int maxLoopCounter, String type, String name, AExpression expression, SBlock block) {
super(location);
this.maxLoopCounter = maxLoopCounter;
this.type = type;
this.name = name;
this.expression = expression;
this.block = block;
}
@Override
void analyze(Variables variables) {
expression.analyze(variables);
expression.expected = expression.actual;
expression = expression.cast(variables);
final Type type;
try {
type = Definition.getType(this.type);
} catch (IllegalArgumentException exception) {
throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
}
variables.incrementScope();
variable = variables.addVariable(location, type, name, true, false);
if (expression.actual.sort == Sort.ARRAY) {
analyzeArray(variables, type);
} else if (expression.actual.sort == Sort.DEF || Iterable.class.isAssignableFrom(expression.actual.clazz)) {
analyzeIterable(variables, type);
} else {
throw location.createError(new IllegalArgumentException("Illegal for each type [" + expression.actual.name + "]."));
}
if (block == null) {
throw location.createError(new IllegalArgumentException("Extraneous for each loop."));
}
block.beginLoop = true;
block.inLoop = true;
block.analyze(variables);
block.statementCount = Math.max(1, block.statementCount);
if (block.loopEscape && !block.anyContinue) {
throw createError(new IllegalArgumentException("Extraneous for loop."));
}
statementCount = 1;
if (maxLoopCounter > 0) {
loopCounterSlot = variables.getVariable(location, "#loop").slot;
}
variables.decrementScope();
}
void analyzeArray(Variables variables, Type type) {
// We must store the array and index as variables for securing slots on the stack, and
// also add the location offset to make the names unique in case of nested for each loops.
array = variables.addVariable(location, expression.actual, "#array" + location.getOffset(), true, false);
index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true, false);
indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1);
cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true);
}
void analyzeIterable(Variables variables, Type type) {
// We must store the iterator as a variable for securing a slot on the stack, and
// also add the location offset to make the name unique in case of nested for each loops.
iterator = variables.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true, false);
if (expression.actual.sort == Sort.DEF) {
method = null;
} else {
method = expression.actual.struct.methods.get(new MethodKey("iterator", 0));
if (method == null) {
throw location.createError(new IllegalArgumentException(
"Unable to create iterator for the type [" + expression.actual.name + "]."));
}
}
cast = AnalyzerCaster.getLegalCast(location, Definition.DEF_TYPE, type, true, true);
}
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
if (array != null) {
writeArray(writer);
} else if (iterator != null) {
writeIterable(writer);
} else {
throw location.createError(new IllegalStateException("Illegal tree structure."));
}
}
void writeArray(MethodWriter writer) {
expression.write(writer);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.slot);
writer.push(-1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.slot);
Label begin = new Label();
Label end = new Label();
writer.mark(begin);
writer.visitIincInsn(index.slot, 1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot);
writer.arrayLength();
writer.ifICmp(MethodWriter.GE, end);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot);
writer.arrayLoad(indexed.type);
writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot);
block.write(writer);
writer.goTo(begin);
writer.mark(end);
}
void writeIterable(MethodWriter writer) {
expression.write(writer);
if (method == null) {
Type itr = Definition.getType("Iterator");
String desc = org.objectweb.asm.Type.getMethodDescriptor(itr.type, Definition.DEF_TYPE.type);
writer.invokeDynamic("iterator", desc, DEF_BOOTSTRAP_HANDLE, (Object)DefBootstrap.ITERATOR);
} else if (java.lang.reflect.Modifier.isInterface(method.owner.clazz.getModifiers())) {
writer.invokeInterface(method.owner.type, method.method);
} else {
writer.invokeVirtual(method.owner.type, method.method);
}
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.slot);
Label begin = new Label();
Label end = new Label();
writer.mark(begin);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot);
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
writer.ifZCmp(MethodWriter.EQ, end);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot);
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot);
block.write(writer);
writer.goTo(begin);
writer.mark(end);
}
}

View File

@ -40,11 +40,11 @@ public final class SFor extends AStatement {
ANode initializer, AExpression condition, AExpression afterthought, SBlock block) {
super(location);
this.maxLoopCounter = maxLoopCounter;
this.initializer = initializer;
this.condition = condition;
this.afterthought = afterthought;
this.block = block;
this.maxLoopCounter = maxLoopCounter;
}
@Override
@ -54,8 +54,8 @@ public final class SFor extends AStatement {
boolean continuous = false;
if (initializer != null) {
if (initializer instanceof SDeclBlock) {
((SDeclBlock)initializer).analyze(variables);
if (initializer instanceof AStatement) {
((AStatement)initializer).analyze(variables);
} else if (initializer instanceof AExpression) {
AExpression initializer = (AExpression)this.initializer;
@ -129,6 +129,7 @@ public final class SFor extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
Label start = new Label();
Label begin = afterthought == null ? start : new Label();
Label end = new Label();

View File

@ -70,6 +70,7 @@ public final class SIf extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
Label fals = new Label();
condition.fals = fals;

View File

@ -91,6 +91,7 @@ public final class SIfElse extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
Label end = new Label();
Label fals = elseblock != null ? new Label() : end;

View File

@ -48,15 +48,17 @@ public final class SSource extends AStatement {
variables.incrementScope();
final AStatement last = statements.get(statements.size() - 1);
AStatement last = statements.get(statements.size() - 1);
for (AStatement statement : statements) {
// TODO: why are we checking only statements 0..n-1 (this effectively checks only the previous statement)
// Note that we do not need to check after the last statement because
// there is no statement that can be unreachable after the last.
if (allEscape) {
throw createError(new IllegalArgumentException("Unreachable statement."));
}
statement.lastSource = statement == last;
statement.analyze(variables);
methodEscape = statement.methodEscape;

View File

@ -35,11 +35,11 @@ public final class STry extends AStatement {
final SBlock block;
final List<SCatch> catches;
public STry(Location location, SBlock block, List<SCatch> traps) {
public STry(Location location, SBlock block, List<SCatch> catches) {
super(location);
this.block = block;
this.catches = Collections.unmodifiableList(traps);
this.catches = Collections.unmodifiableList(catches);
}
@Override
@ -88,6 +88,7 @@ public final class STry extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
Label begin = new Label();
Label end = new Label();
Label exception = new Label();

View File

@ -94,6 +94,7 @@ public final class SWhile extends AStatement {
@Override
void write(MethodWriter writer) {
writer.writeStatementOffset(location);
Label begin = new Label();
Label end = new Label();

View File

@ -70,6 +70,7 @@
* {@link org.elasticsearch.painless.node.SDeclaration} - Represents a single variable declaration.
* {@link org.elasticsearch.painless.node.SDeclBlock} - Represents a series of declarations.
* {@link org.elasticsearch.painless.node.SDo} - Represents a do-while loop.
* {@link org.elasticsearch.painless.node.SEach} - Represents a for each loop shortcut for iterables.
* {@link org.elasticsearch.painless.node.SExpression} - Represents the top-level node for an expression as a statement.
* {@link org.elasticsearch.painless.node.SFor} - Represents a for loop.
* {@link org.elasticsearch.painless.node.SIf} - Represents an if block.
@ -90,7 +91,7 @@
* <p>
* Generally, statement nodes have member data that evaluate legal control-flow during the analysis phase.
* The typical order for statement nodes is for each node to call analyze on it's children during the analysis phase
* and write on it's children during the writing phase. No modifications are made to the structure of statement nodes.
* and write on it's children during the writing phase.
* <p>
* Generally, expression nodes have member data that evaluate static types. The typical order for an expression node
* during the analysis phase looks like the following:

View File

@ -125,6 +125,44 @@ public class BasicStatementTests extends ScriptTestCase {
}
}
public void testIterableForEachStatement() {
assertEquals(6, exec("List l = new ArrayList(); l.add(1); l.add(2); l.add(3); int total = 0;" +
" for (int x : l) total += x; return total"));
assertEquals("123", exec("List l = new ArrayList(); l.add('1'); l.add('2'); l.add('3'); String cat = '';" +
" for (String x : l) cat += x; return cat"));
assertEquals("1236", exec("Map m = new HashMap(); m.put('1', 1); m.put('2', 2); m.put('3', 3);" +
" String cat = ''; int total = 0;" +
" for (Map.Entry e : m.entrySet()) { cat += e.getKey(); total += e.getValue(); } return cat + total"));
}
public void testIterableForEachStatementDef() {
assertEquals(6, exec("def l = new ArrayList(); l.add(1); l.add(2); l.add(3); int total = 0;" +
" for (int x : l) total += x; return total"));
assertEquals("123", exec("def l = new ArrayList(); l.add('1'); l.add('2'); l.add('3'); String cat = '';" +
" for (String x : l) cat += x; return cat"));
assertEquals("1236", exec("def m = new HashMap(); m.put('1', 1); m.put('2', 2); m.put('3', 3);" +
" String cat = ''; int total = 0;" +
" for (Map.Entry e : m.entrySet()) { cat += e.getKey(); total += e.getValue(); } return cat + total"));
}
public void testArrayForEachStatement() {
assertEquals(6, exec("int[] a = new int[3]; a[0] = 1; a[1] = 2; a[2] = 3; int total = 0;" +
" for (int x : a) total += x; return total"));
assertEquals("123", exec("String[] a = new String[3]; a[0] = '1'; a[1] = '2'; a[2] = '3'; def total = '';" +
" for (String x : a) total += x; return total"));
assertEquals(6, exec("int[][] i = new int[3][1]; i[0][0] = 1; i[1][0] = 2; i[2][0] = 3; int total = 0;" +
" for (int[] j : i) total += j[0]; return total"));
}
public void testArrayForEachStatementDef() {
assertEquals(6, exec("def a = new int[3]; a[0] = 1; a[1] = 2; a[2] = 3; int total = 0;" +
" for (int x : a) total += x; return total"));
assertEquals("123", exec("def a = new String[3]; a[0] = '1'; a[1] = '2'; a[2] = '3'; def total = '';" +
" for (String x : a) total += x; return total"));
assertEquals(6, exec("def i = new int[3][1]; i[0][0] = 1; i[1][0] = 2; i[2][0] = 3; int total = 0;" +
" for (int[] j : i) total += j[0]; return total"));
}
public void testDeclarationStatement() {
assertEquals((byte)2, exec("byte a = 2; return a;"));
assertEquals((short)2, exec("short a = 2; return a;"));