/* ____ ____ ____ ____ ______ *\ ** / __// __ \/ __// __ \/ ____/ SOcos COmpiles Scala ** ** __\_ \/ /_/ / /__/ /_/ /\_ \ (c) 2002, LAMP/EPFL ** ** /_____/\____/\___/\____/____/ ** \* */ // $Id$ // TODO: create arrays with ANEWARRAY & friends // TODO: (maybe) add InnerClass attributes to .class files package scalac.backend.jvm; import ch.epfl.lamp.util.Position; import scalac.*; import scalac.backend.*; import scalac.util.*; import scalac.ast.*; import scalac.symtab.*; import scalac.symtab.classfile.ClassfileConstants; import scalac.transformer.*; import org.apache.bcel.*; import org.apache.bcel.generic.*; import org.apache.bcel.classfile.*; import org.apache.bcel.generic.Type; import java.util.*; import java.io.*; /** * Backend generating JVM byte-codes. * * @version 1.0 * @author Michel Schinz */ class GenJVMBCEL { protected final static String JAVA_LANG_OBJECT = "java.lang.Object"; protected final static String JAVA_LANG_STRING = "java.lang.String"; protected final static String JAVA_LANG_STRINGBUFFER = "java.lang.StringBuffer"; protected final static String SCALA_RUNTIME_RUNTIME = "scala.runtime.RunTime"; protected final static String SCALA_UNIT = "scala.Unit"; protected final static String MODULE_INSTANCE_FIELD_NAME = "MODULE$"; protected final static String VOID_NO_ARGS_SIG = Type.getMethodSignature(Type.VOID, Type.NO_ARGS); protected final static String[] EMPTY_STRING_ARRAY = new String[0]; protected final static String UNIT_SIG = (new ObjectType(SCALA_UNIT)).getSignature(); protected final static String CONSTRUCTOR_STRING = Constants.CONSTRUCTOR_NAME; protected final static Name CONSTRUCTOR_NAME = Name.fromString(CONSTRUCTOR_STRING); protected Unit unit = null; protected String sourceFileName = null; // Shortcut names for interfaces. protected final static InstructionConstants ic = null; protected final static Constants cst = null; protected final Global global; protected final Definitions defs; protected final Primitives prims; public GenJVMBCEL(Global global) { this.global = global; this.defs = global.definitions; this.prims = global.primitives; initTypeMap(); initArithPrimMap(); } public void translate(Unit unit) { this.unit = unit; sourceFileName = unit.source.toString(); for (int i = 0; i < unit.body.length; ++i) gen(unit.body[i]); sourceFileName = null; this.unit = null; } // Context protected ClassGen currClass = null; protected String currClassName = null; protected ConstantPoolGen currPool = null; protected MethodGen currMethod = null; protected InstructionList currIL = null; protected Map currLocals = null; protected boolean isModuleClass = false; static class InstrContext { public case Empty; public case New; public case Assign; public case If(InstructionHandle target, boolean when); } // Line numbers attribution protected HashMap/**/ lineAttributedInstrs; protected void gen(Tree tree) { gen(tree, cst.T_VOID, InstrContext.Empty); } protected void gen(Tree tree, byte expectedType) { gen(tree, expectedType, InstrContext.Empty); } protected void gen(Tree tree, InstrContext ctx) { gen(tree, cst.T_VOID, ctx); } protected void gen(Tree[] trees) { for (int i = 0; i < trees.length; ++i) gen(trees[i]); } protected void gen(Tree[] trees, byte expectedType) { for (int i = 0; i < trees.length; ++i) gen(trees[i], expectedType); } protected void gen(Tree[] trees, InstrContext ctx) { for (int i = 0; i < trees.length; ++i) gen(trees[i], ctx); } protected void gen(Tree[] trees, byte expectedType, InstrContext ctx) { for (int i = 0; i < trees.length; ++i) gen(trees[i], expectedType, ctx); } protected void gen(Tree tree, byte expectedType, InstrContext ctx) { Symbol sym = tree.symbol(); byte generatedType = cst.T_VOID; // Remember first instruction associated to this tree, to // generate line numbers. InstructionHandle startHandle; if (currIL != null) startHandle = currIL.getEnd(); else startHandle = null; switch (tree) { case PackageDef(_, Tree.Template impl): gen(impl); break; case ClassDef(_, _, _, _, _, Tree.Template impl) : { Tree.ClassDef classDef = (Tree.ClassDef)tree; boolean oldIsModuleClass = isModuleClass; isModuleClass = Modifiers.Helper.isModClass(sym.flags); enterClass(sym); addValueClassMembers(classDef); if (isModuleClass) addModuleInstanceField(); gen(impl); leaveClass(sym); isModuleClass = oldIsModuleClass; } break; case Template(_, Tree[] body): gen(body); break; case ValDef(_, Name name, _, Tree rhs): { if (currMethod == null) break; // ignore ValDefs in classes, handled elsewhere Type valType = typeStoJ(sym.info()); LocalVariableGen lGen = currMethod.addLocalVariable(name.toString(), valType, currIL.getEnd(), null); int index = lGen.getIndex(); if (rhs != Tree.Empty) gen(rhs, valType.getType()); else { switch (valType.getType()) { case cst.T_BOOLEAN: case cst.T_BYTE: case cst.T_CHAR: case cst.T_SHORT: case cst.T_INT: currIL.append(new PUSH(currPool, 0)); break; case cst.T_LONG: currIL.append(new PUSH(currPool, 0L)); break; case cst.T_FLOAT: currIL.append(new PUSH(currPool, 0F)); break; case cst.T_DOUBLE: currIL.append(new PUSH(currPool, 0D)); break; default: currIL.append(ic.ACONST_NULL); break; } } currIL.append(new Generic_STORE(index, valType)); currLocals.put(sym, new Integer(index)); } break; case DefDef(_, _, _, _, _, Tree rhs): { enterMethod((Tree.DefDef)tree); if (! Modifiers.Helper.isAbstract(sym.flags)) { Type retType = currMethod.getReturnType(); gen(rhs, retType.getType()); currIL.append(new Generic_RETURN(retType)); } leaveMethod(); } break; case LabelDef(_, _, _): global.fail("not implemented yet " + tree); break; case Block(Tree[] stats): { int statsNum = stats.length; for (int i = 0; i < statsNum - 1; ++i) gen(stats[i], cst.T_VOID); if (statsNum == 0) maybeLoadUnit(expectedType); else gen(stats[stats.length - 1], expectedType, ctx); generatedType = expectedType; } break; case Typed(Tree expr, _): gen(expr, expectedType, ctx); generatedType = expectedType; break; case New(Tree.Template templ): { assert templ.body.length == 0; assert templ.parents.length == 1; String className = javaName(tree.type.symbol()); currIL.append(new NEW(currPool.addClass(className))); currIL.append(ic.DUP); gen(templ.parents[0], InstrContext.New); generatedType = cst.T_OBJECT; } break; case Apply(TypeApply(Tree fun, Tree[] args), _): { Type type = typeStoJ(args[0].type); int typeIndex; if (type instanceof ObjectType) typeIndex = currPool.addClass((ObjectType)type); else if (type instanceof ArrayType) typeIndex = currPool.addArrayClass((ArrayType)type); else throw global.fail("unexpected type " + type); genLoadQualifier(fun); if (fun.symbol() == defs.IS) { currIL.append(new INSTANCEOF(typeIndex)); generatedType = cst.T_BOOLEAN; } else if (fun.symbol() == defs.AS) { currIL.append(new CHECKCAST(typeIndex)); generatedType = type.getType(); } else global.fail("unexpected type application"); } break; case Apply(Tree fun, Tree[] args): { if (isPrimitive(fun.symbol())) { Tree.Select selectFun = (Tree.Select)fun; Primitive prim = prims.getPrimitive(fun.symbol()); if (prim == Primitive.CONCAT) { genStringConcatenation(liftStringConcatenations(tree)); generatedType = cst.T_OBJECT; } else { Tree[] allArgs = new Tree[args.length + 1]; allArgs[0] = unbox(selectFun.qualifier); System.arraycopy(args, 0, allArgs, 1, args.length); generatedType = genPrimitive(prim, allArgs, typeStoJ(tree.type).getType(), expectedType, ctx); } } else { Symbol funSym = fun.symbol(); Type[] argTypes = argTypesStoJ(funSym.info()); Type retType = retTypeStoJ(funSym.info()); boolean isStatic = isStaticMember(funSym); if (!isStatic && ctx != InstrContext.New) genLoadQualifier(fun); for (int i = 0; i < args.length; ++i) gen(args[i], argTypes[i].getType()); String className = javaName(funSym.owner()); String methodName = funSym.name.toString(); String methodSig = Type.getMethodSignature(retType, argTypes); if (funSym.owner().isInterface()) { int methodIndex = currPool.addInterfaceMethodref(className, methodName, methodSig); int argsSize = 1; for (int i = 0; i < args.length; ++i) argsSize += argTypes[i].getSize(); currIL.append(new INVOKEINTERFACE(methodIndex, argsSize)); } else { int methodIndex = currPool.addMethodref(className, methodName, methodSig); boolean isConstrCall = (funSym.name == CONSTRUCTOR_NAME); boolean isSuperCall; switch (fun) { case Select(Super(_), _): isSuperCall = true; break; default: isSuperCall = false; break; } if (isConstrCall || isSuperCall) { currIL.append(new INVOKESPECIAL(methodIndex)); if (isConstrCall && isSuperCall && isModuleClass) { // Initialise module instance field ASAP String currClassSig = new ObjectType(currClassName).getSignature(); int fieldRef = currPool.addFieldref(currClassName, MODULE_INSTANCE_FIELD_NAME, currClassSig); currIL.append(ic.THIS); currIL.append(new PUTSTATIC(fieldRef)); } } else if (isStatic) currIL.append(new INVOKESTATIC(methodIndex)); else currIL.append(new INVOKEVIRTUAL(methodIndex)); } generatedType = retType.getType(); } } break; case Ident(Name name): { Type type = typeStoJ(sym.info()); if (sym.isModule()) generatedType = genLoadModule(sym); else if (sym == defs.NULL) { currIL.append(ic.ACONST_NULL); generatedType = expectedType; } else if (sym.owner().isClass()) { currIL.append(ic.THIS); int fieldIdx = currPool.addFieldref(currClassName, name.toString(), type.getSignature()); if (ctx == InstrContext.Assign) { currIL.append(new PUTFIELD(fieldIdx)); generatedType = cst.T_VOID; } else { currIL.append(new GETFIELD(fieldIdx)); generatedType = type.getType(); } } else { assert currLocals.containsKey(sym) : Debug.show(sym) + " not in " + currLocals; int pos = ((Integer)currLocals.get(sym)).intValue(); if (ctx == InstrContext.Assign) { currIL.append(new Generic_STORE(pos, type)); generatedType = cst.T_VOID; } else { currIL.append(new Generic_LOAD(pos, type)); generatedType = type.getType(); } } } break; case Select(Tree qualifier, Name selector): { if (sym.isModule()) generatedType = genLoadModule(sym); else { Type fieldType = typeStoJ(sym.info()); int fieldIdx = currPool.addFieldref(javaName(sym.owner()), selector.toString(), fieldType.getSignature()); if (isStaticMember(sym)) { if (ctx == InstrContext.Assign) { currIL.append(new PUTSTATIC(fieldIdx)); generatedType = cst.T_VOID; } else { currIL.append(new GETSTATIC(fieldIdx)); generatedType = fieldType.getType(); } } else { genLoadQualifier(tree); if (ctx == InstrContext.Assign) { currIL.append(new PUTFIELD(fieldIdx)); generatedType = cst.T_VOID; } else { currIL.append(new GETFIELD(fieldIdx)); generatedType = fieldType.getType(); } } } } break; case Assign(Tree lhs, Tree rhs): { gen(lhs, InstrContext.Assign); InstructionHandle storeHandle = currIL.getEnd(); gen(rhs, typeStoJ(lhs.symbol().info()).getType()); // Work around BCEL bug (see below) currIL.move(storeHandle, currIL.append(ic.NOP).getPrev()); } break; case If(Tree cond, Tree thenp, Tree elsep): { byte finalType = typeStoJ(tree.type).getType(); InstructionHandle fakeElseH = currIL.append(ic.NOP); gen(cond, cst.T_VOID, new InstrContext.If(fakeElseH, false)); InstructionHandle thenH = currIL.append(ic.NOP); gen(thenp, finalType); BranchInstruction gotoAfter = new GOTO(null); currIL.append(gotoAfter); InstructionHandle elseH = currIL.append(ic.NOP); if (elsep == Tree.Empty) maybeLoadUnit(finalType); else gen(elsep, finalType); gotoAfter.setTarget(currIL.append(ic.NOP)); // We cannot move the instructions sooner because BCEL has // a bug which makes it impossible to move instructions at // the end of the list. currIL.move(fakeElseH, elseH); generatedType = finalType; } break; case This(_): currIL.append(ic.THIS); generatedType = cst.T_OBJECT; break; case Literal(Object value): if (value instanceof Integer) { generatedType = cst.T_INT; currIL.append(new PUSH(currPool, (Integer)value)); } else if (value instanceof Long) { generatedType = cst.T_LONG; currIL.append(new PUSH(currPool, (Long)value)); } else if (value instanceof Float) { generatedType = cst.T_FLOAT; currIL.append(new PUSH(currPool, (Float)value)); } else if (value instanceof Double) { generatedType = cst.T_DOUBLE; currIL.append(new PUSH(currPool, (Double)value)); } else if (value instanceof Character) { generatedType = cst.T_CHAR; currIL.append(new PUSH(currPool, (Character)value)); } else if (value instanceof String) { generatedType = cst.T_OBJECT; currIL.append(new PUSH(currPool, (String)value)); } else if (value instanceof Boolean) { generatedType = cst.T_BOOLEAN; currIL.append(new PUSH(currPool, (Boolean)value)); } else throw global.fail("unknown literal " + value); break; case Empty: case AbsTypeDef(_, _, _, _): case AliasTypeDef(_, _, _, _): case TypeApply(_, _): case FunType(_, _): case CompoundType(_, _): case AppliedType(_,_): break; case Sequence(_): case Super(_): case ModuleDef(_,_,_,_): case PatDef(_,_,_): case Import(_, _): case CaseDef(_, _, _): case Visitor(_): case Function(_, _): throw global.fail("unexpected node", tree); case Bad(): throw global.fail("bad tree"); default: throw global.fail("unknown node", tree); } // Pop unneeded result from stack, or widen it if needed. if (expectedType == cst.T_VOID && generatedType != cst.T_VOID) { if (generatedType == cst.T_LONG || generatedType == cst.T_DOUBLE) currIL.append(ic.POP2); else { switch (ctx) { case If(InstructionHandle target, boolean when): assert generatedType == cst.T_BOOLEAN : generatedType; currIL.append(when ? new IFNE(target) : new IFEQ(target)); break; default: currIL.append(ic.POP); } } } else if (! (expectedType == cst.T_VOID || generatedType == expectedType || (generatedType == cst.T_ARRAY && expectedType == cst.T_OBJECT))) genWidenConversion(generatedType, expectedType); // Associate line numbers to instructions we just generated. if (currIL != null) { InstructionHandle ih = (startHandle == null ? currIL.getStart() : startHandle); int prevLine = -1; while (ih != null) { if (lineAttributedInstrs.containsKey(ih)) prevLine = ((Integer)lineAttributedInstrs.get(ih)).intValue(); else { int line = Position.line(tree.pos); lineAttributedInstrs.put(ih, new Integer(line)); if (line != prevLine) { currMethod.addLineNumber(ih, line); prevLine = line; } } ih = ih.getNext(); } } } protected Tree unbox(Tree tree) { switch (tree) { case Apply(Tree fun, Tree[] args): if (prims.getPrimitive(fun.symbol()) == Primitive.BOX) { assert args.length == 1; return args[0]; } else return tree; case Block(Tree[] stats): if (stats.length == 2 && prims.getPrimitive(stats[1].symbol()) == Primitive.BOX) { return stats[0]; } else return tree; default: return tree; } } // Add field containing module instance, and code to // initialize it, to current class. protected void addModuleInstanceField() { FieldGen instanceField = new FieldGen(cst.ACC_PUBLIC | cst.ACC_FINAL | cst.ACC_STATIC, new ObjectType(currClassName), MODULE_INSTANCE_FIELD_NAME, currPool); currClass.addField(instanceField.getField()); InstructionList initIL = new InstructionList(); int constrRef = currPool.addMethodref(currClassName, CONSTRUCTOR_STRING, VOID_NO_ARGS_SIG); initIL.append(new NEW(currPool.addClass(currClassName))); initIL.append(new INVOKESPECIAL(constrRef)); initIL.append(ic.RETURN); MethodGen initMethod = new MethodGen(cst.ACC_PUBLIC | cst.ACC_STATIC, Type.VOID, Type.NO_ARGS, Strings.EMPTY_ARRAY, "", currClassName, initIL, currPool); initMethod.setMaxStack(); currClass.addMethod(initMethod.getMethod()); } // Add value members (i.e. fields) to current class. protected void addValueClassMembers(Tree.ClassDef cDef) { Symbol cSym = cDef.symbol(); Scope.SymbolIterator memberIt = new Scope.UnloadIterator(cSym.members().iterator()); while (memberIt.hasNext()) { Symbol member = memberIt.next(); if (member.isTerm() && !member.isMethod()) { FieldGen fGen = new FieldGen(modifiersStoJ(member.flags), typeStoJ(member.info()), member.name.toString(), currPool); currClass.addField(fGen.getField()); } } } protected void maybeLoadUnit(byte expectedType) { if (expectedType == cst.T_OBJECT) { int unitFieldRef = currPool.addFieldref(SCALA_RUNTIME_RUNTIME, "UNIT_VAL", UNIT_SIG); currIL.append(new GETSTATIC(unitFieldRef)); } } protected byte genLoadModule(Symbol sym) { String javaSymName = javaName(sym.moduleClass()); if (javaSymName.equals(currClassName)) currIL.append(ic.THIS); else { int moduleInstIdx = currPool.addFieldref(javaSymName, MODULE_INSTANCE_FIELD_NAME, typeStoJ(sym.info()).getSignature()); currIL.append(new GETSTATIC(moduleInstIdx)); } return cst.T_OBJECT; } protected void genLoadQualifier(Tree tree) { switch (tree) { case Select(Super(_), _): case Ident(_): currIL.append(ic.THIS); break; case Select(Tree qualifier, _): gen(qualifier, cst.T_OBJECT); break; default: throw global.fail("unknown qualifier"); } } protected boolean isStaticMember(Symbol sym) { return (sym.name != CONSTRUCTOR_NAME) && sym.owner().isModuleClass() && sym.owner().isJava(); } protected boolean isPrimitive(Symbol sym) { if (prims.isPrimitive(sym)) { switch (prims.getPrimitive(sym)) { case POS : case NEG : case ADD : case SUB : case MUL : case DIV : case MOD : case NOT : case OR : case XOR : case AND : case LSL : case LSR : case ASR : case EQ : case NE : case LT : case LE : case GE : case GT : case ZNOT : case ZOR : case ZAND : case NEW_ZARRAY : case NEW_BARRAY : case NEW_SARRAY : case NEW_CARRAY : case NEW_IARRAY : case NEW_LARRAY : case NEW_FARRAY : case NEW_DARRAY : case ZARRAY_GET : case BARRAY_GET : case SARRAY_GET : case CARRAY_GET : case IARRAY_GET : case LARRAY_GET : case FARRAY_GET : case DARRAY_GET : case OARRAY_GET : case ZARRAY_SET : case BARRAY_SET : case SARRAY_SET : case CARRAY_SET : case IARRAY_SET : case LARRAY_SET : case FARRAY_SET : case DARRAY_SET : case OARRAY_SET : case ZARRAY_LENGTH : case BARRAY_LENGTH : case SARRAY_LENGTH : case CARRAY_LENGTH : case IARRAY_LENGTH : case LARRAY_LENGTH : case FARRAY_LENGTH : case DARRAY_LENGTH : case OARRAY_LENGTH : case IS : case AS : case CONCAT : case THROW : case AS_UVALUE : return true; case AS_ZVALUE : case AS_BVALUE : case AS_SVALUE : case AS_CVALUE : case AS_IVALUE : case AS_LVALUE : case AS_FVALUE : case AS_DVALUE : case AS_ZARRAY : case AS_BARRAY : case AS_SARRAY : case AS_CARRAY : case AS_IARRAY : case AS_LARRAY : case AS_FARRAY : case AS_DARRAY : case AS_OARRAY : case NEW_OARRAY : case EQUALS : case HASHCODE : case TOSTRING : case BOX : case APPLY : case UPDATE : case LENGTH : return false; default: throw Debug.abort("unknown primitive", sym); } } else return false; } protected byte genPrimitive(Primitive prim, Tree[] args, byte resType, byte expectedType, InstrContext ctx) { switch (prim) { case POS: case NEG: case ADD: case SUB: case MUL: case DIV: case MOD: case NOT: case OR : case XOR: case AND: case LSL: case LSR: case ASR: return genArithPrim(prim, args, resType, expectedType, ctx); case EQ: case NE: case LT: case LE: case GE: case GT: case ZNOT: case ZOR: case ZAND: return genCompOrLogicalPrim(prim, args, resType, expectedType, ctx); case THROW: assert args.length == 1; return genThrow(args[0]); case NEW_ZARRAY : case NEW_BARRAY : case NEW_SARRAY : case NEW_CARRAY : case NEW_IARRAY : case NEW_LARRAY : case NEW_FARRAY : case NEW_DARRAY : return genArrayCreate(prim, args[1]); // case NEW_OARRAY : // return genArrayCreate(prim, args[1], args[2]); case ZARRAY_SET : case BARRAY_SET : case SARRAY_SET : case CARRAY_SET : case IARRAY_SET : case LARRAY_SET : case FARRAY_SET : case DARRAY_SET : case OARRAY_SET : assert args.length == 4; return genArrayUpdate(args[1], args[2], args[3]); case ZARRAY_GET : case BARRAY_GET : case SARRAY_GET : case CARRAY_GET : case IARRAY_GET : case LARRAY_GET : case FARRAY_GET : case DARRAY_GET : case OARRAY_GET : assert args.length == 3 : "get - " + args.length; return genArrayAccess(args[1], args[2]); case ZARRAY_LENGTH : case BARRAY_LENGTH : case SARRAY_LENGTH : case CARRAY_LENGTH : case IARRAY_LENGTH : case LARRAY_LENGTH : case FARRAY_LENGTH : case DARRAY_LENGTH : case OARRAY_LENGTH : assert args.length == 2 : args.length; return genArrayLength(args[1]); case AS_UVALUE : assert args.length == 1; gen(args[0], cst.T_VOID); return cst.T_VOID; default: throw Debug.abort("unknown primitive ", prim); } } protected Map/**/ arithPrimMap; protected void addPrim(Primitive prim, Instruction z, Instruction i, Instruction l, Instruction f, Instruction d) { arithPrimMap.put(prim, new Instruction[] { z, i, l, f, d }); } protected void initArithPrimMap() { arithPrimMap = new HashMap(); /* boolean int ... long float double */ addPrim(Primitive.ADD , null, ic.IADD , ic.LADD , ic.FADD , ic.DADD); addPrim(Primitive.SUB , null, ic.ISUB , ic.LSUB , ic.FSUB , ic.DSUB); addPrim(Primitive.MUL , null, ic.IMUL , ic.LMUL , ic.FMUL , ic.DMUL); addPrim(Primitive.DIV , null, ic.IDIV , ic.LDIV , ic.FDIV , ic.DDIV); addPrim(Primitive.MOD , null, ic.IREM , ic.LREM , ic.FREM , ic.DREM); addPrim(Primitive.AND , ic.IAND, ic.IAND , ic.LAND , null , null); addPrim(Primitive.OR , ic.IOR, ic.IOR , ic.LOR , null , null); addPrim(Primitive.XOR , ic.IXOR, ic.IXOR , ic.LXOR , null , null); addPrim(Primitive.LSL , null, ic.ISHL , ic.LSHL , null , null); addPrim(Primitive.LSR , null, ic.IUSHR , ic.LUSHR , null , null); addPrim(Primitive.ASR , null, ic.ISHR , ic.LSHR , null , null); addPrim(Primitive.POS , null, null , null , null , null); addPrim(Primitive.NEG , null, ic.INEG , ic.LNEG , ic.FNEG , ic.DNEG); } protected byte genArithPrim(Primitive prim, Tree[] args, byte resType, byte expectedType, InstrContext ctx) { int arity = args.length; int resTypeIdx = getTypeIndex(resType); for (int i = 0; i < arity; ++i) gen(args[i], resType); if (prim == Primitive.NOT) { assert resType == cst.T_INT || resType == cst.T_LONG; boolean isLong = (resType == cst.T_LONG); if (isLong) { currIL.append(new PUSH(currPool, -1L)); currIL.append(ic.LXOR); } else { currIL.append(new PUSH(currPool, -1)); currIL.append(ic.IXOR); } } else { assert arithPrimMap.containsKey(prim); Instruction primInst = ((Instruction[])arithPrimMap.get(prim))[resTypeIdx]; if (primInst != null) currIL.append(primInst); } return resType; } protected byte genCompOrLogicalPrim(Primitive prim, Tree[] args, byte resType, byte expectedType, InstrContext ctx) { // Ensure that all comparisons happen in the context of an // "if". InstructionHandle target; boolean when; InstructionList epilogue = new InstructionList(); byte realResType; switch (ctx) { case InstrContext.If(InstructionHandle t, boolean w): target = t; when = w; realResType = cst.T_VOID; break; default: epilogue.append(ic.ICONST_1); BranchInstruction gotoAfter = new GOTO(null); epilogue.append(gotoAfter); target = epilogue.append(ic.ICONST_0); gotoAfter.setTarget(epilogue.append(ic.NOP)); when = false; realResType = cst.T_BOOLEAN; break; } if (prim == Primitive.ZNOT || prim == Primitive.ZOR || prim == Primitive.ZAND) genLogicalPrim(prim, args, resType, expectedType, target, when); else genCompPrim(prim, args, resType, expectedType, target, when); currIL.append(epilogue); return realResType; } protected byte getMaxType(Tree[] trees) { byte maxType = cst.T_BOOLEAN; int maxTypeIdx = getTypeIndex(maxType); for (int i = 0; i < trees.length; ++i) { byte argType = typeStoJ(trees[i].type).getType(); if (getTypeIndex(argType) > maxTypeIdx) { maxType = argType; maxTypeIdx = getTypeIndex(maxType); } } return maxType; } protected static int tempCounter = 1; protected void genCompPrim(Primitive prim, Tree[] args, byte resType, byte expectedType, InstructionHandle target, boolean when) { byte maxType = getMaxType(args); int maxTypeIdx = getTypeIndex(maxType); int intTypeIdx = getTypeIndex(Type.INT); boolean intCompareWithZero = false; for (int i = 0; i < args.length; ++i) { boolean isIntZero = false; if (maxTypeIdx <= intTypeIdx) { switch (args[i]) { case Literal(Object val): int intVal; if (val instanceof Number) intVal = ((Number)val).intValue(); else if (val instanceof Character) intVal = ((Character)val).charValue(); else if (val instanceof Boolean) intVal = ((Boolean)val).booleanValue() ? 1 : 0; else throw Debug.abort("unknown literal", val); if (intVal == 0) { isIntZero = true; if (i == 0) prim = prim.swap(); } } } if (intCompareWithZero || !isIntZero) gen(args[i], maxType); intCompareWithZero |= isIntZero; } if (maxType == cst.T_OBJECT) { assert prim == Primitive.EQ || prim == Primitive.NE; LocalVariableGen lGen = currMethod.addLocalVariable("temp" + tempCounter++, Type.OBJECT, currIL.getEnd(), null); currIL.append(new ASTORE(lGen.getIndex())); currIL.append(ic.DUP); BranchInstruction ifNonNull = new IFNONNULL(null); currIL.append(ifNonNull); currIL.append(ic.POP); currIL.append(new ALOAD(lGen.getIndex())); if (when ^ (prim != Primitive.EQ)) currIL.append(new IFNULL(target)); else currIL.append(new IFNONNULL(target)); BranchInstruction gotoAfter = new GOTO(null); currIL.append(gotoAfter); InstructionHandle nonNullHandle = currIL.append(new ALOAD(lGen.getIndex())); ifNonNull.setTarget(nonNullHandle); lGen.setEnd(nonNullHandle); String equalsSig = Type.getMethodSignature(Type.BOOLEAN, new Type[] { Type.OBJECT }); int equalsIndex = currPool.addMethodref(JAVA_LANG_OBJECT, "equals", equalsSig); currIL.append(new INVOKEVIRTUAL(equalsIndex)); if (when ^ (prim != Primitive.EQ)) currIL.append(new IFNE(target)); else currIL.append(new IFEQ(target)); gotoAfter.setTarget(currIL.append(ic.NOP)); } else if (maxTypeIdx <= intTypeIdx && !intCompareWithZero) { switch (maybeNegatedPrim(prim, !when)) { case LT: currIL.append(new IF_ICMPLT(target)); break; case LE: currIL.append(new IF_ICMPLE(target)); break; case EQ: currIL.append(new IF_ICMPEQ(target)); break; case NE: currIL.append(new IF_ICMPNE(target)); break; case GE: currIL.append(new IF_ICMPGE(target)); break; case GT: currIL.append(new IF_ICMPGT(target)); break; default: throw global.fail("unknown primitive " + prim); } } else { switch (maxType) { case cst.T_LONG: currIL.append(ic.LCMP); break; case cst.T_FLOAT: currIL.append(ic.FCMPG); break; case cst.T_DOUBLE: currIL.append(ic.DCMPG); break; default: ; // do nothing (int comparison with 0) } switch (maybeNegatedPrim(prim, !when)) { case LT: currIL.append(new IFLT(target)); break; case LE: currIL.append(new IFLE(target)); break; case EQ: currIL.append(new IFEQ(target)); break; case NE: currIL.append(new IFNE(target)); break; case GE: currIL.append(new IFGE(target)); break; case GT: currIL.append(new IFGT(target)); break; default: throw global.fail("unknown primitive " + prim); } } } protected Primitive maybeNegatedPrim(Primitive prim, boolean negate) { return negate ? prim.negate() : prim; } protected void genLogicalPrim(Primitive prim, Tree[] args, byte resType, byte expectedType, InstructionHandle target, boolean when) { if (prim == Primitive.ZNOT) gen(args[0], new InstrContext.If(target, !when)); else { InstructionHandle fakeAfterH = currIL.append(ic.NOP); if (when ^ (prim == Primitive.ZAND)) { // x || y jump if true or x && y jump if false gen(args[0], new InstrContext.If(target, when)); gen(args[1], new InstrContext.If(target, when)); } else { // x || y jump if false or x && y jump if true gen(args[0], new InstrContext.If(fakeAfterH, !when)); gen(args[1], new InstrContext.If(target, when)); currIL.move(fakeAfterH, currIL.append(ic.NOP).getPrev()); } } } protected byte genThrow(Tree arg) { gen(arg, cst.T_OBJECT); currIL.append(new CHECKCAST(currPool.addClass("java.lang.Throwable"))); currIL.append(ic.ATHROW); return cst.T_OBJECT; } protected byte genArrayCreate(Primitive prim, Tree size) { gen(size, cst.T_INT); byte type; switch (prim) { case NEW_ZARRAY : type = cst.T_BOOLEAN; break; case NEW_BARRAY : type = cst.T_BYTE; break; case NEW_SARRAY : type = cst.T_SHORT; break; case NEW_CARRAY : type = cst.T_CHAR; break; case NEW_IARRAY : type = cst.T_INT; break; case NEW_LARRAY : type = cst.T_LONG; break; case NEW_FARRAY : type = cst.T_FLOAT; break; case NEW_DARRAY : type = cst.T_DOUBLE; break; default: throw Debug.abort("unexpected primitive", prim); } currIL.append(new NEWARRAY(type)); return cst.T_ARRAY; } protected byte genArrayUpdate(Tree array, Tree index, Tree value) { ArrayType arrayType = (ArrayType)typeStoJ(array.type); Type elemType = arrayType.getElementType(); gen(array, cst.T_ARRAY); gen(index, cst.T_INT); if (elemType instanceof BasicType) value = unbox(value); gen(value, elemType.getType()); currIL.append(new Generic_ASTORE(elemType)); return cst.T_VOID; } protected byte genArrayAccess(Tree array, Tree index) { ArrayType arrayType = (ArrayType)typeStoJ(array.type); Type elemType = arrayType.getElementType(); gen(array, cst.T_ARRAY); gen(index, cst.T_INT); currIL.append(new Generic_ALOAD(elemType)); return elemType.getType(); } protected byte genArrayLength(Tree array) { gen(array, cst.T_ARRAY); currIL.append(ic.ARRAYLENGTH); return cst.T_INT; } protected Tree[] liftStringConcatenations(Tree tree) { LinkedList accu = new LinkedList(); liftStringConcatenations(tree, accu); return (Tree[])accu.toArray(new Tree[accu.size()]); } protected void liftStringConcatenations(Tree tree, LinkedList accu) { switch (tree) { case Apply(Select(Tree qualifier, Name selector), Tree[] args): { Symbol funSym = ((Tree.Apply)tree).fun.symbol(); if (prims.isPrimitive(funSym) && prims.getPrimitive(funSym) == Primitive.CONCAT) { liftStringConcatenations(qualifier, accu); liftStringConcatenations(args[0], accu); } else accu.addLast(tree); } break; default: accu.addLast(tree); } } protected void genStringConcatenation(Tree[] elements) { ObjectType strBufType = new ObjectType (JAVA_LANG_STRINGBUFFER); int constRef = currPool.addMethodref(JAVA_LANG_STRINGBUFFER, "", VOID_NO_ARGS_SIG); currIL.append (new NEW (currPool.addClass (JAVA_LANG_STRINGBUFFER))); currIL.append (ic.DUP); currIL.append (new INVOKESPECIAL (constRef)); for (int i = 0; i < elements.length; ++i) { Type elemType = typeStoJ(elements[i].type); if (!elemType.equals(Type.STRING) && elemType.getType() == cst.T_OBJECT) elemType = Type.OBJECT; String appendSig = Type.getMethodSignature(strBufType, new Type[] { elemType }); int appendRef = currPool.addMethodref(JAVA_LANG_STRINGBUFFER, "append", appendSig); gen(elements[i], elemType.getType()); currIL.append(new INVOKEVIRTUAL(appendRef)); } String toStringSig = Type.getMethodSignature (Type.STRING, Type.NO_ARGS); final int toStringRef = currPool.addMethodref (JAVA_LANG_STRINGBUFFER, "toString", toStringSig); currIL.append (new INVOKEVIRTUAL (toStringRef)); } protected int getTypeIndex(Type tp) { return getTypeIndex(tp.getType()); } protected int getTypeIndex(byte tp) { switch (tp) { case cst.T_BOOLEAN: return 0; case cst.T_BYTE: case cst.T_CHAR: case cst.T_SHORT: case cst.T_INT: return 1; case cst.T_LONG: return 2; case cst.T_FLOAT: return 3; case cst.T_DOUBLE: return 4; case cst.T_ARRAY: case cst.T_OBJECT: return 5; default: return -1; } } protected Instruction[][] WIDENING_CONVERSION_TABLE = { /* bool int long float double */ /* boolean */ { null, null , null , null , null }, /* int ... */ { null, null , ic.I2L , ic.I2F , ic.I2D }, /* long */ { null, null , null , ic.L2F , ic.L2D }, /* float */ { null, null , null , null , ic.F2D }, /* double */ { null, null , null , null , null } }; protected void genWidenConversion(byte origType, byte finalType) { int origIdx = getTypeIndex(origType); int finalIdx = getTypeIndex(finalType); assert (origIdx <= 4 && finalIdx <= 4) : cst.TYPE_NAMES[origType] + " -> " + cst.TYPE_NAMES[finalType]; Instruction instr = WIDENING_CONVERSION_TABLE[origIdx][finalIdx]; if (instr != null) currIL.append(instr); } protected void dumpModuleMainClass(ClassGen modClassGen) { String moduleName = modClassGen.getClassName(); String mainClassName = moduleName.substring(0, moduleName.length() - 1); ClassGen mainClassGen = new ClassGen(mainClassName, JAVA_LANG_OBJECT, sourceFileName, cst.ACC_SUPER | cst.ACC_PUBLIC | cst.ACC_FINAL, Strings.EMPTY_ARRAY); ConstantPoolGen mainPool = mainClassGen.getConstantPool(); Method[] methods = modClassGen.getMethods(); for (int i = 0; i < methods.length; ++i) { Method m = methods[i]; if (m.isProtected() || m.isPrivate() || m.isStatic() || m.getName().equals("")) continue; MethodGen mGen = new MethodGen(m, moduleName, mainPool); Type[] argTypes = mGen.getArgumentTypes(); Type retType = mGen.getReturnType(); InstructionList mainIL = new InstructionList(); MethodGen mainMGen = new MethodGen(m.getAccessFlags() | cst.ACC_STATIC, retType, argTypes, mGen.getArgumentNames(), mGen.getName(), mainClassName, mainIL, mainPool); int moduleFieldIndex = mainPool.addFieldref(moduleName, MODULE_INSTANCE_FIELD_NAME, (new ObjectType(moduleName)).getSignature()); int mIndex = mainPool.addMethodref(mGen); mainIL.append(new GETSTATIC(moduleFieldIndex)); int pos = 0; for (int j = 0; j < argTypes.length; ++j) { mainIL.append(new Generic_LOAD(pos, argTypes[j])); pos += argTypes[j].getSize(); } mainIL.append(new INVOKEVIRTUAL(mIndex)); mainIL.append(new Generic_RETURN(retType)); mainMGen.setMaxStack(); mainClassGen.addMethod(mainMGen.getMethod()); } addScalaAttr(mainClassGen); JavaClass mainClass = mainClassGen.getJavaClass(); try { mainClass.dump(javaFileName(mainClassName)); } catch (java.io.IOException e) { throw global.fail(e.getMessage()); } } protected void addScalaAttr(ClassGen classGen) { ConstantPoolGen poolGen = classGen.getConstantPool(); int scalaNameIndex = poolGen.addUtf8(ClassfileConstants.SCALA_N.toString()); Unknown scalaAttr = new Unknown(scalaNameIndex, 0, null, poolGen.getConstantPool()); classGen.addAttribute(scalaAttr); } // Context manipulation protected LinkedList/**/ classStack = new LinkedList(); protected void enterClass(Symbol cSym) { String javaName = javaName(cSym); scalac.symtab.Type[] baseTps = cSym.info().parents(); assert baseTps.length > 0 : Debug.show(cSym); int offset; String superClassName; if (cSym.isInterface()) { offset = baseTps[0].isSameAs(defs.ANY_TYPE) ? 1 : 0; superClassName = JAVA_LANG_OBJECT; } else { offset = 1; superClassName = javaName(baseTps[0].symbol()); } String[] interfaceNames = new String[baseTps.length - offset]; for (int i = offset; i < baseTps.length; ++i) { Symbol baseSym = baseTps[i].symbol(); assert baseSym.isInterface() : cSym + " implements " + baseSym; interfaceNames[i - offset] = javaName(baseSym); } ClassGen cGen = new ClassGen(javaName, superClassName, sourceFileName, modifiersStoJ(cSym.flags) | cst.ACC_SUPER, interfaceNames); classStack.addFirst(cGen); updateClassContext(); } protected HashSet seenClasses = new HashSet(); protected void leaveClass(Symbol cSym) { if (isModuleClass) { if (!seenClasses.contains(cSym.fullName())) dumpModuleMainClass(currClass); } else seenClasses.add(cSym.fullName()); addScalaAttr(currClass); JavaClass cls = currClass.getJavaClass(); try { String fileName = javaFileName(cls.getClassName()); cls.dump(fileName); global.operation("wrote " + fileName); } catch (java.io.IOException e) { throw global.fail(e.getMessage()); } classStack.removeFirst(); updateClassContext(); } protected void updateClassContext() { if (classStack.isEmpty()) { currClass = null; currClassName = null; currPool = null; } else { ClassGen cGen = (ClassGen)classStack.getFirst(); currClass = cGen; currClassName = currClass.getClassName(); currPool = currClass.getConstantPool(); } } protected void enterMethod(Tree.DefDef dDef) { Symbol dSym = dDef.symbol(); global.log("entering method " + Debug.toString(dSym) + " (type: " + Debug.toString(dSym.info()) + ")"); Map locals; if (currLocals == null) locals = new HashMap(); else locals = new HashMap(currLocals); Tree.ValDef[] args = dDef.vparams[0]; int argsNum = args.length; Type[] argTypes = new Type[argsNum]; String[] argNames = new String[argsNum]; for (int i = 0, pos = 1; i < argsNum; ++i) { argTypes[i] = typeStoJ(args[i].symbol().info()); argNames[i] = args[i].name.toString(); locals.put(args[i].symbol(), new Integer(pos)); pos += argTypes[i].getSize(); } MethodGen mGen = new MethodGen(modifiersStoJ(dDef.mods), retTypeStoJ(dSym.info()), argTypes, argNames, dDef.name.toString(), currClassName, new InstructionList(), currPool); currMethod = mGen; currLocals = locals; currIL = currMethod.getInstructionList(); lineAttributedInstrs = new HashMap(); } protected void leaveMethod() { global.log(" leaving method"); currMethod.setMaxStack(); currMethod.removeNOPs(); currClass.addMethod(currMethod.getMethod()); currMethod = null; currLocals = null; currIL = null; lineAttributedInstrs = null; } protected int modifiersStoJ(int flags) { int jFlags = 0; if (Modifiers.Helper.isPrivate(flags)) jFlags |= cst.ACC_PRIVATE; else jFlags |= cst.ACC_PUBLIC; if (Modifiers.Helper.isAbstract(flags)) jFlags |= cst.ACC_ABSTRACT; if (Modifiers.Helper.isInterface(flags)) jFlags |= cst.ACC_INTERFACE; if (Modifiers.Helper.isFinal(flags) && !(Modifiers.Helper.isAbstract(flags) || Modifiers.Helper.isInterface(flags))) jFlags |= cst.ACC_FINAL; return jFlags; } protected boolean isUnboxedType(scalac.symtab.Type tp) { switch (tp) { case UnboxedType(_): case UnboxedArrayType(_): return true; default: return false; } } protected HashMap typeMap/**/ = new HashMap(); protected void initTypeMap() { typeMap.put(defs.ANY_CLASS, Type.OBJECT); typeMap.put(defs.ANYREF_CLASS, Type.OBJECT); } protected Type typeStoJ(scalac.symtab.Type tp) { switch (tp) { case UnboxedType(TypeTags.BYTE): return Type.BYTE; case UnboxedType(TypeTags.CHAR): return Type.CHAR; case UnboxedType(TypeTags.SHORT): return Type.SHORT; case UnboxedType(TypeTags.INT): return Type.INT; case UnboxedType(TypeTags.LONG): return Type.LONG; case UnboxedType(TypeTags.FLOAT): return Type.FLOAT; case UnboxedType(TypeTags.DOUBLE): return Type.DOUBLE; case UnboxedType(TypeTags.BOOLEAN): return Type.BOOLEAN; case UnboxedType(TypeTags.UNIT): return Type.VOID; case UnboxedType(TypeTags.STRING): return Type.STRING; case UnboxedArrayType(scalac.symtab.Type elementType): return new ArrayType(typeStoJ(elementType), 1); default: { Symbol sym = tp.symbol(); if (sym == Symbol.NONE) throw global.fail("invalid type ", tp); else if (typeMap.containsKey(sym)) return (Type)typeMap.get(sym); else { Type jTp = new ObjectType(javaName(sym)); typeMap.put(sym, jTp); return jTp; } } } } protected Type[] argTypesStoJ(scalac.symtab.Type tp) { switch (tp) { case MethodType(Symbol[] vparams, _): Type[] argTypes = new Type[vparams.length]; for (int i = 0; i < vparams.length; ++i) argTypes[i] = typeStoJ(vparams[i].info()); return argTypes; default: throw global.fail("invalid method type", tp); } } protected Type retTypeStoJ(scalac.symtab.Type tp) { switch (tp) { case MethodType(_, _): return typeStoJ(tp.resultType()); default: throw global.fail("invalid method type", tp); } } protected String javaName(Symbol sym) { assert sym.isClass() : Debug.show(sym); if (sym == defs.ANY_CLASS || sym == defs.ANYREF_CLASS) return JAVA_LANG_OBJECT; else { StringBuffer buf = new StringBuffer(sym.name.toString()); if ((sym.isModule() || sym.isModuleClass()) && !sym.isJava()) buf.append('$'); for (sym = sym.owner(); !sym.isPackage(); sym = sym.owner()) { buf.insert(0, '$'); buf.insert(0, sym.name); } if (!sym.isRoot()) { buf.insert(0, '.'); buf.insert(0, sym.fullName()); } return buf.toString(); } } protected String javaFileName(String className) { StringTokenizer tokens = new StringTokenizer(className, "."); File file = new File(global.outpath); while (tokens.hasMoreElements()) file = new File(file, tokens.nextToken()); return file.getPath() + ".class"; } // Generic instructions static class Generic_RETURN implements CompoundInstruction { private ReturnInstruction inst; public Generic_RETURN(Type type) { switch (type.getType()) { case cst.T_VOID: inst = ic.RETURN; break; case cst.T_BOOLEAN: case cst.T_BYTE: case cst.T_CHAR: case cst.T_SHORT: case cst.T_INT: inst = ic.IRETURN; break; case cst.T_LONG: inst = ic.LRETURN; break; case cst.T_FLOAT: inst = ic.FRETURN; break; case cst.T_DOUBLE: inst = ic.DRETURN; break; case cst.T_ARRAY: case cst.T_OBJECT: inst = ic.ARETURN; break; default: throw Debug.abort("unexpected type " + type.getType()); } } public InstructionList getInstructionList() { return new InstructionList(inst); } } static class Generic_LOAD implements CompoundInstruction { private LoadInstruction inst; public Generic_LOAD(int pos, Type type) { switch (type.getType()) { case cst.T_BOOLEAN: case cst.T_BYTE: case cst.T_CHAR: case cst.T_SHORT: case cst.T_INT: inst = new ILOAD(pos); break; case cst.T_LONG: inst = new LLOAD(pos); break; case cst.T_FLOAT: inst = new FLOAD(pos); break; case cst.T_DOUBLE: inst = new DLOAD(pos); break; case cst.T_ARRAY: case cst.T_OBJECT: inst = new ALOAD(pos); break; default: throw Debug.abort("unexpected type"); } } public InstructionList getInstructionList() { return new InstructionList(inst); } } static class Generic_STORE implements CompoundInstruction { private StoreInstruction inst; public Generic_STORE(int pos, Type type) { switch (type.getType()) { case cst.T_BOOLEAN: case cst.T_BYTE: case cst.T_CHAR: case cst.T_SHORT: case cst.T_INT: inst = new ISTORE(pos); break; case cst.T_LONG: inst = new LSTORE(pos); break; case cst.T_FLOAT: inst = new FSTORE(pos); break; case cst.T_DOUBLE: inst = new DSTORE(pos); break; case cst.T_ARRAY: case cst.T_OBJECT: inst = new ASTORE(pos); break; default: throw Debug.abort("unexpected type", type); } } public InstructionList getInstructionList() { return new InstructionList(inst); } } static class Generic_ALOAD implements CompoundInstruction { private ArrayInstruction inst; public Generic_ALOAD(Type type) { switch (type.getType()) { case cst.T_BOOLEAN: case cst.T_BYTE: inst = ic.BALOAD; break; case cst.T_CHAR: inst = ic.CALOAD; break; case cst.T_SHORT: inst = ic.SALOAD; break; case cst.T_INT: inst = ic.IALOAD; break; case cst.T_LONG: inst = ic.LALOAD; break; case cst.T_FLOAT: inst = ic.FALOAD; break; case cst.T_DOUBLE: inst = ic.DALOAD; break; case cst.T_ARRAY: case cst.T_OBJECT: inst = ic.AALOAD; break; default: throw Debug.abort("unexpected type"); } } public InstructionList getInstructionList() { return new InstructionList(inst); } } static class Generic_ASTORE implements CompoundInstruction { private ArrayInstruction inst; public Generic_ASTORE(Type type) { switch (type.getType()) { case cst.T_BOOLEAN: case cst.T_BYTE: inst = ic.BASTORE; break; case cst.T_CHAR: inst = ic.CASTORE; break; case cst.T_SHORT: inst = ic.SASTORE; break; case cst.T_INT: inst = ic.IASTORE; break; case cst.T_LONG: inst = ic.LASTORE; break; case cst.T_FLOAT: inst = ic.FASTORE; break; case cst.T_DOUBLE: inst = ic.DASTORE; break; case cst.T_ARRAY: case cst.T_OBJECT: inst = ic.AASTORE; break; default: throw Debug.abort("unexpected type"); } } public InstructionList getInstructionList() { return new InstructionList(inst); } } }