Merge pull request #18951 from rmuir/refactor_variables

Refactor painless variables handling
This commit is contained in:
Robert Muir 2016-06-18 08:21:14 -04:00 committed by GitHub
commit b0efca6473
63 changed files with 977 additions and 631 deletions

View File

@ -109,7 +109,7 @@ final class Compiler {
java.lang.reflect.Constructor<? extends Executable> constructor = java.lang.reflect.Constructor<? extends Executable> constructor =
clazz.getConstructor(String.class, String.class, BitSet.class); clazz.getConstructor(String.class, String.class, BitSet.class);
return constructor.newInstance(name, source, root.getExpressions()); return constructor.newInstance(name, source, root.getStatements());
} catch (Exception exception) { // Catch everything to let the user know this is something caused internally. } 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

@ -0,0 +1,48 @@
/*
* 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;
import java.util.function.Consumer;
/**
* A constant initializer to be added to the class file.
*/
public class Constant {
public final Location location;
public final String name;
public final org.objectweb.asm.Type type;
public final Consumer<MethodWriter> initializer;
/**
* Create a new constant.
*
* @param location the location in the script that is creating it
* @param type the type of the constant
* @param name the name of the constant
* @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the
* value of the constant on the stack. Generating the load instruction is managed by the caller.
*/
public Constant(Location location, org.objectweb.asm.Type type, String name, Consumer<MethodWriter> initializer) {
this.location = location;
this.name = name;
this.type = type;
this.initializer = initializer;
}
}

View File

@ -0,0 +1,72 @@
/*
* 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;
import org.elasticsearch.painless.node.SFunction;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
/**
* Program-wide globals (initializers, synthetic methods, etc)
*/
public class Globals {
private final Map<String,SFunction> syntheticMethods = new HashMap<>();
private final Map<String,Constant> constantInitializers = new HashMap<>();
private final BitSet statements;
/** Create a new Globals from the set of statement boundaries */
public Globals(BitSet statements) {
this.statements = statements;
}
/** Adds a new synthetic method to be written. It must be analyzed! */
public void addSyntheticMethod(SFunction function) {
if (!function.synthetic) {
throw new IllegalStateException("method: " + function.name + " is not synthetic");
}
if (syntheticMethods.put(function.name, function) != null) {
throw new IllegalStateException("synthetic method: " + function.name + " already exists");
}
}
/** Adds a new constant initializer to be written */
public void addConstantInitializer(Constant constant) {
if (constantInitializers.put(constant.name, constant) != null) {
throw new IllegalStateException("constant initializer: " + constant.name + " already exists");
}
}
/** Returns the current synthetic methods */
public Map<String,SFunction> getSyntheticMethods() {
return syntheticMethods;
}
/** Returns the current initializers */
public Map<String,Constant> getConstantInitializers() {
return constantInitializers;
}
/** Returns the set of statement boundaries */
public BitSet getStatements() {
return statements;
}
}

View File

@ -0,0 +1,62 @@
/*
* 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;
import org.elasticsearch.painless.Definition.Type;
import java.util.List;
import java.util.Objects;
/** Extension of locals for lambdas */
// Note: this isn't functional yet, it throws UOE
// TODO: implement slot renumbering for captures.
class LambdaLocals extends Locals {
private List<Variable> captures;
LambdaLocals(Locals parent, List<Parameter> parameters, List<Variable> captures) {
super(parent);
for (Parameter parameter : parameters) {
defineVariable(parameter.location, parameter.type, parameter.name, false);
}
this.captures = Objects.requireNonNull(captures);
}
@Override
public Variable getVariable(Location location, String name) {
Variable variable = lookupVariable(location, name);
if (variable != null) {
return variable;
}
if (getParent() != null) {
variable = getParent().getVariable(location, name);
if (variable != null) {
assert captures != null; // unused right now
// make it read-only, and record that it was used.
throw new UnsupportedOperationException("lambda capture is not supported");
}
}
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
}
@Override
public Type getReturnType() {
return Definition.DEF_TYPE;
}
}

View File

@ -23,141 +23,273 @@ import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import java.util.ArrayDeque; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Consumer; import java.util.Set;
/** /**
* Tracks user defined methods and variables across compilation phases. * Tracks user defined methods and variables across compilation phases.
*/ */
public final class Locals { public class Locals {
/** Reserved word: params map parameter */
public static final String PARAMS = "params";
/** Reserved word: Lucene scorer parameter */
public static final String SCORER = "#scorer";
/** Reserved word: _value variable for aggregations */
public static final String VALUE = "_value";
/** Reserved word: _score variable for search scripts */
public static final String SCORE = "_score";
/** Reserved word: ctx map for executable scripts */
public static final String CTX = "ctx";
/** Reserved word: loop counter */
public static final String LOOP = "#loop";
/** Reserved word: unused */
public static final String THIS = "#this";
/** Reserved word: unused */
public static final String DOC = "doc";
/** Map of always reserved keywords */
public static final Set<String> KEYWORDS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
THIS,PARAMS,SCORER,DOC,VALUE,SCORE,CTX,LOOP
)));
/** Creates a new local variable scope (e.g. loop) inside the current scope */
public static Locals newLocalScope(Locals currentScope) {
return new Locals(currentScope);
}
/** Creates a new lambda scope inside the current scope */
public static Locals newLambdaScope(Locals currentScope, List<Parameter> parameters, List<Variable> captures) {
return new LambdaLocals(currentScope, parameters, captures);
}
/** Creates a new function scope inside the current scope */
public static Locals newFunctionScope(Locals programScope, Type returnType, List<Parameter> parameters, int maxLoopCounter) {
Locals locals = new Locals(programScope, returnType);
for (Parameter parameter : parameters) {
locals.defineVariable(parameter.location, parameter.type, parameter.name, false);
}
// Loop counter to catch infinite loops. Internal use only.
if (maxLoopCounter > 0) {
locals.defineVariable(null, Definition.INT_TYPE, LOOP, true);
}
return locals;
}
/** Creates a new main method scope */
public static Locals newMainMethodScope(Locals programScope, boolean usesScore, boolean usesCtx, int maxLoopCounter) {
Locals locals = new Locals(programScope, Definition.OBJECT_TYPE);
// This reference. Internal use only.
locals.defineVariable(null, Definition.getType("Object"), THIS, true);
// Input map of variables passed to the script.
locals.defineVariable(null, Definition.getType("Map"), PARAMS, true);
// Scorer parameter passed to the script. Internal use only.
locals.defineVariable(null, Definition.DEF_TYPE, SCORER, true);
// Doc parameter passed to the script. TODO: Currently working as a Map, we can do better?
locals.defineVariable(null, Definition.getType("Map"), DOC, true);
// Aggregation _value parameter passed to the script.
locals.defineVariable(null, Definition.DEF_TYPE, VALUE, true);
// Shortcut variables.
// Document's score as a read-only double.
if (usesScore) {
locals.defineVariable(null, Definition.DOUBLE_TYPE, SCORE, true);
}
// The ctx map set by executable scripts as a read-only map.
if (usesCtx) {
locals.defineVariable(null, Definition.getType("Map"), CTX, true);
}
// Loop counter to catch infinite loops. Internal use only.
if (maxLoopCounter > 0) {
locals.defineVariable(null, Definition.INT_TYPE, LOOP, true);
}
return locals;
}
/** Creates a new program scope: the list of methods. It is the parent for all methods */
public static Locals newProgramScope(Collection<Method> methods) {
Locals locals = new Locals(null, null);
for (Method method : methods) {
locals.addMethod(method);
}
return locals;
}
/** Checks if a variable exists or not, in this scope or any parents. */
public final boolean hasVariable(String name) {
Variable variable = lookupVariable(null, name);
if (variable != null) {
return true;
}
if (parent != null) {
return parent.hasVariable(name);
}
return false;
}
/** Accesses a variable. This will throw IAE if the variable does not exist */
public Variable getVariable(Location location, String name) {
Variable variable = lookupVariable(location, name);
if (variable != null) {
return variable;
}
if (parent != null) {
return parent.getVariable(location, name);
}
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
}
/** Looks up a method. Returns null if the method does not exist. */
public final Method getMethod(MethodKey key) {
Method method = lookupMethod(key);
if (method != null) {
return method;
}
if (parent != null) {
return parent.getMethod(key);
}
return null;
}
/** Creates a new variable. Throws IAE if the variable has already been defined (even in a parent) or reserved. */
public final Variable addVariable(Location location, Type type, String name, boolean readonly) {
if (hasVariable(name)) {
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
}
if (KEYWORDS.contains(name)) {
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved."));
}
return defineVariable(location, type, name, readonly);
}
/** Return type of this scope (e.g. int, if inside a function that returns int) */
public Type getReturnType() {
return returnType;
}
/** Returns the top-level program scope. */
public Locals getProgramScope() {
Locals locals = this;
while (locals.getParent() != null) {
locals = locals.getParent();
}
return locals;
}
///// private impl
// parent scope
private final Locals parent;
// return type of this scope
private final Type returnType;
// next slot number to assign
int nextSlotNumber;
// variable name -> variable
Map<String,Variable> variables;
// method name+arity -> methods
Map<MethodKey,Method> methods;
/** /**
* Tracks reserved variables. Must be given to any source of input * Create a new Locals
* prior to beginning the analysis phase so that reserved variables
* are known ahead of time to assign appropriate slots without
* being wasteful.
*/ */
public interface Reserved { Locals(Locals parent) {
void markReserved(String name); this(parent, parent.getReturnType());
boolean isReserved(String name);
void setMaxLoopCounter(int max);
int getMaxLoopCounter();
} }
public static final class ExecuteReserved implements Reserved { /**
public static final String THIS = "#this"; * Create a new Locals with specified return type
public static final String PARAMS = "params"; */
public static final String SCORER = "#scorer"; Locals(Locals parent, Type returnType) {
public static final String DOC = "doc"; this.parent = parent;
public static final String VALUE = "_value"; this.returnType = returnType;
public static final String SCORE = "_score"; if (parent == null) {
public static final String CTX = "ctx"; this.nextSlotNumber = 0;
public static final String LOOP = "#loop"; } else {
this.nextSlotNumber = parent.getNextSlot();
private boolean score = false;
private boolean ctx = false;
private int maxLoopCounter = 0;
@Override
public void markReserved(String name) {
if (SCORE.equals(name)) {
score = true;
} else if (CTX.equals(name)) {
ctx = true;
}
}
@Override
public boolean isReserved(String name) {
return name.equals(THIS) || name.equals(PARAMS) || name.equals(SCORER) || name.equals(DOC) ||
name.equals(VALUE) || name.equals(SCORE) || name.equals(CTX) || name.equals(LOOP);
}
public boolean usesScore() {
return score;
}
public boolean usesCtx() {
return ctx;
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
} }
} }
public static final class FunctionReserved implements Reserved { /** Returns the parent scope */
public static final String THIS = "#this"; Locals getParent() {
public static final String LOOP = "#loop"; return parent;
}
private int maxLoopCounter = 0; /** Looks up a variable at this scope only. Returns null if the variable does not exist. */
Variable lookupVariable(Location location, String name) {
public void markReserved(String name) { if (variables == null) {
// Do nothing. return null;
} }
return variables.get(name);
}
public boolean isReserved(String name) { /** Looks up a method at this scope only. Returns null if the method does not exist. */
return name.equals(THIS) || name.equals(LOOP); Method lookupMethod(MethodKey key) {
if (methods == null) {
return null;
} }
return methods.get(key);
}
@Override
public void setMaxLoopCounter(int max) { /** Defines a variable at this scope internally. */
maxLoopCounter = max; Variable defineVariable(Location location, Type type, String name, boolean readonly) {
if (variables == null) {
variables = new HashMap<>();
} }
Variable variable = new Variable(location, name, type, readonly);
variable.slot = getNextSlot();
variables.put(name, variable); // TODO: check result
nextSlotNumber += type.type.getSize();
return variable;
}
// TODO: make private, thats bogus
public void addMethod(Method method) {
if (methods == null) {
methods = new HashMap<>();
}
methods.put(new MethodKey(method.name, method.arguments.size()), method);
// TODO: check result
}
@Override
public int getMaxLoopCounter() { int getNextSlot() {
return maxLoopCounter; return nextSlotNumber;
}
} }
public static final class Variable { public static final class Variable {
public final Location location; public final Location location;
public final String name; public final String name;
public final Type type; public final Type type;
public final int slot; int slot = -1;
public final boolean readonly; public final boolean readonly;
public boolean read = false; public Variable(Location location, String name, Type type, boolean readonly) {
private Variable(Location location, String name, Type type, int slot, boolean readonly) {
this.location = location; this.location = location;
this.name = name; this.name = name;
this.type = type; this.type = type;
this.slot = slot;
this.readonly = readonly; this.readonly = readonly;
} }
}
public int getSlot() {
public static final class Constant { return slot;
public final Location location;
public final String name;
public final org.objectweb.asm.Type type;
public final Consumer<MethodWriter> initializer;
private Constant(Location location, String name, org.objectweb.asm.Type type, Consumer<MethodWriter> initializer) {
this.location = location;
this.name = name;
this.type = type;
this.initializer = initializer;
} }
} }
public static final class Parameter { public static class Parameter {
public final Location location; public final Location location;
public final String name; public final String name;
public final Type type; public final Type type;
@ -168,197 +300,4 @@ public final class Locals {
this.type = type; this.type = type;
} }
} }
private final Reserved reserved;
private final Map<MethodKey, Method> methods;
private final Map<String, Constant> constants;
private final Type rtnType;
// TODO: this datastructure runs in linear time for nearly all operations. use linkedhashset instead?
private final Deque<Integer> scopes = new ArrayDeque<>();
private final Deque<Variable> variables = new ArrayDeque<>();
public Locals(ExecuteReserved reserved, Map<MethodKey, Method> methods) {
this.reserved = reserved;
this.methods = Collections.unmodifiableMap(methods);
this.constants = new HashMap<>();
this.rtnType = Definition.OBJECT_TYPE;
incrementScope();
// Method variables.
// This reference. Internal use only.
addVariable(null, Definition.getType("Object"), ExecuteReserved.THIS, true, true);
// Input map of variables passed to the script.
addVariable(null, Definition.getType("Map"), ExecuteReserved.PARAMS, true, true);
// Scorer parameter passed to the script. Internal use only.
addVariable(null, Definition.DEF_TYPE, ExecuteReserved.SCORER, true, true);
// Doc parameter passed to the script. TODO: Currently working as a Map, we can do better?
addVariable(null, Definition.getType("Map"), ExecuteReserved.DOC, true, true);
// Aggregation _value parameter passed to the script.
addVariable(null, Definition.DEF_TYPE, ExecuteReserved.VALUE, true, true);
// Shortcut variables.
// Document's score as a read-only double.
if (reserved.usesScore()) {
addVariable(null, Definition.DOUBLE_TYPE, ExecuteReserved.SCORE, true, true);
}
// The ctx map set by executable scripts as a read-only map.
if (reserved.usesCtx()) {
addVariable(null, Definition.getType("Map"), ExecuteReserved.CTX, true, true);
}
// Loop counter to catch infinite loops. Internal use only.
if (reserved.getMaxLoopCounter() > 0) {
addVariable(null, Definition.INT_TYPE, ExecuteReserved.LOOP, true, true);
}
}
public Locals(FunctionReserved reserved, Locals locals, Type rtnType, List<Parameter> parameters) {
this.reserved = reserved;
this.methods = locals.methods;
this.constants = locals.constants;
this.rtnType = rtnType;
incrementScope();
for (Parameter parameter : parameters) {
addVariable(parameter.location, parameter.type, parameter.name, false, false);
}
// Loop counter to catch infinite loops. Internal use only.
if (reserved.getMaxLoopCounter() > 0) {
addVariable(null, Definition.INT_TYPE, ExecuteReserved.LOOP, true, true);
}
}
public int getMaxLoopCounter() {
return reserved.getMaxLoopCounter();
}
public Method getMethod(MethodKey key) {
return methods.get(key);
}
public Type getReturnType() {
return rtnType;
}
public void incrementScope() {
scopes.push(0);
}
public void decrementScope() {
int remove = scopes.pop();
while (remove > 0) {
Variable variable = variables.pop();
// This checks whether or not a variable is used when exiting a local scope.
if (variable.read) {
throw variable.location.createError(new IllegalArgumentException("Variable [" + variable.name + "] is never used."));
}
--remove;
}
}
public Variable getVariable(Location location, String name) {
Iterator<Variable> itr = variables.iterator();
while (itr.hasNext()) {
Variable variable = itr.next();
if (variable.name.equals(name)) {
return variable;
}
}
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is not defined."));
}
public boolean isVariable(String name) {
Iterator<Variable> itr = variables.iterator();
while (itr.hasNext()) {
Variable variable = itr.next();
if (variable.name.equals(name)) {
return true;
}
}
return false;
}
public Variable addVariable(Location location, Type type, String name, boolean readonly, boolean reserved) {
if (!reserved && this.reserved.isReserved(name)) {
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is reserved."));
}
if (isVariable(name)) {
throw location.createError(new IllegalArgumentException("Variable [" + name + "] is already defined."));
}
Variable previous = variables.peekFirst();
int slot = 0;
if (previous != null) {
slot = previous.slot + previous.type.type.getSize();
}
Variable variable = new Variable(location, name, type, slot, readonly);
variables.push(variable);
int update = scopes.pop() + 1;
scopes.push(update);
return variable;
}
/**
* Create a new constant.
*
* @param location the location in the script that is creating it
* @param type the type of the constant
* @param name the name of the constant
* @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the
* value of the constant on the stack. Generating the load instruction is managed by the caller.
* @return the constant
*/
public Constant addConstant(Location location, org.objectweb.asm.Type type, String name, Consumer<MethodWriter> initializer) {
if (constants.containsKey(name)) {
throw location.createError(new IllegalArgumentException("Constant [" + name + "] is already defined."));
}
Constant constant = new Constant(location, name, type, initializer);
constants.put(name, constant);
return constant;
}
/**
* Create a new constant.
*
* @param location the location in the script that is creating it
* @param type the type of the constant
* @param name the name of the constant
* @param initializer code to initialize the constant. It will be called when generating the clinit method and is expected to leave the
* value of the constant on the stack. Generating the load instruction is managed by the caller.
* @return the constant
*/
public Constant addConstant(Location location, Type type, String name, Consumer<MethodWriter> initializer) {
return addConstant(location, type.type, name, initializer);
}
public Collection<Constant> getConstants() {
return constants.values();
}
} }

View File

@ -29,11 +29,12 @@ import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.atn.PredictionMode; import org.antlr.v4.runtime.atn.PredictionMode;
import org.antlr.v4.runtime.tree.TerminalNode; import org.antlr.v4.runtime.tree.TerminalNode;
import org.elasticsearch.painless.CompilerSettings; import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Locals.ExecuteReserved; import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals.FunctionReserved; import org.elasticsearch.painless.node.SFunction.Reserved;
import org.elasticsearch.painless.node.SSource.MainMethodReserved;
import org.elasticsearch.painless.node.SFunction.FunctionReserved;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Locals.Reserved;
import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext; import org.elasticsearch.painless.antlr.PainlessParser.AfterthoughtContext;
import org.elasticsearch.painless.antlr.PainlessParser.ArgumentContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentContext;
import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext; import org.elasticsearch.painless.antlr.PainlessParser.ArgumentsContext;
@ -114,6 +115,7 @@ import org.elasticsearch.painless.node.EConditional;
import org.elasticsearch.painless.node.EDecimal; import org.elasticsearch.painless.node.EDecimal;
import org.elasticsearch.painless.node.EExplicit; import org.elasticsearch.painless.node.EExplicit;
import org.elasticsearch.painless.node.EFunctionRef; import org.elasticsearch.painless.node.EFunctionRef;
import org.elasticsearch.painless.node.ELambda;
import org.elasticsearch.painless.node.ENull; import org.elasticsearch.painless.node.ENull;
import org.elasticsearch.painless.node.ENumeric; import org.elasticsearch.painless.node.ENumeric;
import org.elasticsearch.painless.node.EUnary; import org.elasticsearch.painless.node.EUnary;
@ -151,6 +153,7 @@ import org.objectweb.asm.util.Printer;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.BitSet;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
@ -170,7 +173,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
private final String sourceText; private final String sourceText;
private final Deque<Reserved> reserved = new ArrayDeque<>(); private final Deque<Reserved> reserved = new ArrayDeque<>();
private final List<SFunction> synthetic = new ArrayList<>(); private final Globals globals;
private int syntheticCounter = 0; private int syntheticCounter = 0;
private Walker(String sourceName, String sourceText, CompilerSettings settings, Printer debugStream) { private Walker(String sourceName, String sourceText, CompilerSettings settings, Printer debugStream) {
@ -178,6 +181,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
this.settings = settings; this.settings = settings;
this.sourceName = Location.computeSourceName(sourceName, sourceText); this.sourceName = Location.computeSourceName(sourceName, sourceText);
this.sourceText = sourceText; this.sourceText = sourceText;
this.globals = new Globals(new BitSet(sourceText.length()));
this.source = (SSource)visit(buildAntlrTree(sourceText)); this.source = (SSource)visit(buildAntlrTree(sourceText));
} }
@ -223,7 +227,7 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
@Override @Override
public Object visitSource(SourceContext ctx) { public Object visitSource(SourceContext ctx) {
reserved.push(new ExecuteReserved()); reserved.push(new MainMethodReserved());
List<SFunction> functions = new ArrayList<>(); List<SFunction> functions = new ArrayList<>();
@ -236,11 +240,9 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
for (StatementContext statement : ctx.statement()) { for (StatementContext statement : ctx.statement()) {
statements.add((AStatement)visit(statement)); statements.add((AStatement)visit(statement));
} }
functions.addAll(synthetic);
return new SSource(sourceName, sourceText, debugStream, return new SSource(sourceName, sourceText, debugStream, (MainMethodReserved)reserved.pop(),
(ExecuteReserved)reserved.pop(), location(ctx), functions, statements); location(ctx), functions, globals, statements);
} }
@Override @Override
@ -265,7 +267,8 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
statements.add((AStatement)visit(statement)); statements.add((AStatement)visit(statement));
} }
return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name, paramTypes, paramNames, statements, false); return new SFunction((FunctionReserved)reserved.pop(), location(ctx), rtnType, name,
paramTypes, paramNames, statements, false);
} }
@Override @Override
@ -977,10 +980,8 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
} }
String name = nextLambda(); String name = nextLambda();
synthetic.add(new SFunction((FunctionReserved)reserved.pop(), location(ctx), "def", name, return new ELambda(name, (FunctionReserved)reserved.pop(), location(ctx),
paramTypes, paramNames, statements, true)); paramTypes, paramNames, statements);
return new EFunctionRef(location(ctx), "this", name);
// TODO: use a real node for captures and shit
} }
@Override @Override
@ -1021,8 +1022,8 @@ public final class Walker extends PainlessParserBaseVisitor<Object> {
new EChain(location, new EChain(location,
new LVariable(location, "size")))))); new LVariable(location, "size"))))));
String name = nextLambda(); String name = nextLambda();
synthetic.add(new SFunction(new FunctionReserved(), location, arrayType, name, globals.addSyntheticMethod(new SFunction(new FunctionReserved(), location, arrayType, name,
Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true)); Arrays.asList("int"), Arrays.asList("size"), Arrays.asList(code), true));
return new EFunctionRef(location(ctx), "this", name); return new EFunctionRef(location(ctx), "this", name);
} }
return new EFunctionRef(location(ctx), ctx.decltype().getText(), ctx.NEW().getText()); return new EFunctionRef(location(ctx), ctx.decltype().getText(), ctx.NEW().getText());

View File

@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -111,7 +112,7 @@ public abstract class AExpression extends ANode {
/** /**
* Writes ASM based on the data collected during the analysis phase. * Writes ASM based on the data collected during the analysis phase.
*/ */
abstract void write(MethodWriter writer); abstract void write(MethodWriter writer, Globals globals);
/** /**
* Inserts {@link ECast} nodes into the tree for implicit casts. Also replaces * Inserts {@link ECast} nodes into the tree for implicit casts. Also replaces

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -91,17 +92,17 @@ public abstract class ALink extends ANode {
/** /**
* Write values before a load/store occurs such as an array index. * Write values before a load/store occurs such as an array index.
*/ */
abstract void write(MethodWriter writer); abstract void write(MethodWriter writer, Globals globals);
/** /**
* Write a load for the specific link type. * Write a load for the specific link type.
*/ */
abstract void load(MethodWriter writer); abstract void load(MethodWriter writer, Globals globals);
/** /**
* Write a store for the specific link type. * Write a store for the specific link type.
*/ */
abstract void store(MethodWriter writer); abstract void store(MethodWriter writer, Globals globals);
/** /**
* Used to copy link data from one to another during analysis in the case of replacement. * Used to copy link data from one to another during analysis in the case of replacement.

View File

@ -19,7 +19,9 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -88,7 +90,7 @@ public abstract class AStatement extends ANode {
* Set to the loop counter variable slot as a shortcut if loop statements * Set to the loop counter variable slot as a shortcut if loop statements
* are being counted. * are being counted.
*/ */
int loopCounterSlot = -1; Variable loopCounter = null;
/** /**
* Set to the approximate number of statements in a loop block to prevent * Set to the approximate number of statements in a loop block to prevent
@ -120,5 +122,5 @@ public abstract class AStatement extends ANode {
/** /**
* Writes ASM based on the data collected during the analysis phase. * Writes ASM based on the data collected during the analysis phase.
*/ */
abstract void write(MethodWriter writer); abstract void write(MethodWriter writer, Globals globals);
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
@ -604,7 +605,7 @@ public final class EBinary extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (promote.sort == Sort.STRING && operation == Operation.ADD) { if (promote.sort == Sort.STRING && operation == Operation.ADD) {
@ -612,13 +613,13 @@ public final class EBinary extends AExpression {
writer.writeNewStrings(); writer.writeNewStrings();
} }
left.write(writer); left.write(writer, globals);
if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) { if (!(left instanceof EBinary) || ((EBinary)left).operation != Operation.ADD || left.actual.sort != Sort.STRING) {
writer.writeAppendStrings(left.actual); writer.writeAppendStrings(left.actual);
} }
right.write(writer); right.write(writer, globals);
if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) { if (!(right instanceof EBinary) || ((EBinary)right).operation != Operation.ADD || right.actual.sort != Sort.STRING) {
writer.writeAppendStrings(right.actual); writer.writeAppendStrings(right.actual);
@ -628,14 +629,14 @@ public final class EBinary extends AExpression {
writer.writeToStrings(); writer.writeToStrings();
} }
} else if (operation == Operation.FIND) { } else if (operation == Operation.FIND) {
writeBuildMatcher(writer); writeBuildMatcher(writer, globals);
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND); writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_FIND);
} else if (operation == Operation.MATCH) { } else if (operation == Operation.MATCH) {
writeBuildMatcher(writer); writeBuildMatcher(writer, globals);
writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES); writer.invokeVirtual(Definition.MATCHER_TYPE.type, WriterConstants.MATCHER_MATCHES);
} else { } else {
left.write(writer); left.write(writer, globals);
right.write(writer); right.write(writer, globals);
if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) { if (promote.sort == Sort.DEF || (shiftDistance != null && shiftDistance.sort == Sort.DEF)) {
writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, false); writer.writeDynamicBinaryInstruction(location, actual, left.actual, right.actual, operation, false);
@ -647,9 +648,9 @@ public final class EBinary extends AExpression {
writer.writeBranch(tru, fals); writer.writeBranch(tru, fals);
} }
private void writeBuildMatcher(MethodWriter writer) { private void writeBuildMatcher(MethodWriter writer, Globals globals) {
right.write(writer); right.write(writer, globals);
left.write(writer); left.write(writer, globals);
writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER); writer.invokeVirtual(Definition.PATTERN_TYPE.type, WriterConstants.PATTERN_MATCHER);
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Operation; import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -67,7 +68,7 @@ public final class EBool extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
if (tru != null || fals != null) { if (tru != null || fals != null) {
if (operation == Operation.AND) { if (operation == Operation.AND) {
Label localfals = fals == null ? new Label() : fals; Label localfals = fals == null ? new Label() : fals;
@ -76,8 +77,8 @@ public final class EBool extends AExpression {
right.tru = tru; right.tru = tru;
right.fals = fals; right.fals = fals;
left.write(writer); left.write(writer, globals);
right.write(writer); right.write(writer, globals);
if (fals == null) { if (fals == null) {
writer.mark(localfals); writer.mark(localfals);
@ -89,8 +90,8 @@ public final class EBool extends AExpression {
right.tru = tru; right.tru = tru;
right.fals = fals; right.fals = fals;
left.write(writer); left.write(writer, globals);
right.write(writer); right.write(writer, globals);
if (tru == null) { if (tru == null) {
writer.mark(localtru); writer.mark(localtru);
@ -106,8 +107,8 @@ public final class EBool extends AExpression {
left.fals = localfals; left.fals = localfals;
right.fals = localfals; right.fals = localfals;
left.write(writer); left.write(writer, globals);
right.write(writer); right.write(writer, globals);
writer.push(true); writer.push(true);
writer.goTo(end); writer.goTo(end);
@ -122,8 +123,8 @@ public final class EBool extends AExpression {
left.tru = localtru; left.tru = localtru;
right.fals = localfals; right.fals = localfals;
left.write(writer); left.write(writer, globals);
right.write(writer); right.write(writer, globals);
writer.mark(localtru); writer.mark(localtru);
writer.push(true); writer.push(true);

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -41,7 +42,7 @@ public final class EBoolean extends AExpression {
} }
@Override @Override
void write(MethodWriter adapter) { void write(MethodWriter adapter, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -22,6 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -37,7 +38,7 @@ import java.lang.invoke.LambdaMetafactory;
/** /**
* Represents a capturing function reference. * Represents a capturing function reference.
*/ */
public class ECapturingFunctionRef extends AExpression { public class ECapturingFunctionRef extends AExpression implements ILambda {
public final String type; public final String type;
public final String call; public final String call;
@ -79,21 +80,21 @@ public class ECapturingFunctionRef extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (defPointer != null) { if (defPointer != null) {
// dynamic interface: push captured parameter on stack // dynamic interface: push captured parameter on stack
// TODO: don't do this: its just to cutover :) // TODO: don't do this: its just to cutover :)
writer.push((String)null); writer.push((String)null);
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot); writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
} else if (ref == null) { } else if (ref == null) {
// typed interface, dynamic implementation // typed interface, dynamic implementation
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot); writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
String descriptor = Type.getMethodType(expected.type, captured.type.type).getDescriptor(); String descriptor = Type.getMethodType(expected.type, captured.type.type).getDescriptor();
writer.invokeDynamic(call, descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.REFERENCE, expected.name); writer.invokeDynamic(call, descriptor, DEF_BOOTSTRAP_HANDLE, DefBootstrap.REFERENCE, expected.name);
} else { } else {
// typed interface, typed implementation // typed interface, typed implementation
writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.slot); writer.visitVarInsn(captured.type.type.getOpcode(Opcodes.ILOAD), captured.getSlot());
// convert MethodTypes to asm Type for the constant pool. // convert MethodTypes to asm Type for the constant pool.
String invokedType = ref.invokedType.toMethodDescriptorString(); String invokedType = ref.invokedType.toMethodDescriptorString();
Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString()); Type samMethodType = Type.getMethodType(ref.samMethodType.toMethodDescriptorString());
@ -119,4 +120,14 @@ public class ECapturingFunctionRef extends AExpression {
} }
} }
} }
@Override
public String getPointer() {
return defPointer;
}
@Override
public Type[] getCaptures() {
return new Type[] { captured.type.type };
}
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -50,8 +51,8 @@ final class ECast extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
child.write(writer); child.write(writer, globals);
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.writeCast(cast); writer.writeCast(cast);
writer.writeBranch(tru, fals); writer.writeBranch(tru, fals);

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
@ -286,7 +287,7 @@ public final class EChain extends AExpression {
* 10. call link{[0]}.store(...) -- store the value on the stack into the 0th index of the array x [...] * 10. call link{[0]}.store(...) -- store the value on the stack into the 0th index of the array x [...]
*/ */
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
// For the case where the chain represents a String concatenation // For the case where the chain represents a String concatenation
@ -306,7 +307,7 @@ public final class EChain extends AExpression {
// See individual links for more information on what each of the // See individual links for more information on what each of the
// write, load, and store methods do. // write, load, and store methods do.
for (ALink link : links) { for (ALink link : links) {
link.write(writer); // call the write method on the link to prepare for a load/store operation link.write(writer, globals); // call the write method on the link to prepare for a load/store operation
if (link == last && link.store) { if (link == last && link.store) {
if (cat) { if (cat) {
@ -314,10 +315,10 @@ public final class EChain extends AExpression {
// representing a String concatenation. // representing a String concatenation.
writer.writeDup(link.size, catElementStackSize); // dup the top element and insert it before concat helper on stack writer.writeDup(link.size, catElementStackSize); // dup the top element and insert it before concat helper on stack
link.load(writer); // read the current link's value link.load(writer, globals); // read the current link's value
writer.writeAppendStrings(link.after); // append the link's value using the StringBuilder writer.writeAppendStrings(link.after); // append the link's value using the StringBuilder
expression.write(writer); // write the bytecode for the rhs expression expression.write(writer, globals); // write the bytecode for the rhs expression
if (!(expression instanceof EBinary) || if (!(expression instanceof EBinary) ||
((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) { ((EBinary)expression).operation != Operation.ADD || expression.actual.sort != Sort.STRING) {
@ -331,13 +332,13 @@ public final class EChain extends AExpression {
writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack writer.writeDup(link.after.sort.size, link.size); // if this link is also read from dup the value onto the stack
} }
link.store(writer); // store the link's value from the stack in its respective variable/field/array link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
} else if (operation != null) { } else if (operation != null) {
// Handle the case where we are doing a compound assignment that // Handle the case where we are doing a compound assignment that
// does not represent a String concatenation. // 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 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 link.load(writer, globals); // load the current link's value
if (link.load && post) { if (link.load && post) {
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also
@ -346,7 +347,7 @@ public final class EChain extends AExpression {
writer.writeCast(there); // if necessary cast the current link's value writer.writeCast(there); // if necessary cast the current link's value
// to the promotion type between the lhs and rhs types // to the promotion type between the lhs and rhs types
expression.write(writer); // write the bytecode for the rhs expression expression.write(writer, globals); // write the bytecode for the rhs expression
// XXX: fix these types, but first we need def compound assignment tests. // XXX: fix these types, but first we need def compound assignment tests.
// its tricky here as there are possibly explicit casts, too. // its tricky here as there are possibly explicit casts, too.
// write the operation instruction for compound assignment // write the operation instruction for compound assignment
@ -364,22 +365,22 @@ public final class EChain extends AExpression {
// read from and is not a post increment // read from and is not a post increment
} }
link.store(writer); // store the link's value from the stack in its respective variable/field/array link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
} else { } else {
// Handle the case for a simple write. // Handle the case for a simple write.
expression.write(writer); // write the bytecode for the rhs expression expression.write(writer, globals); // write the bytecode for the rhs expression
if (link.load) { if (link.load) {
writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also read from writer.writeDup(link.after.sort.size, link.size); // dup the value if the link is also read from
} }
link.store(writer); // store the link's value from the stack in its respective variable/field/array link.store(writer, globals); // store the link's value from the stack in its respective variable/field/array
} }
} else { } else {
// Handle the case for a simple read. // Handle the case for a simple read.
link.load(writer); // read the link's value onto the stack link.load(writer, globals); // read the link's value onto the stack
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
@ -436,15 +437,15 @@ public final class EComp extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
boolean branch = tru != null || fals != null; boolean branch = tru != null || fals != null;
left.write(writer); left.write(writer, globals);
if (!right.isNull) { if (!right.isNull) {
right.write(writer); right.write(writer, globals);
} }
Label jump = tru != null ? tru : fals != null ? fals : new Label(); Label jump = tru != null ? tru : fals != null ? fals : new Label();

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.AnalyzerCaster;
@ -78,7 +79,7 @@ public final class EConditional extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
Label localfals = new Label(); Label localfals = new Label();
@ -88,11 +89,11 @@ public final class EConditional extends AExpression {
left.tru = right.tru = tru; left.tru = right.tru = tru;
left.fals = right.fals = fals; left.fals = right.fals = fals;
condition.write(writer); condition.write(writer, globals);
left.write(writer); left.write(writer, globals);
writer.goTo(end); writer.goTo(end);
writer.mark(localfals); writer.mark(localfals);
right.write(writer); right.write(writer, globals);
writer.mark(end); writer.mark(end);
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -63,7 +64,7 @@ final class EConstant extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
Sort sort = actual.sort; Sort sort = actual.sort;
switch (sort) { switch (sort) {

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -57,7 +58,7 @@ public final class EDecimal extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -54,7 +55,7 @@ public final class EExplicit extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.FunctionRef; import org.elasticsearch.painless.FunctionRef;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
@ -35,7 +36,7 @@ import java.lang.invoke.LambdaMetafactory;
/** /**
* Represents a function reference. * Represents a function reference.
*/ */
public class EFunctionRef extends AExpression { public class EFunctionRef extends AExpression implements ILambda {
public final String type; public final String type;
public final String call; public final String call;
@ -83,7 +84,7 @@ public class EFunctionRef extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
if (ref != null) { if (ref != null) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
// convert MethodTypes to asm Type for the constant pool. // convert MethodTypes to asm Type for the constant pool.
@ -114,4 +115,14 @@ public class EFunctionRef extends AExpression {
writer.push((String)null); writer.push((String)null);
} }
} }
@Override
public String getPointer() {
return defPointer;
}
@Override
public Type[] getCaptures() {
return new Type[0]; // no captures
}
} }

View File

@ -20,23 +20,33 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.FunctionReserved; import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.node.SFunction.FunctionReserved;
import org.elasticsearch.painless.Globals;
import org.objectweb.asm.Type;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
public class ELambda extends AExpression { public class ELambda extends AExpression implements ILambda {
final String name;
final FunctionReserved reserved; final FunctionReserved reserved;
final List<String> paramTypeStrs; final List<String> paramTypeStrs;
final List<String> paramNameStrs; final List<String> paramNameStrs;
final List<AStatement> statements; final List<AStatement> statements;
// desugared synthetic method (lambda body)
SFunction desugared;
// method ref (impl detail)
ILambda impl;
public ELambda(FunctionReserved reserved, Location location, public ELambda(String name, FunctionReserved reserved,
List<String> paramTypes, List<String> paramNames, List<AStatement> statements) { Location location, List<String> paramTypes, List<String> paramNames,
List<AStatement> statements) {
super(location); super(location);
this.name = name;
this.reserved = reserved; this.reserved = reserved;
this.paramTypeStrs = Collections.unmodifiableList(paramTypes); this.paramTypeStrs = Collections.unmodifiableList(paramTypes);
this.paramNameStrs = Collections.unmodifiableList(paramNames); this.paramNameStrs = Collections.unmodifiableList(paramNames);
@ -45,11 +55,40 @@ public class ELambda extends AExpression {
@Override @Override
void analyze(Locals locals) { void analyze(Locals locals) {
throw createError(new UnsupportedOperationException("Lambda functions are not supported.")); // desugar lambda body into a synthetic method
desugared = new SFunction(reserved, location, "def", name,
paramTypeStrs, paramNameStrs, statements, true);
desugared.generate();
List<Variable> captures = new ArrayList<>();
desugared.analyze(Locals.newLambdaScope(locals.getProgramScope(), desugared.parameters, captures));
// setup reference
EFunctionRef ref = new EFunctionRef(location, "this", name);
ref.expected = expected;
// hack, create a new scope, with our method, so the ref can see it (impl detail)
locals = Locals.newLocalScope(locals);
locals.addMethod(desugared.method);
ref.analyze(locals);
actual = ref.actual;
impl = ref;
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); AExpression expr = (AExpression) impl;
expr.write(writer, globals);
// add synthetic method to the queue to be written
globals.addSyntheticMethod(desugared);
} }
@Override
public String getPointer() {
return impl.getPointer();
}
@Override
public Type[] getCaptures() {
return impl.getCaptures();
}
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
@ -50,7 +51,7 @@ public final class ENull extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.visitInsn(Opcodes.ACONST_NULL); writer.visitInsn(Opcodes.ACONST_NULL);
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -96,7 +97,7 @@ public final class ENumeric extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
@ -177,7 +178,7 @@ public final class EUnary extends AExpression {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (operation == Operation.NOT) { if (operation == Operation.NOT) {
@ -186,7 +187,7 @@ public final class EUnary extends AExpression {
Label end = new Label(); Label end = new Label();
child.fals = localfals; child.fals = localfals;
child.write(writer); child.write(writer, globals);
writer.push(false); writer.push(false);
writer.goTo(end); writer.goTo(end);
@ -196,11 +197,11 @@ public final class EUnary extends AExpression {
} else { } else {
child.tru = fals; child.tru = fals;
child.fals = tru; child.fals = tru;
child.write(writer); child.write(writer, globals);
} }
} else { } else {
Sort sort = promote.sort; Sort sort = promote.sort;
child.write(writer); child.write(writer, globals);
if (operation == Operation.BWNOT) { if (operation == Operation.BWNOT) {
if (sort == Sort.DEF) { if (sort == Sort.DEF) {

View File

@ -0,0 +1,40 @@
/*
* 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;
/**
* Interface for lambda/method reference nodes. They need special handling by LDefCall.
* <p>
* This is because they know nothing about the target interface, and can only push
* all their captures onto the stack and defer everything until link-time.
*/
interface ILambda {
/** Returns reference to resolve at link-time */
String getPointer();
/** Returns the types of captured parameters. Can be empty */
org.objectweb.asm.Type[] getCaptures();
/** Returns the number of captured parameters */
default int getCaptureCount() {
return getCaptures().length;
}
}

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -55,18 +56,18 @@ public final class LArrayLength extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.arrayLength(); writer.arrayLength();
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -69,18 +70,18 @@ public final class LBrace extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
index.write(writer); index.write(writer, globals);
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.arrayLoad(after.type); writer.arrayLoad(after.type);
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.arrayStore(after.type); writer.arrayStore(after.type);
} }

View File

@ -24,6 +24,7 @@ import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Struct; import org.elasticsearch.painless.Definition.Struct;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -86,16 +87,16 @@ public final class LCallInvoke extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
for (AExpression argument : arguments) { for (AExpression argument : arguments) {
argument.write(writer); argument.write(writer, globals);
} }
if (java.lang.reflect.Modifier.isStatic(method.modifiers)) { if (java.lang.reflect.Modifier.isStatic(method.modifiers)) {
@ -108,7 +109,7 @@ public final class LCallInvoke extends ALink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -77,23 +78,23 @@ public class LCallLocal extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
for (AExpression argument : arguments) { for (AExpression argument : arguments) {
argument.write(writer); argument.write(writer, globals);
} }
writer.invokeStatic(CLASS_TYPE, method.method); writer.invokeStatic(CLASS_TYPE, method.method);
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.AnalyzerCaster;
@ -61,18 +62,18 @@ public final class LCast extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.writeCast(cast); writer.writeCast(cast);
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -53,12 +54,12 @@ final class LDefArray extends ALink implements IDefLink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
index.write(writer); index.write(writer, globals);
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type); String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type, index.actual.type);
@ -66,7 +67,7 @@ final class LDefArray extends ALink implements IDefLink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type); String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, index.actual.type, after.type);

View File

@ -20,10 +20,12 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.objectweb.asm.Type;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -64,13 +66,11 @@ final class LDefCall extends ALink implements IDefLink {
expression.internal = true; expression.internal = true;
expression.analyze(locals); expression.analyze(locals);
if (expression instanceof EFunctionRef) { if (expression instanceof ILambda) {
pointers.add(((EFunctionRef)expression).defPointer); ILambda lambda = (ILambda) expression;
pointers.add(lambda.getPointer());
recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference
} else if (expression instanceof ECapturingFunctionRef) { totalCaptures += lambda.getCaptureCount();
pointers.add(((ECapturingFunctionRef)expression).defPointer);
recipe |= (1L << (argument + totalCaptures)); // mark argument as deferred reference
totalCaptures++;
} }
expression.expected = expression.actual; expression.expected = expression.actual;
@ -84,12 +84,12 @@ final class LDefCall extends ALink implements IDefLink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
StringBuilder signature = new StringBuilder(); StringBuilder signature = new StringBuilder();
@ -100,11 +100,13 @@ final class LDefCall extends ALink implements IDefLink {
for (AExpression argument : arguments) { for (AExpression argument : arguments) {
signature.append(argument.actual.type.getDescriptor()); signature.append(argument.actual.type.getDescriptor());
if (argument instanceof ECapturingFunctionRef) { if (argument instanceof ILambda) {
ECapturingFunctionRef capturingRef = (ECapturingFunctionRef) argument; ILambda lambda = (ILambda) argument;
signature.append(capturingRef.captured.type.type.getDescriptor()); for (Type capture : lambda.getCaptures()) {
signature.append(capture.getDescriptor());
}
} }
argument.write(writer); argument.write(writer, globals);
} }
signature.append(')'); signature.append(')');
@ -119,7 +121,7 @@ final class LDefCall extends ALink implements IDefLink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -50,12 +51,12 @@ final class LDefField extends ALink implements IDefLink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type); String desc = Type.getMethodDescriptor(after.type, Definition.DEF_TYPE.type);
@ -63,7 +64,7 @@ final class LDefField extends ALink implements IDefLink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type); String desc = Type.getMethodDescriptor(Definition.VOID_TYPE.type, Definition.DEF_TYPE.type, after.type);

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Field; import org.elasticsearch.painless.Definition.Field;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
@ -100,12 +101,12 @@ public final class LField extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) { if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {
@ -116,7 +117,7 @@ public final class LField extends ALink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isStatic(field.modifiers)) { if (java.lang.reflect.Modifier.isStatic(field.modifiers)) {

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
@ -74,12 +75,12 @@ final class LListShortcut extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
index.write(writer); index.write(writer, globals);
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
@ -94,7 +95,7 @@ final class LListShortcut extends ALink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
@ -73,12 +74,12 @@ final class LMapShortcut extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
index.write(writer); index.write(writer, globals);
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
@ -93,7 +94,7 @@ final class LMapShortcut extends ALink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -74,16 +75,16 @@ public final class LNewArray extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
for (AExpression argument : arguments) { for (AExpression argument : arguments) {
argument.write(writer); argument.write(writer, globals);
} }
if (arguments.size() > 1) { if (arguments.size() > 1) {
@ -94,7 +95,7 @@ public final class LNewArray extends ALink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Struct; import org.elasticsearch.painless.Definition.Struct;
@ -93,12 +94,12 @@ public final class LNewObj extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.newInstance(after.type); writer.newInstance(after.type);
@ -107,14 +108,14 @@ public final class LNewObj extends ALink {
} }
for (AExpression argument : arguments) { for (AExpression argument : arguments) {
argument.write(writer); argument.write(writer, globals);
} }
writer.invokeConstructor(constructor.owner.type, constructor.method); writer.invokeConstructor(constructor.owner.type, constructor.method);
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -19,9 +19,10 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Constant;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
@ -64,25 +65,26 @@ public final class LRegex extends ALink {
throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "].")); throw createError(new IllegalArgumentException("Regex constant may only be read [" + pattern + "]."));
} }
constant = locals.addConstant(location, Definition.PATTERN_TYPE, "regexAt$" + location.getOffset(), this::initializeConstant); constant = new Constant(location, Definition.PATTERN_TYPE.type, "regexAt$" + location.getOffset(), this::initializeConstant);
after = Definition.PATTERN_TYPE; after = Definition.PATTERN_TYPE;
return this; return this;
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type); writer.getStatic(WriterConstants.CLASS_TYPE, constant.name, Definition.PATTERN_TYPE.type);
globals.addConstantInitializer(constant);
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
@ -79,12 +80,12 @@ final class LShortcut extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) { if (java.lang.reflect.Modifier.isInterface(getter.owner.clazz.getModifiers())) {
@ -99,7 +100,7 @@ final class LShortcut extends ALink {
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location); writer.writeDebugInfo(location);
if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) { if (java.lang.reflect.Modifier.isInterface(setter.owner.clazz.getModifiers())) {

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -54,17 +55,17 @@ public final class LStatic extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -51,17 +52,17 @@ public final class LString extends ALink {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.push(string); writer.push(string);
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }

View File

@ -21,6 +21,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Locals.Variable;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
@ -32,7 +33,7 @@ public final class LVariable extends ALink {
final String name; final String name;
int slot; Variable variable;
public LVariable(Location location, String name) { public LVariable(Location location, String name) {
super(location, 0); super(location, 0);
@ -46,30 +47,29 @@ public final class LVariable extends ALink {
throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined.")); throw createError(new IllegalArgumentException("Illegal variable [" + name + "] access with target already defined."));
} }
Variable variable = locals.getVariable(location, name); variable = locals.getVariable(location, name);
if (store && variable.readonly) { if (store && variable.readonly) {
throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only.")); throw createError(new IllegalArgumentException("Variable [" + variable.name + "] is read-only."));
} }
slot = variable.slot;
after = variable.type; after = variable.type;
return this; return this;
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
// Do nothing. // Do nothing.
} }
@Override @Override
void load(MethodWriter writer) { void load(MethodWriter writer, Globals globals) {
writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), slot); writer.visitVarInsn(after.type.getOpcode(Opcodes.ILOAD), variable.getSlot());
} }
@Override @Override
void store(MethodWriter writer) { void store(MethodWriter writer, Globals globals) {
writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), slot); writer.visitVarInsn(after.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
} }
} }

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -70,11 +71,11 @@ public final class SBlock extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
for (AStatement statement : statements) { for (AStatement statement : statements) {
statement.continu = continu; statement.continu = continu;
statement.brake = brake; statement.brake = brake;
statement.write(writer); statement.write(writer, globals);
} }
} }
} }

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -45,7 +46,7 @@ public final class SBreak extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.goTo(brake); writer.goTo(brake);
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -65,7 +66,7 @@ public final class SCatch extends AStatement {
throw createError(new ClassCastException("Not an exception type [" + this.type + "].")); throw createError(new ClassCastException("Not an exception type [" + this.type + "]."));
} }
variable = locals.addVariable(location, type, name, true, false); variable = locals.addVariable(location, type, name, true);
if (block != null) { if (block != null) {
block.lastSource = lastSource; block.lastSource = lastSource;
@ -84,18 +85,18 @@ public final class SCatch extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
Label jump = new Label(); Label jump = new Label();
writer.mark(jump); writer.mark(jump);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
if (block != null) { if (block != null) {
block.continu = continu; block.continu = continu;
block.brake = brake; block.brake = brake;
block.write(writer); block.write(writer, globals);
} }
writer.visitTryCatchBlock(begin, end, jump, variable.type.type.getInternalName()); writer.visitTryCatchBlock(begin, end, jump, variable.type.type.getInternalName());

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -48,7 +49,7 @@ public final class SContinue extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.goTo(continu); writer.goTo(continu);
} }
} }

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -49,9 +50,9 @@ public final class SDeclBlock extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
for (AStatement declaration : declarations) { for (AStatement declaration : declarations) {
declaration.write(writer); declaration.write(writer, globals);
} }
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
@ -62,11 +63,11 @@ public final class SDeclaration extends AStatement {
expression = expression.cast(locals); expression = expression.cast(locals);
} }
variable = locals.addVariable(location, type, name, false, false); variable = locals.addVariable(location, type, name, false);
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
if (expression == null) { if (expression == null) {
@ -83,9 +84,9 @@ public final class SDeclaration extends AStatement {
default: writer.visitInsn(Opcodes.ACONST_NULL); default: writer.visitInsn(Opcodes.ACONST_NULL);
} }
} else { } else {
expression.write(writer); expression.write(writer, globals);
} }
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
} }
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
@ -42,7 +43,7 @@ public final class SDo extends AStatement {
@Override @Override
void analyze(Locals locals) { void analyze(Locals locals) {
locals.incrementScope(); locals = Locals.newLocalScope(locals);
if (block == null) { if (block == null) {
throw createError(new IllegalArgumentException("Extraneous do while loop.")); throw createError(new IllegalArgumentException("Extraneous do while loop."));
@ -76,15 +77,13 @@ public final class SDo extends AStatement {
statementCount = 1; statementCount = 1;
if (locals.getMaxLoopCounter() > 0) { if (locals.hasVariable(Locals.LOOP)) {
loopCounterSlot = locals.getVariable(location, "#loop").slot; loopCounter = locals.getVariable(location, Locals.LOOP);
} }
locals.decrementScope();
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
Label start = new Label(); Label start = new Label();
@ -95,14 +94,14 @@ public final class SDo extends AStatement {
block.continu = begin; block.continu = begin;
block.brake = end; block.brake = end;
block.write(writer); block.write(writer, globals);
writer.mark(begin); writer.mark(begin);
condition.fals = end; condition.fals = end;
condition.write(writer); condition.write(writer, globals);
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location); writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location);
writer.goTo(start); writer.goTo(start);
writer.mark(end); writer.mark(end);

View File

@ -22,6 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.AnalyzerCaster; import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.DefBootstrap; import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Cast; import org.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.MethodKey;
@ -85,9 +86,9 @@ public class SEach extends AStatement {
throw createError(new IllegalArgumentException("Not a type [" + this.type + "].")); throw createError(new IllegalArgumentException("Not a type [" + this.type + "]."));
} }
locals.incrementScope(); locals = Locals.newLocalScope(locals);
variable = locals.addVariable(location, type, name, true, false); variable = locals.addVariable(location, type, name, true);
if (expression.actual.sort == Sort.ARRAY) { if (expression.actual.sort == Sort.ARRAY) {
analyzeArray(locals, type); analyzeArray(locals, type);
@ -112,18 +113,16 @@ public class SEach extends AStatement {
statementCount = 1; statementCount = 1;
if (locals.getMaxLoopCounter() > 0) { if (locals.hasVariable(Locals.LOOP)) {
loopCounterSlot = locals.getVariable(location, "#loop").slot; loopCounter = locals.getVariable(location, Locals.LOOP);
} }
locals.decrementScope();
} }
void analyzeArray(Locals variables, Type type) { void analyzeArray(Locals variables, Type type) {
// We must store the array and index as variables for securing slots on the stack, and // 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. // 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); array = variables.addVariable(location, expression.actual, "#array" + location.getOffset(), true);
index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true, false); index = variables.addVariable(location, Definition.INT_TYPE, "#index" + location.getOffset(), true);
indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1); indexed = Definition.getType(expression.actual.struct, expression.actual.dimensions - 1);
cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true); cast = AnalyzerCaster.getLegalCast(location, indexed, type, true, true);
} }
@ -131,7 +130,7 @@ public class SEach extends AStatement {
void analyzeIterable(Locals variables, Type type) { void analyzeIterable(Locals variables, Type type) {
// We must store the iterator as a variable for securing a slot on the stack, and // 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. // 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); iterator = variables.addVariable(location, Definition.getType("Iterator"), "#itr" + location.getOffset(), true);
if (expression.actual.sort == Sort.DEF) { if (expression.actual.sort == Sort.DEF) {
method = null; method = null;
@ -148,49 +147,49 @@ public class SEach extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
if (array != null) { if (array != null) {
writeArray(writer); writeArray(writer, globals);
} else if (iterator != null) { } else if (iterator != null) {
writeIterable(writer); writeIterable(writer, globals);
} else { } else {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }
void writeArray(MethodWriter writer) { void writeArray(MethodWriter writer, Globals globals) {
expression.write(writer); expression.write(writer, globals);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.slot); writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ISTORE), array.getSlot());
writer.push(-1); writer.push(-1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.slot); writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ISTORE), index.getSlot());
Label begin = new Label(); Label begin = new Label();
Label end = new Label(); Label end = new Label();
writer.mark(begin); writer.mark(begin);
writer.visitIincInsn(index.slot, 1); writer.visitIincInsn(index.getSlot(), 1);
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot); writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot); writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
writer.arrayLength(); writer.arrayLength();
writer.ifICmp(MethodWriter.GE, end); writer.ifICmp(MethodWriter.GE, end);
writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.slot); writer.visitVarInsn(array.type.type.getOpcode(Opcodes.ILOAD), array.getSlot());
writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.slot); writer.visitVarInsn(index.type.type.getOpcode(Opcodes.ILOAD), index.getSlot());
writer.arrayLoad(indexed.type); writer.arrayLoad(indexed.type);
writer.writeCast(cast); writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
block.write(writer); block.write(writer, globals);
writer.goTo(begin); writer.goTo(begin);
writer.mark(end); writer.mark(end);
} }
void writeIterable(MethodWriter writer) { void writeIterable(MethodWriter writer, Globals globals) {
expression.write(writer); expression.write(writer, globals);
if (method == null) { if (method == null) {
Type itr = Definition.getType("Iterator"); Type itr = Definition.getType("Iterator");
@ -202,23 +201,23 @@ public class SEach extends AStatement {
writer.invokeVirtual(method.owner.type, method.method); writer.invokeVirtual(method.owner.type, method.method);
} }
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.slot); writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ISTORE), iterator.getSlot());
Label begin = new Label(); Label begin = new Label();
Label end = new Label(); Label end = new Label();
writer.mark(begin); writer.mark(begin);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot); writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT); writer.invokeInterface(ITERATOR_TYPE, ITERATOR_HASNEXT);
writer.ifZCmp(MethodWriter.EQ, end); writer.ifZCmp(MethodWriter.EQ, end);
writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.slot); writer.visitVarInsn(iterator.type.type.getOpcode(Opcodes.ILOAD), iterator.getSlot());
writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT); writer.invokeInterface(ITERATOR_TYPE, ITERATOR_NEXT);
writer.writeCast(cast); writer.writeCast(cast);
writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.slot); writer.visitVarInsn(variable.type.type.getOpcode(Opcodes.ISTORE), variable.getSlot());
block.write(writer); block.write(writer, globals);
writer.goTo(begin); writer.goTo(begin);
writer.mark(end); writer.mark(end);

View File

@ -22,6 +22,7 @@ package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -63,9 +64,9 @@ public final class SExpression extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
expression.write(writer); expression.write(writer, globals);
if (methodEscape) { if (methodEscape) {
writer.returnValue(); writer.returnValue();

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
@ -46,7 +47,7 @@ public final class SFor extends AStatement {
@Override @Override
void analyze(Locals locals) { void analyze(Locals locals) {
locals.incrementScope(); locals = Locals.newLocalScope(locals);
boolean continuous = false; boolean continuous = false;
@ -116,15 +117,13 @@ public final class SFor extends AStatement {
statementCount = 1; statementCount = 1;
if (locals.getMaxLoopCounter() > 0) { if (locals.hasVariable(Locals.LOOP)) {
loopCounterSlot = locals.getVariable(location, "#loop").slot; loopCounter = locals.getVariable(location, Locals.LOOP);
} }
locals.decrementScope();
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
Label start = new Label(); Label start = new Label();
@ -132,11 +131,11 @@ public final class SFor extends AStatement {
Label end = new Label(); Label end = new Label();
if (initializer instanceof SDeclBlock) { if (initializer instanceof SDeclBlock) {
((SDeclBlock)initializer).write(writer); ((SDeclBlock)initializer).write(writer, globals);
} else if (initializer instanceof AExpression) { } else if (initializer instanceof AExpression) {
AExpression initializer = (AExpression)this.initializer; AExpression initializer = (AExpression)this.initializer;
initializer.write(writer); initializer.write(writer, globals);
writer.writePop(initializer.expected.sort.size); writer.writePop(initializer.expected.sort.size);
} }
@ -144,7 +143,7 @@ public final class SFor extends AStatement {
if (condition != null) { if (condition != null) {
condition.fals = end; condition.fals = end;
condition.write(writer); condition.write(writer, globals);
} }
boolean allEscape = false; boolean allEscape = false;
@ -158,15 +157,15 @@ public final class SFor extends AStatement {
++statementCount; ++statementCount;
} }
writer.writeLoopCounter(loopCounterSlot, statementCount, location); writer.writeLoopCounter(loopCounter.getSlot(), statementCount, location);
block.write(writer); block.write(writer, globals);
} else { } else {
writer.writeLoopCounter(loopCounterSlot, 1, location); writer.writeLoopCounter(loopCounter.getSlot(), 1, location);
} }
if (afterthought != null) { if (afterthought != null) {
writer.mark(begin); writer.mark(begin);
afterthought.write(writer); afterthought.write(writer, globals);
} }
if (afterthought != null || !allEscape) { if (afterthought != null || !allEscape) {

View File

@ -19,27 +19,26 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Def; import org.elasticsearch.painless.Def;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.Sort; import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type; import org.elasticsearch.painless.Definition.Type;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Parameter; import org.elasticsearch.painless.Locals.Parameter;
import org.elasticsearch.painless.Locals.FunctionReserved;
import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.WriterConstants; import org.elasticsearch.painless.WriterConstants;
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle; import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.BitSet;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -51,17 +50,17 @@ import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
public class SFunction extends AStatement { public class SFunction extends AStatement {
final FunctionReserved reserved; final FunctionReserved reserved;
final String rtnTypeStr; final String rtnTypeStr;
final String name; public final String name;
final List<String> paramTypeStrs; final List<String> paramTypeStrs;
final List<String> paramNameStrs; final List<String> paramNameStrs;
final List<AStatement> statements; final List<AStatement> statements;
final boolean synthetic; public final boolean synthetic;
Type rtnType = null; Type rtnType = null;
List<Parameter> parameters = new ArrayList<>(); List<Parameter> parameters = new ArrayList<>();
Method method = null; Method method = null;
Locals locals = null; Variable loop = null;
public SFunction(FunctionReserved reserved, Location location, public SFunction(FunctionReserved reserved, Location location,
String rtnType, String name, List<String> paramTypes, String rtnType, String name, List<String> paramTypes,
@ -115,10 +114,7 @@ public class SFunction extends AStatement {
throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "].")); throw createError(new IllegalArgumentException("Cannot generate an empty function [" + name + "]."));
} }
this.locals = new Locals(reserved, locals, rtnType, parameters); locals = Locals.newLocalScope(locals);
locals = this.locals;
locals.incrementScope();
AStatement last = statements.get(statements.size() - 1); AStatement last = statements.get(statements.size() - 1);
@ -141,37 +137,33 @@ public class SFunction extends AStatement {
throw createError(new IllegalArgumentException("Not all paths provide a return value for method [" + name + "].")); throw createError(new IllegalArgumentException("Not all paths provide a return value for method [" + name + "]."));
} }
locals.decrementScope(); if (reserved.getMaxLoopCounter() > 0) {
loop = locals.getVariable(null, FunctionReserved.LOOP);
String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size()); }
locals.addConstant(location, WriterConstants.METHOD_HANDLE_TYPE, staticHandleFieldName, this::initializeConstant);
} }
/** Writes the function to given ClassVisitor. */ /** Writes the function to given ClassVisitor. */
void write (ClassVisitor writer, BitSet statements) { void write (ClassVisitor writer, Globals globals) {
int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC; int access = Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC;
if (synthetic) { if (synthetic) {
access |= Opcodes.ACC_SYNTHETIC; access |= Opcodes.ACC_SYNTHETIC;
} }
final MethodWriter function = new MethodWriter(access, method.method, writer, statements); final MethodWriter function = new MethodWriter(access, method.method, writer, globals.getStatements());
write(function); write(function, globals);
function.endMethod(); function.endMethod();
} }
@Override @Override
void write(MethodWriter function) { void write(MethodWriter function, Globals globals) {
if (reserved.getMaxLoopCounter() > 0) { if (reserved.getMaxLoopCounter() > 0) {
// if there is infinite loop protection, we do this once: // if there is infinite loop protection, we do this once:
// int #loop = settings.getMaxLoopCounter() // int #loop = settings.getMaxLoopCounter()
Variable loop = locals.getVariable(null, FunctionReserved.LOOP);
function.push(reserved.getMaxLoopCounter()); function.push(reserved.getMaxLoopCounter());
function.visitVarInsn(Opcodes.ISTORE, loop.slot); function.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
} }
for (AStatement statement : statements) { for (AStatement statement : statements) {
statement.write(function); statement.write(function, globals);
} }
if (!methodEscape) { if (!methodEscape) {
@ -181,6 +173,10 @@ public class SFunction extends AStatement {
throw createError(new IllegalStateException("Illegal tree structure.")); throw createError(new IllegalStateException("Illegal tree structure."));
} }
} }
String staticHandleFieldName = Def.getUserFunctionHandleFieldName(name, parameters.size());
globals.addConstantInitializer(new Constant(location, WriterConstants.METHOD_HANDLE_TYPE,
staticHandleFieldName, this::initializeConstant));
} }
private void initializeConstant(MethodWriter writer) { private void initializeConstant(MethodWriter writer) {
@ -191,4 +187,43 @@ public class SFunction extends AStatement {
false); false);
writer.push(handle); writer.push(handle);
} }
/**
* Tracks reserved variables. Must be given to any source of input
* prior to beginning the analysis phase so that reserved variables
* are known ahead of time to assign appropriate slots without
* being wasteful.
*/
public interface Reserved {
void markReserved(String name);
boolean isReserved(String name);
void setMaxLoopCounter(int max);
int getMaxLoopCounter();
}
public static final class FunctionReserved implements Reserved {
public static final String THIS = "#this";
public static final String LOOP = "#loop";
private int maxLoopCounter = 0;
public void markReserved(String name) {
// Do nothing.
}
public boolean isReserved(String name) {
return name.equals(THIS) || name.equals(LOOP);
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
}
}
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
@ -58,9 +59,7 @@ public final class SIf extends AStatement {
ifblock.inLoop = inLoop; ifblock.inLoop = inLoop;
ifblock.lastLoop = lastLoop; ifblock.lastLoop = lastLoop;
locals.incrementScope(); ifblock.analyze(Locals.newLocalScope(locals));
ifblock.analyze(locals);
locals.decrementScope();
anyContinue = ifblock.anyContinue; anyContinue = ifblock.anyContinue;
anyBreak = ifblock.anyBreak; anyBreak = ifblock.anyBreak;
@ -68,17 +67,17 @@ public final class SIf extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
Label fals = new Label(); Label fals = new Label();
condition.fals = fals; condition.fals = fals;
condition.write(writer); condition.write(writer, globals);
ifblock.continu = continu; ifblock.continu = continu;
ifblock.brake = brake; ifblock.brake = brake;
ifblock.write(writer); ifblock.write(writer, globals);
writer.mark(fals); writer.mark(fals);
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
@ -60,9 +61,7 @@ public final class SIfElse extends AStatement {
ifblock.inLoop = inLoop; ifblock.inLoop = inLoop;
ifblock.lastLoop = lastLoop; ifblock.lastLoop = lastLoop;
locals.incrementScope(); ifblock.analyze(Locals.newLocalScope(locals));
ifblock.analyze(locals);
locals.decrementScope();
anyContinue = ifblock.anyContinue; anyContinue = ifblock.anyContinue;
anyBreak = ifblock.anyBreak; anyBreak = ifblock.anyBreak;
@ -76,9 +75,7 @@ public final class SIfElse extends AStatement {
elseblock.inLoop = inLoop; elseblock.inLoop = inLoop;
elseblock.lastLoop = lastLoop; elseblock.lastLoop = lastLoop;
locals.incrementScope(); elseblock.analyze(Locals.newLocalScope(locals));
elseblock.analyze(locals);
locals.decrementScope();
methodEscape = ifblock.methodEscape && elseblock.methodEscape; methodEscape = ifblock.methodEscape && elseblock.methodEscape;
loopEscape = ifblock.loopEscape && elseblock.loopEscape; loopEscape = ifblock.loopEscape && elseblock.loopEscape;
@ -89,18 +86,18 @@ public final class SIfElse extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
Label end = new Label(); Label end = new Label();
Label fals = elseblock != null ? new Label() : end; Label fals = elseblock != null ? new Label() : end;
condition.fals = fals; condition.fals = fals;
condition.write(writer); condition.write(writer, globals);
ifblock.continu = continu; ifblock.continu = continu;
ifblock.brake = brake; ifblock.brake = brake;
ifblock.write(writer); ifblock.write(writer, globals);
if (!ifblock.allEscape) { if (!ifblock.allEscape) {
writer.goTo(end); writer.goTo(end);
@ -110,7 +107,7 @@ public final class SIfElse extends AStatement {
elseblock.continu = continu; elseblock.continu = continu;
elseblock.brake = brake; elseblock.brake = brake;
elseblock.write(writer); elseblock.write(writer, globals);
writer.mark(end); writer.mark(end);
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -51,9 +52,9 @@ public final class SReturn extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
expression.write(writer); expression.write(writer, globals);
writer.returnValue(); writer.returnValue();
} }
} }

View File

@ -19,13 +19,14 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Constant;
import org.elasticsearch.painless.Definition.Method; import org.elasticsearch.painless.Definition.Method;
import org.elasticsearch.painless.Definition.MethodKey; import org.elasticsearch.painless.Definition.MethodKey;
import org.elasticsearch.painless.Executable; import org.elasticsearch.painless.Executable;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Locals.Constant;
import org.elasticsearch.painless.Locals.ExecuteReserved;
import org.elasticsearch.painless.Locals.Variable; import org.elasticsearch.painless.Locals.Variable;
import org.elasticsearch.painless.node.SFunction.Reserved;
import org.elasticsearch.painless.WriterConstants; import org.elasticsearch.painless.WriterConstants;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -35,7 +36,9 @@ import org.objectweb.asm.Opcodes;
import org.objectweb.asm.util.Printer; import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.TraceClassVisitor; import org.objectweb.asm.util.TraceClassVisitor;
import java.util.ArrayList;
import java.util.BitSet; import java.util.BitSet;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -56,24 +59,28 @@ public final class SSource extends AStatement {
final String name; final String name;
final String source; final String source;
final Printer debugStream; final Printer debugStream;
final ExecuteReserved reserved; final MainMethodReserved reserved;
final List<SFunction> functions; final List<SFunction> functions;
final Globals globals;
final List<AStatement> statements; final List<AStatement> statements;
private Locals locals; private Locals mainMethod;
private BitSet expressions;
private byte[] bytes; private byte[] bytes;
public SSource(String name, String source, Printer debugStream, ExecuteReserved reserved, Location location, public SSource(String name, String source, Printer debugStream, MainMethodReserved reserved, Location location,
List<SFunction> functions, List<AStatement> statements) { List<SFunction> functions, Globals globals, List<AStatement> statements) {
super(location); super(location);
this.name = name; this.name = name;
this.source = source; this.source = source;
this.debugStream = debugStream; this.debugStream = debugStream;
this.reserved = reserved; this.reserved = reserved;
// process any synthetic functions generated by walker (because right now, thats still easy)
functions.addAll(globals.getSyntheticMethods().values());
globals.getSyntheticMethods().clear();
this.functions = Collections.unmodifiableList(functions); this.functions = Collections.unmodifiableList(functions);
this.statements = Collections.unmodifiableList(statements); this.statements = Collections.unmodifiableList(statements);
this.globals = globals;
} }
public void analyze() { public void analyze() {
@ -89,21 +96,22 @@ public final class SSource extends AStatement {
} }
} }
locals = new Locals(reserved, methods); analyze(Locals.newProgramScope(methods.values()));
analyze(locals);
} }
@Override @Override
void analyze(Locals locals) { void analyze(Locals program) {
for (SFunction function : functions) { for (SFunction function : functions) {
function.analyze(locals); Locals functionLocals = Locals.newFunctionScope(program, function.rtnType, function.parameters,
function.reserved.getMaxLoopCounter());
function.analyze(functionLocals);
} }
if (statements == null || statements.isEmpty()) { if (statements == null || statements.isEmpty()) {
throw createError(new IllegalArgumentException("Cannot generate an empty script.")); throw createError(new IllegalArgumentException("Cannot generate an empty script."));
} }
locals.incrementScope(); mainMethod = Locals.newMainMethodScope(program, reserved.usesScore(), reserved.usesCtx(), reserved.getMaxLoopCounter());
AStatement last = statements.get(statements.size() - 1); AStatement last = statements.get(statements.size() - 1);
@ -116,13 +124,11 @@ public final class SSource extends AStatement {
statement.lastSource = statement == last; statement.lastSource = statement == last;
statement.analyze(locals); statement.analyze(mainMethod);
methodEscape = statement.methodEscape; methodEscape = statement.methodEscape;
allEscape = statement.allEscape; allEscape = statement.allEscape;
} }
locals.decrementScope();
} }
public void write() { public void write() {
@ -144,10 +150,8 @@ public final class SSource extends AStatement {
visitor.visit(classVersion, classAccess, className, null, classBase, classInterfaces); visitor.visit(classVersion, classAccess, className, null, classBase, classInterfaces);
visitor.visitSource(Location.computeSourceName(name, source), null); visitor.visitSource(Location.computeSourceName(name, source), null);
expressions = new BitSet(source.length());
// Write the constructor: // Write the constructor:
MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, expressions); MethodWriter constructor = new MethodWriter(Opcodes.ACC_PUBLIC, CONSTRUCTOR, visitor, globals.getStatements());
constructor.visitCode(); constructor.visitCode();
constructor.loadThis(); constructor.loadThis();
constructor.loadArgs(); constructor.loadArgs();
@ -156,20 +160,31 @@ public final class SSource extends AStatement {
constructor.endMethod(); constructor.endMethod();
// Write the execute method: // Write the execute method:
MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, expressions); MethodWriter execute = new MethodWriter(Opcodes.ACC_PUBLIC, EXECUTE, visitor, globals.getStatements());
execute.visitCode(); execute.visitCode();
write(execute); write(execute, globals);
execute.endMethod(); execute.endMethod();
// Write all functions: // Write all functions:
for (SFunction function : functions) { for (SFunction function : functions) {
function.write(visitor, expressions); function.write(visitor, globals);
}
// Write all synthetic functions. Note that this process may add more :)
while (!globals.getSyntheticMethods().isEmpty()) {
List<SFunction> current = new ArrayList<>(globals.getSyntheticMethods().values());
globals.getSyntheticMethods().clear();
for (SFunction function : current) {
function.write(visitor, globals);
}
} }
// Write the constants // Write the constants
if (false == locals.getConstants().isEmpty()) { if (false == globals.getConstantInitializers().isEmpty()) {
Collection<Constant> inits = globals.getConstantInitializers().values();
// Fields // Fields
for (Constant constant : locals.getConstants()) { for (Constant constant : inits) {
visitor.visitField( visitor.visitField(
Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, Opcodes.ACC_FINAL | Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
constant.name, constant.name,
@ -180,8 +195,8 @@ public final class SSource extends AStatement {
// Initialize the constants in a static initializer // Initialize the constants in a static initializer
final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC, final MethodWriter clinit = new MethodWriter(Opcodes.ACC_STATIC,
WriterConstants.CLINIT, visitor, expressions); WriterConstants.CLINIT, visitor, globals.getStatements());
for (Constant constant : locals.getConstants()) { for (Constant constant : inits) {
constant.initializer.accept(clinit); constant.initializer.accept(clinit);
clinit.putStatic(CLASS_TYPE, constant.name, constant.type); clinit.putStatic(CLASS_TYPE, constant.name, constant.type);
} }
@ -196,44 +211,44 @@ public final class SSource extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
if (reserved.usesScore()) { if (reserved.usesScore()) {
// if the _score value is used, we do this once: // if the _score value is used, we do this once:
// final double _score = scorer.score(); // final double _score = scorer.score();
Variable scorer = locals.getVariable(null, ExecuteReserved.SCORER); Variable scorer = mainMethod.getVariable(null, Locals.SCORER);
Variable score = locals.getVariable(null, ExecuteReserved.SCORE); Variable score = mainMethod.getVariable(null, Locals.SCORE);
writer.visitVarInsn(Opcodes.ALOAD, scorer.slot); writer.visitVarInsn(Opcodes.ALOAD, scorer.getSlot());
writer.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE); writer.invokeVirtual(WriterConstants.SCORER_TYPE, WriterConstants.SCORER_SCORE);
writer.visitInsn(Opcodes.F2D); writer.visitInsn(Opcodes.F2D);
writer.visitVarInsn(Opcodes.DSTORE, score.slot); writer.visitVarInsn(Opcodes.DSTORE, score.getSlot());
} }
if (reserved.usesCtx()) { if (reserved.usesCtx()) {
// if the _ctx value is used, we do this once: // if the _ctx value is used, we do this once:
// final Map<String,Object> ctx = input.get("ctx"); // final Map<String,Object> ctx = input.get("ctx");
Variable input = locals.getVariable(null, ExecuteReserved.PARAMS); Variable input = mainMethod.getVariable(null, Locals.PARAMS);
Variable ctx = locals.getVariable(null, ExecuteReserved.CTX); Variable ctx = mainMethod.getVariable(null, Locals.CTX);
writer.visitVarInsn(Opcodes.ALOAD, input.slot); writer.visitVarInsn(Opcodes.ALOAD, input.getSlot());
writer.push(ExecuteReserved.CTX); writer.push(Locals.CTX);
writer.invokeInterface(MAP_TYPE, MAP_GET); writer.invokeInterface(MAP_TYPE, MAP_GET);
writer.visitVarInsn(Opcodes.ASTORE, ctx.slot); writer.visitVarInsn(Opcodes.ASTORE, ctx.getSlot());
} }
if (reserved.getMaxLoopCounter() > 0) { if (reserved.getMaxLoopCounter() > 0) {
// if there is infinite loop protection, we do this once: // if there is infinite loop protection, we do this once:
// int #loop = settings.getMaxLoopCounter() // int #loop = settings.getMaxLoopCounter()
Variable loop = locals.getVariable(null, ExecuteReserved.LOOP); Variable loop = mainMethod.getVariable(null, Locals.LOOP);
writer.push(reserved.getMaxLoopCounter()); writer.push(reserved.getMaxLoopCounter());
writer.visitVarInsn(Opcodes.ISTORE, loop.slot); writer.visitVarInsn(Opcodes.ISTORE, loop.getSlot());
} }
for (AStatement statement : statements) { for (AStatement statement : statements) {
statement.write(writer); statement.write(writer, globals);
} }
if (!methodEscape) { if (!methodEscape) {
@ -242,11 +257,50 @@ public final class SSource extends AStatement {
} }
} }
public BitSet getExpressions() { public BitSet getStatements() {
return expressions; return globals.getStatements();
} }
public byte[] getBytes() { public byte[] getBytes() {
return bytes; return bytes;
} }
public static final class MainMethodReserved implements Reserved {
private boolean score = false;
private boolean ctx = false;
private int maxLoopCounter = 0;
@Override
public void markReserved(String name) {
if (Locals.SCORE.equals(name)) {
score = true;
} else if (Locals.CTX.equals(name)) {
ctx = true;
}
}
@Override
public boolean isReserved(String name) {
return Locals.KEYWORDS.contains(name);
}
public boolean usesScore() {
return score;
}
public boolean usesCtx() {
return ctx;
}
@Override
public void setMaxLoopCounter(int max) {
maxLoopCounter = max;
}
@Override
public int getMaxLoopCounter() {
return maxLoopCounter;
}
}
} }

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.MethodWriter; import org.elasticsearch.painless.MethodWriter;
@ -50,9 +51,9 @@ public final class SThrow extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
expression.write(writer); expression.write(writer, globals);
writer.throwException(); writer.throwException();
} }
} }

View File

@ -19,6 +19,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
@ -52,9 +53,7 @@ public final class STry extends AStatement {
block.inLoop = inLoop; block.inLoop = inLoop;
block.lastLoop = lastLoop; block.lastLoop = lastLoop;
locals.incrementScope(); block.analyze(Locals.newLocalScope(locals));
block.analyze(locals);
locals.decrementScope();
methodEscape = block.methodEscape; methodEscape = block.methodEscape;
loopEscape = block.loopEscape; loopEscape = block.loopEscape;
@ -69,9 +68,7 @@ public final class STry extends AStatement {
catc.inLoop = inLoop; catc.inLoop = inLoop;
catc.lastLoop = lastLoop; catc.lastLoop = lastLoop;
locals.incrementScope(); catc.analyze(Locals.newLocalScope(locals));
catc.analyze(locals);
locals.decrementScope();
methodEscape &= catc.methodEscape; methodEscape &= catc.methodEscape;
loopEscape &= catc.loopEscape; loopEscape &= catc.loopEscape;
@ -86,7 +83,7 @@ public final class STry extends AStatement {
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
Label begin = new Label(); Label begin = new Label();
@ -97,7 +94,7 @@ public final class STry extends AStatement {
block.continu = continu; block.continu = continu;
block.brake = brake; block.brake = brake;
block.write(writer); block.write(writer, globals);
if (!block.allEscape) { if (!block.allEscape) {
writer.goTo(exception); writer.goTo(exception);
@ -109,7 +106,7 @@ public final class STry extends AStatement {
catc.begin = begin; catc.begin = begin;
catc.end = end; catc.end = end;
catc.exception = catches.size() > 1 ? exception : null; catc.exception = catches.size() > 1 ? exception : null;
catc.write(writer); catc.write(writer, globals);
} }
if (!block.allEscape || catches.size() > 1) { if (!block.allEscape || catches.size() > 1) {

View File

@ -20,6 +20,7 @@
package org.elasticsearch.painless.node; package org.elasticsearch.painless.node;
import org.elasticsearch.painless.Definition; import org.elasticsearch.painless.Definition;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location; import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.Locals; import org.elasticsearch.painless.Locals;
import org.objectweb.asm.Label; import org.objectweb.asm.Label;
@ -42,7 +43,7 @@ public final class SWhile extends AStatement {
@Override @Override
void analyze(Locals locals) { void analyze(Locals locals) {
locals.incrementScope(); locals = Locals.newLocalScope(locals);
condition.expected = Definition.BOOLEAN_TYPE; condition.expected = Definition.BOOLEAN_TYPE;
condition.analyze(locals); condition.analyze(locals);
@ -82,15 +83,13 @@ public final class SWhile extends AStatement {
statementCount = 1; statementCount = 1;
if (locals.getMaxLoopCounter() > 0) { if (locals.hasVariable(Locals.LOOP)) {
loopCounterSlot = locals.getVariable(location, "#loop").slot; loopCounter = locals.getVariable(location, Locals.LOOP);
} }
locals.decrementScope();
} }
@Override @Override
void write(MethodWriter writer) { void write(MethodWriter writer, Globals globals) {
writer.writeStatementOffset(location); writer.writeStatementOffset(location);
Label begin = new Label(); Label begin = new Label();
@ -99,16 +98,16 @@ public final class SWhile extends AStatement {
writer.mark(begin); writer.mark(begin);
condition.fals = end; condition.fals = end;
condition.write(writer); condition.write(writer, globals);
if (block != null) { if (block != null) {
writer.writeLoopCounter(loopCounterSlot, Math.max(1, block.statementCount), location); writer.writeLoopCounter(loopCounter.getSlot(), Math.max(1, block.statementCount), location);
block.continu = begin; block.continu = begin;
block.brake = end; block.brake = end;
block.write(writer); block.write(writer, globals);
} else { } else {
writer.writeLoopCounter(loopCounterSlot, 1, location); writer.writeLoopCounter(loopCounter.getSlot(), 1, location);
} }
if (block == null || !block.allEscape) { if (block == null || !block.allEscape) {

View File

@ -30,7 +30,7 @@ public class ReservedWordTests extends ScriptTestCase {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("int _score = 5; return _score;"); exec("int _score = 5; return _score;");
}); });
assertTrue(expected.getMessage().contains("Variable [_score] is reserved")); assertTrue(expected.getMessage().contains("Variable [_score] is already defined"));
} }
/** check that we can't write to _score, its read-only! */ /** check that we can't write to _score, its read-only! */
@ -46,7 +46,7 @@ public class ReservedWordTests extends ScriptTestCase {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("int doc = 5; return doc;"); exec("int doc = 5; return doc;");
}); });
assertTrue(expected.getMessage().contains("Variable [doc] is reserved")); assertTrue(expected.getMessage().contains("Variable [doc] is already defined"));
} }
/** check that we can't write to doc, its read-only! */ /** check that we can't write to doc, its read-only! */
@ -62,7 +62,7 @@ public class ReservedWordTests extends ScriptTestCase {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("int ctx = 5; return ctx;"); exec("int ctx = 5; return ctx;");
}); });
assertTrue(expected.getMessage().contains("Variable [ctx] is reserved")); assertTrue(expected.getMessage().contains("Variable [ctx] is already defined"));
} }
/** check that we can't write to ctx, its read-only! */ /** check that we can't write to ctx, its read-only! */
@ -83,7 +83,7 @@ public class ReservedWordTests extends ScriptTestCase {
IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> { IllegalArgumentException expected = expectScriptThrows(IllegalArgumentException.class, () -> {
exec("int _value = 5; return _value;"); exec("int _value = 5; return _value;");
}); });
assertTrue(expected.getMessage().contains("Variable [_value] is reserved")); assertTrue(expected.getMessage().contains("Variable [_value] is already defined"));
} }
/** check that we can't write to _value, its read-only! */ /** check that we can't write to _value, its read-only! */