Skip to content

Commit d09965a

Browse files
committed
Add ClassWriter to Painless writing pass (#47140)
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.
1 parent 444b47c commit d09965a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+819
-651
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.painless;
21+
22+
import org.objectweb.asm.ClassVisitor;
23+
import org.objectweb.asm.Opcodes;
24+
import org.objectweb.asm.Type;
25+
import org.objectweb.asm.commons.Method;
26+
import org.objectweb.asm.util.Printer;
27+
import org.objectweb.asm.util.TraceClassVisitor;
28+
29+
import java.io.Closeable;
30+
import java.util.BitSet;
31+
32+
/**
33+
* Manages the top level writers for class and possibly
34+
* clinit if necessary.
35+
*/
36+
public class ClassWriter implements Closeable {
37+
38+
protected final CompilerSettings compilerSettings;
39+
protected final BitSet statements;
40+
41+
protected final org.objectweb.asm.ClassWriter classWriter;
42+
protected final ClassVisitor classVisitor;
43+
protected MethodWriter clinitWriter = null;
44+
45+
public ClassWriter(CompilerSettings compilerSettings, BitSet statements, Printer debugStream,
46+
Class<?> baseClass, int classFrames, int classAccess, String className, String[] classInterfaces) {
47+
48+
this.compilerSettings = compilerSettings;
49+
this.statements = statements;
50+
51+
classWriter = new org.objectweb.asm.ClassWriter(classFrames);
52+
ClassVisitor visitor = classWriter;
53+
54+
if (compilerSettings.isPicky()) {
55+
visitor = new SimpleChecksAdapter(visitor);
56+
}
57+
58+
if (debugStream != null) {
59+
visitor = new TraceClassVisitor(visitor, debugStream, null);
60+
}
61+
62+
classVisitor = visitor;
63+
classVisitor.visit(WriterConstants.CLASS_VERSION, classAccess, className, null,
64+
Type.getType(baseClass).getInternalName(), classInterfaces);
65+
}
66+
67+
public ClassVisitor getClassVisitor() {
68+
return classVisitor;
69+
}
70+
71+
/**
72+
* Lazy loads the {@link MethodWriter} for clinit, so that if it's not
73+
* necessary the method is never created for the class.
74+
*/
75+
public MethodWriter getClinitWriter() {
76+
if (clinitWriter == null) {
77+
clinitWriter = new MethodWriter(Opcodes.ACC_STATIC, WriterConstants.CLINIT, classVisitor, statements, compilerSettings);
78+
clinitWriter.visitCode();
79+
}
80+
81+
return clinitWriter;
82+
}
83+
84+
public MethodWriter newMethodWriter(int access, Method method) {
85+
return new MethodWriter(access, method, classVisitor, statements, compilerSettings);
86+
}
87+
88+
@Override
89+
public void close() {
90+
if (clinitWriter != null) {
91+
clinitWriter.returnValue();
92+
clinitWriter.endMethod();
93+
}
94+
95+
classVisitor.visitEnd();
96+
}
97+
98+
public byte[] getClassBytes() {
99+
return classWriter.toByteArray();
100+
}
101+
}

modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessLexer.java

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
// ANTLR GENERATED CODE: DO NOT EDIT
22
package org.elasticsearch.painless.antlr;
3-
import org.antlr.v4.runtime.Lexer;
3+
44
import org.antlr.v4.runtime.CharStream;
5-
import org.antlr.v4.runtime.Token;
6-
import org.antlr.v4.runtime.TokenStream;
7-
import org.antlr.v4.runtime.*;
8-
import org.antlr.v4.runtime.atn.*;
5+
import org.antlr.v4.runtime.Lexer;
6+
import org.antlr.v4.runtime.RuleContext;
7+
import org.antlr.v4.runtime.RuntimeMetaData;
8+
import org.antlr.v4.runtime.Vocabulary;
9+
import org.antlr.v4.runtime.VocabularyImpl;
10+
import org.antlr.v4.runtime.atn.ATN;
11+
import org.antlr.v4.runtime.atn.ATNDeserializer;
12+
import org.antlr.v4.runtime.atn.LexerATNSimulator;
13+
import org.antlr.v4.runtime.atn.PredictionContextCache;
914
import org.antlr.v4.runtime.dfa.DFA;
10-
import org.antlr.v4.runtime.misc.*;
1115

1216
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
1317
abstract class PainlessLexer extends Lexer {

modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/PainlessParser.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
// ANTLR GENERATED CODE: DO NOT EDIT
22
package org.elasticsearch.painless.antlr;
3-
import org.antlr.v4.runtime.atn.*;
3+
4+
import org.antlr.v4.runtime.FailedPredicateException;
5+
import org.antlr.v4.runtime.NoViableAltException;
6+
import org.antlr.v4.runtime.Parser;
7+
import org.antlr.v4.runtime.ParserRuleContext;
8+
import org.antlr.v4.runtime.RecognitionException;
9+
import org.antlr.v4.runtime.RuleContext;
10+
import org.antlr.v4.runtime.RuntimeMetaData;
11+
import org.antlr.v4.runtime.TokenStream;
12+
import org.antlr.v4.runtime.Vocabulary;
13+
import org.antlr.v4.runtime.VocabularyImpl;
14+
import org.antlr.v4.runtime.atn.ATN;
15+
import org.antlr.v4.runtime.atn.ATNDeserializer;
16+
import org.antlr.v4.runtime.atn.ParserATNSimulator;
17+
import org.antlr.v4.runtime.atn.PredictionContextCache;
418
import org.antlr.v4.runtime.dfa.DFA;
5-
import org.antlr.v4.runtime.*;
6-
import org.antlr.v4.runtime.misc.*;
7-
import org.antlr.v4.runtime.tree.*;
19+
import org.antlr.v4.runtime.tree.ParseTreeVisitor;
20+
import org.antlr.v4.runtime.tree.TerminalNode;
21+
822
import java.util.List;
9-
import java.util.Iterator;
10-
import java.util.ArrayList;
1123

1224
@SuppressWarnings({"all", "warnings", "unchecked", "unused", "cast"})
1325
class PainlessParser extends Parser {

modules/lang-painless/src/main/java/org/elasticsearch/painless/antlr/Walker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@
141141
import org.elasticsearch.painless.node.SBlock;
142142
import org.elasticsearch.painless.node.SBreak;
143143
import org.elasticsearch.painless.node.SCatch;
144+
import org.elasticsearch.painless.node.SClass;
144145
import org.elasticsearch.painless.node.SContinue;
145146
import org.elasticsearch.painless.node.SDeclBlock;
146147
import org.elasticsearch.painless.node.SDeclaration;
@@ -152,7 +153,6 @@
152153
import org.elasticsearch.painless.node.SIf;
153154
import org.elasticsearch.painless.node.SIfElse;
154155
import org.elasticsearch.painless.node.SReturn;
155-
import org.elasticsearch.painless.node.SClass;
156156
import org.elasticsearch.painless.node.SThrow;
157157
import org.elasticsearch.painless.node.STry;
158158
import org.elasticsearch.painless.node.SWhile;

modules/lang-painless/src/main/java/org/elasticsearch/painless/node/ANode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.painless.node;
2121

22+
import org.elasticsearch.painless.ClassWriter;
2223
import org.elasticsearch.painless.CompilerSettings;
2324
import org.elasticsearch.painless.Globals;
2425
import org.elasticsearch.painless.Locals;
@@ -75,7 +76,7 @@ public abstract class ANode {
7576
/**
7677
* Writes ASM based on the data collected during the analysis phase.
7778
*/
78-
abstract void write(MethodWriter writer, Globals globals);
79+
abstract void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals);
7980

8081
/**
8182
* Create an error with location information pointing to this node.

modules/lang-painless/src/main/java/org/elasticsearch/painless/node/AStoreable.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.painless.node;
2121

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

9697
/**
9798
* Called to load a storable used for compound assignments.
9899
*/
99-
abstract void load(MethodWriter writer, Globals globals);
100+
abstract void load(ClassWriter classWriter, MethodWriter writer, Globals globals);
100101

101102
/**
102103
* Called to store a storabable to local memory.
103104
*/
104-
abstract void store(MethodWriter writer, Globals globals);
105+
abstract void store(ClassWriter classWriter, MethodWriter writer, Globals globals);
105106

106107
/**
107108
* 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

modules/lang-painless/src/main/java/org/elasticsearch/painless/node/EAssignment.java

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222

2323
import org.elasticsearch.painless.AnalyzerCaster;
24+
import org.elasticsearch.painless.ClassWriter;
2425
import org.elasticsearch.painless.CompilerSettings;
2526
import org.elasticsearch.painless.DefBootstrap;
2627
import org.elasticsearch.painless.Globals;
@@ -250,8 +251,8 @@ private void analyzeSimple(Locals locals) {
250251
* also read from.
251252
*/
252253
@Override
253-
void write(MethodWriter writer, Globals globals) {
254-
writer.writeDebugInfo(location);
254+
void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
255+
methodWriter.writeDebugInfo(location);
255256

256257
// For the case where the assignment represents a String concatenation
257258
// we must, depending on the Java version, write a StringBuilder or
@@ -261,84 +262,87 @@ void write(MethodWriter writer, Globals globals) {
261262
int catElementStackSize = 0;
262263

263264
if (cat) {
264-
catElementStackSize = writer.writeNewStrings();
265+
catElementStackSize = methodWriter.writeNewStrings();
265266
}
266267

267268
// Cast the lhs to a storeable to perform the necessary operations to store the rhs.
268269
AStoreable lhs = (AStoreable)this.lhs;
269-
lhs.setup(writer, globals); // call the setup method on the lhs to prepare for a load/store operation
270+
lhs.setup(classWriter, methodWriter, globals); // call the setup method on the lhs to prepare for a load/store operation
270271

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

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

280-
rhs.write(writer, globals); // write the bytecode for the rhs
281+
rhs.write(classWriter, methodWriter, globals); // write the bytecode for the rhs
281282

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

286-
writer.writeToStrings(); // put the value for string concat onto the stack
287-
writer.writeCast(back); // if necessary, cast the String to the lhs actual type
287+
methodWriter.writeToStrings(); // put the value for string concat onto the stack
288+
methodWriter.writeCast(back); // if necessary, cast the String to the lhs actual type
288289

289290
if (lhs.read) {
290-
writer.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // if this lhs is also read
291+
methodWriter.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // if this lhs is also read
291292
// from dup the value onto the stack
292293
}
293294

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

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

303304
if (lhs.read && post) {
304-
writer.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // dup the value if the lhs is also
305-
// read from and is a post increment
305+
methodWriter.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // dup the value if the
306+
// lhs is also
307+
// read from and is a post
308+
// increment
306309
}
307310

308-
writer.writeCast(there); // if necessary cast the current lhs's value
309-
// to the promotion type between the lhs and rhs types
310-
rhs.write(writer, globals); // write the bytecode for the rhs
311+
methodWriter.writeCast(there); // if necessary cast the current lhs's value
312+
// to the promotion type between the lhs and rhs types
313+
rhs.write(classWriter, methodWriter, globals); // write the bytecode for the rhs
311314

312315
// XXX: fix these types, but first we need def compound assignment tests.
313316
// its tricky here as there are possibly explicit casts, too.
314317
// write the operation instruction for compound assignment
315318
if (promote == def.class) {
316-
writer.writeDynamicBinaryInstruction(
319+
methodWriter.writeDynamicBinaryInstruction(
317320
location, promote, def.class, def.class, operation, DefBootstrap.OPERATOR_COMPOUND_ASSIGNMENT);
318321
} else {
319-
writer.writeBinaryInstruction(location, promote, operation);
322+
methodWriter.writeBinaryInstruction(location, promote, operation);
320323
}
321324

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

324327
if (lhs.read && !post) {
325-
writer.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // dup the value if the lhs is also
326-
// read from and is not a post
327-
// increment
328+
methodWriter.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // dup the value if the lhs
329+
// is also
330+
// read from and is not a post
331+
// increment
328332
}
329333

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

334-
rhs.write(writer, globals); // write the bytecode for the rhs rhs
338+
rhs.write(classWriter, methodWriter, globals); // write the bytecode for the rhs rhs
335339

336340
if (lhs.read) {
337-
writer.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // dup the value if the lhs
341+
methodWriter.writeDup(MethodWriter.getType(lhs.actual).getSize(), lhs.accessElementCount()); // dup the value if the lhs
338342
// is also read from
339343
}
340344

341-
lhs.store(writer, globals); // store the lhs's value from the stack in its respective variable/field/array
345+
lhs.store(classWriter, methodWriter, globals); // store the lhs's value from the stack in its respective variable/field/array
342346
}
343347
}
344348

0 commit comments

Comments
 (0)