/* NSC -- new Scala compiler * Copyright 2005-2009 LAMP/EPFL * @author Martin Odersky */ // $Id: GenICode.scala 18627 2009-09-01 16:12:00Z dragos $ package scala.tools.nsc package backend package icode import scala.collection.mutable.{Map, HashMap, ListBuffer, Buffer, HashSet} import scala.tools.nsc.symtab._ import scala.tools.nsc.util.Position /** This class ... * * @author Iulian Dragos * @version 1.0 */ // TODO: // - switches with alternatives abstract class GenICode extends SubComponent { import global._ import icodes._ import icodes.opcodes._ val phaseName = "icode" override def newPhase(prev: Phase) = new ICodePhase(prev) class ICodePhase(prev: Phase) extends StdPhase(prev) { override def description = "Generate ICode from the AST" var unit: CompilationUnit = _ // We assume definitions are alread initialized val STRING = REFERENCE(definitions.StringClass) // this depends on the backend! should be changed. val ANY_REF_CLASS = REFERENCE(definitions.ObjectClass) val SCALA_ALL = REFERENCE(definitions.NothingClass) val SCALA_ALLREF = REFERENCE(definitions.NullClass) val THROWABLE = REFERENCE(definitions.ThrowableClass) val BoxesRunTime_equals = if (!forMSIL) definitions.getMember(definitions.BoxesRunTimeClass, nme.equals_) else definitions.getMember(definitions.getClass("scala.runtime.Comparator").linkedModuleOfClass, nme.equals_) override def run { scalaPrimitives.init classes.clear super.run } override def apply(unit: CompilationUnit): Unit = { this.unit = unit unit.icode.clear log("Generating icode for " + unit) gen(unit.body) this.unit = null } def gen(tree: Tree): Context = gen(tree, new Context()) def gen(trees: List[Tree], ctx: Context): Context = { var ctx1 = ctx for (t <- trees) ctx1 = gen(t, ctx1) ctx1 } /////////////////// Code generation /////////////////////// def gen(tree: Tree, ctx: Context): Context = tree match { case EmptyTree => ctx case PackageDef(pid, stats) => gen(stats, ctx setPackage pid.name) case ClassDef(mods, name, _, impl) => log("Generating class: " + tree.symbol.fullNameString) val outerClass = ctx.clazz ctx setClass (new IClass(tree.symbol) setCompilationUnit unit) addClassFields(ctx, tree.symbol); classes += (tree.symbol -> ctx.clazz) unit.icode += ctx.clazz gen(impl, ctx) ctx setClass outerClass // !! modules should be eliminated by refcheck... or not? case ModuleDef(mods, name, impl) => abort("Modules should not reach backend!") case ValDef(mods, name, tpt, rhs) => ctx // we use the symbol to add fields case DefDef(mods, name, tparams, vparamss, tpt, rhs) => if (settings.debug.value) log("Entering method " + name) val m = new IMethod(tree.symbol) m.sourceFile = unit.source.toString() m.returnType = if (tree.symbol.isConstructor) UNIT else toTypeKind(tree.symbol.info.resultType) ctx.clazz.addMethod(m) var ctx1 = ctx.enterMethod(m, tree.asInstanceOf[DefDef]) addMethodParams(ctx1, vparamss) m.native = m.symbol.hasAnnotation(definitions.NativeAttr) if (!m.isDeferred && !m.native) { ctx1 = genLoad(rhs, ctx1, m.returnType); // reverse the order of the local variables, to match the source-order m.locals = m.locals.reverse rhs match { case Block(_, Return(_)) => () case Return(_) => () case EmptyTree => error("Concrete method has no definition: " + tree) case _ => if (ctx1.bb.isEmpty) ctx1.bb.emit(RETURN(m.returnType), rhs.pos) else ctx1.bb.emit(RETURN(m.returnType)) } ctx1.bb.close prune(ctx1.method) } else ctx1.method.setCode(null) ctx1 case Template(_, _, body) => gen(body, ctx) case _ => abort("Illegal tree in gen: " + tree) } private def genStat(trees: List[Tree], ctx: Context): Context = { var currentCtx = ctx for (t <- trees) currentCtx = genStat(t, currentCtx) currentCtx } /** * Generate code for the given tree. The trees should contain statements * and not produce any value. Use genLoad for expressions which leave * a value on top of the stack. * * @param tree ... * @param ctx ... * @return a new context. This is necessary for control flow instructions * which may change the current basic block. */ private def genStat(tree: Tree, ctx: Context): Context = { tree match { case Assign(lhs @ Select(_, _), rhs) => if (lhs.symbol.isStaticMember) { val ctx1 = genLoad(rhs, ctx, toTypeKind(lhs.symbol.info)) ctx1.bb.emit(STORE_FIELD(lhs.symbol, true), tree.pos) ctx1 } else { var ctx1 = genLoadQualifier(lhs, ctx) ctx1 = genLoad(rhs, ctx1, toTypeKind(lhs.symbol.info)) ctx1.bb.emit(STORE_FIELD(lhs.symbol, false), tree.pos) ctx1 } case Assign(lhs, rhs) => val ctx1 = genLoad(rhs, ctx, toTypeKind(lhs.symbol.info)) val Some(l) = ctx.method.lookupLocal(lhs.symbol) ctx1.bb.emit(STORE_LOCAL(l), tree.pos) ctx1 case _ => genLoad(tree, ctx, UNIT) } } /** * Generate code for trees that produce values on the stack * * @param tree The tree to be translated * @param ctx The current context * @param expectedType The type of the value to be generated on top of the * stack. * @return The new context. The only thing that may change is the current * basic block (as the labels map is mutable). */ private def genLoad(tree: Tree, ctx: Context, expectedType: TypeKind): Context = { var generatedType = expectedType if (settings.debug.value) log("at line: " + (if (tree.pos.isDefined) tree.pos.line else tree.pos)) /** * Generate code for primitive arithmetic operations. */ def genArithmeticOp(tree: Tree, ctx: Context, code: Int): Context = { val Apply(fun @ Select(larg, _), args) = tree var ctx1 = ctx var resKind = toTypeKind(larg.tpe) if (settings.debug.value) { assert(args.length <= 1, "Too many arguments for primitive function: " + fun.symbol) assert(resKind.isNumericType | resKind == BOOL, resKind.toString() + " is not a numeric or boolean type " + "[operation: " + fun.symbol + "]") } args match { // unary operation case Nil => ctx1 = genLoad(larg, ctx1, resKind) code match { case scalaPrimitives.POS => () // nothing case scalaPrimitives.NEG => ctx1.bb.emit(CALL_PRIMITIVE(Negation(resKind)), larg.pos) case scalaPrimitives.NOT => ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(NOT, resKind)), larg.pos) case _ => abort("Unknown unary operation: " + fun.symbol.fullNameString + " code: " + code) } generatedType = resKind // binary operation case rarg :: Nil => resKind = getMaxType(larg.tpe :: rarg.tpe :: Nil); if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code)) assert(resKind.isIntType | resKind == BOOL, resKind.toString() + " incompatible with arithmetic modulo operation: " + ctx1); ctx1 = genLoad(larg, ctx1, resKind); ctx1 = genLoad(rarg, ctx1, // check .NET size of shift arguments! if (scalaPrimitives.isShiftOp(code)) INT else resKind) generatedType = resKind code match { case scalaPrimitives.ADD => ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(ADD, resKind)), tree.pos) case scalaPrimitives.SUB => ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(SUB, resKind)), tree.pos) case scalaPrimitives.MUL => ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(MUL, resKind)), tree.pos) case scalaPrimitives.DIV => ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(DIV, resKind)), tree.pos) case scalaPrimitives.MOD => ctx1.bb.emit(CALL_PRIMITIVE(Arithmetic(REM, resKind)), tree.pos) case scalaPrimitives.OR => ctx1.bb.emit(CALL_PRIMITIVE(Logical(OR, resKind)), tree.pos) case scalaPrimitives.XOR => ctx1.bb.emit(CALL_PRIMITIVE(Logical(XOR, resKind)), tree.pos) case scalaPrimitives.AND => ctx1.bb.emit(CALL_PRIMITIVE(Logical(AND, resKind)), tree.pos) case scalaPrimitives.LSL => ctx1.bb.emit(CALL_PRIMITIVE(Shift(LSL, resKind)), tree.pos) generatedType = resKind case scalaPrimitives.LSR => ctx1.bb.emit(CALL_PRIMITIVE(Shift(LSR, resKind)), tree.pos) generatedType = resKind case scalaPrimitives.ASR => ctx1.bb.emit(CALL_PRIMITIVE(Shift(ASR, resKind)), tree.pos) generatedType = resKind case _ => abort("Unknown primitive: " + fun.symbol + "[" + code + "]") } case _ => abort("Too many arguments for primitive function: " + tree) } ctx1 } /** Generate primitive array operations. * * @param tree ... * @param ctx ... * @param code ... * @return ... */ def genArrayOp(tree: Tree, ctx: Context, code: Int): Context = { import scalaPrimitives._ val Apply(Select(arrayObj, _), args) = tree val k = toTypeKind(arrayObj.tpe) val ARRAY(elem) = k var ctx1 = genLoad(arrayObj, ctx, k) if (scalaPrimitives.isArrayGet(code)) { // load argument on stack if (settings.debug.value) assert(args.length == 1, "Too many arguments for array get operation: " + tree); ctx1 = genLoad(args.head, ctx1, INT) generatedType = elem } else if (scalaPrimitives.isArraySet(code)) { if (settings.debug.value) assert(args.length == 2, "Too many arguments for array set operation: " + tree); ctx1 = genLoad(args.head, ctx1, INT) ctx1 = genLoad(args.tail.head, ctx1, toTypeKind(args.tail.head.tpe)) // the following line should really be here, but because of bugs in erasure // we pretend we generate whatever type is expected from us. //generatedType = UNIT } else generatedType = INT code match { case ZARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(BOOL)), tree.pos) case BARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(BYTE)), tree.pos) case SARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(SHORT)), tree.pos) case CARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(CHAR)), tree.pos) case IARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(INT)), tree.pos) case LARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(LONG)), tree.pos) case FARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(FLOAT)), tree.pos) case DARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(DOUBLE)), tree.pos) case OARRAY_LENGTH => ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(ANY_REF_CLASS)), tree.pos) case ZARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(BOOL), tree.pos) case BARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(BYTE), tree.pos) case SARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(SHORT), tree.pos) case CARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(CHAR), tree.pos) case IARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(INT), tree.pos) case LARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(LONG), tree.pos) case FARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(FLOAT), tree.pos) case DARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(DOUBLE), tree.pos) case OARRAY_GET => ctx1.bb.emit(LOAD_ARRAY_ITEM(ANY_REF_CLASS), tree.pos) case ZARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(BOOL), tree.pos) case BARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(BYTE), tree.pos) case SARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(SHORT), tree.pos) case CARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(CHAR), tree.pos) case IARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(INT), tree.pos) case LARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(LONG), tree.pos) case FARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(FLOAT), tree.pos) case DARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(DOUBLE), tree.pos) case OARRAY_SET => ctx1.bb.emit(STORE_ARRAY_ITEM(ANY_REF_CLASS), tree.pos) case _ => abort("Unknown operation on arrays: " + tree + " code: " + code) } ctx1 } // genLoad val resCtx: Context = tree match { case LabelDef(name, params, rhs) => val ctx1 = ctx.newBlock if (isLoopHeaderLabel(name)) ctx1.bb.loopHeader = true; ctx1.labels.get(tree.symbol) match { case Some(label) => label.anchor(ctx1.bb) label.patch(ctx.method.code) case None => ctx1.labels += (tree.symbol -> (new Label(tree.symbol) anchor ctx1.bb setParams (params map (_.symbol)))); ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))); if (settings.debug.value) log("Adding label " + tree.symbol); } ctx.bb.emit(JUMP(ctx1.bb), tree.pos) ctx.bb.close genLoad(rhs, ctx1, expectedType /*toTypeKind(tree.symbol.info.resultType)*/) case ValDef(_, nme.THIS, _, _) => if (settings.debug.value) log("skipping trivial assign to _$this: " + tree) ctx case ValDef(_, _, _, rhs) => val sym = tree.symbol val local = ctx.method.addLocal(new Local(sym, toTypeKind(sym.info), false)) if (rhs == EmptyTree) { if (settings.debug.value) log("Uninitialized variable " + tree + " at: " + (tree.pos)); ctx.bb.emit(getZeroOf(local.kind)) } var ctx1 = ctx if (rhs != EmptyTree) ctx1 = genLoad(rhs, ctx, local.kind); ctx1.bb.emit(STORE_LOCAL(local), tree.pos) ctx1.scope.add(local) ctx1.bb.emit(SCOPE_ENTER(local)) generatedType = UNIT ctx1 case If(cond, thenp, elsep) => var thenCtx = ctx.newBlock var elseCtx = ctx.newBlock val contCtx = ctx.newBlock genCond(cond, ctx, thenCtx, elseCtx) val ifKind = toTypeKind(tree.tpe) val thenKind = toTypeKind(thenp.tpe) val elseKind = if (elsep == EmptyTree) UNIT else toTypeKind(elsep.tpe) generatedType = ifKind // we need to drop unneeded results, if one branch gives // unit and the other gives something on the stack, because // the type of 'if' is scala.Any, and its erasure would be Object. // But unboxed units are not Objects... if (thenKind == UNIT || elseKind == UNIT) { if (settings.debug.value) log("Will drop result from an if branch"); thenCtx = genLoad(thenp, thenCtx, UNIT) elseCtx = genLoad(elsep, elseCtx, UNIT) if (settings.debug.value) assert(expectedType == UNIT, "I produce UNIT in a context where " + expectedType + " is expected!") generatedType = UNIT } else { thenCtx = genLoad(thenp, thenCtx, ifKind) elseCtx = genLoad(elsep, elseCtx, ifKind) } thenCtx.bb.emit(JUMP(contCtx.bb)) thenCtx.bb.close if (elsep == EmptyTree) elseCtx.bb.emit(JUMP(contCtx.bb), tree.pos) else elseCtx.bb.emit(JUMP(contCtx.bb)) elseCtx.bb.close contCtx case Return(expr) => val returnedKind = toTypeKind(expr.tpe) var ctx1 = genLoad(expr, ctx, returnedKind) val oldcleanups = ctx1.cleanups lazy val tmp = ctx1.makeLocal(tree.pos, expr.tpe, "tmp") var saved = false for (op <- ctx1.cleanups) op match { case MonitorRelease(m) => if (settings.debug.value) log("removing " + m + " from cleanups: " + ctx1.cleanups) ctx1.bb.emit(LOAD_LOCAL(m)) ctx1.bb.emit(MONITOR_EXIT()) ctx1.exitSynchronized(m) case Finalizer(f) => if (settings.debug.value) log("removing " + f + " from cleanups: " + ctx1.cleanups) if (returnedKind != UNIT && mayCleanStack(f) && !saved) { ctx1.bb.emit(STORE_LOCAL(tmp)) saved = true } // we have to run this without the same finalizer in // the list, otherwise infinite recursion happens for // finalizers that contain 'return' ctx1 = genLoad(f, ctx1.removeFinalizer(f), UNIT) } ctx1.cleanups = oldcleanups if (saved) ctx1.bb.emit(LOAD_LOCAL(tmp)) adapt(returnedKind, ctx1.method.returnType, ctx1, tree.pos) ctx1.bb.emit(RETURN(ctx.method.returnType), tree.pos) ctx1.bb.enterIgnoreMode generatedType = expectedType ctx1 case Try(block, catches, finalizer) => val kind = toTypeKind(tree.tpe) var tmp: Local = null val guardResult = kind != UNIT && mayCleanStack(finalizer) if (guardResult) { tmp = ctx.makeLocal(tree.pos, tree.tpe, "tmp") } def duplicateFinalizer = (new DuplicateLabels(ctx.labels.keySet))(ctx, finalizer) var handlers = for (CaseDef(pat, _, body) <- catches.reverse) yield pat match { case Typed(Ident(nme.WILDCARD), tpt) => (tpt.tpe.typeSymbol, kind, { ctx: Context => ctx.bb.emit(DROP(REFERENCE(tpt.tpe.typeSymbol))); val ctx1 = genLoad(body, ctx, kind); if (guardResult) { ctx1.bb.emit(STORE_LOCAL(tmp)) val ctx2 = genLoad(duplicateFinalizer, ctx1, UNIT) ctx2.bb.emit(LOAD_LOCAL(tmp)) ctx2 } else genLoad(duplicateFinalizer, ctx1, UNIT); }) case Ident(nme.WILDCARD) => (definitions.ThrowableClass, kind, { ctx: Context => ctx.bb.emit(DROP(REFERENCE(definitions.ThrowableClass))) val ctx1 = genLoad(body, ctx, kind) if (guardResult) { ctx1.bb.emit(STORE_LOCAL(tmp)) val ctx2 = genLoad(duplicateFinalizer, ctx1, UNIT) ctx2.bb.emit(LOAD_LOCAL(tmp)) ctx2 } else genLoad(duplicateFinalizer, ctx1, UNIT) }) case Bind(name, _) => val exception = ctx.method.addLocal(new Local(pat.symbol, toTypeKind(pat.symbol.tpe), false)) (pat.symbol.tpe.typeSymbol, kind, { ctx: Context => ctx.bb.emit(STORE_LOCAL(exception), pat.pos); val ctx1 = genLoad(body, ctx, kind); if (guardResult) { ctx1.bb.emit(STORE_LOCAL(tmp)) val ctx2 = genLoad(duplicateFinalizer, ctx1, UNIT) ctx2.bb.emit(LOAD_LOCAL(tmp)) ctx2 } else genLoad(duplicateFinalizer, ctx1, UNIT); }) } ctx.Try( bodyCtx => { generatedType = kind; //toTypeKind(block.tpe); val ctx1 = genLoad(block, bodyCtx, generatedType); if (guardResult) { val tmp = ctx1.makeLocal(tree.pos, tree.tpe, "tmp") ctx1.bb.emit(STORE_LOCAL(tmp)) val ctx2 = genLoad(duplicateFinalizer, ctx1, UNIT) ctx2.bb.emit(LOAD_LOCAL(tmp)) ctx2 } else genLoad(duplicateFinalizer, ctx1, UNIT) }, handlers, finalizer) case Throw(expr) => val ctx1 = genLoad(expr, ctx, THROWABLE) ctx1.bb.emit(THROW(), tree.pos) ctx1.bb.enterIgnoreMode generatedType = SCALA_ALL ctx1 case New(tpt) => abort("Unexpected New") case Apply(TypeApply(fun, targs), _) => val sym = fun.symbol var ctx1 = ctx var cast = false if (sym == definitions.Object_isInstanceOf) cast = false else if (sym == definitions.Object_asInstanceOf) cast = true else abort("Unexpected type application " + fun + "[sym: " + sym.fullNameString + "]" + " in: " + tree) val Select(obj, _) = fun val l = toTypeKind(obj.tpe) val r = toTypeKind(targs.head.tpe) ctx1 = genLoadQualifier(fun, ctx) if (l.isValueType && r.isValueType) genConversion(l, r, ctx1, cast) else if (l.isValueType) { ctx1.bb.emit(DROP(l), fun.pos) if (cast) { ctx1.bb.emit(NEW(REFERENCE(definitions.getClass("ClassCastException")))) ctx1.bb.emit(DUP(ANY_REF_CLASS)) ctx1.bb.emit(THROW()) } else ctx1.bb.emit(CONSTANT(Constant(false))) } else if (r.isValueType && cast) { assert(false, tree) /* Erasure should have added an unboxing operation to prevent that. */ } else if (r.isValueType) ctx.bb.emit(IS_INSTANCE(REFERENCE(definitions.boxedClass(r.toType.typeSymbol)))) else genCast(l, r, ctx1, cast); generatedType = if (cast) r else BOOL; ctx1 // 'super' call: Note: since constructors are supposed to // return an instance of what they construct, we have to take // special care. On JVM they are 'void', and Scala forbids (syntactically) // to call super constructors explicitly and/or use their 'returned' value. // therefore, we can ignore this fact, and generate code that leaves nothing // on the stack (contrary to what the type in the AST says). case Apply(fun @ Select(Super(_, mix), _), args) => if (settings.debug.value) log("Call to super: " + tree); val invokeStyle = SuperCall(mix) // if (fun.symbol.isConstructor) Static(true) else SuperCall(mix); ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos) val ctx1 = genLoadArguments(args, fun.symbol.info.paramTypes, ctx) ctx1.bb.emit(CALL_METHOD(fun.symbol, invokeStyle), tree.pos) generatedType = if (fun.symbol.isConstructor) UNIT else toTypeKind(fun.symbol.info.resultType) ctx1 // 'new' constructor call: Note: since constructors are // thought to return an instance of what they construct, // we have to 'simulate' it by DUPlicating the freshly created // instance (on JVM, <init> methods return VOID). case Apply(fun @ Select(New(tpt), nme.CONSTRUCTOR), args) => val ctor = fun.symbol if (settings.debug.value) assert(ctor.isClassConstructor, "'new' call to non-constructor: " + ctor.name) generatedType = toTypeKind(tpt.tpe) if (settings.debug.value) assert(generatedType.isReferenceType || generatedType.isArrayType, "Non reference type cannot be instantiated: " + generatedType) var ctx1 = ctx generatedType match { case arr @ ARRAY(elem) => ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx) val dims = arr.dimensions var elemKind = arr.elementKind if (args.length > dims) unit.error(tree.pos, "too many arguments for array constructor: found " + args.length + " but array has only " + dims + " dimension(s)") if (args.length != dims) for (i <- args.length until dims) elemKind = ARRAY(elemKind) ctx1.bb.emit(CREATE_ARRAY(elemKind, args.length), tree.pos) case rt @ REFERENCE(cls) => if (settings.debug.value) assert(ctor.owner == cls, "Symbol " + ctor.owner.fullNameString + " is different than " + tpt) val nw = NEW(rt) ctx1.bb.emit(nw, tree.pos) ctx1.bb.emit(DUP(generatedType)) ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx) val init = CALL_METHOD(ctor, Static(true)) nw.init = init ctx1.bb.emit(init, tree.pos) case _ => abort("Cannot instantiate " + tpt + "of kind: " + generatedType) } ctx1 case Apply(fun @ _, List(expr)) if (definitions.isBox(fun.symbol)) => if (settings.debug.value) log("BOX : " + fun.symbol.fullNameString); val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe)) val nativeKind = toTypeKind(expr.tpe) if (settings.Xdce.value) { // we store this boxed value to a local, even if not really needed. // boxing optimization might use it, and dead code elimination will // take care of unnecessary stores var loc1 = ctx.makeLocal(tree.pos, expr.tpe, "boxed") ctx1.bb.emit(STORE_LOCAL(loc1)) ctx1.bb.emit(LOAD_LOCAL(loc1)) } ctx1.bb.emit(BOX(nativeKind), expr.pos) generatedType = toTypeKind(fun.symbol.tpe.resultType) ctx1 case Apply(fun @ _, List(expr)) if (definitions.isUnbox(fun.symbol)) => if (settings.debug.value) log("UNBOX : " + fun.symbol.fullNameString) val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe)) val boxType = toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType ctx1.bb.emit(UNBOX(boxType), expr.pos) ctx1 case Apply(fun, args) => val sym = fun.symbol if (sym.isLabel) { // jump to a label val label = ctx.labels.get(sym) match { case Some(l) => l // it is a forward jump, scan for labels case None => log("Performing scan for label because of forward jump.") scanForLabels(ctx.defdef, ctx) ctx.labels.get(sym) match { case Some(l) => log("Found label: " + l) l case _ => abort("Unknown label target: " + sym + " at: " + (fun.pos) + ": ctx: " + ctx) } } val ctx1 = genLoadLabelArguments(args, label, ctx) if (label.anchored) ctx1.bb.emit(JUMP(label.block), tree.pos) else ctx1.bb.emit(PJUMP(label), tree.pos) ctx1.bb.close ctx1.newBlock } else if (isPrimitive(sym)) { // primitive method call val Select(receiver, _) = fun val code = scalaPrimitives.getPrimitive(sym, receiver.tpe) var ctx1 = ctx if (scalaPrimitives.isArithmeticOp(code)) { ctx1 = genArithmeticOp(tree, ctx1, code) } else if (code == scalaPrimitives.CONCAT) { ctx1 = genStringConcat(tree, ctx1) generatedType = STRING } else if (scalaPrimitives.isArrayOp(code)) { ctx1 = genArrayOp(tree, ctx1, code) } else if (scalaPrimitives.isLogicalOp(code) || scalaPrimitives.isComparisonOp(code)) { val trueCtx = ctx1.newBlock val falseCtx = ctx1.newBlock val afterCtx = ctx1.newBlock genCond(tree, ctx1, trueCtx, falseCtx) trueCtx.bb.emit(CONSTANT(Constant(true)), tree.pos) trueCtx.bb.emit(JUMP(afterCtx.bb)) trueCtx.bb.close falseCtx.bb.emit(CONSTANT(Constant(false)), tree.pos) falseCtx.bb.emit(JUMP(afterCtx.bb)) falseCtx.bb.close generatedType = BOOL ctx1 = afterCtx } else if (code == scalaPrimitives.SYNCHRONIZED) { val monitor = ctx.makeLocal(tree.pos, definitions.ObjectClass.tpe, "monitor") ctx1 = genLoadQualifier(fun, ctx1) ctx1.bb.emit(DUP(ANY_REF_CLASS)) ctx1.bb.emit(STORE_LOCAL(monitor)) ctx1.bb.emit(MONITOR_ENTER(), tree.pos) ctx1.enterSynchronized(monitor) if (settings.debug.value) log("synchronized block start"); ctx1 = ctx1.Try( bodyCtx => { val ctx1 = genLoad(args.head, bodyCtx, expectedType /* toTypeKind(tree.tpe.resultType) */) ctx1.bb.emit(LOAD_LOCAL(monitor)) ctx1.bb.emit(MONITOR_EXIT(), tree.pos) ctx1 }, List( // tree.tpe / fun.tpe is object, which is no longer true after this transformation (NoSymbol, expectedType, exhCtx => { exhCtx.bb.emit(LOAD_LOCAL(monitor)) exhCtx.bb.emit(MONITOR_EXIT(), tree.pos) exhCtx.bb.emit(THROW()) exhCtx.bb.enterIgnoreMode exhCtx })), EmptyTree); if (settings.debug.value) log("synchronized block end with block " + ctx1.bb + " closed=" + ctx1.bb.closed); ctx1.exitSynchronized(monitor) } else if (scalaPrimitives.isCoercion(code)) { ctx1 = genLoad(receiver, ctx1, toTypeKind(receiver.tpe)) genCoercion(tree, ctx1, code) generatedType = scalaPrimitives.generatedKind(code) } else abort("Primitive operation not handled yet: " + sym.fullNameString + "(" + fun.symbol.simpleName + ") " + " at: " + (tree.pos)); ctx1 } else { // normal method call if (settings.debug.value) log("Gen CALL_METHOD with sym: " + sym + " isStaticSymbol: " + sym.isStaticMember); var invokeStyle = if (sym.isStaticMember) Static(false) else if (sym.hasFlag(Flags.PRIVATE) || sym.isClassConstructor) Static(true) else Dynamic var ctx1 = if (invokeStyle.hasInstance) genLoadQualifier(fun, ctx) else ctx ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx1) val hostClass = fun match { case Select(qualifier, _) if (qualifier.tpe.typeSymbol != definitions.ArrayClass) => qualifier.tpe.typeSymbol case _ => sym.owner } if (settings.debug.value && hostClass != sym.owner) log("Set more precise host class for " + sym.fullNameString + " host: " + hostClass); ctx1.bb.emit(CALL_METHOD(sym, invokeStyle) setHostClass hostClass, tree.pos) if (sym == ctx1.method.symbol) { ctx1.method.recursive = true } generatedType = if (sym.isClassConstructor) UNIT else toTypeKind(sym.info.resultType); ctx1 } case ApplyDynamic(qual, args) => ctx.clazz.bootstrapClass = Some("scala.runtime.DynamicDispatch") val ctx1 = genLoad(qual, ctx, ANY_REF_CLASS) genLoadArguments(args, tree.symbol.info.paramTypes, ctx1) ctx1.bb.emit(CALL_METHOD(tree.symbol, InvokeDynamic), tree.pos) ctx1 case This(qual) => assert(tree.symbol == ctx.clazz.symbol || tree.symbol.isModuleClass, "Trying to access the this of another class: " + "tree.symbol = " + tree.symbol + ", ctx.clazz.symbol = " + ctx.clazz.symbol + " compilation unit:"+unit) if (tree.symbol.isModuleClass && tree.symbol != ctx.clazz.symbol) { if (settings.debug.value) log("LOAD_MODULE from 'This': " + tree.symbol); assert(!tree.symbol.isPackageClass, "Cannot use package as value: " + tree) ctx.bb.emit(LOAD_MODULE(tree.symbol), tree.pos) generatedType = REFERENCE(tree.symbol) } else { ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos) if (tree.symbol == definitions.ArrayClass) generatedType = REFERENCE(definitions.BoxedAnyArrayClass) else generatedType = REFERENCE(ctx.clazz.symbol) } ctx case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) => if (settings.debug.value) { assert(tree.symbol.isModule, "Selection of non-module from empty package: " + tree.toString() + " sym: " + tree.symbol + " at: " + (tree.pos)) log("LOAD_MODULE from Select(<emptypackage>): " + tree.symbol); } assert(!tree.symbol.isPackageClass, "Cannot use package as value: " + tree) ctx.bb.emit(LOAD_MODULE(tree.symbol), tree.pos) ctx case Select(qualifier, selector) => val sym = tree.symbol generatedType = toTypeKind(sym.info) if (sym.isModule) { if (settings.debug.value) log("LOAD_MODULE from Select(qualifier, selector): " + sym); assert(!tree.symbol.isPackageClass, "Cannot use package as value: " + tree) ctx.bb.emit(LOAD_MODULE(sym), tree.pos); ctx } else if (sym.isStaticMember) { ctx.bb.emit(LOAD_FIELD(sym, true), tree.pos) ctx } else { val ctx1 = genLoadQualifier(tree, ctx) ctx1.bb.emit(LOAD_FIELD(sym, false), tree.pos) ctx1 } case Ident(name) => if (!tree.symbol.isPackage) { if (tree.symbol.isModule) { if (settings.debug.value) log("LOAD_MODULE from Ident(name): " + tree.symbol); assert(!tree.symbol.isPackageClass, "Cannot use package as value: " + tree) ctx.bb.emit(LOAD_MODULE(tree.symbol), tree.pos) generatedType = toTypeKind(tree.symbol.info) } else { try { val Some(l) = ctx.method.lookupLocal(tree.symbol) ctx.bb.emit(LOAD_LOCAL(l), tree.pos) generatedType = l.kind } catch { case ex: MatchError => throw new Error("symbol " + tree.symbol + " does not exist in " + ctx.method) } } } ctx case Literal(value) => if (value.tag != UnitTag) (value.tag, expectedType) match { case (IntTag, LONG) => ctx.bb.emit(CONSTANT(Constant(value.longValue)), tree.pos); generatedType = LONG case (FloatTag, DOUBLE) => ctx.bb.emit(CONSTANT(Constant(value.doubleValue)), tree.pos); generatedType = DOUBLE case (NullTag, _) => ctx.bb.emit(CONSTANT(value), tree.pos); generatedType = SCALA_ALLREF case _ => ctx.bb.emit(CONSTANT(value), tree.pos); generatedType = toTypeKind(tree.tpe) } ctx case Block(stats, expr) => ctx.enterScope var ctx1 = genStat(stats, ctx) ctx1 = genLoad(expr, ctx1, expectedType) ctx1.exitScope ctx1 case Typed(Super(_, _), _) => genLoad(This(ctx.clazz.symbol), ctx, expectedType) case Typed(expr, _) => genLoad(expr, ctx, expectedType) case Assign(_, _) => generatedType = UNIT genStat(tree, ctx) case ArrayValue(tpt @ TypeTree(), elems) => var ctx1 = ctx val elmKind = toTypeKind(tpt.tpe) generatedType = ARRAY(elmKind) ctx1.bb.emit(CONSTANT(new Constant(elems.length)), tree.pos) ctx1.bb.emit(CREATE_ARRAY(elmKind, 1)) // inline array literals var i = 0 while (i < elems.length) { ctx1.bb.emit(DUP(generatedType), tree.pos) ctx1.bb.emit(CONSTANT(new Constant(i))) ctx1 = genLoad(elems(i), ctx1, elmKind) ctx1.bb.emit(STORE_ARRAY_ITEM(elmKind)) i = i + 1 } ctx1 case Match(selector, cases) => if (settings.debug.value) log("Generating SWITCH statement."); var ctx1 = genLoad(selector, ctx, INT) val afterCtx = ctx1.newBlock var caseCtx: Context = null generatedType = toTypeKind(tree.tpe) var targets: List[BasicBlock] = Nil var tags: List[Int] = Nil var default: BasicBlock = afterCtx.bb for (caze <- cases) caze match { case CaseDef(Literal(value), EmptyTree, body) => tags = value.intValue :: tags val tmpCtx = ctx1.newBlock targets = tmpCtx.bb :: targets caseCtx = genLoad(body, tmpCtx , generatedType) caseCtx.bb.emit(JUMP(afterCtx.bb), caze.pos) caseCtx.bb.close case CaseDef(Ident(nme.WILDCARD), EmptyTree, body) => val tmpCtx = ctx1.newBlock default = tmpCtx.bb caseCtx = genLoad(body, tmpCtx , generatedType) caseCtx.bb.emit(JUMP(afterCtx.bb), caze.pos) caseCtx.bb.close case _ => abort("Invalid case statement in switch-like pattern match: " + tree + " at: " + (tree.pos)) } ctx1.bb.emit(SWITCH(tags.reverse map (x => List(x)), (default :: targets).reverse), tree.pos) ctx1.bb.close afterCtx case EmptyTree => if (expectedType != UNIT) ctx.bb.emit(getZeroOf(expectedType)) ctx case _ => abort("Unexpected tree in genLoad: " + tree + " at: " + (tree.pos)) } // emit conversion if (generatedType != expectedType) adapt(generatedType, expectedType, resCtx, tree.pos); resCtx } private def adapt(from: TypeKind, to: TypeKind, ctx: Context, pos: Position): Unit = { if (!(from <:< to) && !(from == SCALA_ALLREF && to == SCALA_ALL)) { to match { case UNIT => ctx.bb.emit(DROP(from), pos) if (settings.debug.value) log("Dropped an " + from); case _ => if (settings.debug.value) assert(from != UNIT, "Can't convert from UNIT to " + to + " at: " + pos) assert(!from.isReferenceType && !to.isReferenceType, "type error: can't convert from " + from + " to " + to +" in unit "+this.unit) ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)), pos); } } else if (from == SCALA_ALL) { ctx.bb.emit(THROW()) ctx.bb.enterIgnoreMode } else if (from == SCALA_ALLREF) { ctx.bb.emit(DROP(from)) ctx.bb.emit(CONSTANT(Constant(null))) } else (from, to) match { case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, LONG))) case _ => () } } /** Load the qualifier of `tree' on top of the stack. */ private def genLoadQualifier(tree: Tree, ctx: Context): Context = tree match { case Select(qualifier, _) => genLoad(qualifier, ctx, toTypeKind(qualifier.tpe)) case _ => abort("Unknown qualifier " + tree) } /** * Generate code that loads args into label parameters. */ private def genLoadLabelArguments(args: List[Tree], label: Label, ctx: Context): Context = { if (settings.debug.value) assert(args.length == label.params.length, "Wrong number of arguments in call to label " + label.symbol) var ctx1 = ctx var arg = args var param = label.params val stores: ListBuffer[Instruction] = new ListBuffer // store arguments in reverse order on the stack while (arg != Nil) { arg.head match { case This(_) if param.head.name == nme.THIS => //println("skipping trivial argument for " + param.head) () // skip trivial arguments case Ident(_) if arg.head.symbol == param.head => //println("skipping trivial argument for " + param.head) () // skip trivial arguments case _ => val Some(l) = ctx.method.lookupLocal(param.head) ctx1 = genLoad(arg.head, ctx1, l.kind) if (param.head.name == nme.THIS) STORE_THIS(toTypeKind(ctx1.clazz.symbol.tpe)).setPos(arg.head.pos) +: stores else { STORE_LOCAL(l).setPos(arg.head.pos) +: stores } } arg = arg.tail param = param.tail } //println("stores: " + stores) ctx1.bb.emit(stores) ctx1 } private def genLoadArguments(args: List[Tree], tpes: List[Type], ctx: Context): Context = { var ctx1 = ctx var arg = args var tpe = tpes while (arg != Nil) { ctx1 = genLoad(arg.head, ctx1, toTypeKind(tpe.head)) arg = arg.tail tpe = tpe.tail } ctx1 } def genConversion(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) = { if (cast) ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to))) else { ctx.bb.emit(DROP(from)) ctx.bb.emit(CONSTANT(Constant(from == to))) } } def genCast(from: TypeKind, to: TypeKind, ctx: Context, cast: Boolean) = ctx.bb.emit(if (cast) CHECK_CAST(to) else IS_INSTANCE(to)) def zeroOf(k: TypeKind): Tree = k match { case UNIT => Literal(()) case BOOL => Literal(false) case BYTE => Literal(0: Byte) case SHORT => Literal(0: Short) case CHAR => Literal(0: Char) case INT => Literal(0: Int) case LONG => Literal(0: Long) case FLOAT => Literal(0.0f) case DOUBLE => Literal(0.0d) case REFERENCE(cls) => Literal(null: Any) case ARRAY(elem) => Literal(null: Any) case BOXED(_) => Literal(null: Any) case ConcatClass => abort("no zero of ConcatClass") } def getZeroOf(k: TypeKind): Instruction = k match { case UNIT => CONSTANT(Constant(())) case BOOL => CONSTANT(Constant(false)) case BYTE => CONSTANT(Constant(0: Byte)) case SHORT => CONSTANT(Constant(0: Short)) case CHAR => CONSTANT(Constant(0: Char)) case INT => CONSTANT(Constant(0: Int)) case LONG => CONSTANT(Constant(0: Long)) case FLOAT => CONSTANT(Constant(0.0f)) case DOUBLE => CONSTANT(Constant(0.0d)) case REFERENCE(cls) => CONSTANT(Constant(null: Any)) case ARRAY(elem) => CONSTANT(Constant(null: Any)) case BOXED(_) => CONSTANT(Constant(null: Any)) case ConcatClass => abort("no zero of ConcatClass") } /** Is the given symbol a primitive operation? */ def isPrimitive(fun: Symbol): Boolean = scalaPrimitives.isPrimitive(fun) /** Generate coercion denoted by "code" */ def genCoercion(tree: Tree, ctx: Context, code: Int) = { import scalaPrimitives._ code match { case B2B => () case B2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, CHAR)), tree.pos) case B2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, SHORT)), tree.pos) case B2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, INT)), tree.pos) case B2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, LONG)), tree.pos) case B2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, FLOAT)), tree.pos) case B2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(BYTE, DOUBLE)), tree.pos) case S2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, BYTE)), tree.pos) case S2S => () case S2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, CHAR)), tree.pos) case S2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, INT)), tree.pos) case S2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, LONG)), tree.pos) case S2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, FLOAT)), tree.pos) case S2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(SHORT, DOUBLE)), tree.pos) case C2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, BYTE)), tree.pos) case C2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, SHORT)), tree.pos) case C2C => () case C2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, INT)), tree.pos) case C2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, LONG)), tree.pos) case C2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, FLOAT)), tree.pos) case C2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(CHAR, DOUBLE)), tree.pos) case I2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, BYTE)), tree.pos) case I2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, SHORT)), tree.pos) case I2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, CHAR)), tree.pos) case I2I => () case I2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, LONG)), tree.pos) case I2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, FLOAT)), tree.pos) case I2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(INT, DOUBLE)), tree.pos) case L2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, BYTE)), tree.pos) case L2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, SHORT)), tree.pos) case L2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, CHAR)), tree.pos) case L2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, INT)), tree.pos) case L2L => () case L2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, FLOAT)), tree.pos) case L2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(LONG, DOUBLE)), tree.pos) case F2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, BYTE)), tree.pos) case F2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, SHORT)), tree.pos) case F2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, CHAR)), tree.pos) case F2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, INT)), tree.pos) case F2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, LONG)), tree.pos) case F2F => () case F2D => ctx.bb.emit(CALL_PRIMITIVE(Conversion(FLOAT, DOUBLE)), tree.pos) case D2B => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, BYTE)), tree.pos) case D2S => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, SHORT)), tree.pos) case D2C => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, CHAR)), tree.pos) case D2I => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, INT)), tree.pos) case D2L => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, LONG)), tree.pos) case D2F => ctx.bb.emit(CALL_PRIMITIVE(Conversion(DOUBLE, FLOAT)), tree.pos) case D2D => () case _ => abort("Unknown coercion primitive: " + code) } } /** Generate string concatenation. * * @param tree ... * @param ctx ... * @return ... */ def genStringConcat(tree: Tree, ctx: Context): Context = { val Apply(Select(larg, _), rarg) = tree var ctx1 = ctx val concatenations = liftStringConcat(tree) if (settings.debug.value) log("Lifted string concatenations for " + tree + "\n to: " + concatenations); ctx1.bb.emit(CALL_PRIMITIVE(StartConcat), tree.pos); for (elem <- concatenations) { val kind = toTypeKind(elem.tpe) ctx1 = genLoad(elem, ctx1, kind) ctx1.bb.emit(CALL_PRIMITIVE(StringConcat(kind)), elem.pos) } ctx1.bb.emit(CALL_PRIMITIVE(EndConcat), tree.pos) ctx1 } /** * Returns a list of trees that each should be concatenated, from * left to right. It turns a chained call like "a".+("b").+("c") into * a list of arguments. */ def liftStringConcat(tree: Tree): List[Tree] = tree match { case Apply(fun @ Select(larg, method), rarg) => if (isPrimitive(fun.symbol) && scalaPrimitives.getPrimitive(fun.symbol) == scalaPrimitives.CONCAT) liftStringConcat(larg) ::: rarg else List(tree) case _ => List(tree) } /** * Traverse the tree and store label stubs in the context. This is * necessary to handle forward jumps, because at a label application * with arguments, the symbols of the corresponding LabelDef parameters * are not yet known. * * Since it is expensive to traverse each method twice, this method is called * only when forward jumps really happen, and then it re-traverses the whole * method, scanning for LabelDefs. * * TODO: restrict the scanning to smaller subtrees than the whole method. * It is sufficient to scan the trees of the innermost enclosing block. */ private def scanForLabels(tree: Tree, ctx: Context): Unit = new Traverser() { override def traverse(tree: Tree): Unit = tree match { case LabelDef(name, params, rhs) => if (!ctx.labels.contains(tree.symbol)) { ctx.labels += (tree.symbol -> (new Label(tree.symbol) setParams(params map (_.symbol)))); ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))); } super.traverse(rhs) case _ => super.traverse(tree) } } traverse(tree); /** * Generate code for conditional expressions. The two basic blocks * represent the continuation in case of success/failure of the * test. */ private def genCond(tree: Tree, ctx: Context, thenCtx: Context, elseCtx: Context): Unit = { def genComparisonOp(l: Tree, r: Tree, code: Int) { // special-case reference (in)equality test for null if (code == scalaPrimitives.ID || code == scalaPrimitives.NI) { val expr: Tree = (l, r) match { case (Literal(Constant(null)), expr) => expr case (expr, Literal(Constant(null))) => expr case _ => null } if (expr ne null) { val ctx1 = genLoad(expr, ctx, ANY_REF_CLASS) if (code == scalaPrimitives.ID) ctx1.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ANY_REF_CLASS)) else ctx1.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, NE, ANY_REF_CLASS)) ctx1.bb.close return } } val op: TestOp = code match { case scalaPrimitives.LT => LT case scalaPrimitives.LE => LE case scalaPrimitives.GT => GT case scalaPrimitives.GE => GE case scalaPrimitives.ID | scalaPrimitives.EQ => EQ case scalaPrimitives.NI | scalaPrimitives.NE => NE case _ => abort("Unknown comparison primitive: " + code) } val kind = getMaxType(l.tpe :: r.tpe :: Nil) var ctx1 = genLoad(l, ctx, kind); ctx1 = genLoad(r, ctx1, kind); ctx1.bb.emit(CJUMP(thenCtx.bb, elseCtx.bb, op, kind), r.pos) ctx1.bb.close } if (settings.debug.value) log("Entering genCond with tree: " + tree); tree match { case Apply(fun, args) if isPrimitive(fun.symbol) => val code = scalaPrimitives.getPrimitive(fun.symbol) if (code == scalaPrimitives.ZNOT) { val Select(leftArg, _) = fun genCond(leftArg, ctx, elseCtx, thenCtx) } else if ((code == scalaPrimitives.EQ || code == scalaPrimitives.NE)) { val Select(leftArg, _) = fun; if (toTypeKind(leftArg.tpe).isReferenceType) { if (code == scalaPrimitives.EQ) genEqEqPrimitive(leftArg, args.head, ctx, thenCtx, elseCtx) else genEqEqPrimitive(leftArg, args.head, ctx, elseCtx, thenCtx) } else genComparisonOp(leftArg, args.head, code); } else if (scalaPrimitives.isComparisonOp(code)) { val Select(leftArg, _) = fun genComparisonOp(leftArg, args.head, code) } else { code match { case scalaPrimitives.ZAND => val Select(leftArg, _) = fun val ctxInterm = ctx.newBlock genCond(leftArg, ctx, ctxInterm, elseCtx) genCond(args.head, ctxInterm, thenCtx, elseCtx) case scalaPrimitives.ZOR => val Select(leftArg, _) = fun val ctxInterm = ctx.newBlock genCond(leftArg, ctx, thenCtx, ctxInterm) genCond(args.head, ctxInterm, thenCtx, elseCtx) case _ => // TODO (maybe): deal with the equals case here // Current semantics: rich equals (from runtime.Comparator) only when == is used // See genEqEqPrimitive for implementation var ctx1 = genLoad(tree, ctx, BOOL) ctx1.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL), tree.pos) ctx1.bb.close } } case _ => var ctx1 = genLoad(tree, ctx, BOOL) ctx1.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL), tree.pos) ctx1.bb.close } } /** * Generate the "==" code for object references. It is equivalent of * if (l eq null) r eq null else l.equals(r); * * @param l left-hand side of the '==' * @param r right-hand side of the '==' * @param ctx current context * @param thenCtx target context if the comparison yields true * @param elseCtx target context if the comparison yields false */ def genEqEqPrimitive(l: Tree, r: Tree, ctx: Context, thenCtx: Context, elseCtx: Context): Unit = { def eqEqTempName: Name = "eqEqTemp$" def getTempLocal: Local = ctx.method.lookupLocal(eqEqTempName) match { case Some(local) => local case None => val local = ctx.makeLocal(l.pos, definitions.AnyRefClass.typeConstructor, eqEqTempName.toString) //assert(!l.pos.source.isEmpty, "bad position, unit = "+unit+", tree = "+l+", pos = "+l.pos.source) assert(l.pos.source == unit.source) assert(r.pos.source == unit.source) local.start = (l.pos).line local.end = (r.pos).line local } /** True if the equality comparison is between values that require the use of the rich equality * comparator (scala.runtime.Comparator.equals). This is the case when either side of the * comparison might have a run-time type subtype of java.lang.Number or java.lang.Character. * When it is statically known that both sides are equal and subtypes of Number of Character, * not using the rich equality is possible (their own equals method will do ok.)*/ def mustUseAnyComparator: Boolean = { def isBoxed(sym: Symbol): Boolean = ((sym isNonBottomSubClass definitions.BoxedNumberClass) || (!forMSIL && (sym isNonBottomSubClass definitions.BoxedCharacterClass))) val lsym = l.tpe.typeSymbol val rsym = r.tpe.typeSymbol (lsym == definitions.ObjectClass) || (rsym == definitions.ObjectClass) || (lsym != rsym) && (isBoxed(lsym) || isBoxed(rsym)) } if (mustUseAnyComparator) { val ctx1 = genLoad(l, ctx, ANY_REF_CLASS) val ctx2 = genLoad(r, ctx1, ANY_REF_CLASS) ctx2.bb.emit(CALL_METHOD(BoxesRunTime_equals, Static(false))) ctx2.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)) ctx2.bb.close } else { (l, r) match { // null == expr -> expr eq null case (Literal(Constant(null)), expr) => val ctx1 = genLoad(expr, ctx, ANY_REF_CLASS) ctx1.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ANY_REF_CLASS)) ctx1.bb.close // expr == null -> if(expr eq null) true else expr.equals(null) case (expr, Literal(Constant(null))) => val eqEqTempLocal = getTempLocal var ctx1 = genLoad(expr, ctx, ANY_REF_CLASS) ctx1.bb.emit(DUP(ANY_REF_CLASS)) ctx1.bb.emit(STORE_LOCAL(eqEqTempLocal), l.pos) val nonNullCtx = ctx1.newBlock ctx1.bb.emit(CZJUMP(thenCtx.bb, nonNullCtx.bb, EQ, ANY_REF_CLASS)) ctx1.bb.close nonNullCtx.bb.emit(LOAD_LOCAL(eqEqTempLocal), l.pos) nonNullCtx.bb.emit(CONSTANT(Constant(null)), r.pos) nonNullCtx.bb.emit(CALL_METHOD(definitions.Object_equals, Dynamic)) nonNullCtx.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)) nonNullCtx.bb.close // l == r -> if (l eq null) r eq null else l.equals(r) case _ => val eqEqTempLocal = getTempLocal var ctx1 = genLoad(l, ctx, ANY_REF_CLASS) ctx1 = genLoad(r, ctx1, ANY_REF_CLASS) val nullCtx = ctx1.newBlock val nonNullCtx = ctx1.newBlock ctx1.bb.emit(STORE_LOCAL(eqEqTempLocal), l.pos) ctx1.bb.emit(DUP(ANY_REF_CLASS)) ctx1.bb.emit(CZJUMP(nullCtx.bb, nonNullCtx.bb, EQ, ANY_REF_CLASS)) ctx1.bb.close nullCtx.bb.emit(DROP(ANY_REF_CLASS), l.pos) // type of AnyRef nullCtx.bb.emit(LOAD_LOCAL(eqEqTempLocal)) nullCtx.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ANY_REF_CLASS)) nullCtx.bb.close nonNullCtx.bb.emit(LOAD_LOCAL(eqEqTempLocal), l.pos) nonNullCtx.bb.emit(CALL_METHOD(definitions.Object_equals, Dynamic)) nonNullCtx.bb.emit(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)) nonNullCtx.bb.close } } } /** * Add all fields of the given class symbol to the current ICode * class. */ private def addClassFields(ctx: Context, cls: Symbol) { if (settings.debug.value) assert(ctx.clazz.symbol eq cls, "Classes are not the same: " + ctx.clazz.symbol + ", " + cls) for (f <- cls.info.decls.iterator) if (!f.isMethod && f.isTerm) ctx.clazz.addField(new IField(f)); } /** * Add parameters to the current ICode method. It is assumed the methods * have been uncurried, so the list of lists contains just one list. */ private def addMethodParams(ctx: Context, vparamss: List[List[ValDef]]) { vparamss match { case Nil => () case vparams :: Nil => for (p <- vparams) { val lv = new Local(p.symbol, toTypeKind(p.symbol.info), true) ctx.method.addParam(lv) ctx.scope.add(lv) ctx.bb.varsInScope += lv } ctx.method.params = ctx.method.params.reverse case _ => abort("Malformed parameter list: " + vparamss) } } /** Does this tree have a try-catch block? */ def mayCleanStack(tree: Tree): Boolean = { var hasTry = false new Traverser() { override def traverse(t: Tree) = t match { case Try(_, _, _) => hasTry = true case _ => super.traverse(t) } }.traverse(tree); hasTry } /** * If the block consists of a single unconditional jump, prune * it by replacing the instructions in the predecessor to jump * directly to the JUMP target of the block. * * @param method ... */ def prune(method: IMethod) = { var changed = false var n = 0 def prune0(block: BasicBlock): Unit = { val optCont = block.lastInstruction match { case JUMP(b) if (b != block) => Some(b); case _ => None } if (block.size == 1 && optCont != None) { val Some(cont) = optCont; val pred = block.predecessors; log("Preds: " + pred + " of " + block + " (" + optCont + ")"); pred foreach { p => p.lastInstruction match { case CJUMP(succ, fail, cond, kind) => if (settings.debug.value) log("Pruning empty if branch."); changed = true p.replaceInstruction(p.lastInstruction, if (block == succ) if (block == fail) CJUMP(cont, cont, cond, kind) else CJUMP(cont, fail, cond, kind) else if (block == fail) CJUMP(succ, cont, cond, kind) else abort("Could not find block in preds: " + method + " " + block + " " + pred + " " + p)) case CZJUMP(succ, fail, cond, kind) => if (settings.debug.value) log("Pruning empty ifz branch."); changed = true p.replaceInstruction(p.lastInstruction, if (block == succ) if (block == fail) CZJUMP(cont, cont, cond, kind) else CZJUMP(cont, fail, cond, kind) else if (block == fail) CZJUMP(succ, cont, cond, kind) else abort("Could not find block in preds")) case JUMP(b) => if (settings.debug.value) log("Pruning empty JMP branch."); changed = true val replaced = p.replaceInstruction(p.lastInstruction, JUMP(cont)) if (settings.debug.value) assert(replaced, "Didn't find p.lastInstruction") case SWITCH(tags, labels) => if (settings.debug.value) log("Pruning empty SWITCH branch."); changed = true p.replaceInstruction(p.lastInstruction, SWITCH(tags, labels map (l => if (l == block) cont else l))) } } if (changed) { log("Removing block: " + block) method.code.removeBlock(block) for (e <- method.exh) { e.covered = e.covered filter (_ != block) e.blocks = e.blocks filter (_ != block) if (e.startBlock eq block) e setStartBlock cont; } } } } do { changed = false n += 1 method.code traverse prune0 } while (changed) if (settings.debug.value) log("Prune fixpoint reached in " + n + " iterations."); } def getMaxType(ts: List[Type]): TypeKind = { def maxType(a: TypeKind, b: TypeKind): TypeKind = a maxType b; val kinds = ts map toTypeKind kinds reduceLeft maxType } def isLoopHeaderLabel(name: Name): Boolean = name.startsWith("while$") || name.startsWith("doWhile$") /** Tree transformer that duplicates code and at the same time creates * fresh symbols for existing labels. Since labels may be used before * they are defined (forward jumps), all labels found are mapped to fresh * symbols. References to the same label (use or definition) will remain * consistent after this transformation (both the use and the definition of * some label l will be mapped to the same label l'). * * Note: If the tree fragment passed to the duplicator contains unbound * label names, the bind to the outer labeldef will be lost! That's because * a use of an unbound label l will be transformed to l', and the corresponding * label def, being outside the scope of this transformation, will not be updated. * * All LabelDefs are entered into the context label map, since it makes no sense * to delay it any more: they will be used at some point. */ class DuplicateLabels(boundLabels: collection.Set[Symbol]) extends Transformer { val labels: Map[Symbol, Symbol] = new HashMap var method: Symbol = _ var ctx: Context = _ def apply(ctx: Context, t: Tree) = { this.method = ctx.method.symbol this.ctx = ctx transform(t) } override def transform(t: Tree): Tree = { t match { case t @ Apply(fun, args) if (t.symbol.isLabel && !boundLabels(t.symbol)) => if (!labels.isDefinedAt(t.symbol)) { val oldLabel = t.symbol val sym = method.newLabel(oldLabel.pos, unit.fresh.newName(oldLabel.pos, oldLabel.name.toString)) sym.setInfo(oldLabel.tpe) labels(oldLabel) = sym } val tree = Apply(global.gen.mkAttributedRef(labels(t.symbol)), transformTrees(args)).setPos(t.pos) tree.tpe = t.tpe tree case t @ LabelDef(name, params, rhs) => val name1 = unit.fresh.newName(t.pos, name.toString) if (!labels.isDefinedAt(t.symbol)) { val oldLabel = t.symbol val sym = method.newLabel(oldLabel.pos, name1) sym.setInfo(oldLabel.tpe) labels(oldLabel) = sym } val tree = treeCopy.LabelDef(t, name1, params, transform(rhs)) tree.symbol = labels(t.symbol) ctx.labels += (tree.symbol -> (new Label(tree.symbol) setParams(params map (_.symbol)))); ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))); tree case _ => super.transform(t) } } } /////////////////////// Context //////////////////////////////// abstract class Cleanup; case class MonitorRelease(m: Local) extends Cleanup { override def equals(other: Any) = m == other; } case class Finalizer(f: Tree) extends Cleanup { override def equals(other: Any) = f == other; } /** * The Context class keeps information relative to the current state * in code generation */ class Context { /** The current package. */ var packg: Name = _ /** The current class. */ var clazz: IClass = _ /** The current method. */ var method: IMethod = _ /** The current basic block. */ var bb: BasicBlock = _ /** Map from label symbols to label objects. */ var labels: HashMap[Symbol, Label] = new HashMap() /** Current method definition. */ var defdef: DefDef = _ /** current exception handlers */ var handlers: List[ExceptionHandler] = Nil /** The current monitors or finalizers, to be cleaned up upon `return'. */ var cleanups: List[Cleanup] = Nil /** The current exception handler, when we generate code for one. */ var currentExceptionHandler: Option[ExceptionHandler] = None /** The current local variable scope. */ var scope: Scope = EmptyScope var handlerCount = 0 override def toString(): String = { val buf = new StringBuilder() buf.append("\tpackage: ").append(packg).append('\n') buf.append("\tclazz: ").append(clazz).append('\n') buf.append("\tmethod: ").append(method).append('\n') buf.append("\tbb: ").append(bb).append('\n') buf.append("\tlabels: ").append(labels).append('\n') buf.append("\texception handlers: ").append(handlers).append('\n') buf.append("\tcleanups: ").append(cleanups).append('\n') buf.append("\tscope: ").append(scope).append('\n') buf.toString() } def this(other: Context) = { this() this.packg = other.packg this.clazz = other.clazz this.method = other.method this.bb = other.bb this.labels = other.labels this.defdef = other.defdef this.handlers = other.handlers this.handlerCount = other.handlerCount this.cleanups = other.cleanups this.currentExceptionHandler = other.currentExceptionHandler this.scope = other.scope } def setPackage(p: Name): this.type = { this.packg = p this } def setClass(c: IClass): this.type = { this.clazz = c this } def setMethod(m: IMethod): this.type = { this.method = m this } def setBasicBlock(b: BasicBlock): this.type = { this.bb = b this } def enterSynchronized(monitor: Local): this.type = { cleanups = MonitorRelease(monitor) :: cleanups this } def exitSynchronized(monitor: Local): this.type = { assert(cleanups.head == monitor, "Bad nesting of cleanup operations: " + cleanups + " trying to exit from monitor: " + monitor) cleanups = cleanups.tail this } def addFinalizer(f: Tree): this.type = { cleanups = Finalizer(f) :: cleanups; this } def removeFinalizer(f: Tree): this.type = { assert(cleanups.head == f, "Illegal nesting of cleanup operations: " + cleanups + " while exiting finalizer " + f); cleanups = cleanups.tail this } /** Prepare a new context upon entry into a method. * * @param m ... * @param d ... * @return ... */ def enterMethod(m: IMethod, d: DefDef): Context = { val ctx1 = new Context(this) setMethod(m) ctx1.labels = new HashMap() ctx1.method.code = new Code(m.symbol.simpleName.toString(), m) ctx1.bb = ctx1.method.code.startBlock ctx1.defdef = d ctx1.scope = EmptyScope ctx1.enterScope ctx1 } /** Return a new context for a new basic block. */ def newBlock: Context = { val block = method.code.newBlock handlers foreach (h => h addCoveredBlock block) currentExceptionHandler match { case Some(e) => e.addBlock(block) case None => () } block.varsInScope = new HashSet() block.varsInScope ++= scope.varsInScope new Context(this) setBasicBlock block } def enterScope = { scope = new Scope(scope) } def exitScope = { if (bb.size > 0) { scope.locals foreach { lv => bb.emit(SCOPE_EXIT(lv)) } } scope = scope.outer } /** Create a new exception handler and adds it in the list * of current exception handlers. All new blocks will be * 'covered' by this exception handler (in addition to the * previously active handlers). */ def newHandler(cls: Symbol, resultKind: TypeKind): ExceptionHandler = { handlerCount += 1 val exh = new ExceptionHandler(method, "" + handlerCount, cls) exh.resultKind = resultKind method.addHandler(exh) handlers = exh :: handlers if (settings.debug.value) log("added handler: " + exh); exh } /** Add an active exception handler in this context. It will cover all new basic blocks * created from now on. */ private def addActiveHandler(exh: ExceptionHandler) { handlerCount += 1 handlers = exh :: handlers if (settings.debug.value) log("added handler: " + exh); } /** Return a new context for generating code for the given * exception handler. */ def enterHandler(exh: ExceptionHandler): Context = { currentExceptionHandler = Some(exh) val ctx = newBlock exh.setStartBlock(ctx.bb) ctx } /** Remove the given handler from the list of active exception handlers. */ def removeHandler(exh: ExceptionHandler): Unit = { assert(handlerCount > 0 && handlers.head == exh, "Wrong nesting of exception handlers." + this + " for " + exh) handlerCount -= 1 handlers = handlers.tail if (settings.debug.value) log("removed handler: " + exh); } /** Clone the current context */ def dup: Context = new Context(this) /** Make a fresh local variable. It ensures the 'name' is unique. */ def makeLocal(pos: Position, tpe: Type, name: String): Local = { val sym = method.symbol.newVariable(pos, unit.fresh.newName(pos, name)) .setInfo(tpe) .setFlag(Flags.SYNTHETIC) this.method.addLocal(new Local(sym, toTypeKind(tpe), false)) } /** * Generate exception handlers for the body. Body is evaluated * with a context where all the handlers are active. Handlers are * evaluated in the 'outer' context. * * It returns the resulting context, with the same active handlers as * before the call. Use it like: * * <code> ctx.Try( ctx => { * ctx.bb.emit(...) // protected block * }, (definitions.ThrowableClass, * ctx => { * ctx.bb.emit(...); // exception handler * }), (AnotherExceptionClass, * ctx => {... * } ))</code> */ def Try(body: Context => Context, handlers: List[(Symbol, TypeKind, (Context => Context))], finalizer: Tree) = { val outerCtx = this.dup // context for generating exception handlers, covered by finalizer val finalizerCtx = this.dup // context for generating finalizer handler val afterCtx = outerCtx.newBlock val finalizerExh = if (finalizer != EmptyTree) Some({ val exh = outerCtx.newHandler(NoSymbol, toTypeKind(finalizer.tpe)) // finalizer covers exception handlers this.addActiveHandler(exh) // .. and body aswell val ctx = finalizerCtx.enterHandler(exh) val exception = ctx.makeLocal(finalizer.pos, definitions.ThrowableClass.tpe, "exc") if (settings.Xdce.value) ctx.bb.emit(LOAD_EXCEPTION()) ctx.bb.emit(STORE_LOCAL(exception)); val ctx1 = genLoad(finalizer, ctx, UNIT); ctx1.bb.emit(LOAD_LOCAL(exception)); ctx1.bb.emit(THROW()); ctx1.bb.enterIgnoreMode; ctx1.bb.close exh }) else None val exhs = handlers.map { handler => val exh = this.newHandler(handler._1, handler._2) var ctx1 = outerCtx.enterHandler(exh) if (settings.Xdce.value) ctx1.bb.emit(LOAD_EXCEPTION()) ctx1 = handler._3(ctx1) ctx1.bb.emit(JUMP(afterCtx.bb)) ctx1.bb.close exh } val bodyCtx = this.newBlock if (finalizer != EmptyTree) bodyCtx.addFinalizer(finalizer) val finalCtx = body(bodyCtx) outerCtx.bb.emit(JUMP(bodyCtx.bb)) outerCtx.bb.close exhs.reverse foreach finalCtx.removeHandler if (finalizer != EmptyTree) { finalCtx.removeFinalizer(finalizer) } finalCtx.bb.emit(JUMP(afterCtx.bb)) finalCtx.bb.close afterCtx } } } /** * Represent a label in the current method code. In order * to support forward jumps, labels can be created without * having a deisgnated target block. They can later be attached * by calling `anchor'. */ class Label(val symbol: Symbol) { var anchored = false var block: BasicBlock = _ var params: List[Symbol] = _ private var toPatch: List[Instruction] = Nil /** Fix this label to the given basic block. */ def anchor(b: BasicBlock): Label = { assert(!anchored, "Cannot anchor an already anchored label!") anchored = true this.block = b this } def setParams(p: List[Symbol]): Label = { assert(params eq null, "Cannot set label parameters twice!") params = p this } /** Add an instruction that refers to this label. */ def addCallingInstruction(i: Instruction) = toPatch = i :: toPatch; /** * Patch the code by replacing pseudo call instructions with * jumps to the given basic block. */ def patch(code: Code) { def substMap: Map[Instruction, Instruction] = { val map = new HashMap[Instruction, Instruction]() toPatch foreach (i => map += (i -> patch(i))) map } val map = substMap code traverse (_.subst(map)) } /** * Return the patched instruction. If the given instruction * jumps to this label, replace it with the basic block. Otherwise, * return the same instruction. Conditional jumps have more than one * label, so they are replaced only if all labels are anchored. */ def patch(instr: Instruction): Instruction = { assert(anchored, "Cannot patch until this label is anchored: " + this) instr match { case PJUMP(self) if (self == this) => JUMP(block) case PCJUMP(self, failure, cond, kind) if (self == this && failure.anchored) => CJUMP(block, failure.block, cond, kind) case PCJUMP(success, self, cond, kind) if (self == this && success.anchored) => CJUMP(success.block, block, cond, kind) case PCZJUMP(self, failure, cond, kind) if (self == this && failure.anchored) => CZJUMP(block, failure.block, cond, kind) case PCZJUMP(success, self, cond, kind) if (self == this && success.anchored) => CZJUMP(success.block, block, cond, kind) case _ => instr } } override def toString() = symbol.toString() } ///////////////// Fake instructions ////////////////////////// /** * Pseudo jump: it takes a Label instead of a basick block. * It is used temporarily during code generation. It is replaced * by a real JUMP instruction when all labels are resolved. */ abstract class PseudoJUMP(label: Label) extends Instruction { override def toString(): String = "PJUMP " + label.symbol override def consumed = 0 override def produced = 0 // register with the given label if (!label.anchored) label.addCallingInstruction(this); } case class PJUMP(whereto: Label) extends PseudoJUMP(whereto) case class PCJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind) extends PseudoJUMP(success) { override def toString(): String = "PCJUMP (" + kind + ") " + success.symbol.simpleName + " : " + failure.symbol.simpleName if (!failure.anchored) failure.addCallingInstruction(this) } case class PCZJUMP(success: Label, failure: Label, cond: TestOp, kind: TypeKind) extends PseudoJUMP(success) { override def toString(): String = "PCZJUMP (" + kind + ") " + success.symbol.simpleName + " : " + failure.symbol.simpleName if (!failure.anchored) failure.addCallingInstruction(this) } /** Local variable scopes. Keep track of line numbers for debugging info. */ class Scope(val outer: Scope) { val locals: ListBuffer[Local] = new ListBuffer def add(l: Local) = locals += l def remove(l: Local) = locals -= l /** Return all locals that are in scope. */ def varsInScope: Buffer[Local] = outer.varsInScope.clone() ++ locals override def toString() = outer.toString() + locals.mkString("[", ", ", "]") } object EmptyScope extends Scope(null) { override def toString() = "[]" override def varsInScope: Buffer[Local] = new ListBuffer } }