diff options
Diffstat (limited to 'src/main/javassist/compiler/CodeGen.java')
-rw-r--r-- | src/main/javassist/compiler/CodeGen.java | 1559 |
1 files changed, 1559 insertions, 0 deletions
diff --git a/src/main/javassist/compiler/CodeGen.java b/src/main/javassist/compiler/CodeGen.java new file mode 100644 index 00000000..9a5f6771 --- /dev/null +++ b/src/main/javassist/compiler/CodeGen.java @@ -0,0 +1,1559 @@ +/* + * This file is part of the Javassist toolkit. + * + * The contents of this file are subject to the Mozilla Public License + * Version 1.1 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * either http://www.mozilla.org/MPL/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * The Original Code is Javassist. + * + * The Initial Developer of the Original Code is Shigeru Chiba. Portions + * created by Shigeru Chiba are Copyright (C) 1999-2003 Shigeru Chiba. + * All Rights Reserved. + * + * Contributor(s): + * + * The development of this software is supported in part by the PRESTO + * program (Sakigake Kenkyu 21) of Japan Science and Technology Corporation. + */ + +package javassist.compiler; + +import java.util.ArrayList; +import javassist.compiler.ast.*; +import javassist.bytecode.*; + +/* The code generator is implemeted by three files: + * CodeGen.java, MemberCodeGen.java, and JvstCodeGen. + * I just wanted to split a big file into three smaller ones. + */ + +public abstract class CodeGen extends Visitor implements Opcode, TokenId { + static final String javaLangObject = "java.lang.Object"; + static final String jvmJavaLangObject = "java/lang/Object"; + + static final String javaLangString = "java.lang.String"; + static final String jvmJavaLangString = "java/lang/String"; + + protected Bytecode bytecode; + private int tempVar; + + /** + * Must be true if compilation is for a static method. + */ + public boolean inStaticMethod; + + protected ArrayList breakList, continueList; + + /* The following fields are used by atXXX() methods + * for returning the type of the compiled expression. + */ + protected int exprType; // VOID, NULL, CLASS, BOOLEAN, INT, ... + protected int arrayDim; + protected String className; // JVM-internal representation + + public CodeGen(Bytecode b) { + bytecode = b; + tempVar = -1; + inStaticMethod = false; + breakList = null; + continueList = null; + } + + protected static void fatal() throws CompileError { + throw new CompileError("fatal"); + } + + public static boolean is2word(int type, int dim) { + return dim == 0 && (type == DOUBLE || type == LONG); + } + + public int getMaxLocals() { return bytecode.getMaxLocals(); } + + public void setMaxLocals(int n) { + bytecode.setMaxLocals(n); + } + + protected void incMaxLocals(int size) { + bytecode.incMaxLocals(size); + } + + /** + * Returns a local variable that single or double words can be + * stored in. + */ + protected int getTempVar() { + if (tempVar < 0) { + tempVar = getMaxLocals(); + incMaxLocals(2); + } + + return tempVar; + } + + protected int getLocalVar(Declarator d) { + int v = d.getLocalVar(); + if (v < 0) { + v = getMaxLocals(); // delayed variable allocation. + d.setLocalVar(v); + incMaxLocals(1); + } + + return v; + } + + /** + * Returns the JVM-internal representation of this class name. + */ + protected abstract String getThisName(); + + /** + * Returns the JVM-internal representation of this super class name. + */ + protected abstract String getSuperName() throws CompileError; + + /* Converts a class name into a JVM-internal representation. + * + * It may also expand a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected abstract String resolveClassName(ASTList name) + throws CompileError; + + /* Expands a simple class name to java.lang.*. + * For example, this converts Object into java/lang/Object. + */ + protected abstract String resolveClassName(String jvmClassName) + throws CompileError; + + /** + * @param name the JVM-internal representation. + * name is not exapnded to java.lang.*. + */ + protected static String toJvmArrayName(String name, int dim) { + if (name == null) + return null; + + if (dim == 0) + return name; + else { + StringBuffer sbuf = new StringBuffer(); + int d = dim; + while (d-- > 0) + sbuf.append('['); + + sbuf.append('L'); + sbuf.append(name); + sbuf.append(';'); + + return sbuf.toString(); + } + } + + protected static String toJvmTypeName(int type, int dim) { + char c = 'I'; + switch(type) { + case BOOLEAN : + c = 'Z'; + break; + case BYTE : + c = 'B'; + break; + case CHAR : + c = 'C'; + break; + case SHORT : + c = 'S'; + break; + case INT : + c = 'I'; + break; + case LONG : + c = 'J'; + break; + case FLOAT : + c = 'F'; + break; + case DOUBLE : + c = 'D'; + break; + case VOID : + c = 'V'; + break; + } + + StringBuffer sbuf = new StringBuffer(); + while (dim-- > 0) + sbuf.append('['); + + sbuf.append(c); + return sbuf.toString(); + } + + + public void atASTList(ASTList n) throws CompileError { fatal(); } + + public void atPair(Pair n) throws CompileError { fatal(); } + + public void atSymbol(Symbol n) throws CompileError { fatal(); } + + public void atFieldDecl(FieldDecl field) throws CompileError { + field.getInit().accept(this); + } + + public void atMethodDecl(MethodDecl method) throws CompileError { + ASTList mods = method.getModifiers(); + setMaxLocals(1); + while (mods != null) { + Keyword k = (Keyword)mods.head(); + mods = mods.tail(); + if (k.get() == STATIC) { + setMaxLocals(0); + inStaticMethod = true; + } + } + + ASTList params = method.getParams(); + while (params != null) { + atDeclarator((Declarator)params.head()); + params = params.tail(); + } + + Stmnt s = method.getBody(); + atMethodBody(s, method.isConstructor(), + method.getReturn().getType() == VOID); + } + + public void atMethodBody(Stmnt s, boolean isCons, boolean isVoid) + throws CompileError + { + if (s == null) + return; + + if (isCons && needsSuperCall(s)) + insertDefaultSuperCall(); + + s.accept(this); + if (isVoid + && (bytecode.read(bytecode.currentPc() - 1) & 0xff) + != Opcode.RETURN) { + bytecode.addOpcode(Opcode.RETURN); + } + } + + private boolean needsSuperCall(Stmnt body) { + if (body.getOperator() == BLOCK) { + Stmnt first = (Stmnt)body.head(); + if (first != null && first.getOperator() == EXPR) { + ASTree expr = first.head(); + if (expr != null && expr instanceof Expr + && ((Expr)expr).getOperator() == CALL) { + ASTree target = ((Expr)expr).head(); + if (target instanceof Keyword) { + int token = ((Keyword)target).get(); + return token != THIS && token != SUPER; + } + } + } + } + + return true; + } + + protected abstract void insertDefaultSuperCall() throws CompileError; + + public void atStmnt(Stmnt st) throws CompileError { + if (st == null) + return; // empty + + int op = st.getOperator(); + if (op == EXPR) { + ASTree expr = st.getLeft(); + if (expr instanceof AssignExpr) + atAssignExpr((AssignExpr)expr, false); + else if (isPlusPlusExpr(expr)) { + Expr e = (Expr)expr; + atPlusPlus(e.getOperator(), e.oprand1(), e, false); + } + else { + expr.accept(this); + if (is2word(exprType, arrayDim)) + bytecode.addOpcode(POP2); + else if (exprType != VOID) + bytecode.addOpcode(POP); + } + } + else if (op == DECL || op == BLOCK) { + ASTList list = st; + while (list != null) { + ASTree h = list.head(); + list = list.tail(); + if (h != null) + h.accept(this); + } + } + else if (op == IF) + atIfStmnt(st); + else if (op == WHILE || op == DO) + atWhileStmnt(st, op == WHILE); + else if (op == FOR) + atForStmnt(st); + else if (op == BREAK || op == CONTINUE) + atBreakStmnt(st, op == BREAK); + else if (op == TokenId.RETURN) + atReturnStmnt(st); + else if (op == THROW) + atThrowStmnt(st); + else if (op == TRY) + atTryStmnt(st); + else // LABEL, SWITCH label stament might be null?. + throw new CompileError( + "sorry, not supported statement: TokenId " + op); + } + + private void atIfStmnt(Stmnt st) throws CompileError { + ASTree expr = st.head(); + Stmnt thenp = (Stmnt)st.tail().head(); + Stmnt elsep = (Stmnt)st.tail().tail().head(); + booleanExpr(false, expr); + int pc = bytecode.currentPc(); + int pc2 = 0; + bytecode.addIndex(0); // correct later + + if (thenp != null) + thenp.accept(this); + + if (elsep != null) { + bytecode.addOpcode(Opcode.GOTO); + pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + } + + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + + if (elsep != null) { + elsep.accept(this); + bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1); + } + } + + private void atWhileStmnt(Stmnt st, boolean notDo) throws CompileError { + ArrayList prevBreakList = breakList; + ArrayList prevContList = continueList; + breakList = new ArrayList(); + continueList = new ArrayList(); + + ASTree expr = st.head(); + Stmnt body = (Stmnt)st.tail(); + + int pc = 0; + if (notDo) { + bytecode.addOpcode(Opcode.GOTO); + pc = bytecode.currentPc(); + bytecode.addIndex(0); + } + + int pc2 = bytecode.currentPc(); + if (body != null) + body.accept(this); + + int pc3 = bytecode.currentPc(); + if (notDo) + bytecode.write16bit(pc, pc3 - pc + 1); + + booleanExpr(true, expr); + bytecode.addIndex(pc2 - bytecode.currentPc() + 1); + + patchGoto(breakList, bytecode.currentPc()); + patchGoto(continueList, pc3); + continueList = prevContList; + breakList = prevBreakList; + } + + private void patchGoto(ArrayList list, int targetPc) { + int n = list.size(); + for (int i = 0; i < n; ++i) { + int pc = ((Integer)list.get(i)).intValue(); + bytecode.write16bit(pc, targetPc - pc + 1); + } + } + + private void atForStmnt(Stmnt st) throws CompileError { + ArrayList prevBreakList = breakList; + ArrayList prevContList = continueList; + breakList = new ArrayList(); + continueList = new ArrayList(); + + Stmnt init = (Stmnt)st.head(); + ASTList p = st.tail(); + ASTree expr = p.head(); + p = p.tail(); + Stmnt update = (Stmnt)p.head(); + Stmnt body = (Stmnt)p.tail(); + + if (init != null) + init.accept(this); + + int pc = bytecode.currentPc(); + int pc2 = 0; + if (expr != null) { + booleanExpr(false, expr); + pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + } + + if (body != null) + body.accept(this); + + int pc3 = bytecode.currentPc(); + if (update != null) + update.accept(this); + + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(pc - bytecode.currentPc() + 1); + + int pc4 = bytecode.currentPc(); + if (expr != null) + bytecode.write16bit(pc2, pc4 - pc2 + 1); + + patchGoto(breakList, pc4); + patchGoto(continueList, pc3); + continueList = prevContList; + breakList = prevBreakList; + } + + private void atBreakStmnt(Stmnt st, boolean notCont) + throws CompileError + { + if (st.head() != null) + throw new CompileError( + "sorry, not support labeled break or continue"); + + bytecode.addOpcode(Opcode.GOTO); + Integer pc = new Integer(bytecode.currentPc()); + bytecode.addIndex(0); + if (notCont) + breakList.add(pc); + else + continueList.add(pc); + } + + protected void atReturnStmnt(Stmnt st) throws CompileError { + atReturnStmnt2(st.getLeft()); + } + + protected final void atReturnStmnt2(ASTree result) throws CompileError { + int op; + if (result == null) + op = Opcode.RETURN; + else { + result.accept(this); + if (arrayDim > 0) + op = ARETURN; + else { + int type = exprType; + if (type == DOUBLE) + op = DRETURN; + else if (type == FLOAT) + op = FRETURN; + else if (type == LONG) + op = LRETURN; + else if (isRefType(type)) + op = ARETURN; + else + op = IRETURN; + } + } + + bytecode.addOpcode(op); + } + + private void atThrowStmnt(Stmnt st) throws CompileError { + ASTree e = st.getLeft(); + e.accept(this); + if (exprType != CLASS || arrayDim > 0) + throw new CompileError("bad throw statement"); + + bytecode.addOpcode(ATHROW); + } + + protected abstract void atTryStmnt(Stmnt st) throws CompileError; + + private static boolean isPlusPlusExpr(ASTree expr) { + if (expr instanceof Expr) { + int op = ((Expr)expr).getOperator(); + return op == PLUSPLUS || op == MINUSMINUS; + } + + return false; + } + + public void atDeclarator(Declarator d) throws CompileError { + d.setLocalVar(getMaxLocals()); + d.setClassName(resolveClassName(d.getClassName())); + + int size; + if (is2word(d.getType(), d.getArrayDim())) + size = 2; + else + size = 1; + + incMaxLocals(size); + + /* NOTE: Array initializers has not been supported. + */ + ASTree init = d.getInitializer(); + if (init != null) + atVariableAssign(null, '=', null, d, init, false); + } + + public abstract void atNewExpr(NewExpr n) throws CompileError; + + public void atAssignExpr(AssignExpr expr) throws CompileError { + atAssignExpr(expr, true); + } + + protected void atAssignExpr(AssignExpr expr, boolean doDup) + throws CompileError + { + // =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, >>>= + int op = expr.getOperator(); + ASTree left = expr.oprand1(); + ASTree right = expr.oprand2(); + if (left instanceof Variable) + atVariableAssign(expr, op, (Variable)left, + ((Variable)left).getDeclarator(), + right, doDup); + else { + if (left instanceof Expr) { + Expr e = (Expr)left; + if (e.getOperator() == ARRAY) { + atArrayAssign(expr, op, (Expr)left, right, doDup); + return; + } + } + + atFieldAssign(expr, op, left, right, doDup); + } + } + + protected static void badAssign(Expr expr) throws CompileError { + String msg; + if (expr == null) + msg = "incompatible type for assignment"; + else + msg = "incompatible type for " + expr.getName(); + + throw new CompileError(msg); + } + + /* op is either =, %=, &=, *=, /=, +=, -=, ^=, |=, <<=, >>=, or >>>=. + * + * expr and var can be null. + */ + private void atVariableAssign(Expr expr, int op, Variable var, + Declarator d, ASTree right, + boolean doDup) throws CompileError + { + int varType = d.getType(); + int varArray = d.getArrayDim(); + String varClass = d.getClassName(); + int varNo = getLocalVar(d); + + if (op != '=') + atVariable(var); + + atAssignCore(expr, op, right, varType, varArray, varClass); + + if (doDup) + if (is2word(varType, varArray)) + bytecode.addOpcode(DUP2); + else + bytecode.addOpcode(DUP); + + if (varArray > 0) + bytecode.addAstore(varNo); + else if (varType == DOUBLE) + bytecode.addDstore(varNo); + else if (varType == FLOAT) + bytecode.addFstore(varNo); + else if (varType == LONG) + bytecode.addLstore(varNo); + else if (isRefType(varType)) + bytecode.addAstore(varNo); + else + bytecode.addIstore(varNo); + + exprType = varType; + arrayDim = varArray; + className = varClass; + } + + private void atArrayAssign(Expr expr, int op, Expr array, + ASTree right, boolean doDup) throws CompileError + { + arrayAccess(array.oprand1(), array.oprand2()); + + if (op != '=') { + bytecode.addOpcode(DUP2); + bytecode.addOpcode(getArrayReadOp(exprType, arrayDim)); + } + + int aType = exprType; + int aDim = arrayDim; + String cname = className; + + atAssignCore(expr, op, right, aType, aDim, cname); + + if (doDup) + if (is2word(aType, aDim)) + bytecode.addOpcode(DUP2_X2); + else + bytecode.addOpcode(DUP_X2); + + bytecode.addOpcode(getArrayWriteOp(aType, aDim)); + exprType = aType; + arrayDim = aDim; + className = cname; + } + + protected abstract void atFieldAssign(Expr expr, int op, ASTree left, + ASTree right, boolean doDup) throws CompileError; + + protected void atAssignCore(Expr expr, int op, ASTree right, + int type, int dim, String cname) + throws CompileError + { + right.accept(this); + if (invalidDim(exprType, arrayDim, className, type, dim, cname, false) + || (op != '=' && dim > 0)) + badAssign(expr); + + if (op == PLUS_E && dim == 0 && type == CLASS) + atStringConcatExpr(expr, type, dim, cname); + else if (op != '=') { + int token = assignOps[op - MOD_E]; + int k = lookupBinOp(token); + if (k < 0) + fatal(); + + atArithBinExpr(expr, token, k, type); + } + + if (op != '=' || (dim == 0 && !isRefType(type))) + atNumCastExpr(exprType, type); + + // type check should be done here. + } + + private boolean invalidDim(int srcType, int srcDim, String srcClass, + int destType, int destDim, String destClass, + boolean isCast) + { + if (srcDim != destDim) + if (srcType == NULL) + return false; + else if (destDim == 0 && destType == CLASS + && jvmJavaLangObject.equals(destClass)) + return false; + else if (isCast && srcDim == 0 && srcType == CLASS + && jvmJavaLangObject.equals(srcClass)) + return false; + else + return true; + + return false; + } + + public void atCondExpr(CondExpr expr) throws CompileError { + booleanExpr(false, expr.condExpr()); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + expr.thenExpr().accept(this); + bytecode.addOpcode(Opcode.GOTO); + int pc2 = bytecode.currentPc(); + bytecode.addIndex(0); + bytecode.write16bit(pc, bytecode.currentPc() - pc + 1); + expr.elseExpr().accept(this); + bytecode.write16bit(pc2, bytecode.currentPc() - pc2 + 1); + } + + private final int[] binOp = { + '+', DADD, FADD, LADD, IADD, + '-', DSUB, FSUB, LSUB, ISUB, + '*', DMUL, FMUL, LMUL, IMUL, + '/', DDIV, FDIV, LDIV, IDIV, + '%', DREM, FREM, LREM, IREM, + '|', NOP, NOP, LOR, IOR, + '^', NOP, NOP, LXOR, IXOR, + '&', NOP, NOP, LAND, IAND, + LSHIFT, NOP, NOP, LSHL, ISHL, + RSHIFT, NOP, NOP, LSHR, ISHR, + ARSHIFT, NOP, NOP, LUSHR, IUSHR }; + + private int lookupBinOp(int token) { + int[] code = binOp; + int s = code.length; + for (int k = 0; k < s; k = k + 5) + if (code[k] == token) + return k; + + return -1; + } + + public void atBinExpr(BinExpr expr) throws CompileError { + int token = expr.getOperator(); + + /* arithmetic operators: +, -, *, /, %, |, ^, &, <<, >>, >>> + */ + int k = lookupBinOp(token); + if (k >= 0) { + expr.oprand1().accept(this); + int type1 = exprType; + int dim1 = arrayDim; + String cname1 = className; + expr.oprand2().accept(this); + if (dim1 != arrayDim) + throw new CompileError("incompatible array types"); + + if (token == '+' && dim1 == 0 + && (type1 == CLASS || exprType == CLASS)) + atStringConcatExpr(expr, type1, dim1, cname1); + else + atArithBinExpr(expr, token, k, type1); + + return; + } + + /* equation: &&, ||, ==, !=, <=, >=, <, > + */ + booleanExpr(true, expr); + bytecode.addIndex(7); + bytecode.addIconst(0); // false + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(4); + bytecode.addIconst(1); // true + } + + /* arrayDim values of the two oprands must be equal. + * If an oprand type is not a numeric type, this method + * throws an exception. + */ + private void atArithBinExpr(Expr expr, int token, + int index, int type1) throws CompileError + { + if (arrayDim != 0) + badTypes(expr); + + int type2 = exprType; + if (token == LSHIFT || token == RSHIFT || token == ARSHIFT) + if (type2 == INT || type2 == SHORT + || type2 == CHAR || type2 == BYTE) + exprType = type1; + else + badTypes(expr); + else + convertOprandTypes(type1, type2, expr); + + int p = typePrecedence(exprType); + if (p >= 0) { + int op = binOp[index + p + 1]; + if (op != NOP) { + if (p == P_INT) + exprType = INT; // type1 may be BYTE, ... + + bytecode.addOpcode(op); + return; + } + } + + badTypes(expr); + } + + private void atStringConcatExpr(Expr expr, int type1, int dim1, + String cname1) throws CompileError + { + int type2 = exprType; + int dim2 = arrayDim; + boolean type2Is2 = is2word(type2, dim2); + boolean type2IsString + = (type2 == CLASS && jvmJavaLangString.equals(className)); + + if (type2Is2) + convToString(type2, dim2); + + if (is2word(type1, dim1)) { + bytecode.addOpcode(DUP_X2); + bytecode.addOpcode(POP); + } + else + bytecode.addOpcode(SWAP); + + convToString(type1, dim1); + bytecode.addOpcode(SWAP); + + if (!type2Is2 && !type2IsString) + convToString(type2, dim2); + + bytecode.addInvokevirtual(javaLangString, "concat", + "(Ljava/lang/String;)Ljava/lang/String;"); + exprType = CLASS; + arrayDim = 0; + className = jvmJavaLangString; + } + + private void convToString(int type, int dim) throws CompileError { + final String method = "valueOf"; + + if (isRefType(type) || dim > 0) + bytecode.addInvokestatic(javaLangString, method, + "(Ljava/lang/Object;)Ljava/lang/String;"); + else if (type == DOUBLE) + bytecode.addInvokestatic(javaLangString, method, + "(D)Ljava/lang/String;"); + else if (type == FLOAT) + bytecode.addInvokestatic(javaLangString, method, + "(F)Ljava/lang/String;"); + else if (type == LONG) + bytecode.addInvokestatic(javaLangString, method, + "(J)Ljava/lang/String;"); + else if (type == BOOLEAN) + bytecode.addInvokestatic(javaLangString, method, + "(Z)Ljava/lang/String;"); + else if (type == CHAR) + bytecode.addInvokestatic(javaLangString, method, + "(C)Ljava/lang/String;"); + else if (type == VOID) + throw new CompileError("void type expression"); + else /* INT, BYTE, SHORT */ + bytecode.addInvokestatic(javaLangString, method, + "(I)Ljava/lang/String;"); + } + + /* Produces the opcode to branch if the condition is true. + * The oprand is not produced. + * + * If false is returned, the branch occurs if the condition is + * false. + */ + private void booleanExpr(boolean branchIf, ASTree expr) + throws CompileError + { + boolean isAndAnd; + int op = getCompOperator(expr); + if (op == EQ) { // ==, !=, ... + BinExpr bexpr = (BinExpr)expr; + int type1 = compileOprands(bexpr); + compareExpr(branchIf, bexpr.getOperator(), type1, bexpr); + } + else if (op == '!') + booleanExpr(!branchIf, ((Expr)expr).oprand1()); + else if ((isAndAnd = (op == ANDAND)) || op == OROR) { + BinExpr bexpr = (BinExpr)expr; + booleanExpr(!isAndAnd, bexpr.oprand1()); + int pc = bytecode.currentPc(); + bytecode.addIndex(0); // correct later + + booleanExpr(isAndAnd, bexpr.oprand2()); + bytecode.write16bit(pc, bytecode.currentPc() - pc + 3); + if (branchIf != isAndAnd) { + bytecode.addIndex(6); // skip GOTO instruction + bytecode.addOpcode(Opcode.GOTO); + } + } + else { // others + expr.accept(this); + bytecode.addOpcode(branchIf ? IFNE : IFEQ); + } + + exprType = BOOLEAN; + arrayDim = 0; + } + + private static int getCompOperator(ASTree expr) throws CompileError { + if (expr instanceof Expr) { + Expr bexpr = (Expr)expr; + int token = bexpr.getOperator(); + if (token == '!') + return '!'; + else if ((bexpr instanceof BinExpr) + && token != OROR && token != ANDAND + && token != '&' && token != '|') + return EQ; // ==, !=, ... + else + return token; + } + + return ' '; // others + } + + private int compileOprands(BinExpr expr) throws CompileError { + expr.oprand1().accept(this); + int type1 = exprType; + int dim1 = arrayDim; + expr.oprand2().accept(this); + if (dim1 != arrayDim) + throw new CompileError("incompatible array types"); + + return type1; + } + + private final int ifOp[] = { EQ, IF_ICMPEQ, IF_ICMPNE, + NEQ, IF_ICMPNE, IF_ICMPEQ, + LE, IF_ICMPLE, IF_ICMPGT, + GE, IF_ICMPGE, IF_ICMPLT, + '<', IF_ICMPLT, IF_ICMPGE, + '>', IF_ICMPGT, IF_ICMPLE }; + + private final int ifOp2[] = { EQ, IFEQ, IFNE, + NEQ, IFNE, IFEQ, + LE, IFLE, IFGT, + GE, IFGE, IFLT, + '<', IFLT, IFGE, + '>', IFGT, IFLE }; + + /* Produces the opcode to branch if the condition is true. + * The oprands are not produced. + * + * Parameter expr - compare expression ==, !=, <=, >=, <, > + */ + private void compareExpr(boolean branchIf, + int token, int type1, BinExpr expr) + throws CompileError + { + if (arrayDim == 0) + convertOprandTypes(type1, exprType, expr); + + int p = typePrecedence(exprType); + if (p == P_OTHER || arrayDim > 0) + if (token == EQ) + bytecode.addOpcode(branchIf ? IF_ACMPEQ : IF_ACMPNE); + else if (token == NEQ) + bytecode.addOpcode(branchIf ? IF_ACMPNE : IF_ACMPEQ); + else + badTypes(expr); + else + if (p == P_INT) { + int op[] = ifOp; + for (int i = 0; i < op.length; i += 3) + if (op[i] == token) { + bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]); + return; + } + + badTypes(expr); + } + else { + if (p == P_DOUBLE) + if (token == '<' || token == LE) + bytecode.addOpcode(DCMPG); + else + bytecode.addOpcode(DCMPL); + else if (p == P_FLOAT) + if (token == '<' || token == LE) + bytecode.addOpcode(FCMPG); + else + bytecode.addOpcode(FCMPL); + else if (p == P_LONG) + bytecode.addOpcode(LCMP); // 1: >, 0: =, -1: < + else + fatal(); + + int[] op = ifOp2; + for (int i = 0; i < op.length; i += 3) + if (op[i] == token) { + bytecode.addOpcode(op[i + (branchIf ? 1 : 2)]); + return; + } + + badTypes(expr); + } + } + + protected static void badTypes(Expr expr) throws CompileError { + throw new CompileError("invalid types for " + expr.getName()); + } + + private static final int P_DOUBLE = 0; + private static final int P_FLOAT = 1; + private static final int P_LONG = 2; + private static final int P_INT = 3; + private static final int P_OTHER = -1; + + protected static boolean isRefType(int type) { + return type == CLASS || type == NULL; + } + + private static int typePrecedence(int type) { + if (type == DOUBLE) + return P_DOUBLE; + else if (type == FLOAT) + return P_FLOAT; + else if (type == LONG) + return P_LONG; + else if (isRefType(type)) + return P_OTHER; + else if (type == VOID) + return P_OTHER; // this is wrong, but ... + else + return P_INT; // BOOLEAN, BYTE, CHAR, SHORT, INT + } + + private static final int[] castOp = { + /* D F L I */ + /* double */ NOP, D2F, D2L, D2I, + /* float */ F2D, NOP, F2L, F2I, + /* long */ L2D, L2F, NOP, L2I, + /* other */ I2D, I2F, I2L, NOP }; + + /* do implicit type conversion. + * arrayDim values of the two oprands must be zero. + */ + private void convertOprandTypes(int type1, int type2, Expr expr) + throws CompileError + { + boolean rightStrong; + int type1_p = typePrecedence(type1); + int type2_p = typePrecedence(type2); + + if (type2_p < 0 && type1_p < 0) // not primitive types + return; + + if (type2_p < 0 || type1_p < 0) // either is not a primitive type + badTypes(expr); + + int op, result_type; + if (type1_p <= type2_p) { + rightStrong = false; + exprType = type1; + op = castOp[type2_p * 4 + type1_p]; + result_type = type1_p; + } + else { + rightStrong = true; + op = castOp[type1_p * 4 + type2_p]; + result_type = type2_p; + } + + if (rightStrong) { + if (result_type == P_DOUBLE || result_type == P_LONG) { + if (type1_p == P_DOUBLE || type1_p == P_LONG) + bytecode.addOpcode(DUP2_X2); + else + bytecode.addOpcode(DUP2_X1); + + bytecode.addOpcode(POP2); + bytecode.addOpcode(op); + bytecode.addOpcode(DUP2_X2); + bytecode.addOpcode(POP2); + } + else if (result_type == P_FLOAT) { + bytecode.addOpcode(SWAP); + bytecode.addOpcode(op); + bytecode.addOpcode(SWAP); + } + else + fatal(); + } + else if (op != NOP) + bytecode.addOpcode(op); + } + + public void atCastExpr(CastExpr expr) throws CompileError { + String cname = resolveClassName(expr.getClassName()); + String toClass = checkCastExpr(expr, cname); + int srcType = exprType; + exprType = expr.getType(); + arrayDim = expr.getArrayDim(); + className = cname; + if (toClass == null) + atNumCastExpr(srcType, exprType); // built-in type + else + bytecode.addCheckcast(toClass); + } + + public void atInstanceOfExpr(InstanceOfExpr expr) throws CompileError { + String cname = resolveClassName(expr.getClassName()); + String toClass = checkCastExpr(expr, cname); + bytecode.addInstanceof(toClass); + exprType = BOOLEAN; + arrayDim = 0; + } + + private String checkCastExpr(CastExpr expr, String name) + throws CompileError + { + final String msg = "invalid cast"; + ASTree oprand = expr.getOprand(); + int dim = expr.getArrayDim(); + int type = expr.getType(); + oprand.accept(this); + int srcType = exprType; + if (invalidDim(srcType, arrayDim, className, type, dim, name, true) + || srcType == VOID || type == VOID) + throw new CompileError(msg); + + if (type == CLASS) { + if (!isRefType(srcType)) + throw new CompileError(msg); + + return toJvmArrayName(name, dim); + } + else + if (dim > 0) + return toJvmTypeName(type, dim); + else + return null; // built-in type + } + + void atNumCastExpr(int srcType, int destType) + throws CompileError + { + if (srcType == destType) + return; + + int op, op2; + int stype = typePrecedence(srcType); + int dtype = typePrecedence(destType); + if (0 <= stype && stype < 3) + op = castOp[stype * 4 + dtype]; + else + op = NOP; + + if (destType == DOUBLE) + op2 = I2D; + else if (destType == FLOAT) + op2 = I2F; + else if (destType == LONG) + op2 = I2L; + else if (destType == SHORT) + op2 = I2S; + else if (destType == CHAR) + op2 = I2C; + else if (destType == BYTE) + op2 = I2B; + else + op2 = NOP; + + if (op != NOP) + bytecode.addOpcode(op); + + if (op == NOP || op == L2I || op == F2I || op == D2I) + if (op2 != NOP) + bytecode.addOpcode(op2); + } + + public void atExpr(Expr expr) throws CompileError { + // method call, array access, member access, + // (unary) +, (unary) -, ++, --, !, ~ + + int token = expr.getOperator(); + ASTree oprand = expr.oprand1(); + if (token == CALL) // method call + atMethodCall(expr); + else if (token == '.') + if (((Symbol)expr.oprand2()).get().equals("length")) + atArrayLength(expr); + else + atFieldRead(expr); + else if (token == MEMBER) { // field read + if (!atClassObject(expr)) // .class + atFieldRead(expr); + } + else if (token == ARRAY) + atArrayRead(oprand, expr.oprand2()); + else if (token == PLUSPLUS || token == MINUSMINUS) + atPlusPlus(token, oprand, expr, true); + else if (token == '!') { + booleanExpr(false, expr); + bytecode.addIndex(7); + bytecode.addIconst(1); + bytecode.addOpcode(Opcode.GOTO); + bytecode.addIndex(4); + bytecode.addIconst(0); + } + else { + expr.oprand1().accept(this); + int type = typePrecedence(exprType); + if (arrayDim > 0) + badType(expr); + + if (token == '-') { + if (type == P_DOUBLE) + bytecode.addOpcode(DNEG); + else if (type == P_FLOAT) + bytecode.addOpcode(FNEG); + else if (type == P_LONG) + bytecode.addOpcode(LNEG); + else if (type == P_INT) { + bytecode.addOpcode(INEG); + exprType = INT; // type may be BYTE, ... + } + else + badType(expr); + } + else if (token == '~') { + if (type == P_INT) { + bytecode.addIconst(-1); + bytecode.addOpcode(IXOR); + exprType = INT; // type may be BYTE. ... + } + else if (type == P_LONG) { + bytecode.addLconst(-1); + bytecode.addOpcode(LXOR); + } + else + badType(expr); + + } + else if (token == '+') { + if (type == P_OTHER) + badType(expr); + + // do nothing. ignore. + } + else + fatal(); + } + } + + protected static void badType(Expr expr) throws CompileError { + throw new CompileError("invalid type for " + expr.getName()); + } + + protected abstract void atMethodCall(Expr expr) throws CompileError; + + protected abstract void atFieldRead(ASTree expr) throws CompileError; + + public boolean atClassObject(Expr expr) throws CompileError { + if (!((Symbol)expr.oprand2()).get().equals("class")) + return false; + + if (resolveClassName((ASTList)expr.oprand1()) == null) + return false; + + throw new CompileError(".class is not supported"); + } + + public void atArrayLength(Expr expr) throws CompileError { + expr.oprand1().accept(this); + if (arrayDim == 0) + throw new CompileError(".length applied to a non array"); + + bytecode.addOpcode(ARRAYLENGTH); + exprType = INT; + arrayDim = 0; + } + + public void atArrayRead(ASTree array, ASTree index) + throws CompileError + { + int op; + arrayAccess(array, index); + bytecode.addOpcode(getArrayReadOp(exprType, arrayDim)); + } + + protected void arrayAccess(ASTree array, ASTree index) + throws CompileError + { + array.accept(this); + int type = exprType; + int dim = arrayDim; + if (dim == 0) + throw new CompileError("bad array access"); + + String cname = className; + + index.accept(this); + if (typePrecedence(exprType) != P_INT || arrayDim > 0) + throw new CompileError("bad array index"); + + exprType = type; + arrayDim = dim - 1; + className = cname; + } + + protected static int getArrayReadOp(int type, int dim) { + int op; + if (dim > 0) + return AALOAD; + + switch (type) { + case DOUBLE : + return DALOAD; + case FLOAT : + return FALOAD; + case LONG : + return LALOAD; + case INT : + return IALOAD; + case SHORT : + return SALOAD; + case CHAR : + return CALOAD; + case BYTE : + case BOOLEAN : + return BALOAD; + default : + return AALOAD; + } + } + + protected static int getArrayWriteOp(int type, int dim) { + int op; + if (dim > 0) + return AASTORE; + + switch (type) { + case DOUBLE : + return DASTORE; + case FLOAT : + return FASTORE; + case LONG : + return LASTORE; + case INT : + return IASTORE; + case CHAR : + return CASTORE; + case BYTE : + case BOOLEAN : + return BASTORE; + default : + return AASTORE; + } + } + + private void atPlusPlus(int token, ASTree oprand, Expr expr, + boolean doDup) throws CompileError + { + boolean isPost = oprand == null; // ++i or i++? + if (isPost) + oprand = expr.oprand2(); + + if (oprand instanceof Variable) { + Declarator d = ((Variable)oprand).getDeclarator(); + int t = exprType = d.getType(); + arrayDim = d.getArrayDim(); + int var = getLocalVar(d); + if (arrayDim > 0) + badType(expr); + + if (t == DOUBLE) { + bytecode.addDload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP2); + + bytecode.addDconst(1.0); + bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP2); + + bytecode.addDstore(var); + } + else if (t == LONG) { + bytecode.addLload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP2); + + bytecode.addLconst((long)1); + bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP2); + + bytecode.addLstore(var); + } + else if (t == FLOAT) { + bytecode.addFload(var); + if (doDup && isPost) + bytecode.addOpcode(DUP); + + bytecode.addFconst(1.0f); + bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB); + if (doDup && !isPost) + bytecode.addOpcode(DUP); + + bytecode.addFstore(var); + } + else if (t == BYTE || t == CHAR || t == SHORT || t == INT) { + if (doDup && isPost) + bytecode.addIload(var); + + bytecode.addOpcode(IINC); + bytecode.add(var); + bytecode.add(token == PLUSPLUS ? 1 : -1); + + if (doDup && !isPost) + bytecode.addIload(var); + } + else + badType(expr); + } + else { + if (oprand instanceof Expr) { + Expr e = (Expr)oprand; + if (e.getOperator() == ARRAY) { + atArrayPlusPlus(token, isPost, e, doDup); + return; + } + } + + atFieldPlusPlus(token, isPost, oprand, expr, doDup); + } + } + + public void atArrayPlusPlus(int token, boolean isPost, + Expr expr, boolean doDup) throws CompileError + { + arrayAccess(expr.oprand1(), expr.oprand2()); + int t = exprType; + int dim = arrayDim; + if (dim > 0) + badType(expr); + + bytecode.addOpcode(DUP2); + bytecode.addOpcode(getArrayReadOp(t, arrayDim)); + int dup_code = is2word(t, dim) ? DUP2_X2 : DUP_X2; + atPlusPlusCore(dup_code, doDup, token, isPost, expr); + bytecode.addOpcode(getArrayWriteOp(t, dim)); + } + + protected void atPlusPlusCore(int dup_code, boolean doDup, + int token, boolean isPost, + Expr expr) throws CompileError + { + int t = exprType; + + if (doDup && isPost) + bytecode.addOpcode(dup_code); + + if (t == INT || t == BYTE || t == CHAR || t == SHORT) { + bytecode.addIconst(1); + bytecode.addOpcode(token == PLUSPLUS ? IADD : ISUB); + exprType = INT; + } + else if (t == LONG) { + bytecode.addLconst((long)1); + bytecode.addOpcode(token == PLUSPLUS ? LADD : LSUB); + } + else if (t == FLOAT) { + bytecode.addFconst(1.0f); + bytecode.addOpcode(token == PLUSPLUS ? FADD : FSUB); + } + else if (t == DOUBLE) { + bytecode.addDconst(1.0); + bytecode.addOpcode(token == PLUSPLUS ? DADD : DSUB); + } + else + badType(expr); + + if (doDup && !isPost) + bytecode.addOpcode(dup_code); + } + + protected abstract void atFieldPlusPlus(int token, boolean isPost, + ASTree oprand, Expr expr, boolean doDup) throws CompileError; + + public abstract void atMember(Member n) throws CompileError; + + public void atVariable(Variable v) throws CompileError { + Declarator d = v.getDeclarator(); + exprType = d.getType(); + arrayDim = d.getArrayDim(); + className = d.getClassName(); + int var = getLocalVar(d); + + if (arrayDim > 0) + bytecode.addAload(var); + else + switch (exprType) { + case CLASS : + bytecode.addAload(var); + break; + case LONG : + bytecode.addLload(var); + break; + case FLOAT : + bytecode.addFload(var); + break; + case DOUBLE : + bytecode.addDload(var); + break; + default : // BOOLEAN, BYTE, CHAR, SHORT, INT + bytecode.addIload(var); + break; + } + } + + public void atKeyword(Keyword k) throws CompileError { + arrayDim = 0; + int token = k.get(); + switch (token) { + case TRUE : + bytecode.addIconst(1); + exprType = BOOLEAN; + break; + case FALSE : + bytecode.addIconst(0); + exprType = BOOLEAN; + break; + case NULL : + bytecode.addOpcode(ACONST_NULL); + exprType = NULL; + break; + case THIS : + case SUPER : + if (inStaticMethod) + throw new CompileError("not-available: " + + (token == THIS ? "this" : "super")); + + bytecode.addAload(0); + exprType = CLASS; + if (token == THIS) + className = getThisName(); + else + className = getSuperName(); + break; + default : + fatal(); + } + } + + public void atStringL(StringL s) throws CompileError { + exprType = CLASS; + arrayDim = 0; + className = "java/lang/String"; + bytecode.addLdc(s.get()); + } + + public void atIntConst(IntConst i) throws CompileError { + arrayDim = 0; + long value = i.get(); + int type = i.getType(); + if (type == IntConstant || type == CharConstant) { + exprType = (type == IntConstant ? INT : CHAR); + bytecode.addIconst((int)value); + } + else { + exprType = LONG; + bytecode.addLconst(value); + } + } + + public void atDoubleConst(DoubleConst d) throws CompileError { + arrayDim = 0; + if (d.getType() == DoubleConstant) { + exprType = DOUBLE; + bytecode.addDconst(d.get()); + } + else { + exprType = FLOAT; + bytecode.addFconst((float)d.get()); + } + } +} |