Skip to content

Commit

Permalink
Split Painless AST into a "user" tree and an "ir" tree (elastic#51278)
Browse files Browse the repository at this point in the history
This PR takes the existing Painless AST responsible for both semantic 
checking and writing Java ASM and splits it into two separate trees. The first 
tree, termed the "user" tree, is now responsible for semantic checking and 
generation of a second "ir" tree. The second tree, termed the "ir" tree, is 
responsible for generating the Java ASM bytecode. This change takes the 
nodes in nearly a 1:1 ratio with the exception of some improved super 
classes for the "ir" nodes to help with reduction of boilerplate 
getters/setters. The Painless AST remains mutable for this PR. This change 
simply takes the existing AST nodes, splits them into a "user" node and an 
equivalent "ir" node. Each "user" node will generate it's equivalent "ir" node 
during a separate phase, but eventually this will be combined into a single 
phase.
  • Loading branch information
jdconrad authored Jan 24, 2020
1 parent c9c60bc commit 70729f3
Show file tree
Hide file tree
Showing 139 changed files with 6,032 additions and 2,010 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@

import org.elasticsearch.bootstrap.BootstrapInfo;
import org.elasticsearch.painless.antlr.Walker;
import org.elasticsearch.painless.ir.ClassNode;
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.painless.node.SClass;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.symbol.ScriptRoot;
import org.objectweb.asm.util.Printer;

import java.lang.reflect.Method;
Expand Down Expand Up @@ -212,13 +214,14 @@ ScriptRoot compile(Loader loader, Set<String> extractedVariables, String name, S
SClass root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, painlessLookup, null);
root.extractVariables(extractedVariables);
ScriptRoot scriptRoot = root.analyze(painlessLookup, settings);
Map<String, Object> statics = root.write();
ClassNode classNode = root.writeClass();
Map<String, Object> statics = classNode.write();

try {
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, root.getBytes());
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, classNode.getBytes());
clazz.getField("$NAME").set(null, name);
clazz.getField("$SOURCE").set(null, source);
clazz.getField("$STATEMENTS").set(null, root.getStatements());
clazz.getField("$STATEMENTS").set(null, classNode.getStatements());
clazz.getField("$DEFINITION").set(null, painlessLookup);

for (Map.Entry<String, Object> statik : statics.entrySet()) {
Expand All @@ -244,8 +247,9 @@ byte[] compile(String name, String source, CompilerSettings settings, Printer de
debugStream);
root.extractVariables(new HashSet<>());
root.analyze(painlessLookup, settings);
root.write();
ClassNode classNode = root.writeClass();
classNode.write();

return root.getBytes();
return classNode.getBytes();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.elasticsearch.painless.lookup.PainlessLookup;
import org.elasticsearch.painless.lookup.PainlessLookupBuilder;
import org.elasticsearch.painless.spi.Whitelist;
import org.elasticsearch.painless.symbol.ScriptRoot;
import org.elasticsearch.script.ScriptContext;
import org.elasticsearch.script.ScriptEngine;
import org.elasticsearch.script.ScriptException;
Expand Down Expand Up @@ -340,7 +341,7 @@ private <T> T generateFactory(
GeneratorAdapter deterAdapter = new GeneratorAdapter(Opcodes.ASM5, isResultDeterministic,
writer.visitMethod(Opcodes.ACC_PUBLIC, methodName, isResultDeterministic.getDescriptor(), null, null));
deterAdapter.visitCode();
deterAdapter.push(scriptRoot.deterministic);
deterAdapter.push(scriptRoot.isDeterministic());
deterAdapter.returnValue();
deterAdapter.endMethod();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class PainlessParser extends Parser {
RULE_lamtype = 31, RULE_funcref = 32;
public static final String[] ruleNames = {
"source", "function", "parameters", "statement", "rstatement", "dstatement",
"trailer", "block", "empty", "initializer", "afterthought", "declaration",
"trailer", "block", "empty", "initializer", "afterthought", "declaration",
"decltype", "declvar", "trap", "expression", "unary", "chain", "primary",
"postfix", "postdot", "callinvoke", "fieldaccess", "braceaccess", "arrayinitializer",
"listinitializer", "mapinitializer", "maptoken", "arguments", "argument",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.ir;

import java.util.ArrayList;
import java.util.List;

public abstract class ArgumentsNode extends ExpressionNode {

/* ---- begin tree structure ---- */

private final List<ExpressionNode> argumentNodes = new ArrayList<>();

public void addArgumentNode(ExpressionNode argumentNode) {
argumentNodes.add(argumentNode);
}

public List<ExpressionNode> getArgumentNodes() {
return argumentNodes;
}

/* ---- end tree structure */

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* 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.ir;


import org.elasticsearch.painless.ClassWriter;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.Operation;
import org.elasticsearch.painless.lookup.PainlessCast;
import org.elasticsearch.painless.lookup.PainlessLookupUtility;
import org.elasticsearch.painless.lookup.def;

public class AssignmentNode extends BinaryNode {

/* ---- begin node data ---- */

private boolean pre;
private boolean post;
private Operation operation;
private boolean read;
private boolean cat; // set to true for a compound assignment String concatenation
private Class<?> compoundType;
private PainlessCast there;
private PainlessCast back;

public void setPre(boolean pre) {
this.pre = pre;
}

public boolean getPre() {
return pre;
}

public void setPost(boolean post) {
this.post = post;
}

public boolean getPost() {
return post;
}

public void setOperation(Operation operation) {
this.operation = operation;
}

public Operation getOperation() {
return operation;
}

public void setRead(boolean read) {
this.read = read;
}

public boolean getRead() {
return read;
}

public void setCat(boolean cat) {
this.cat = cat;
}

public boolean getCat() {
return cat;
}

public void setCompoundType(Class<?> compoundType) {
this.compoundType = compoundType;
}

public Class<?> getCompoundType() {
return compoundType;
}

public String getCompoundCanonicalTypeName() {
return PainlessLookupUtility.typeToCanonicalTypeName(compoundType);
}

public void setThere(PainlessCast there) {
this.there = there;
}

public PainlessCast getThere() {
return there;
}

public void setBack(PainlessCast back) {
this.back = back;
}

public PainlessCast getBack() {
return back;
}

/* ---- end node data ---- */

@Override
protected void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
methodWriter.writeDebugInfo(location);

// For the case where the assignment represents a String concatenation
// we must, depending on the Java version, write a StringBuilder or
// track types going onto the stack. This must be done before the
// lhs is read because we need the StringBuilder to be placed on the
// stack ahead of any potential concatenation arguments.
int catElementStackSize = 0;

if (cat) {
catElementStackSize = methodWriter.writeNewStrings();
}

getLeftNode().setup(classWriter, methodWriter, globals); // call the setup method on the lhs to prepare for a load/store operation

if (cat) {
// Handle the case where we are doing a compound assignment
// representing a String concatenation.

methodWriter.writeDup(getLeftNode().accessElementCount(), catElementStackSize); // dup the top element and insert it
// before concat helper on stack
getLeftNode().load(classWriter, methodWriter, globals); // read the current lhs's value
methodWriter.writeAppendStrings(getLeftNode().getExpressionType()); // append the lhs's value using the StringBuilder

getRightNode().write(classWriter, methodWriter, globals); // write the bytecode for the rhs

// check to see if the rhs has already done a concatenation
if (getRightNode() instanceof BinaryMathNode == false || ((BinaryMathNode)getRightNode()).getCat() == false) {
// append the rhs's value since it's hasn't already
methodWriter.writeAppendStrings(getRightNode().getExpressionType());
}

methodWriter.writeToStrings(); // put the value for string concat onto the stack
methodWriter.writeCast(back); // if necessary, cast the String to the lhs actual type

if (read) {
// if this lhs is also read from dup the value onto the stack
methodWriter.writeDup(MethodWriter.getType(
getLeftNode().getExpressionType()).getSize(), getLeftNode().accessElementCount());
}

// store the lhs's value from the stack in its respective variable/field/array
getLeftNode().store(classWriter, methodWriter, globals);
} else if (operation != null) {
// Handle the case where we are doing a compound assignment that
// does not represent a String concatenation.

methodWriter.writeDup(getLeftNode().accessElementCount(), 0); // if necessary, dup the previous lhs's value
// to be both loaded from and stored to
getLeftNode().load(classWriter, methodWriter, globals); // load the current lhs's value

if (read && post) {
// dup the value if the lhs is also read from and is a post increment
methodWriter.writeDup(MethodWriter.getType(
getLeftNode().getExpressionType()).getSize(), getLeftNode().accessElementCount());
}

methodWriter.writeCast(there); // if necessary cast the current lhs's value
// to the promotion type between the lhs and rhs types
getRightNode().write(classWriter, methodWriter, globals); // write the bytecode for the rhs

// XXX: fix these types, but first we need def compound assignment tests.
// its tricky here as there are possibly explicit casts, too.
// write the operation instruction for compound assignment
if (compoundType == def.class) {
methodWriter.writeDynamicBinaryInstruction(
location, compoundType, def.class, def.class, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
} else {
methodWriter.writeBinaryInstruction(location, compoundType, operation);
}

methodWriter.writeCast(back); // if necessary cast the promotion type value back to the lhs's type

if (read && !post) {
// dup the value if the lhs is also read from and is not a post increment
methodWriter.writeDup(MethodWriter.getType(
getLeftNode().getExpressionType()).getSize(), getLeftNode().accessElementCount());
}

// store the lhs's value from the stack in its respective variable/field/array
getLeftNode().store(classWriter, methodWriter, globals);
} else {
// Handle the case for a simple write.

getRightNode().write(classWriter, methodWriter, globals); // write the bytecode for the rhs rhs

if (read) {
// dup the value if the lhs is also read from
methodWriter.writeDup(MethodWriter.getType(
getLeftNode().getExpressionType()).getSize(), getLeftNode().accessElementCount());
}

// store the lhs's value from the stack in its respective variable/field/array
getLeftNode().store(classWriter, methodWriter, globals);
}
}
}
Loading

0 comments on commit 70729f3

Please sign in to comment.