Skip to content

Commit

Permalink
Add ClassWriter to Painless writing pass (#47140)
Browse files Browse the repository at this point in the history
This the first part of a series to allow nodes to write all of their appropriate 
pieces to the class. Currently, nodes must add their bindings, constants, and 
functions to main SClass node for delayed writing. This instead adds a 
Painless version of ClassWriter to the write pass. The Painless ClassWriter 
contains an appropriate ClassVisitor that can be accessed in any node 
during the process along with access to the clinit method, and finally a 
shortcut for creating new MethodWriter. The next step will be removing the 
delayed writing in SClass, and instead, delegate all writing responsibilities to 
the nodes.
  • Loading branch information
jdconrad authored Sep 27, 2019
1 parent 4568d30 commit 50c2da9
Show file tree
Hide file tree
Showing 69 changed files with 820 additions and 654 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.Method;
import org.objectweb.asm.util.Printer;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.Closeable;
import java.util.BitSet;

/**
* Manages the top level writers for class and possibly
* clinit if necessary.
*/
public class ClassWriter implements Closeable {

protected final CompilerSettings compilerSettings;
protected final BitSet statements;

protected final org.objectweb.asm.ClassWriter classWriter;
protected final ClassVisitor classVisitor;
protected MethodWriter clinitWriter = null;

public ClassWriter(CompilerSettings compilerSettings, BitSet statements, Printer debugStream,
Class<?> baseClass, int classFrames, int classAccess, String className, String[] classInterfaces) {

this.compilerSettings = compilerSettings;
this.statements = statements;

classWriter = new org.objectweb.asm.ClassWriter(classFrames);
ClassVisitor visitor = classWriter;

if (compilerSettings.isPicky()) {
visitor = new SimpleChecksAdapter(visitor);
}

if (debugStream != null) {
visitor = new TraceClassVisitor(visitor, debugStream, null);
}

classVisitor = visitor;
classVisitor.visit(WriterConstants.CLASS_VERSION, classAccess, className, null,
Type.getType(baseClass).getInternalName(), classInterfaces);
}

public ClassVisitor getClassVisitor() {
return classVisitor;
}

/**
* Lazy loads the {@link MethodWriter} for clinit, so that if it's not
* necessary the method is never created for the class.
*/
public MethodWriter getClinitWriter() {
if (clinitWriter == null) {
clinitWriter = new MethodWriter(Opcodes.ACC_STATIC, WriterConstants.CLINIT, classVisitor, statements, compilerSettings);
clinitWriter.visitCode();
}

return clinitWriter;
}

public MethodWriter newMethodWriter(int access, Method method) {
return new MethodWriter(access, method, classVisitor, statements, compilerSettings);
}

@Override
public void close() {
if (clinitWriter != null) {
clinitWriter.returnValue();
clinitWriter.endMethod();
}

classVisitor.visitEnd();
}

public byte[] getClassBytes() {
return classWriter.toByteArray();
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
// ANTLR GENERATED CODE: DO NOT EDIT
package org.elasticsearch.painless.antlr;
import org.antlr.v4.runtime.Lexer;

import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.atn.*;
import org.antlr.v4.runtime.Lexer;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.RuntimeMetaData;
import org.antlr.v4.runtime.Vocabulary;
import org.antlr.v4.runtime.VocabularyImpl;
import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.atn.ATNDeserializer;
import org.antlr.v4.runtime.atn.LexerATNSimulator;
import org.antlr.v4.runtime.atn.PredictionContextCache;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.misc.*;

@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
abstract class PainlessLexer extends Lexer {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
// ANTLR GENERATED CODE: DO NOT EDIT
package org.elasticsearch.painless.antlr;
import org.antlr.v4.runtime.atn.*;

import org.antlr.v4.runtime.FailedPredicateException;
import org.antlr.v4.runtime.NoViableAltException;
import org.antlr.v4.runtime.Parser;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.RuleContext;
import org.antlr.v4.runtime.RuntimeMetaData;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.Vocabulary;
import org.antlr.v4.runtime.VocabularyImpl;
import org.antlr.v4.runtime.atn.ATN;
import org.antlr.v4.runtime.atn.ATNDeserializer;
import org.antlr.v4.runtime.atn.ParserATNSimulator;
import org.antlr.v4.runtime.atn.PredictionContextCache;
import org.antlr.v4.runtime.dfa.DFA;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.misc.*;
import org.antlr.v4.runtime.tree.*;
import org.antlr.v4.runtime.tree.ParseTreeVisitor;
import org.antlr.v4.runtime.tree.TerminalNode;

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

@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
class PainlessParser extends Parser {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@
import org.elasticsearch.painless.node.SBlock;
import org.elasticsearch.painless.node.SBreak;
import org.elasticsearch.painless.node.SCatch;
import org.elasticsearch.painless.node.SClass;
import org.elasticsearch.painless.node.SContinue;
import org.elasticsearch.painless.node.SDeclBlock;
import org.elasticsearch.painless.node.SDeclaration;
Expand All @@ -152,7 +153,6 @@
import org.elasticsearch.painless.node.SIf;
import org.elasticsearch.painless.node.SIfElse;
import org.elasticsearch.painless.node.SReturn;
import org.elasticsearch.painless.node.SClass;
import org.elasticsearch.painless.node.SThrow;
import org.elasticsearch.painless.node.STry;
import org.elasticsearch.painless.node.SWhile;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.elasticsearch.painless.node;

import org.elasticsearch.painless.ClassWriter;
import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
Expand Down Expand Up @@ -75,7 +76,7 @@ public abstract class ANode {
/**
* Writes ASM based on the data collected during the analysis phase.
*/
abstract void write(MethodWriter writer, Globals globals);
abstract void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals);

/**
* Create an error with location information pointing to this node.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package org.elasticsearch.painless.node;

import org.elasticsearch.painless.ClassWriter;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
Expand Down Expand Up @@ -91,17 +92,17 @@ abstract class AStoreable extends AExpression {
* Called before a storeable node is loaded or stored. Used to load prefixes and
* push load/store constants onto the stack if necessary.
*/
abstract void setup(MethodWriter writer, Globals globals);
abstract void setup(ClassWriter classWriter, MethodWriter writer, Globals globals);

/**
* Called to load a storable used for compound assignments.
*/
abstract void load(MethodWriter writer, Globals globals);
abstract void load(ClassWriter classWriter, MethodWriter writer, Globals globals);

/**
* Called to store a storabable to local memory.
*/
abstract void store(MethodWriter writer, Globals globals);
abstract void store(ClassWriter classWriter, MethodWriter writer, Globals globals);

/**
* Writes the opcodes to flip a negative array index (meaning slots from the end of the array) into a 0-based one (meaning slots from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@


import org.elasticsearch.painless.AnalyzerCaster;
import org.elasticsearch.painless.ClassWriter;
import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.DefBootstrap;
import org.elasticsearch.painless.Globals;
Expand Down Expand Up @@ -250,8 +251,8 @@ private void analyzeSimple(Locals locals) {
* also read from.
*/
@Override
void write(MethodWriter writer, Globals globals) {
writer.writeDebugInfo(location);
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
Expand All @@ -261,86 +262,87 @@ void write(MethodWriter writer, Globals globals) {
int catElementStackSize = 0;

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

// Cast the lhs to a storeable to perform the necessary operations to store the rhs.
AStoreable lhs = (AStoreable)this.lhs;
lhs.setup(writer, globals); // call the setup method on the lhs to prepare for a load/store operation
lhs.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.

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

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

if (!(rhs instanceof EBinary) || !((EBinary)rhs).cat) { // check to see if the rhs has already done a concatenation
writer.writeAppendStrings(rhs.actual); // append the rhs's value since it's hasn't already
methodWriter.writeAppendStrings(rhs.actual); // append the rhs's value since it's hasn't already
}

writer.writeToStrings(); // put the value for string concat onto the stack
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
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 (lhs.read) {
writer.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // if this lhs is also read
methodWriter.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // if this lhs is also read
// from dup the value onto the stack
}

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

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

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

writer.writeCast(there); // if necessary cast the current lhs's value
// to the promotion type between the lhs and rhs types
rhs.write(writer, globals); // write the bytecode for the rhs
methodWriter.writeCast(there); // if necessary cast the current lhs's value
// to the promotion type between the lhs and rhs types
rhs.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 (promote == def.class) {
writer.writeDynamicBinaryInstruction(
methodWriter.writeDynamicBinaryInstruction(
location, promote, def.class, def.class, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
} else {
writer.writeBinaryInstruction(location, promote, operation);
methodWriter.writeBinaryInstruction(location, promote, operation);
}

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

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

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

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

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

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

Expand Down
Loading

0 comments on commit 50c2da9

Please sign in to comment.