Move binding member field generation to Painless semantic pass (#47739)

This adds an SField node that operates similarly to SFunction as a top level 
node meant only for use in an SClass node. Member fields are generated 
for both class bindings and instance bindings using the new SField node 
during the semantic pass, and information is no longer passed through 
Globals for this during the write pass.
This commit is contained in:
Jack Conradson 2019-10-09 10:24:19 -07:00
parent 8f86469d3f
commit 076d3073b5
4 changed files with 124 additions and 47 deletions

View File

@ -28,8 +28,6 @@ import java.util.Map;
*/
public class Globals {
private final Map<String,Constant> constantInitializers = new HashMap<>();
private final Map<String,Class<?>> classBindings = new HashMap<>();
private final Map<Object,String> instanceBindings = new HashMap<>();
private final BitSet statements;
/** Create a new Globals from the set of statement boundaries */
@ -44,34 +42,11 @@ public class Globals {
}
}
/** Adds a new class binding to be written as a local variable */
public String addClassBinding(Class<?> type) {
String name = "$class_binding$" + classBindings.size();
classBindings.put(name, type);
return name;
}
/** Adds a new binding to be written as a local variable */
public String addInstanceBinding(Object instance) {
return instanceBindings.computeIfAbsent(instance, key -> "$instance_binding$" + instanceBindings.size());
}
/** Returns the current initializers */
public Map<String,Constant> getConstantInitializers() {
return constantInitializers;
}
/** Returns the current bindings */
public Map<String,Class<?>> getClassBindings() {
return classBindings;
}
/** Returns the current bindings */
public Map<Object,String> getInstanceBindings() {
return instanceBindings;
}
/** Returns the set of statement boundaries */
public BitSet getStatements() {
return statements;

View File

@ -40,6 +40,9 @@ import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.painless.WriterConstants.CLASS_TYPE;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
/**
* Represents a user-defined call.
@ -54,6 +57,7 @@ public final class ECallLocal extends AExpression {
private PainlessClassBinding classBinding = null;
private int classBindingOffset = 0;
private PainlessInstanceBinding instanceBinding = null;
private String bindingName = null;
public ECallLocal(Location location, String name, List<AExpression> arguments) {
super(location);
@ -138,9 +142,15 @@ public final class ECallLocal extends AExpression {
} else if (classBinding != null) {
typeParameters = new ArrayList<>(classBinding.typeParameters);
actual = classBinding.returnType;
bindingName = scriptRoot.getNextSyntheticName("class_binding");
scriptRoot.getClassNode().addField(new SField(location,
ACC_PRIVATE, bindingName, classBinding.javaConstructor.getDeclaringClass(), null));
} else if (instanceBinding != null) {
typeParameters = new ArrayList<>(instanceBinding.typeParameters);
actual = instanceBinding.returnType;
bindingName = scriptRoot.getNextSyntheticName("instance_binding");
scriptRoot.getClassNode().addField(new SField(location,
ACC_STATIC | ACC_PUBLIC, bindingName, instanceBinding.targetInstance.getClass(), instanceBinding.targetInstance));
} else {
throw new IllegalStateException("Illegal tree structure.");
}
@ -178,14 +188,13 @@ public final class ECallLocal extends AExpression {
methodWriter.invokeStatic(Type.getType(importedMethod.targetClass),
new Method(importedMethod.javaMethod.getName(), importedMethod.methodType.toMethodDescriptorString()));
} else if (classBinding != null) {
String name = globals.addClassBinding(classBinding.javaConstructor.getDeclaringClass());
Type type = Type.getType(classBinding.javaConstructor.getDeclaringClass());
int javaConstructorParameterCount = classBinding.javaConstructor.getParameterCount() - classBindingOffset;
Label nonNull = new Label();
methodWriter.loadThis();
methodWriter.getField(CLASS_TYPE, name, type);
methodWriter.getField(CLASS_TYPE, bindingName, type);
methodWriter.ifNonNull(nonNull);
methodWriter.loadThis();
methodWriter.newInstance(type);
@ -200,11 +209,11 @@ public final class ECallLocal extends AExpression {
}
methodWriter.invokeConstructor(type, Method.getMethod(classBinding.javaConstructor));
methodWriter.putField(CLASS_TYPE, name, type);
methodWriter.putField(CLASS_TYPE, bindingName, type);
methodWriter.mark(nonNull);
methodWriter.loadThis();
methodWriter.getField(CLASS_TYPE, name, type);
methodWriter.getField(CLASS_TYPE, bindingName, type);
for (int argument = 0; argument < classBinding.javaMethod.getParameterCount(); ++argument) {
arguments.get(argument + javaConstructorParameterCount).write(classWriter, methodWriter, globals);
@ -212,11 +221,10 @@ public final class ECallLocal extends AExpression {
methodWriter.invokeVirtual(type, Method.getMethod(classBinding.javaMethod));
} else if (instanceBinding != null) {
String name = globals.addInstanceBinding(instanceBinding.targetInstance);
Type type = Type.getType(instanceBinding.targetInstance.getClass());
methodWriter.loadThis();
methodWriter.getStatic(CLASS_TYPE, name, type);
methodWriter.getStatic(CLASS_TYPE, bindingName, type);
for (int argument = 0; argument < instanceBinding.javaMethod.getParameterCount(); ++argument) {
arguments.get(argument).write(classWriter, methodWriter, globals);

View File

@ -83,6 +83,7 @@ public final class SClass extends AStatement {
private final String name;
private final Printer debugStream;
private final List<SFunction> functions = new ArrayList<>();
private final List<SField> fields = new ArrayList<>();
private final Globals globals;
private final List<AStatement> statements;
@ -112,6 +113,10 @@ public final class SClass extends AStatement {
functions.add(function);
}
void addField(SField field) {
fields.add(field);
}
@Override
public void storeSettings(CompilerSettings settings) {
for (SFunction function : functions) {
@ -289,6 +294,11 @@ public final class SClass extends AStatement {
function.write(classWriter, globals);
}
// Write all fields:
for (SField field : fields) {
field.write(classWriter);
}
// Write the constants
if (false == globals.getConstantInitializers().isEmpty()) {
Collection<Constant> inits = globals.getConstantInitializers().values();
@ -315,20 +325,6 @@ public final class SClass extends AStatement {
clinit.endMethod();
}
// Write class binding variables
for (Map.Entry<String, Class<?>> classBinding : globals.getClassBindings().entrySet()) {
String name = classBinding.getKey();
String descriptor = Type.getType(classBinding.getValue()).getDescriptor();
classVisitor.visitField(Opcodes.ACC_PRIVATE, name, descriptor, null, null).visitEnd();
}
// Write instance binding variables
for (Map.Entry<Object, String> instanceBinding : globals.getInstanceBindings().entrySet()) {
String name = instanceBinding.getValue();
String descriptor = Type.getType(instanceBinding.getKey().getClass()).getDescriptor();
classVisitor.visitField(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, name, descriptor, null, null).visitEnd();
}
// Write any needsVarName methods for used variables
for (org.objectweb.asm.commons.Method needsMethod : scriptClassInfo.getNeedsMethods()) {
String name = needsMethod.getName();
@ -349,8 +345,10 @@ public final class SClass extends AStatement {
Map<String, Object> statics = new HashMap<>();
statics.put("$FUNCTIONS", table.getFunctionTable());
for (Map.Entry<Object, String> instanceBinding : globals.getInstanceBindings().entrySet()) {
statics.put(instanceBinding.getValue(), instanceBinding.getKey());
for (SField field : fields) {
if (field.getInstance() != null) {
statics.put(field.getName(), field.getInstance());
}
}
return statics;

View File

@ -0,0 +1,96 @@
/*
* 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;
import org.elasticsearch.painless.ClassWriter;
import org.elasticsearch.painless.CompilerSettings;
import org.elasticsearch.painless.Globals;
import org.elasticsearch.painless.Locals;
import org.elasticsearch.painless.Location;
import org.elasticsearch.painless.MethodWriter;
import org.elasticsearch.painless.ScriptRoot;
import org.objectweb.asm.Type;
import java.util.Set;
/**
* Represents a member field for its parent class (internal only).
*/
public class SField extends ANode {
private final int access;
private final String name;
private final Class<?> type;
private final Object instance;
/**
* Standard constructor.
* @param location original location in the source
* @param access asm constants for field modifiers
* @param name name of the field
* @param type type of the field
* @param instance initial value for the field
*/
public SField(Location location, int access, String name, Class<?> type, Object instance) {
super(location);
this.access = access;
this.name = name;
this.type = type;
this.instance = instance;
}
public String getName() {
return name;
}
public Object getInstance() {
return instance;
}
@Override
void storeSettings(CompilerSettings settings) {
throw createError(new UnsupportedOperationException("unexpected node"));
}
@Override
void extractVariables(Set<String> variables) {
throw createError(new UnsupportedOperationException("unexpected node"));
}
@Override
void analyze(ScriptRoot scriptRoot, Locals locals) {
throw createError(new UnsupportedOperationException("unexpected node"));
}
@Override
void write(ClassWriter classWriter, MethodWriter methodWriter, Globals globals) {
throw createError(new UnsupportedOperationException("unexpected node"));
}
void write(ClassWriter classWriter) {
classWriter.getClassVisitor().visitField(access, name, Type.getType(type).getDescriptor(), null, null).visitEnd();
}
@Override
public String toString() {
return singleLineToString(name, type);
}
}