/*
* Copyright 2003-2009 the original author or authors.
*
* Licensed 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.codehaus.groovy.classgen.asm;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.Variable;
import org.codehaus.groovy.ast.expr.CastExpression;
import org.codehaus.groovy.ast.expr.ClassExpression;
import org.codehaus.groovy.ast.expr.ConstantExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.classgen.ClassGeneratorException;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
public class OperandStack {
// type conversions
private static final MethodCaller asTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "asType");
private static final MethodCaller castToTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "castToType");
private WriterController controller;
private ArrayList<ClassNode> stack = new ArrayList();
public OperandStack(WriterController wc) {
this.controller = wc;
}
public int getStackLength() {
return stack.size();
}
public void popDownTo(int elements) {
int last = stack.size();
MethodVisitor mv = controller.getMethodVisitor();
while (last>elements) {
last--;
ClassNode element = stack.remove(last);
if (isTwoSlotType(element)) {
mv.visitInsn(POP2);
} else {
mv.visitInsn(POP);
}
}
}
/**
* returns true for long and double
*/
private boolean isTwoSlotType(ClassNode type) {
return type==ClassHelper.long_TYPE || type==ClassHelper.double_TYPE;
}
/**
* ensure last marked parameter on the stack is a primitive boolean
* if mark==stack size, we assume an empty expression or statement.
* was used and we will use the value given in emptyDefault as boolean
* if mark==stack.size()-1 the top element will be cast to boolean using
* Groovy truth.
* In other cases we throw a GroovyBugError
*/
public void castToBool(int mark, boolean emptyDefault) {
int size = stack.size();
MethodVisitor mv = controller.getMethodVisitor();
if (mark==size) {
// no element, so use emptyDefault
if (emptyDefault) {
mv.visitIntInsn(BIPUSH, 1);
} else {
mv.visitIntInsn(BIPUSH, 0);
}
stack.add(null);
} else if (mark==stack.size()-1) {
ClassNode last = stack.get(size-1);
// nothing to do in that case
if (last == ClassHelper.boolean_TYPE) return;
// not a primitive type, so call booleanUnbox
if (!ClassHelper.isPrimitiveType(last)) {
BytecodeHelper.unbox(mv,ClassHelper.boolean_TYPE);
} else {
primitive2b(mv,last);
}
} else {
throw new GroovyBugError(
"operand stack contains "+stack.size()+
" elements, but we expected only "+mark
);
}
stack.set(mark,ClassHelper.boolean_TYPE);
}
/**
* convert primitive (not boolean) to boolean or byte.
* type needs to be a primitive type (not checked)
*/
private void primitive2b(MethodVisitor mv, ClassNode type) {
if (type==ClassHelper.double_TYPE) {
mv.visitInsn(D2I);
mv.visitInsn(I2B);
} else if (type==ClassHelper.long_TYPE) {
mv.visitInsn(L2I);
mv.visitInsn(I2B);
} else if (type==ClassHelper.float_TYPE) {
mv.visitInsn(F2I);
mv.visitInsn(I2B);
} else if (type==ClassHelper.int_TYPE) {
mv.visitInsn(I2B);
}
// other cases can be used directly
}
/**
* remove operand stack top element using bytecode pop
*/
public void pop() {
popDownTo(stack.size()-1);
}
public Label jump(int ifIns) {
Label label = new Label();
jump(ifIns,label);
return label;
}
public void jump(int ifIns, Label label) {
controller.getMethodVisitor().visitJumpInsn(ifIns, label);
// remove the boolean from the operand stack tracker
remove(1);
}
/**
* duplicate top element
*/
public void dup() {
ClassNode type = getTopOperand();
stack.add(type);
MethodVisitor mv = controller.getMethodVisitor();
if (type == ClassHelper.double_TYPE || type == ClassHelper.long_TYPE) {
mv.visitInsn(DUP2);
} else {
mv.visitInsn(DUP);
}
}
public ClassNode box() {
MethodVisitor mv = controller.getMethodVisitor();
int size = stack.size();
ClassNode type = stack.get(size-1);
if (BytecodeHelper.box(mv, type)) {
type = ClassHelper.getWrapper(type);
BytecodeHelper.doCast(mv, type);
}
stack.set(size-1, type);
return type;
}
/**
* Remove amount elements from the operand stack, without using pop.
* For example after a method invocation
*/
public void remove(int amount) {
int size = stack.size();
for (int i=size-1; i>size-1-amount; i--) {
try {
stack.remove(i);
} catch (ArrayIndexOutOfBoundsException ai) {
System.err.println("index problem in "+controller.getSourceUnit().getName());
throw ai;
}
}
}
/**
* push operand on stack
*/
public void push(ClassNode type) {
stack.add(type);
}
/**
* swap two top level operands
*/
public void swap() {
MethodVisitor mv = controller.getMethodVisitor();
int size = stack.size();
ClassNode b = stack.get(size-1);
ClassNode a = stack.get(size-2);
// dup_x1: ---
// dup_x2: aab -> baab
// dup2_x1: abb -> bbabb
// dup2_x2: aabb -> bbaabb
// b = top element, a = element under b
// top element at right
if (isTwoSlotType(a)) { // aa
if (isTwoSlotType(b)) { // aabb
// aabb -> bbaa
mv.visitInsn(DUP2_X2); // bbaabb
mv.visitInsn(POP2); // bbaa
} else {
// aab -> baa
mv.visitInsn(DUP_X2); // baab
mv.visitInsn(POP); // baa
}
} else { // a
if (isTwoSlotType(b)) { //abb
// abb -> bba
mv.visitInsn(DUP2_X1); // bbabb
mv.visitInsn(POP2); // bba
} else {
// ab -> ba
mv.visitInsn(SWAP);
}
}
stack.set(size-1,a);
stack.set(size-2,b);
}
/**
* replace top level element with new element of given type
*/
public void replace(ClassNode type) {
int size = stack.size();
try {
if (size==0) throw new ArrayIndexOutOfBoundsException("size==0");
} catch (ArrayIndexOutOfBoundsException ai) {
System.err.println("index problem in "+controller.getSourceUnit().getName());
throw ai;
}
stack.set(size-1, type);
}
/**
* replace n top level elements with new element of given type
*/
public void replace(ClassNode type, int n) {
remove(n);
push(type);
}
/**
* do Groovy cast for top level element
*/
public void doGroovyCast(ClassNode targetType) {
doConvertAndCast(targetType,false);
}
public void doGroovyCast(Variable v) {
ClassNode targetType = v.getOriginType();
doConvertAndCast(targetType,false);
}
public void doAsType(ClassNode targetType) {
doConvertAndCast(targetType,true);
}
private void doConvertAndCast(ClassNode targetType, boolean coerce) {
int size = stack.size();
try {
if (size==0) throw new ArrayIndexOutOfBoundsException("size==0");
} catch (ArrayIndexOutOfBoundsException ai) {
throw ai;
}
ClassNode top = stack.get(size-1);
targetType = targetType.redirect();
if (targetType == top) return;
MethodVisitor mv = controller.getMethodVisitor();
if (coerce) {
if (top.isDerivedFrom(targetType)) return;
box();
(new ClassExpression(targetType)).visit(controller.getAcg());
remove(1);
asTypeMethod.call(mv);
BytecodeHelper.doCast(mv,targetType);
replace(targetType);
return;
}
boolean primTarget = ClassHelper.isPrimitiveType(targetType);
boolean primTop = ClassHelper.isPrimitiveType(top);
if (primTop && primTarget) {
//TODO: use jvm primitive conversions
// here we box and unbox to get the goal type
if (convertPrimitive(top, targetType)) {
replace(targetType);
return;
}
box();
} else if (primTop) {
// top is primitive, target is not
// so box and do groovy cast
box();
(new ClassExpression(targetType)).visit(controller.getAcg());
remove(1);
castToTypeMethod.call(mv);
} else if (primTarget) {
// top is not primitive so unbox
// leave that BH#doCast later
} else if (!(top.isDerivedFrom(targetType))) {
// top and target are not primitive and top is not derived from target
(new ClassExpression(targetType)).visit(controller.getAcg());
remove(1);
castToTypeMethod.call(mv);
}
BytecodeHelper.doCast(mv,targetType);
replace(targetType);
}
private boolean convertFromInt(ClassNode target) {
int convertCode = 0;
if (target==ClassHelper.char_TYPE){
convertCode = I2C;
} else if (target==ClassHelper.byte_TYPE){
convertCode = I2B;
} else if (target==ClassHelper.short_TYPE){
convertCode = I2S;
} else if (target==ClassHelper.long_TYPE){
convertCode = I2L;
} else if (target==ClassHelper.float_TYPE){
convertCode = I2F;
} else if (target==ClassHelper.double_TYPE){
convertCode = I2D;
} else {
return false;
}
controller.getMethodVisitor().visitInsn(convertCode);
return true;
}
private boolean convertFromDouble(ClassNode target) {
MethodVisitor mv = controller.getMethodVisitor();
if (target==ClassHelper.int_TYPE){
mv.visitInsn(D2I);
return true;
} else if ( target==ClassHelper.char_TYPE ||
target==ClassHelper.byte_TYPE ||
target==ClassHelper.short_TYPE)
{
mv.visitInsn(D2I);
return convertFromInt(target);
} else if (target==ClassHelper.long_TYPE){
mv.visitInsn(D2L);
return true;
} else if (target==ClassHelper.float_TYPE){
mv.visitInsn(D2F);
return true;
}
return false;
}
private boolean convertFromFloat(ClassNode target) {
MethodVisitor mv = controller.getMethodVisitor();
if (target==ClassHelper.int_TYPE){
mv.visitInsn(F2I);
return true;
} else if ( target==ClassHelper.char_TYPE ||
target==ClassHelper.byte_TYPE ||
target==ClassHelper.short_TYPE)
{
mv.visitInsn(F2I);
return convertFromInt(target);
} else if (target==ClassHelper.long_TYPE){
mv.visitInsn(F2L);
return true;
} else if (target==ClassHelper.double_TYPE){
mv.visitInsn(F2D);
return true;
}
return false;
}
private boolean convertPrimitive(ClassNode top, ClassNode target) {
if (top==target) return true;
if (top==ClassHelper.int_TYPE) {
return convertFromInt(target);
} else if ( top==ClassHelper.char_TYPE ||
top==ClassHelper.byte_TYPE ||
top==ClassHelper.short_TYPE)
{
if (target==ClassHelper.int_TYPE) return true;
return convertFromInt(target);
} else if ( top==ClassHelper.float_TYPE) {
return convertFromFloat(target);
} else if ( top==ClassHelper.double_TYPE) {
return convertFromDouble(target);
}
return false;
}
/**
* load the constant on the operand stack.
*/
public void pushConstant(ConstantExpression expression) {
MethodVisitor mv = controller.getMethodVisitor();
Object value = expression.getValue();
ClassNode type = expression.getType().redirect();
boolean asPrimitive = ClassHelper.isPrimitiveType(type);
if (value == null) {
mv.visitInsn(ACONST_NULL);
} else if (asPrimitive) {
mv.visitLdcInsn(value);
} else if (value instanceof Character) {
mv.visitLdcInsn(value);
BytecodeHelper.box(mv, type); // does not change this.stack field contents
} else if (value instanceof Number) {
if (value instanceof BigDecimal) {
String className = BytecodeHelper.getClassInternalName(value.getClass().getName());
mv.visitTypeInsn(NEW, className);
mv.visitInsn(DUP);
mv.visitLdcInsn(value.toString());
mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V");
} else if (value instanceof BigInteger) {
String className = BytecodeHelper.getClassInternalName(value.getClass().getName());
mv.visitTypeInsn(NEW, className);
mv.visitInsn(DUP);
mv.visitLdcInsn(value.toString());
mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V");
} else if (value instanceof Long) {
long l = (Long) value;
if (l==0) {
mv.visitInsn(LCONST_0);
} else if (l==1) {
mv.visitInsn(LCONST_1);
} else {
mv.visitLdcInsn(value);
}
BytecodeHelper.box(mv, ClassHelper.getUnwrapper(type)); // does not change this.stack field contents
BytecodeHelper.doCast(mv, type);
} else {
mv.visitLdcInsn(value);
BytecodeHelper.box(mv, ClassHelper.getUnwrapper(type)); // does not change this.stack field contents
BytecodeHelper.doCast(mv, type);
}
} else if (value instanceof Boolean) {
Boolean bool = (Boolean) value;
String text = (bool.booleanValue()) ? "TRUE" : "FALSE";
mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;");
} else if (value instanceof String) {
mv.visitLdcInsn(value);
} else {
throw new ClassGeneratorException(
"Cannot generate bytecode for constant: " + value + " of type: " + type.getName());
}
push(type);
}
public void pushDynamicName(Expression name) {
if (name instanceof ConstantExpression) {
ConstantExpression ce = (ConstantExpression) name;
Object value = ce.getValue();
if (value instanceof String) {
pushConstant(ce);
return;
}
}
new CastExpression(ClassHelper.STRING_TYPE, name).visit(controller.getAcg());
}
public void loadOrStoreVariable(BytecodeVariable variable, boolean useReferenceDirectly) {
CompileStack compileStack = controller.getCompileStack();
if (compileStack.isLHS()) {
storeVar(variable);
} else {
MethodVisitor mv = controller.getMethodVisitor();
int idx = variable.getIndex();
ClassNode type = variable.getType();
if (variable.isHolder()) {
mv.visitVarInsn(ALOAD, idx);
if (!useReferenceDirectly) {
mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;");
BytecodeHelper.doCast(mv, type);
push(type);
} else {
push(ClassHelper.REFERENCE_TYPE);
}
} else {
load(type,idx);
}
}
}
public void storeVar(BytecodeVariable variable) {
MethodVisitor mv = controller.getMethodVisitor();
int idx = variable.getIndex();
ClassNode type = variable.getType();
// value is on stack
if (variable.isHolder()) {
box();
mv.visitVarInsn(ALOAD, idx);
mv.visitTypeInsn(CHECKCAST, "groovy/lang/Reference");
mv.visitInsn(SWAP);
mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
} else {
doGroovyCast(type);
if (type == ClassHelper.double_TYPE) {
mv.visitVarInsn(DSTORE, idx);
} else if (type == ClassHelper.float_TYPE) {
mv.visitVarInsn(FSTORE, idx);
} else if (type == ClassHelper.long_TYPE) {
mv.visitVarInsn(LSTORE, idx);
} else if (
type == ClassHelper.boolean_TYPE
|| type == ClassHelper.char_TYPE
|| type == ClassHelper.byte_TYPE
|| type == ClassHelper.int_TYPE
|| type == ClassHelper.short_TYPE) {
mv.visitVarInsn(ISTORE, idx);
} else {
mv.visitVarInsn(ASTORE, idx);
}
}
// remove RHS value from operand stack
remove(1);
}
public void load(ClassNode type, int idx) {
MethodVisitor mv = controller.getMethodVisitor();
BytecodeHelper.load(mv, type, idx);
push(type);
}
public void pushBool(boolean inclusive) {
MethodVisitor mv = controller.getMethodVisitor();
mv.visitLdcInsn(new Boolean(inclusive));
push(ClassHelper.boolean_TYPE);
}
public String toString() {
return "OperandStack(size="+stack.size()+":"+stack.toString()+")";
}
public ClassNode getTopOperand() {
int size = stack.size();
try {
if (size==0) throw new ArrayIndexOutOfBoundsException("size==0");
} catch (ArrayIndexOutOfBoundsException ai) {
System.err.println("index problem in "+controller.getSourceUnit().getName());
throw ai;
}
return stack.get(size-1);
}
}