/*
* 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.elasticsearch.painless.Definition.Cast;
import org.elasticsearch.painless.Definition.Sort;
import org.elasticsearch.painless.Definition.Type;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Deque;
import java.util.List;
import static org.elasticsearch.painless.WriterConstants.CHAR_TO_STRING;
import static org.elasticsearch.painless.WriterConstants.DEF_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_BOOLEAN;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_BYTE_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_BYTE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_CHAR_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_CHAR_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_DOUBLE_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_DOUBLE_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_FLOAT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_FLOAT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_INT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_INT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_LONG_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_LONG_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_EXPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_TO_SHORT_IMPLICIT;
import static org.elasticsearch.painless.WriterConstants.DEF_UTIL_TYPE;
import static org.elasticsearch.painless.WriterConstants.INDY_STRING_CONCAT_BOOTSTRAP_HANDLE;
import static org.elasticsearch.painless.WriterConstants.MAX_INDY_STRING_CONCAT_ARGS;
import static org.elasticsearch.painless.WriterConstants.PAINLESS_ERROR_TYPE;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_BOOLEAN;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_CHAR;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_DOUBLE;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_FLOAT;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_INT;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_LONG;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_OBJECT;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_APPEND_STRING;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_CONSTRUCTOR;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TOSTRING;
import static org.elasticsearch.painless.WriterConstants.STRINGBUILDER_TYPE;
import static org.elasticsearch.painless.WriterConstants.STRING_TO_CHAR;
import static org.elasticsearch.painless.WriterConstants.STRING_TYPE;
import static org.elasticsearch.painless.WriterConstants.UTILITY_TYPE;
/**
* Extension of {@link GeneratorAdapter} with some utility methods.
* <p>
* Set of methods used during the writing phase of compilation
* shared by the nodes of the Painless tree.
*/
public final class MethodWriter extends GeneratorAdapter {
private final BitSet statements;
private final CompilerSettings settings;
private final Deque<List<org.objectweb.asm.Type>> stringConcatArgs =
(INDY_STRING_CONCAT_BOOTSTRAP_HANDLE == null) ? null : new ArrayDeque<>();
public MethodWriter(int access, Method method, ClassVisitor cw, BitSet statements, CompilerSettings settings) {
super(Opcodes.ASM5, cw.visitMethod(access, method.getName(), method.getDescriptor(), null, null),
access, method.getName(), method.getDescriptor());
this.statements = statements;
this.settings = settings;
}
/**
* Marks a new statement boundary.
* <p>
* This is invoked for each statement boundary (leaf {@code S*} nodes).
*/
public void writeStatementOffset(Location location) {
int offset = location.getOffset();
// ensure we don't have duplicate stuff going in here. can catch bugs
// (e.g. nodes get assigned wrong offsets by antlr walker)
assert statements.get(offset) == false;
statements.set(offset);
}
/**
* Encodes the offset into the line number table as {@code offset + 1}.
* <p>
* This is invoked before instructions that can hit exceptions.
*/
public void writeDebugInfo(Location location) {
// TODO: maybe track these in bitsets too? this is trickier...
Label label = new Label();
visitLabel(label);
visitLineNumber(location.getOffset() + 1, label);
}
public void writeLoopCounter(int slot, int count, Location location) {
assert slot != -1;
writeDebugInfo(location);
final Label end = new Label();
iinc(slot, -count);
visitVarInsn(Opcodes.ILOAD, slot);
push(0);
ifICmp(GeneratorAdapter.GT, end);
throwException(PAINLESS_ERROR_TYPE, "The maximum number of statements that can be executed in a loop has been reached.");
mark(end);
}
public void writeCast(final Cast cast) {
if (cast != null) {
if (cast.from.sort == Sort.CHAR && cast.to.sort == Sort.STRING) {
invokeStatic(UTILITY_TYPE, CHAR_TO_STRING);
} else if (cast.from.sort == Sort.STRING && cast.to.sort == Sort.CHAR) {
invokeStatic(UTILITY_TYPE, STRING_TO_CHAR);
} else if (cast.unboxFrom != null) {
unbox(cast.unboxFrom.type);
writeCast(cast.from, cast.to);
} else if (cast.unboxTo != null) {
if (cast.from.sort == Sort.DEF) {
if (cast.explicit) {
if (cast.to.sort == Sort.BOOL_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.to.sort == Sort.BYTE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_EXPLICIT);
else if (cast.to.sort == Sort.SHORT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_EXPLICIT);
else if (cast.to.sort == Sort.CHAR_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_EXPLICIT);
else if (cast.to.sort == Sort.INT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_EXPLICIT);
else if (cast.to.sort == Sort.LONG_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_EXPLICIT);
else if (cast.to.sort == Sort.FLOAT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_EXPLICIT);
else if (cast.to.sort == Sort.DOUBLE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_EXPLICIT);
else throw new IllegalStateException("Illegal tree structure.");
} else {
if (cast.to.sort == Sort.BOOL_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BOOLEAN);
else if (cast.to.sort == Sort.BYTE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_BYTE_IMPLICIT);
else if (cast.to.sort == Sort.SHORT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_SHORT_IMPLICIT);
else if (cast.to.sort == Sort.CHAR_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_CHAR_IMPLICIT);
else if (cast.to.sort == Sort.INT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_INT_IMPLICIT);
else if (cast.to.sort == Sort.LONG_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_LONG_IMPLICIT);
else if (cast.to.sort == Sort.FLOAT_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_FLOAT_IMPLICIT);
else if (cast.to.sort == Sort.DOUBLE_OBJ) invokeStatic(DEF_UTIL_TYPE, DEF_TO_DOUBLE_IMPLICIT);
else throw new IllegalStateException("Illegal tree structure.");
}
} else {
writeCast(cast.from, cast.to);
unbox(cast.unboxTo.type);
}
} else if (cast.boxFrom != null) {
box(cast.boxFrom.type);
writeCast(cast.from, cast.to);
} else if (cast.boxTo != null) {
writeCast(cast.from, cast.to);
box(cast.boxTo.type);
} else {
writeCast(cast.from, cast.to);
}
}
}
private void writeCast(final Type from, final Type to) {
if (from.equals(to)) {
return;
}
if (from.sort.numeric && from.sort.primitive && to.sort.numeric && to.sort.primitive) {
cast(from.type, to.type);
} else {
if (!to.clazz.isAssignableFrom(from.clazz)) {
checkCast(to.type);
}
}
}
/**
* Proxy the box method to use valueOf instead to ensure that the modern boxing methods are used.
*/
@Override
public void box(org.objectweb.asm.Type type) {
valueOf(type);
}
public void writeBranch(final Label tru, final Label fals) {
if (tru != null) {
visitJumpInsn(Opcodes.IFNE, tru);
} else if (fals != null) {
visitJumpInsn(Opcodes.IFEQ, fals);
}
}
/** Starts a new string concat.
* @return the size of arguments pushed to stack (the object that does string concats, e.g. a StringBuilder)
*/
public int writeNewStrings() {
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
// Java 9+: we just push our argument collector onto deque
stringConcatArgs.push(new ArrayList<>());
return 0; // nothing added to stack
} else {
// Java 8: create a StringBuilder in bytecode
newInstance(STRINGBUILDER_TYPE);
dup();
invokeConstructor(STRINGBUILDER_TYPE, STRINGBUILDER_CONSTRUCTOR);
return 1; // StringBuilder on stack
}
}
public void writeAppendStrings(final Type type) {
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
// Java 9+: record type information
stringConcatArgs.peek().add(type.type);
// prevent too many concat args.
// If there are too many, do the actual concat:
if (stringConcatArgs.peek().size() >= MAX_INDY_STRING_CONCAT_ARGS) {
writeToStrings();
writeNewStrings();
// add the return value type as new first param for next concat:
stringConcatArgs.peek().add(STRING_TYPE);
}
} else {
// Java 8: push a StringBuilder append
switch (type.sort) {
case BOOL: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_BOOLEAN); break;
case CHAR: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_CHAR); break;
case BYTE:
case SHORT:
case INT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_INT); break;
case LONG: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_LONG); break;
case FLOAT: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_FLOAT); break;
case DOUBLE: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_DOUBLE); break;
case STRING: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_STRING); break;
default: invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_APPEND_OBJECT);
}
}
}
public void writeToStrings() {
if (INDY_STRING_CONCAT_BOOTSTRAP_HANDLE != null) {
// Java 9+: use type information and push invokeDynamic
final String desc = org.objectweb.asm.Type.getMethodDescriptor(STRING_TYPE,
stringConcatArgs.pop().stream().toArray(org.objectweb.asm.Type[]::new));
invokeDynamic("concat", desc, INDY_STRING_CONCAT_BOOTSTRAP_HANDLE);
} else {
// Java 8: call toString() on StringBuilder
invokeVirtual(STRINGBUILDER_TYPE, STRINGBUILDER_TOSTRING);
}
}
/** Writes a dynamic binary instruction: returnType, lhs, and rhs can be different */
public void writeDynamicBinaryInstruction(Location location, Type returnType, Type lhs, Type rhs,
Operation operation, int flags) {
org.objectweb.asm.Type methodType = org.objectweb.asm.Type.getMethodType(returnType.type, lhs.type, rhs.type);
switch (operation) {
case MUL:
invokeDefCall("mul", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case DIV:
invokeDefCall("div", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case REM:
invokeDefCall("rem", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case ADD:
// if either side is primitive, then the + operator should always throw NPE on null,
// so we don't need a special NPE guard.
// otherwise, we need to allow nulls for possible string concatenation.
boolean hasPrimitiveArg = lhs.clazz.isPrimitive() || rhs.clazz.isPrimitive();
if (!hasPrimitiveArg) {
flags |= DefBootstrap.OPERATOR_ALLOWS_NULL;
}
invokeDefCall("add", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case SUB:
invokeDefCall("sub", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case LSH:
invokeDefCall("lsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break;
case USH:
invokeDefCall("ush", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break;
case RSH:
invokeDefCall("rsh", methodType, DefBootstrap.SHIFT_OPERATOR, flags);
break;
case BWAND:
invokeDefCall("and", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case XOR:
invokeDefCall("xor", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
case BWOR:
invokeDefCall("or", methodType, DefBootstrap.BINARY_OPERATOR, flags);
break;
default:
throw location.createError(new IllegalStateException("Illegal tree structure."));
}
}
/** Writes a static binary instruction */
public void writeBinaryInstruction(Location location, Type type, Operation operation) {
final Sort sort = type.sort;
if ((sort == Sort.FLOAT || sort == Sort.DOUBLE) &&
(operation == Operation.LSH || operation == Operation.USH ||
operation == Operation.RSH || operation == Operation.BWAND ||
operation == Operation.XOR || operation == Operation.BWOR)) {
throw location.createError(new IllegalStateException("Illegal tree structure."));
}
switch (operation) {
case MUL: math(GeneratorAdapter.MUL, type.type); break;
case DIV: math(GeneratorAdapter.DIV, type.type); break;
case REM: math(GeneratorAdapter.REM, type.type); break;
case ADD: math(GeneratorAdapter.ADD, type.type); break;
case SUB: math(GeneratorAdapter.SUB, type.type); break;
case LSH: math(GeneratorAdapter.SHL, type.type); break;
case USH: math(GeneratorAdapter.USHR, type.type); break;
case RSH: math(GeneratorAdapter.SHR, type.type); break;
case BWAND: math(GeneratorAdapter.AND, type.type); break;
case XOR: math(GeneratorAdapter.XOR, type.type); break;
case BWOR: math(GeneratorAdapter.OR, type.type); break;
default:
throw location.createError(new IllegalStateException("Illegal tree structure."));
}
}
public void writeDup(final int size, final int xsize) {
if (size == 1) {
if (xsize == 2) {
dupX2();
} else if (xsize == 1) {
dupX1();
} else {
dup();
}
} else if (size == 2) {
if (xsize == 2) {
dup2X2();
} else if (xsize == 1) {
dup2X1();
} else {
dup2();
}
}
}
public void writePop(final int size) {
if (size == 1) {
pop();
} else if (size == 2) {
pop2();
}
}
@Override
public void endMethod() {
if (stringConcatArgs != null && !stringConcatArgs.isEmpty()) {
throw new IllegalStateException("String concat bytecode not completed.");
}
super.endMethod();
}
@Override
public void visitEnd() {
throw new AssertionError("Should never call this method on MethodWriter, use endMethod() instead");
}
/**
* Writes a dynamic call for a def method.
* @param name method name
* @param methodType callsite signature
* @param flavor type of call
* @param params flavor-specific parameters
*/
public void invokeDefCall(String name, org.objectweb.asm.Type methodType, int flavor, Object... params) {
Object[] args = new Object[params.length + 2];
args[0] = settings.getInitialCallSiteDepth();
args[1] = flavor;
System.arraycopy(params, 0, args, 2, params.length);
invokeDynamic(name, methodType.getDescriptor(), DEF_BOOTSTRAP_HANDLE, args);
}
}