Skip to content

Commit 303aaf4

Browse files
authored
Continue removal of custom script code in ClassNode in Painless (#52495)
This continues down the path of removing custom code required for Elasticsearch Painless scripts from the user and ir nodes. This change uses ir nodes to inject gets methods, needs methods, and the bootstrap method as required. There is no reason for the SClass/ClassNode to know about these specific items. Though, these appear as separate phases for now, this is purely transitional. Eventually, these will be rolled into a single "build" phase where the user tree is semantically checked and converted into an ir tree. This intermediate step is necessary to make the changes occur in smaller consumable steps.
1 parent dc070a5 commit 303aaf4

15 files changed

+490
-221
lines changed

modules/lang-painless/src/main/java/org/elasticsearch/painless/Compiler.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -209,20 +209,18 @@ private static void addFactoryMethod(Map<String, Class<?>> additionalClasses, Cl
209209
ScriptRoot compile(Loader loader, String name, String source, CompilerSettings settings) {
210210
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
211211
SClass root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, painlessLookup, null);
212-
ScriptRoot scriptRoot = root.analyze(painlessLookup, settings);
212+
ScriptRoot scriptRoot = new ScriptRoot(painlessLookup, settings, scriptClassInfo, root);
213+
root.analyze(scriptRoot);
213214
ClassNode classNode = root.writeClass();
215+
DefBootstrapInjectionPhase.phase(classNode);
214216
ScriptInjectionPhase.phase(scriptRoot, classNode);
215-
Map<String, Object> statics = classNode.write();
217+
byte[] bytes = classNode.write();
216218

217219
try {
218-
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, classNode.getBytes());
219-
clazz.getField("$NAME").set(null, name);
220-
clazz.getField("$SOURCE").set(null, source);
221-
clazz.getField("$STATEMENTS").set(null, classNode.getStatements());
222-
clazz.getField("$DEFINITION").set(null, painlessLookup);
223-
224-
for (Map.Entry<String, Object> statik : statics.entrySet()) {
225-
clazz.getField(statik.getKey()).set(null, statik.getValue());
220+
Class<? extends PainlessScript> clazz = loader.defineScript(CLASS_NAME, bytes);
221+
222+
for (Map.Entry<String, Object> staticConstant : scriptRoot.getStaticConstants().entrySet()) {
223+
clazz.getField(staticConstant.getKey()).set(null, staticConstant.getValue());
226224
}
227225

228226
return scriptRoot;
@@ -241,11 +239,12 @@ ScriptRoot compile(Loader loader, String name, String source, CompilerSettings s
241239
byte[] compile(String name, String source, CompilerSettings settings, Printer debugStream) {
242240
ScriptClassInfo scriptClassInfo = new ScriptClassInfo(painlessLookup, scriptClass);
243241
SClass root = Walker.buildPainlessTree(scriptClassInfo, name, source, settings, painlessLookup, debugStream);
244-
ScriptRoot scriptRoot = root.analyze(painlessLookup, settings);
242+
ScriptRoot scriptRoot = new ScriptRoot(painlessLookup, settings, scriptClassInfo, root);
243+
root.analyze(scriptRoot);
245244
ClassNode classNode = root.writeClass();
245+
DefBootstrapInjectionPhase.phase(classNode);
246246
ScriptInjectionPhase.phase(scriptRoot, classNode);
247-
classNode.write();
248247

249-
return classNode.getBytes();
248+
return classNode.write();
250249
}
251250
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package org.elasticsearch.painless;/*
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+
import org.elasticsearch.painless.ir.BlockNode;
21+
import org.elasticsearch.painless.ir.CallNode;
22+
import org.elasticsearch.painless.ir.CallSubNode;
23+
import org.elasticsearch.painless.ir.ClassNode;
24+
import org.elasticsearch.painless.ir.FieldNode;
25+
import org.elasticsearch.painless.ir.FunctionNode;
26+
import org.elasticsearch.painless.ir.MemberFieldNode;
27+
import org.elasticsearch.painless.ir.ReturnNode;
28+
import org.elasticsearch.painless.ir.StaticNode;
29+
import org.elasticsearch.painless.ir.VariableNode;
30+
import org.elasticsearch.painless.lookup.PainlessLookup;
31+
import org.elasticsearch.painless.lookup.PainlessMethod;
32+
import org.elasticsearch.painless.symbol.FunctionTable;
33+
import org.objectweb.asm.Opcodes;
34+
35+
import java.lang.invoke.CallSite;
36+
import java.lang.invoke.MethodHandles.Lookup;
37+
import java.lang.invoke.MethodType;
38+
import java.util.Arrays;
39+
40+
/**
41+
* This injects additional ir nodes required for
42+
* resolving the def type at runtime. This includes injection
43+
* of ir nodes to add a function to call
44+
* {@link DefBootstrap#bootstrap(PainlessLookup, FunctionTable, Lookup, String, MethodType, int, int, Object...)}
45+
* to do the runtime resolution.
46+
*/
47+
public class DefBootstrapInjectionPhase {
48+
49+
public static void phase(ClassNode classNode) {
50+
injectStaticFields(classNode);
51+
injectDefBootstrapMethod(classNode);
52+
}
53+
54+
// adds static fields required for def bootstrapping
55+
protected static void injectStaticFields(ClassNode classNode) {
56+
Location internalLocation = new Location("$internal$DefBootstrapInjectionPhase$injectStaticFields", 0);
57+
int modifiers = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
58+
59+
FieldNode fieldNode = new FieldNode();
60+
fieldNode.setLocation(internalLocation);
61+
fieldNode.setModifiers(modifiers);
62+
fieldNode.setFieldType(PainlessLookup.class);
63+
fieldNode.setName("$DEFINITION");
64+
65+
classNode.addFieldNode(fieldNode);
66+
67+
fieldNode = new FieldNode();
68+
fieldNode.setLocation(internalLocation);
69+
fieldNode.setModifiers(modifiers);
70+
fieldNode.setFieldType(FunctionTable.class);
71+
fieldNode.setName("$FUNCTIONS");
72+
73+
classNode.addFieldNode(fieldNode);
74+
}
75+
76+
// adds the bootstrap method required for dynamic binding for def type resolution
77+
protected static void injectDefBootstrapMethod(ClassNode classNode) {
78+
Location internalLocation = new Location("$internal$DefBootstrapInjectionPhase$injectDefBootstrapMethod", 0);
79+
80+
try {
81+
FunctionNode functionNode = new FunctionNode();
82+
functionNode.setLocation(internalLocation);
83+
functionNode.setReturnType(CallSite.class);
84+
functionNode.setName("$bootstrapDef");
85+
functionNode.getTypeParameters().addAll(
86+
Arrays.asList(Lookup.class, String.class, MethodType.class, int.class, int.class, Object[].class));
87+
functionNode.getParameterNames().addAll(
88+
Arrays.asList("methodHandlesLookup", "name", "type", "initialDepth", "flavor", "args"));
89+
functionNode.setStatic(true);
90+
functionNode.setVarArgs(true);
91+
functionNode.setSynthetic(true);
92+
functionNode.setMaxLoopCounter(0);
93+
94+
classNode.addFunctionNode(functionNode);
95+
96+
BlockNode blockNode = new BlockNode();
97+
blockNode.setLocation(internalLocation);
98+
blockNode.setAllEscape(true);
99+
blockNode.setStatementCount(1);
100+
101+
functionNode.setBlockNode(blockNode);
102+
103+
ReturnNode returnNode = new ReturnNode();
104+
returnNode.setLocation(internalLocation);
105+
106+
blockNode.addStatementNode(returnNode);
107+
108+
CallNode callNode = new CallNode();
109+
callNode.setLocation(internalLocation);
110+
callNode.setExpressionType(CallSite.class);
111+
112+
returnNode.setExpressionNode(callNode);
113+
114+
StaticNode staticNode = new StaticNode();
115+
staticNode.setLocation(internalLocation);
116+
staticNode.setExpressionType(DefBootstrap.class);
117+
118+
callNode.setLeftNode(staticNode);
119+
120+
CallSubNode callSubNode = new CallSubNode();
121+
callSubNode.setLocation(internalLocation);
122+
callSubNode.setExpressionType(CallSite.class);
123+
callSubNode.setMethod(new PainlessMethod(
124+
DefBootstrap.class.getMethod("bootstrap",
125+
PainlessLookup.class,
126+
FunctionTable.class,
127+
Lookup.class,
128+
String.class,
129+
MethodType.class,
130+
int.class,
131+
int.class,
132+
Object[].class),
133+
DefBootstrap.class,
134+
CallSite.class,
135+
Arrays.asList(
136+
PainlessLookup.class,
137+
FunctionTable.class,
138+
Lookup.class,
139+
String.class,
140+
MethodType.class,
141+
int.class,
142+
int.class,
143+
Object[].class),
144+
null,
145+
null,
146+
null
147+
)
148+
);
149+
callSubNode.setBox(DefBootstrap.class);
150+
151+
callNode.setRightNode(callSubNode);
152+
153+
MemberFieldNode memberFieldNode = new MemberFieldNode();
154+
memberFieldNode.setLocation(internalLocation);
155+
memberFieldNode.setExpressionType(PainlessLookup.class);
156+
memberFieldNode.setName("$DEFINITION");
157+
memberFieldNode.setStatic(true);
158+
159+
callSubNode.addArgumentNode(memberFieldNode);
160+
161+
memberFieldNode = new MemberFieldNode();
162+
memberFieldNode.setLocation(internalLocation);
163+
memberFieldNode.setExpressionType(FunctionTable.class);
164+
memberFieldNode.setName("$FUNCTIONS");
165+
memberFieldNode.setStatic(true);
166+
167+
callSubNode.addArgumentNode(memberFieldNode);
168+
169+
VariableNode variableNode = new VariableNode();
170+
variableNode.setLocation(internalLocation);
171+
variableNode.setExpressionType(Lookup.class);
172+
variableNode.setName("methodHandlesLookup");
173+
174+
callSubNode.addArgumentNode(variableNode);
175+
176+
variableNode = new VariableNode();
177+
variableNode.setLocation(internalLocation);
178+
variableNode.setExpressionType(String.class);
179+
variableNode.setName("name");
180+
181+
callSubNode.addArgumentNode(variableNode);
182+
183+
variableNode = new VariableNode();
184+
variableNode.setLocation(internalLocation);
185+
variableNode.setExpressionType(MethodType.class);
186+
variableNode.setName("type");
187+
188+
callSubNode.addArgumentNode(variableNode);
189+
190+
variableNode = new VariableNode();
191+
variableNode.setLocation(internalLocation);
192+
variableNode.setExpressionType(int.class);
193+
variableNode.setName("initialDepth");
194+
195+
callSubNode.addArgumentNode(variableNode);
196+
197+
variableNode = new VariableNode();
198+
variableNode.setLocation(internalLocation);
199+
variableNode.setExpressionType(int.class);
200+
variableNode.setName("flavor");
201+
202+
callSubNode.addArgumentNode(variableNode);
203+
204+
variableNode = new VariableNode();
205+
variableNode.setLocation(internalLocation);
206+
variableNode.setExpressionType(Object[].class);
207+
variableNode.setName("args");
208+
209+
callSubNode.addArgumentNode(variableNode);
210+
} catch (Exception exception) {
211+
throw new RuntimeException(exception);
212+
}
213+
}
214+
215+
private DefBootstrapInjectionPhase() {
216+
// do nothing
217+
}
218+
}

0 commit comments

Comments
 (0)