summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/icode/GenICode.scala')
-rw-r--r--src/compiler/scala/tools/nsc/backend/icode/GenICode.scala2239
1 files changed, 0 insertions, 2239 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
deleted file mode 100644
index b6f9bcc9ab..0000000000
--- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala
+++ /dev/null
@@ -1,2239 +0,0 @@
-/* NSC -- new Scala compiler
- * Copyright 2005-2013 LAMP/EPFL
- * @author Martin Odersky
- */
-
-
-package scala
-package tools.nsc
-package backend
-package icode
-
-import scala.collection.{ mutable, immutable }
-import scala.collection.mutable.{ ListBuffer, Buffer }
-import scala.tools.nsc.symtab._
-import scala.annotation.switch
-
-/**
- * @author Iulian Dragos
- * @version 1.0
- */
-abstract class GenICode extends SubComponent {
- import global._
- import icodes._
- import icodes.opcodes._
- import definitions._
- import scalaPrimitives.{
- isArrayOp, isComparisonOp, isLogicalOp,
- isUniversalEqualityOp, isReferenceEqualityOp
- }
- import platform.isMaybeBoxed
-
- private val bCodeICodeCommon: jvm.BCodeICodeCommon[global.type] = new jvm.BCodeICodeCommon(global)
- import bCodeICodeCommon._
-
- val phaseName = "icode"
-
- override def newPhase(prev: Phase) = new ICodePhase(prev)
-
- @inline private def debugassert(cond: => Boolean, msg: => Any) {
- if (settings.debug)
- assert(cond, msg)
- }
-
- class ICodePhase(prev: Phase) extends StdPhase(prev) {
-
- override def description = "Generate ICode from the AST"
-
- var unit: CompilationUnit = NoCompilationUnit
-
- override def run() {
- if (!settings.isBCodeActive) {
- scalaPrimitives.init()
- classes.clear()
- }
- super.run()
- }
-
- override def apply(unit: CompilationUnit): Unit = {
- if (settings.isBCodeActive) { return }
- this.unit = unit
- unit.icode.clear()
- informProgress("Generating icode for " + unit)
- gen(unit.body)
- this.unit = NoCompilationUnit
- }
-
- 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
- }
-
- /** If the selector type has a member with the right name,
- * it is the host class; otherwise the symbol's owner.
- */
- def findHostClass(selector: Type, sym: Symbol) = selector member sym.name match {
- case NoSymbol => debuglog(s"Rejecting $selector as host class for $sym") ; sym.owner
- case _ => selector.typeSymbol
- }
-
- /////////////////// 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) =>
- debuglog("Generating class: " + tree.symbol.fullName)
- 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.clazz.methods = ctx.clazz.methods.reverse // preserve textual order
- ctx.clazz.fields = ctx.clazz.fields.reverse // preserve textual order
- ctx setClass outerClass
-
- // !! modules should be eliminated by refcheck... or not?
- case ModuleDef(mods, name, impl) =>
- abort("Modules should not reach backend! " + tree)
-
- case ValDef(mods, name, tpt, rhs) =>
- ctx // we use the symbol to add fields
-
- case DefDef(mods, name, tparams, vparamss, tpt, rhs) =>
- debuglog("Entering method " + name)
- val m = new IMethod(tree.symbol)
- m.sourceFile = unit.source
- 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.isAbstractMethod && !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 =>
- globalError("Concrete method has no definition: " + tree + (
- if (settings.debug) "(found: " + m.symbol.owner.info.decls.toList.mkString(", ") + ")"
- else "")
- )
- case _ => if (ctx1.bb.isEmpty)
- ctx1.bb.closeWith(RETURN(m.returnType), rhs.pos)
- else
- ctx1.bb.closeWith(RETURN(m.returnType))
- }
- if (!ctx1.bb.closed) ctx1.bb.close()
- prune(ctx1.method)
- } else
- ctx1.method.setCode(NoCode)
- ctx1
-
- case Template(_, _, body) =>
- gen(body, ctx)
-
- case _ =>
- abort("Illegal tree in gen: " + tree)
- }
-
- private def genStat(trees: List[Tree], ctx: Context): Context =
- trees.foldLeft(ctx)((currentCtx, t) => genStat(t, 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.
- *
- * @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) =>
- val isStatic = lhs.symbol.isStaticMember
- var ctx1 = if (isStatic) ctx else genLoadQualifier(lhs, ctx)
-
- ctx1 = genLoad(rhs, ctx1, toTypeKind(lhs.symbol.info))
- ctx1.bb.emit(STORE_FIELD(lhs.symbol, isStatic), 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)
- }
-
- private def genThrow(expr: Tree, ctx: Context): (Context, TypeKind) = {
- require(expr.tpe <:< ThrowableTpe, expr.tpe)
-
- val thrownKind = toTypeKind(expr.tpe)
- val ctx1 = genLoad(expr, ctx, thrownKind)
- ctx1.bb.emit(THROW(expr.tpe.typeSymbol), expr.pos)
- ctx1.bb.enterIgnoreMode()
-
- (ctx1, NothingReference)
- }
-
- /**
- * Generate code for primitive arithmetic operations.
- * Returns (Context, Generated Type)
- */
- private def genArithmeticOp(tree: Tree, ctx: Context, code: Int): (Context, TypeKind) = {
- val Apply(fun @ Select(larg, _), args) = tree
- var ctx1 = ctx
- var resKind = toTypeKind(larg.tpe)
-
- debugassert(args.length <= 1,
- "Too many arguments for primitive function: " + fun.symbol)
- debugassert(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.fullName +
- " code: " + code)
- }
-
- // binary operation
- case rarg :: Nil =>
- resKind = getMaxType(larg.tpe :: rarg.tpe :: Nil)
- if (scalaPrimitives.isShiftOp(code) || scalaPrimitives.isBitwiseOp(code))
- assert(resKind.isIntegralType | 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)
-
- val primitiveOp = code match {
- case scalaPrimitives.ADD => Arithmetic(ADD, resKind)
- case scalaPrimitives.SUB => Arithmetic(SUB, resKind)
- case scalaPrimitives.MUL => Arithmetic(MUL, resKind)
- case scalaPrimitives.DIV => Arithmetic(DIV, resKind)
- case scalaPrimitives.MOD => Arithmetic(REM, resKind)
- case scalaPrimitives.OR => Logical(OR, resKind)
- case scalaPrimitives.XOR => Logical(XOR, resKind)
- case scalaPrimitives.AND => Logical(AND, resKind)
- case scalaPrimitives.LSL => Shift(LSL, resKind)
- case scalaPrimitives.LSR => Shift(LSR, resKind)
- case scalaPrimitives.ASR => Shift(ASR, resKind)
- case _ => abort("Unknown primitive: " + fun.symbol + "[" + code + "]")
- }
- ctx1.bb.emit(CALL_PRIMITIVE(primitiveOp), tree.pos)
-
- case _ =>
- abort("Too many arguments for primitive function: " + tree)
- }
- (ctx1, resKind)
- }
-
- /** Generate primitive array operations.
- */
- private def genArrayOp(tree: Tree, ctx: Context, code: Int, expectedType: TypeKind): (Context, TypeKind) = {
- import scalaPrimitives._
- val Apply(Select(arrayObj, _), args) = tree
- val k = toTypeKind(arrayObj.tpe)
- val ARRAY(elem) = k
- var ctx1 = genLoad(arrayObj, ctx, k)
- val elementType = typeOfArrayOp.getOrElse(code, abort("Unknown operation on arrays: " + tree + " code: " + code))
-
- var generatedType = expectedType
-
- if (scalaPrimitives.isArrayGet(code)) {
- // load argument on stack
- debugassert(args.length == 1,
- "Too many arguments for array get operation: " + tree)
- ctx1 = genLoad(args.head, ctx1, INT)
- generatedType = elem
- ctx1.bb.emit(LOAD_ARRAY_ITEM(elementType), tree.pos)
- // it's tempting to just drop array loads of type Null instead
- // of adapting them but array accesses can cause
- // ArrayIndexOutOfBounds so we can't. Besides, Array[Null]
- // probably isn't common enough to figure out an optimization
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- }
- else if (scalaPrimitives.isArraySet(code)) {
- debugassert(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
-
- ctx1.bb.emit(STORE_ARRAY_ITEM(elementType), tree.pos)
- }
- else {
- generatedType = INT
- ctx1.bb.emit(CALL_PRIMITIVE(ArrayLength(elementType)), tree.pos)
- }
-
- (ctx1, generatedType)
- }
- private def genSynchronized(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val Apply(fun, args) = tree
- val monitor = ctx.makeLocal(tree.pos, ObjectTpe, "monitor")
- var monitorResult: Local = null
- val argTpe = args.head.tpe
- val hasResult = expectedType != UNIT
- if (hasResult)
- monitorResult = ctx.makeLocal(tree.pos, argTpe, "monitorResult")
-
- var ctx1 = genLoadQualifier(fun, ctx)
- ctx1.bb.emit(Seq(
- DUP(ObjectReference),
- STORE_LOCAL(monitor),
- MONITOR_ENTER() setPos tree.pos
- ))
- ctx1.enterSynchronized(monitor)
- debuglog("synchronized block start")
-
- ctx1 = ctx1.Try(
- bodyCtx => {
- val ctx2 = genLoad(args.head, bodyCtx, expectedType /* toTypeKind(tree.tpe.resultType) */)
- if (hasResult)
- ctx2.bb.emit(STORE_LOCAL(monitorResult))
- ctx2.bb.emit(Seq(
- LOAD_LOCAL(monitor),
- MONITOR_EXIT() setPos tree.pos
- ))
- ctx2
- }, List(
- // tree.tpe / fun.tpe is object, which is no longer true after this transformation
- (ThrowableClass, expectedType, exhCtx => {
- exhCtx.bb.emit(Seq(
- LOAD_LOCAL(monitor),
- MONITOR_EXIT() setPos tree.pos,
- THROW(ThrowableClass)
- ))
- exhCtx.bb.enterIgnoreMode()
- exhCtx
- })), EmptyTree, tree)
-
- debuglog("synchronized block end with block %s closed=%s".format(ctx1.bb, ctx1.bb.closed))
- ctx1.exitSynchronized(monitor)
- if (hasResult)
- ctx1.bb.emit(LOAD_LOCAL(monitorResult))
- (ctx1, expectedType)
- }
-
- private def genLoadIf(tree: If, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val If(cond, thenp, elsep) = tree
-
- 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)
-
- // 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...
- def hasUnitBranch = thenKind == UNIT || elseKind == UNIT
- val resKind = if (hasUnitBranch) UNIT else ifKind
-
- if (hasUnitBranch)
- debuglog("Will drop result from an if branch")
-
- thenCtx = genLoad(thenp, thenCtx, resKind)
- elseCtx = genLoad(elsep, elseCtx, resKind)
-
- debugassert(!hasUnitBranch || expectedType == UNIT,
- "I produce UNIT in a context where " + expectedType + " is expected!")
-
- // alternatives may be already closed by a tail-recursive jump
- val contReachable = !(thenCtx.bb.ignore && elseCtx.bb.ignore)
- thenCtx.bb.closeWith(JUMP(contCtx.bb))
- elseCtx.bb.closeWith(
- if (elsep == EmptyTree) JUMP(contCtx.bb)
- else JUMP(contCtx.bb) setPos tree.pos
- )
-
- contCtx.bb killUnless contReachable
- (contCtx, resKind)
- }
- private def genLoadTry(tree: Try, ctx: Context, setGeneratedType: TypeKind => Unit): Context = {
- val Try(block, catches, finalizer) = tree
- val kind = toTypeKind(tree.tpe)
-
- val caseHandlers =
- for (CaseDef(pat, _, body) <- catches.reverse) yield {
- def genWildcardHandler(sym: Symbol): (Symbol, TypeKind, Context => Context) =
- (sym, kind, ctx => {
- ctx.bb.emit(DROP(REFERENCE(sym))) // drop the loaded exception
- genLoad(body, ctx, kind)
- })
-
- pat match {
- case Typed(Ident(nme.WILDCARD), tpt) => genWildcardHandler(tpt.tpe.typeSymbol)
- case Ident(nme.WILDCARD) => genWildcardHandler(ThrowableClass)
- case Bind(_, _) =>
- val exception = ctx.method addLocal new Local(pat.symbol, toTypeKind(pat.symbol.tpe), false) // the exception will be loaded and stored into this local
-
- (pat.symbol.tpe.typeSymbol, kind, {
- ctx: Context =>
- ctx.bb.emit(STORE_LOCAL(exception), pat.pos)
- genLoad(body, ctx, kind)
- })
- }
- }
-
- ctx.Try(
- bodyCtx => {
- setGeneratedType(kind)
- genLoad(block, bodyCtx, kind)
- },
- caseHandlers,
- finalizer,
- tree)
- }
-
- private def genPrimitiveOp(tree: Apply, ctx: Context, expectedType: TypeKind): (Context, TypeKind) = {
- val sym = tree.symbol
- val Apply(fun @ Select(receiver, _), _) = tree
- val code = scalaPrimitives.getPrimitive(sym, receiver.tpe)
-
- if (scalaPrimitives.isArithmeticOp(code))
- genArithmeticOp(tree, ctx, code)
- else if (code == scalaPrimitives.CONCAT)
- (genStringConcat(tree, ctx), StringReference)
- else if (code == scalaPrimitives.HASH)
- (genScalaHash(receiver, ctx), INT)
- else if (isArrayOp(code))
- genArrayOp(tree, ctx, code, expectedType)
- else if (isLogicalOp(code) || isComparisonOp(code)) {
- val trueCtx, falseCtx, afterCtx = ctx.newBlock()
-
- genCond(tree, ctx, trueCtx, falseCtx)
- trueCtx.bb.emitOnly(
- CONSTANT(Constant(true)) setPos tree.pos,
- JUMP(afterCtx.bb)
- )
- falseCtx.bb.emitOnly(
- CONSTANT(Constant(false)) setPos tree.pos,
- JUMP(afterCtx.bb)
- )
- (afterCtx, BOOL)
- }
- else if (code == scalaPrimitives.SYNCHRONIZED)
- genSynchronized(tree, ctx, expectedType)
- else if (scalaPrimitives.isCoercion(code)) {
- val ctx1 = genLoad(receiver, ctx, toTypeKind(receiver.tpe))
- genCoercion(tree, ctx1, code)
- (ctx1, scalaPrimitives.generatedKind(code))
- }
- else abort(
- "Primitive operation not handled yet: " + sym.fullName + "(" +
- fun.symbol.simpleName + ") " + " at: " + (tree.pos)
- )
- }
-
- /**
- * 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
- debuglog("at line: " + (if (tree.pos.isDefined) tree.pos.line else tree.pos))
-
- val resCtx: Context = tree match {
- case LabelDef(name, params, rhs) =>
- def genLoadLabelDef = {
- val ctx1 = ctx.newBlock() // note: we cannot kill ctx1 if ctx is in ignore mode because
- // label defs can be the target of jumps from other locations.
- // that means label defs can lead to unreachable code without
- // proper reachability analysis
-
- if (nme.isLoopHeaderLabel(name))
- ctx1.bb.loopHeader = true
-
- ctx1.labels.get(tree.symbol) match {
- case Some(label) =>
- debuglog("Found existing label for " + tree.symbol.fullLocationString)
- label.anchor(ctx1.bb)
- label.patch(ctx.method.code)
-
- case None =>
- val pair = (tree.symbol -> (new Label(tree.symbol) anchor ctx1.bb setParams (params map (_.symbol))))
- debuglog("Adding label " + tree.symbol.fullLocationString + " in genLoad.")
- ctx1.labels += pair
- ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)))
- }
-
- ctx.bb.closeWith(JUMP(ctx1.bb), tree.pos)
- genLoad(rhs, ctx1, expectedType /*toTypeKind(tree.symbol.info.resultType)*/)
- }
- genLoadLabelDef
-
- case ValDef(_, name, _, rhs) =>
- def genLoadValDef =
- if (name == nme.THIS) {
- debuglog("skipping trivial assign to _$this: " + tree)
- ctx
- } else {
- val sym = tree.symbol
- val local = ctx.method.addLocal(new Local(sym, toTypeKind(sym.info), false))
-
- if (rhs == EmptyTree) {
- debuglog("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
- }
- genLoadValDef
-
- case t @ If(cond, thenp, elsep) =>
- val (newCtx, resKind) = genLoadIf(t, ctx, expectedType)
- generatedType = resKind
- newCtx
-
- case Return(expr) =>
- def genLoadReturn = {
- val returnedKind = toTypeKind(expr.tpe)
- debuglog("Return(" + expr + ") with returnedKind = " + returnedKind)
-
- var ctx1 = genLoad(expr, ctx, returnedKind)
- lazy val tmp = ctx1.makeLocal(tree.pos, expr.tpe, "tmp")
- val saved = savingCleanups(ctx1) {
- var savedFinalizer = false
- ctx1.cleanups foreach {
- case MonitorRelease(m) =>
- debuglog("removing " + m + " from cleanups: " + ctx1.cleanups)
- ctx1.bb.emit(Seq(LOAD_LOCAL(m), MONITOR_EXIT()))
- ctx1.exitSynchronized(m)
-
- case Finalizer(f, finalizerCtx) =>
- debuglog("removing " + f + " from cleanups: " + ctx1.cleanups)
- if (returnedKind != UNIT && mayCleanStack(f)) {
- log("Emitting STORE_LOCAL for " + tmp + " to save finalizer.")
- ctx1.bb.emit(STORE_LOCAL(tmp))
- savedFinalizer = true
- }
-
- // duplicate finalizer (takes care of anchored labels)
- val f1 = duplicateFinalizer(Set.empty ++ ctx1.labels.keySet, ctx1, f)
-
- // we have to run this without the same finalizer in
- // the list, otherwise infinite recursion happens for
- // finalizers that contain 'return'
- val fctx = finalizerCtx.newBlock()
- fctx.bb killIf ctx1.bb.ignore
- ctx1.bb.closeWith(JUMP(fctx.bb))
- ctx1 = genLoad(f1, fctx, UNIT)
- }
- savedFinalizer
- }
-
- if (saved) {
- log("Emitting LOAD_LOCAL for " + tmp + " after saving finalizer.")
- 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
- }
- genLoadReturn
-
- case t @ Try(_, _, _) =>
- genLoadTry(t, ctx, generatedType = _)
-
- case Throw(expr) =>
- val (ctx1, expectedType) = genThrow(expr, ctx)
- generatedType = expectedType
- ctx1
-
- case New(tpt) =>
- abort("Unexpected New(" + tpt.summaryString + "/" + tpt + ") received in icode.\n" +
- " Call was genLoad" + ((tree, ctx, expectedType)))
-
- case Apply(TypeApply(fun, targs), _) =>
- def genLoadApply1 = {
- val sym = fun.symbol
- val cast = sym match {
- case Object_isInstanceOf => false
- case Object_asInstanceOf => true
- case _ => abort("Unexpected type application " + fun + "[sym: " + sym.fullName + "]" + " in: " + tree)
- }
-
- val Select(obj, _) = fun
- val l = toTypeKind(obj.tpe)
- val r = toTypeKind(targs.head.tpe)
- val 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(Seq(
- NEW(REFERENCE(definitions.ClassCastExceptionClass)),
- DUP(ObjectReference),
- THROW(definitions.ClassCastExceptionClass)
- ))
- } else
- ctx1.bb.emit(CONSTANT(Constant(false)))
- } else if (r.isValueType && cast) {
- /* Erasure should have added an unboxing operation to prevent that. */
- abort("should have been unboxed by erasure: " + tree)
- } 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
- }
- genLoadApply1
-
- // '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) =>
- def genLoadApply2 = {
- debuglog("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
- }
- genLoadApply2
-
- // '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) =>
- def genLoadApply3 = {
- val ctor = fun.symbol
- debugassert(ctor.isClassConstructor,
- "'new' call to non-constructor: " + ctor.name)
-
- generatedType = toTypeKind(tpt.tpe)
- debugassert(generatedType.isReferenceType || generatedType.isArrayType,
- "Non reference type cannot be instantiated: " + generatedType)
-
- generatedType match {
- case arr @ ARRAY(elem) =>
- val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx)
- val dims = arr.dimensions
- var elemKind = arr.elementKind
- if (args.length > dims)
- reporter.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)
- ctx1
-
- case rt @ REFERENCE(cls) =>
- debugassert(ctor.owner == cls,
- "Symbol " + ctor.owner.fullName + " is different than " + tpt)
-
- val nw = NEW(rt)
- ctx.bb.emit(nw, tree.pos)
- ctx.bb.emit(DUP(generatedType))
- val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx)
-
- val init = CALL_METHOD(ctor, Static(onInstance = true))
- nw.init = init
- ctx1.bb.emit(init, tree.pos)
- ctx1
- case _ =>
- abort("Cannot instantiate " + tpt + " of kind: " + generatedType)
- }
- }
- genLoadApply3
-
- case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isBox(fun.symbol) =>
- def genLoadApply4 = {
- debuglog("BOX : " + fun.symbol.fullName)
- val ctx1 = genLoad(expr, ctx, toTypeKind(expr.tpe))
- val nativeKind = toTypeKind(expr.tpe)
- if (settings.Xdce) {
- // 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
- val 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
- }
- genLoadApply4
-
- case Apply(fun @ _, List(expr)) if (currentRun.runDefinitions.isUnbox(fun.symbol)) =>
- debuglog("UNBOX : " + fun.symbol.fullName)
- 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 app @ Apply(fun, args) =>
- def genLoadApply6 = {
- val sym = fun.symbol
-
- if (sym.isLabel) { // jump to a label
- val label = ctx.labels.getOrElse(sym, {
- // it is a forward jump, scan for labels
- resolveForwardLabel(ctx.defdef, ctx, sym)
- ctx.labels.get(sym) match {
- case Some(l) =>
- debuglog("Forward jump for " + sym.fullLocationString + ": scan found label " + l)
- l
- case _ =>
- abort("Unknown label target: " + sym + " at: " + (fun.pos) + ": ctx: " + ctx)
- }
- })
- // note: when one of the args to genLoadLabelArguments is a jump to a label,
- // it will call back into genLoad and arrive at this case, which will then set ctx1.bb.ignore to true,
- // this is okay, since we're jumping unconditionally, so the loads and jumps emitted by the outer
- // call to genLoad (by calling genLoadLabelArguments and emitOnly) can safely be ignored,
- // however, as emitOnly will close the block, which reverses its instructions (when it's still open),
- // we better not reverse when the block has already been closed but is in ignore mode
- // (if it's not in ignore mode, double-closing is an error)
- val ctx1 = genLoadLabelArguments(args, label, ctx)
- ctx1.bb.emitOnly(if (label.anchored) JUMP(label.block) else PJUMP(label))
- ctx1.bb.enterIgnoreMode()
- ctx1
- } else if (isPrimitive(sym)) { // primitive method call
- val (newCtx, resKind) = genPrimitiveOp(app, ctx, expectedType)
- generatedType = resKind
- newCtx
- } else { // normal method call
- debuglog("Gen CALL_METHOD with sym: " + sym + " isStaticSymbol: " + sym.isStaticMember)
- val invokeStyle =
- if (sym.isStaticMember)
- Static(onInstance = false)
- else if (sym.isPrivate || sym.isClassConstructor)
- Static(onInstance = true)
- else
- Dynamic
-
- var ctx1 = if (invokeStyle.hasInstance) genLoadQualifier(fun, ctx) else ctx
- ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx1)
- val cm = CALL_METHOD(sym, invokeStyle)
-
- /* In a couple cases, squirrel away a little extra information in the
- * CALL_METHOD for use by GenASM.
- */
- fun match {
- case Select(qual, _) =>
- val qualSym = findHostClass(qual.tpe, sym)
- if (qualSym == ArrayClass) {
- val kind = toTypeKind(qual.tpe)
- cm setTargetTypeKind kind
- log(s"Stored target type kind for {$sym.fullName} as $kind")
- }
- else {
- cm setHostClass qualSym
- if (qual.tpe.typeSymbol != qualSym)
- log(s"Precisified host class for $sym from ${qual.tpe.typeSymbol.fullName} to ${qualSym.fullName}")
- }
- case _ =>
- }
- ctx1.bb.emit(cm, tree.pos)
- ctx1.method.updateRecursive(sym)
- generatedType =
- if (sym.isClassConstructor) UNIT
- else toTypeKind(sym.info.resultType)
- // deal with methods that return Null
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- ctx1
- }
- }
- genLoadApply6
-
- case ApplyDynamic(qual, args) =>
- // TODO - this is where we'd catch dynamic applies for invokedynamic.
- sys.error("No invokedynamic support yet.")
- // val ctx1 = genLoad(qual, ctx, ObjectReference)
- // genLoadArguments(args, tree.symbol.info.paramTypes, ctx1)
- // ctx1.bb.emit(CALL_METHOD(tree.symbol, InvokeDynamic), tree.pos)
- // ctx1
-
- case This(qual) =>
- def genLoadThis = {
- 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) {
- genLoadModule(ctx, tree)
- generatedType = REFERENCE(tree.symbol)
- } else {
- ctx.bb.emit(THIS(ctx.clazz.symbol), tree.pos)
- generatedType = REFERENCE(
- if (tree.symbol == ArrayClass) ObjectClass else ctx.clazz.symbol
- )
- }
- ctx
- }
- genLoadThis
-
- case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) =>
- debugassert(tree.symbol.isModule,
- "Selection of non-module from empty package: " + tree +
- " sym: " + tree.symbol + " at: " + (tree.pos)
- )
- genLoadModule(ctx, tree)
-
- case Select(qualifier, selector) =>
- def genLoadSelect = {
- val sym = tree.symbol
- generatedType = toTypeKind(sym.info)
- val hostClass = findHostClass(qualifier.tpe, sym)
- debuglog(s"Host class of $sym with qual $qualifier (${qualifier.tpe}) is $hostClass")
- val qualSafeToElide = treeInfo isQualifierSafeToElide qualifier
-
- def genLoadQualUnlessElidable: Context =
- if (qualSafeToElide) ctx else genLoadQualifier(tree, ctx)
-
- if (sym.isModule) {
- genLoadModule(genLoadQualUnlessElidable, tree)
- } else {
- val isStatic = sym.isStaticMember
- val ctx1 = if (isStatic) genLoadQualUnlessElidable
- else genLoadQualifier(tree, ctx)
- ctx1.bb.emit(LOAD_FIELD(sym, isStatic) setHostClass hostClass, tree.pos)
- // it's tempting to drop field accesses of type Null instead of adapting them,
- // but field access can cause static class init so we can't. Besides, fields
- // of type Null probably aren't common enough to figure out an optimization
- adaptNullRef(generatedType, expectedType, ctx1, tree.pos)
- ctx1
- }
- }
- genLoadSelect
-
- case Ident(name) =>
- def genLoadIdent = {
- val sym = tree.symbol
- if (!sym.hasPackageFlag) {
- if (sym.isModule) {
- genLoadModule(ctx, tree)
- generatedType = toTypeKind(sym.info)
- } else {
- ctx.method.lookupLocal(sym) match {
- case Some(l) =>
- ctx.bb.emit(LOAD_LOCAL(l), tree.pos)
- generatedType = l.kind
- case None =>
- val saved = settings.uniqid
- settings.uniqid.value = true
- try {
- val methodCode = unit.body.collect { case dd: DefDef
- if dd.symbol == ctx.method.symbol => showCode(dd);
- }.headOption.getOrElse("<unknown>")
- abort(s"symbol $sym does not exist in ${ctx.method}, which contains locals ${ctx.method.locals.mkString(",")}. \nMethod code: $methodCode")
- }
- finally settings.uniqid.value = saved
- }
- }
- }
- ctx
- }
- genLoadIdent
-
- case Literal(value) =>
- def genLoadLiteral = {
- 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 = NullReference
- case _ =>
- ctx.bb.emit(CONSTANT(value), tree.pos)
- generatedType = toTypeKind(tree.tpe)
- }
- ctx
- }
- genLoadLiteral
-
- 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) =>
- def genLoadArrayValue = {
- var ctx1 = ctx
- val elmKind = toTypeKind(tpt.tpe)
- generatedType = ARRAY(elmKind)
- val elems = _elems.toIndexedSeq
-
- 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
- }
- genLoadArrayValue
-
- case Match(selector, cases) =>
- def genLoadMatch = {
- debuglog("Generating SWITCH statement.")
- val ctx1 = genLoad(selector, ctx, INT) // TODO: Java 7 allows strings in switches (so, don't assume INT and don't convert the literals using intValue)
- val afterCtx = ctx1.newBlock()
- afterCtx.bb killIf ctx1.bb.ignore
- var afterCtxReachable = false
- 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 @ CaseDef(pat, guard, body) <- cases) {
- assert(guard == EmptyTree, guard)
- val tmpCtx = ctx1.newBlock()
- tmpCtx.bb killIf ctx1.bb.ignore
- pat match {
- case Literal(value) =>
- tags = value.intValue :: tags
- targets = tmpCtx.bb :: targets
- case Ident(nme.WILDCARD) =>
- default = tmpCtx.bb
- case Alternative(alts) =>
- alts foreach {
- case Literal(value) =>
- tags = value.intValue :: tags
- targets = tmpCtx.bb :: targets
- case _ =>
- abort("Invalid case in alternative in switch-like pattern match: " +
- tree + " at: " + tree.pos)
- }
- case _ =>
- abort("Invalid case statement in switch-like pattern match: " +
- tree + " at: " + (tree.pos))
- }
-
- caseCtx = genLoad(body, tmpCtx, generatedType)
- afterCtxReachable ||= !caseCtx.bb.ignore
- // close the block unless it's already been closed by the body, which closes the block if it ends in a jump (which is emitted to have alternatives share their body)
- caseCtx.bb.closeWith(JUMP(afterCtx.bb) setPos caze.pos)
- }
- afterCtxReachable ||= (default == afterCtx)
- ctx1.bb.emitOnly(
- SWITCH(tags.reverse map (x => List(x)), (default :: targets).reverse) setPos tree.pos
- )
- afterCtx.bb killUnless afterCtxReachable
- afterCtx
- }
- genLoadMatch
-
- case EmptyTree =>
- if (expectedType != UNIT)
- ctx.bb.emit(getZeroOf(expectedType))
- ctx
-
- case _ =>
- abort("Unexpected tree in genLoad: " + tree + "/" + tree.getClass + " at: " + tree.pos)
- }
-
- // emit conversion
- if (generatedType != expectedType) {
- tree match {
- case Literal(Constant(null)) if generatedType == NullReference && expectedType != UNIT =>
- // literal null on the stack (as opposed to a boxed null, see SI-8233),
- // we can bypass `adapt` which would otherwise emit a redundant [DROP, CONSTANT(null)]
- // except one case: when expected type is UNIT (unboxed) where we need to emit just a DROP
- case _ =>
- adapt(generatedType, expectedType, resCtx, tree.pos)
- }
- }
-
- resCtx
- }
-
- /**
- * If we have a method call, field load, or array element load of type Null then
- * we need to convince the JVM that we have a null value because in Scala
- * land Null is a subtype of all ref types, but in JVM land scala.runtime.Null$
- * is not. Note we don't have to adapt loads of locals because the JVM type
- * system for locals does have a null type which it tracks internally. As
- * long as we adapt these other things, the JVM will know that a Scala local of
- * type Null is holding a null.
- */
- private def adaptNullRef(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) {
- debuglog(s"GenICode#adaptNullRef($from, $to, $ctx, $pos)")
-
- // Don't need to adapt null to unit because we'll just drop it anyway. Don't
- // need to adapt to Object or AnyRef because the JVM is happy with
- // upcasting Null to them.
- // We do have to adapt from NullReference to NullReference because we could be storing
- // this value into a local of type Null and we want the JVM to see that it's
- // a null value so we don't have to also adapt local loads.
- if (from == NullReference && to != UNIT && to != ObjectReference && to != AnyRefReference) {
- assert(to.isRefOrArrayType, s"Attempt to adapt a null to a non reference type $to.")
- // adapt by dropping what we've got and pushing a null which
- // will convince the JVM we really do have null
- ctx.bb.emit(DROP(from), pos)
- ctx.bb.emit(CONSTANT(Constant(null)), pos)
- }
- }
-
- private def adapt(from: TypeKind, to: TypeKind, ctx: Context, pos: Position) {
- // An awful lot of bugs explode here - let's leave ourselves more clues.
- // A typical example is an overloaded type assigned after typer.
- debuglog(s"GenICode#adapt($from, $to, $ctx, $pos)")
-
- def coerce(from: TypeKind, to: TypeKind) = ctx.bb.emit(CALL_PRIMITIVE(Conversion(from, to)), pos)
-
- (from, to) match {
- // The JVM doesn't have a Nothing equivalent, so it doesn't know that a method of type Nothing can't actually return. So for instance, with
- // def f: String = ???
- // we need
- // 0: getstatic #25; //Field scala/Predef$.MODULE$:Lscala/Predef$;
- // 3: invokevirtual #29; //Method scala/Predef$.$qmark$qmark$qmark:()Lscala/runtime/Nothing$;
- // 6: athrow
- // So this case tacks on the ahtrow which makes the JVM happy because class Nothing is declared as a subclass of Throwable
- case (NothingReference, _) =>
- ctx.bb.emit(THROW(ThrowableClass))
- ctx.bb.enterIgnoreMode()
- case (NullReference, REFERENCE(_)) =>
- // SI-8223 we can't assume that the stack contains a `null`, it might contain a Null$
- ctx.bb.emit(Seq(DROP(from), CONSTANT(Constant(null))))
- case _ if from isAssignabledTo to =>
- ()
- case (_, UNIT) =>
- ctx.bb.emit(DROP(from), pos)
- // otherwise we'd better be doing a primitive -> primitive coercion or there's a problem
- case _ if !from.isRefOrArrayType && !to.isRefOrArrayType =>
- coerce(from, to)
- case _ =>
- assert(false, s"Can't convert from $from to $to in unit ${unit.source} at $pos")
- }
- }
-
- /** 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 = {
- debugassert(
- args.length == label.params.length,
- "Wrong number of arguments in call to label " + label.symbol
- )
- var ctx1 = ctx
-
- def isTrivial(kv: (Tree, Symbol)) = kv match {
- case (This(_), p) if p.name == nme.THIS => true
- case (arg @ Ident(_), p) if arg.symbol == p => true
- case _ => false
- }
-
- val stores = args zip label.params filterNot isTrivial map {
- case (arg, param) =>
- val local = ctx.method.lookupLocal(param).get
- ctx1 = genLoad(arg, ctx1, local.kind)
-
- val store =
- if (param.name == nme.THIS) STORE_THIS(toTypeKind(ctx1.clazz.symbol.tpe))
- else STORE_LOCAL(local)
-
- store setPos arg.pos
- }
-
- // store arguments in reverse order on the stack
- ctx1.bb.emit(stores.reverse)
- ctx1
- }
-
- private def genLoadArguments(args: List[Tree], tpes: List[Type], ctx: Context): Context =
- (args zip tpes).foldLeft(ctx) {
- case (res, (arg, tpe)) =>
- genLoad(arg, res, toTypeKind(tpe))
- }
-
- private def genLoadModule(ctx: Context, tree: Tree): Context = {
- // Working around SI-5604. Rather than failing the compile when we see
- // a package here, check if there's a package object.
- val sym = (
- if (!tree.symbol.isPackageClass) tree.symbol
- else tree.symbol.info.member(nme.PACKAGE) match {
- case NoSymbol => abort("Cannot use package as value: " + tree)
- case s =>
- devWarning(s"Found ${tree.symbol} where a package object is required. Converting to ${s.moduleClass}")
- s.moduleClass
- }
- )
- debuglog("LOAD_MODULE from %s: %s".format(tree.shortClass, sym))
- ctx.bb.emit(LOAD_MODULE(sym), tree.pos)
- ctx
- }
-
- 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 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: @switch) 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)
- }
- }
-
- /** The Object => String overload.
- */
- private lazy val String_valueOf: Symbol = getMember(StringModule, nme.valueOf) filter (sym =>
- sym.info.paramTypes match {
- case List(pt) => pt.typeSymbol == ObjectClass
- case _ => false
- }
- )
-
- // I wrote it this way before I realized all the primitive types are
- // boxed at this point, so I'd have to unbox them. Keeping it around in
- // case we want to get more precise.
- //
- // private def valueOfForType(tp: Type): Symbol = {
- // val xs = getMember(StringModule, nme.valueOf) filter (sym =>
- // // We always exclude the Array[Char] overload because java throws an NPE if
- // // you pass it a null. It will instead find the Object one, which doesn't.
- // sym.info.paramTypes match {
- // case List(pt) => pt.typeSymbol != ArrayClass && (tp <:< pt)
- // case _ => false
- // }
- // )
- // xs.alternatives match {
- // case List(sym) => sym
- // case _ => NoSymbol
- // }
- // }
-
- /** Generate string concatenation.
- */
- def genStringConcat(tree: Tree, ctx: Context): Context = {
- liftStringConcat(tree) match {
- // Optimization for expressions of the form "" + x. We can avoid the StringBuilder.
- case List(Literal(Constant("")), arg) =>
- debuglog("Rewriting \"\" + x as String.valueOf(x) for: " + arg)
- val ctx1 = genLoad(arg, ctx, ObjectReference)
- ctx1.bb.emit(CALL_METHOD(String_valueOf, Static(onInstance = false)), arg.pos)
- ctx1
- case concatenations =>
- debuglog("Lifted string concatenations for " + tree + "\n to: " + concatenations)
- var ctx1 = ctx
- 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
- }
- }
-
- /** Generate the scala ## method.
- */
- def genScalaHash(tree: Tree, ctx: Context): Context = {
- val hashMethod = {
- ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule))
- getMember(ScalaRunTimeModule, nme.hash_)
- }
-
- val ctx1 = genLoad(tree, ctx, ObjectReference)
- ctx1.bb.emit(CALL_METHOD(hashMethod, Static(onInstance = false)))
- 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)
- }
-
- /**
- * Find the label denoted by `lsym` and enter it in context `ctx`.
- *
- * We only enter one symbol at a time, even though we might traverse the same
- * tree more than once per method. That's because we cannot enter labels that
- * might be duplicated (for instance, inside finally blocks).
- *
- * 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 resolveForwardLabel(tree: Tree, ctx: Context, lsym: Symbol): Unit = tree foreachPartial {
- case t @ LabelDef(_, params, rhs) if t.symbol == lsym =>
- ctx.labels.getOrElseUpdate(t.symbol, {
- val locals = params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false))
- ctx.method addLocals locals
-
- new Label(t.symbol) setParams (params map (_.symbol))
- })
- rhs
- }
-
- /**
- * 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): Boolean =
- {
- /**
- * Generate the de-sugared comparison mechanism that will underly an '=='
- *
- * @param l left-hand side of the '=='
- * @param r right-hand side of the '=='
- * @param code the comparison operator to use
- * @return true if either branch can continue normally to a follow on block, false otherwise
- */
- def genComparisonOp(l: Tree, r: Tree, code: Int): Boolean = {
- 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)
- }
-
- // special-case reference (in)equality test for null (null eq x, x eq null)
- lazy val nonNullSide = ifOneIsNull(l, r)
- if (isReferenceEqualityOp(code) && nonNullSide != null) {
- val ctx1 = genLoad(nonNullSide, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb.emitOnly(
- CZJUMP(thenCtx.bb, elseCtx.bb, op, ObjectReference)
- )
- branchesReachable
- }
- else {
- val kind = getMaxType(l.tpe :: r.tpe :: Nil)
- var ctx1 = genLoad(l, ctx, kind)
- ctx1 = genLoad(r, ctx1, kind)
- val branchesReachable = !ctx1.bb.ignore
-
- ctx1.bb.emitOnly(
- CJUMP(thenCtx.bb, elseCtx.bb, op, kind) setPos r.pos
- )
- branchesReachable
- }
- }
-
- debuglog("Entering genCond with tree: " + tree)
-
- // the default emission
- def default(): Boolean = {
- val ctx1 = genLoad(tree, ctx, BOOL)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb.closeWith(CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL) setPos tree.pos)
- branchesReachable
- }
-
- tree match {
- // The comparison symbol is in ScalaPrimitives's "primitives" map
- case Apply(fun, args) if isPrimitive(fun.symbol) =>
- import scalaPrimitives.{ ZNOT, ZAND, ZOR, EQ, getPrimitive }
-
- // lhs and rhs of test
- lazy val Select(lhs, _) = fun
- lazy val rhs = args.head
-
- def genZandOrZor(and: Boolean): Boolean = {
- val ctxInterm = ctx.newBlock()
-
- val lhsBranchesReachable = if (and) genCond(lhs, ctx, ctxInterm, elseCtx)
- else genCond(lhs, ctx, thenCtx, ctxInterm)
- // If lhs is known to throw, we can kill the just created ctxInterm.
- ctxInterm.bb killUnless lhsBranchesReachable
-
- val rhsBranchesReachable = genCond(rhs, ctxInterm, thenCtx, elseCtx)
-
- // Reachable means "it does not always throw", i.e. "it might not throw".
- // In an expression (a && b) or (a || b), the b branch might not be evaluated.
- // Such an expression is therefore known to throw only if both expressions throw. Or,
- // successors are reachable if either of the two is reachable (SI-8625).
- lhsBranchesReachable || rhsBranchesReachable
- }
- def genRefEq(isEq: Boolean) = {
- val f = genEqEqPrimitive(lhs, rhs, ctx) _
- if (isEq) f(thenCtx, elseCtx)
- else f(elseCtx, thenCtx)
- }
-
- getPrimitive(fun.symbol) match {
- case ZNOT => genCond(lhs, ctx, elseCtx, thenCtx)
- case ZAND => genZandOrZor(and = true)
- case ZOR => genZandOrZor(and = false)
- case code =>
- // x == y where LHS is reference type
- if (isUniversalEqualityOp(code) && toTypeKind(lhs.tpe).isReferenceType) {
- if (code == EQ) genRefEq(isEq = true)
- else genRefEq(isEq = false)
- }
- else if (isComparisonOp(code))
- genComparisonOp(lhs, rhs, code)
- else
- default()
- }
-
- case _ => default()
- }
- }
-
- /**
- * 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
- * @return true if either branch can continue normally to a follow on block, false otherwise
- */
- def genEqEqPrimitive(l: Tree, r: Tree, ctx: Context)(thenCtx: Context, elseCtx: Context): Boolean = {
- def getTempLocal = ctx.method.lookupLocal(nme.EQEQ_LOCAL_VAR) getOrElse {
- ctx.makeLocal(l.pos, AnyRefTpe, nme.EQEQ_LOCAL_VAR.toString)
- }
-
- /* 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 areSameFinals = l.tpe.isFinalType && r.tpe.isFinalType && (l.tpe =:= r.tpe)
- !areSameFinals && isMaybeBoxed(l.tpe.typeSymbol) && isMaybeBoxed(r.tpe.typeSymbol)
- }
-
- if (mustUseAnyComparator) {
- // when -optimise is on we call the @inline-version of equals, found in ScalaRunTime
- val equalsMethod: Symbol = {
- if (!settings.optimise) {
- if (l.tpe <:< BoxedNumberClass.tpe) {
- if (r.tpe <:< BoxedNumberClass.tpe) platform.externalEqualsNumNum
- else if (r.tpe <:< BoxedCharacterClass.tpe) platform.externalEqualsNumObject // will be externalEqualsNumChar in 2.12, SI-9030
- else platform.externalEqualsNumObject
- } else platform.externalEquals
- } else {
- ctx.bb.emit(LOAD_MODULE(ScalaRunTimeModule))
- getMember(ScalaRunTimeModule, nme.inlinedEquals)
- }
- }
-
- val ctx1 = genLoad(l, ctx, ObjectReference)
- val ctx2 = genLoad(r, ctx1, ObjectReference)
- val branchesReachable = !ctx2.bb.ignore
- ctx2.bb.emitOnly(
- CALL_METHOD(equalsMethod, if (settings.optimise) Dynamic else Static(onInstance = false)),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- }
- else {
- if (isNull(l)) {
- // null == expr -> expr eq null
- val ctx1 = genLoad(r, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- branchesReachable
- } else if (isNull(r)) {
- // expr == null -> expr eq null
- val ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1.bb emitOnly CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- branchesReachable
- } else if (isNonNullExpr(l)) {
- // Avoid null check if L is statically non-null.
- //
- // "" == expr -> "".equals(expr)
- // Nil == expr -> Nil.equals(expr)
- //
- // Common enough (through pattern matching) to treat this specially here rather than
- // hoping that -Yconst-opt is enabled. The impossible branches for null checks lead
- // to spurious "branch not covered" warnings in Jacoco code coverage.
- var ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- ctx1 = genLoad(r, ctx1, ObjectReference)
- ctx1.bb emitOnly(
- CALL_METHOD(Object_equals, Dynamic),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- } else {
- val eqEqTempLocal = getTempLocal
- var ctx1 = genLoad(l, ctx, ObjectReference)
- val branchesReachable = !ctx1.bb.ignore
- lazy val nonNullCtx = {
- val block = ctx1.newBlock()
- block.bb killUnless branchesReachable
- block
- }
-
- // l == r -> if (l eq null) r eq null else l.equals(r)
- ctx1 = genLoad(r, ctx1, ObjectReference)
- val nullCtx = ctx1.newBlock()
- nullCtx.bb killUnless branchesReachable
-
- ctx1.bb.emitOnly(
- STORE_LOCAL(eqEqTempLocal) setPos l.pos,
- DUP(ObjectReference),
- CZJUMP(nullCtx.bb, nonNullCtx.bb, EQ, ObjectReference)
- )
- nullCtx.bb.emitOnly(
- DROP(ObjectReference) setPos l.pos, // type of AnyRef
- LOAD_LOCAL(eqEqTempLocal),
- CZJUMP(thenCtx.bb, elseCtx.bb, EQ, ObjectReference)
- )
- nonNullCtx.bb.emitOnly(
- LOAD_LOCAL(eqEqTempLocal) setPos l.pos,
- CALL_METHOD(Object_equals, Dynamic),
- CZJUMP(thenCtx.bb, elseCtx.bb, NE, BOOL)
- )
- branchesReachable
- }
- }
- }
-
- /**
- * Add all fields of the given class symbol to the current ICode
- * class.
- */
- private def addClassFields(ctx: Context, cls: Symbol) {
- debugassert(ctx.clazz.symbol eq cls,
- "Classes are not the same: " + ctx.clazz.symbol + ", " + cls)
-
- /* Non-method term members are fields, except for module members. Module
- * members can only happen on .NET (no flatten) for inner traits. There,
- * a module symbol is generated (transformInfo in mixin) which is used
- * as owner for the members of the implementation class (so that the
- * backend emits them as static).
- * No code is needed for this module symbol.
- */
- for (f <- cls.info.decls ; if !f.isMethod && f.isTerm && !f.isModule)
- 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 = tree exists {
- case Try(_, _, _) => true
- case _ => false
- }
-
- /**
- * 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.
- */
- 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.isDefined) {
- val Some(cont) = optCont
- val pred = block.predecessors
- debuglog("Preds: " + pred + " of " + block + " (" + optCont + ")")
- pred foreach { p =>
- changed = true
- p.lastInstruction match {
- case CJUMP(succ, fail, cond, kind) if (succ == block || fail == block) =>
- debuglog("Pruning empty if branch.")
- 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 (succ == block || fail == block) =>
- debuglog("Pruning empty ifz branch.")
- 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 (b == block) =>
- debuglog("Pruning empty JMP branch.")
- val replaced = p.replaceInstruction(p.lastInstruction, JUMP(cont))
- debugassert(replaced, "Didn't find p.lastInstruction")
-
- case SWITCH(tags, labels) if (labels contains block) =>
- debuglog("Pruning empty SWITCH branch.")
- p.replaceInstruction(p.lastInstruction,
- SWITCH(tags, labels map (l => if (l == block) cont else l)))
-
- // the last instr of the predecessor `p` is not a jump to the block `block`.
- // this happens when `block` is part of an exception handler covering `b`.
- case _ => ()
- }
- }
- if (changed) {
- debuglog("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.blocks foreach prune0
- } while (changed)
-
- debuglog("Prune fixpoint reached in " + n + " iterations.")
- }
-
- def getMaxType(ts: List[Type]): TypeKind =
- ts map toTypeKind reduceLeft (_ maxType _)
-
- /** 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: Set[Symbol]) extends Transformer {
- val labels = perRunCaches.newMap[Symbol, Symbol]()
- 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 = {
- val sym = t.symbol
- def getLabel(pos: Position, name: Name) =
- labels.getOrElseUpdate(sym,
- method.newLabel(unit.freshTermName(name.toString), sym.pos) setInfo sym.tpe
- )
-
- t match {
- case t @ Apply(_, args) if sym.isLabel && !boundLabels(sym) =>
- val newSym = getLabel(sym.pos, sym.name)
- Apply(global.gen.mkAttributedRef(newSym), transformTrees(args)) setPos t.pos setType t.tpe
-
- case t @ LabelDef(name, params, rhs) =>
- val newSym = getLabel(t.pos, name)
- val tree = treeCopy.LabelDef(t, newSym.name, params, transform(rhs))
- tree.symbol = newSym
-
- val pair = (newSym -> (new Label(newSym) setParams (params map (_.symbol))))
- log("Added " + pair + " to labels.")
- ctx.labels += pair
- ctx.method.addLocals(params map (p => new Local(p.symbol, toTypeKind(p.symbol.info), false)))
-
- tree
-
- case _ => super.transform(t)
- }
- }
- }
-
- /////////////////////// Context ////////////////////////////////
-
- sealed abstract class Cleanup(val value: AnyRef) {
- def contains(x: AnyRef) = value == x
- }
- case class MonitorRelease(m: Local) extends Cleanup(m) { }
- case class Finalizer(f: Tree, ctx: Context) extends Cleanup (f) { }
-
- def duplicateFinalizer(boundLabels: Set[Symbol], targetCtx: Context, finalizer: Tree) = {
- (new DuplicateLabels(boundLabels))(targetCtx, finalizer)
- }
-
- def savingCleanups[T](ctx: Context)(body: => T): T = {
- val saved = ctx.cleanups
- try body
- finally ctx.cleanups = saved
- }
-
- /**
- * 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 = perRunCaches.newMap[Symbol, Label]()
-
- /** 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 exception handlers we are currently generating code for */
- var currentExceptionHandlers: List[ExceptionHandler] = Nil
-
- /** The current local variable scope. */
- var scope: Scope = EmptyScope
-
- var handlerCount = 0
-
- override def toString =
- s"package $packg { class $clazz { def $method { bb=$bb } } }"
-
- def loadException(ctx: Context, exh: ExceptionHandler, pos: Position) = {
- debuglog("Emitting LOAD_EXCEPTION for class: " + exh.loadExceptionClass)
- ctx.bb.emit(LOAD_EXCEPTION(exh.loadExceptionClass) setPos pos, pos)
- }
-
- 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.currentExceptionHandlers = other.currentExceptionHandlers
- 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 contains monitor,
- "Bad nesting of cleanup operations: " + cleanups + " trying to exit from monitor: " + monitor)
- cleanups = cleanups.tail
- this
- }
-
- def addFinalizer(f: Tree, ctx: Context): this.type = {
- cleanups = Finalizer(f, ctx) :: cleanups
- this
- }
-
- /** Prepare a new context upon entry into a method.
- */
- def enterMethod(m: IMethod, d: DefDef): Context = {
- val ctx1 = new Context(this) setMethod(m)
- ctx1.labels = mutable.HashMap()
- ctx1.method.code = new Code(m)
- ctx1.bb = ctx1.method.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 (_ addCoveredBlock block)
- currentExceptionHandlers foreach (_ addBlock block)
- block.varsInScope.clear()
- block.varsInScope ++= scope.varsInScope
- new Context(this) setBasicBlock block
- }
-
- def enterScope() {
- scope = new Scope(scope)
- }
-
- def exitScope() {
- if (bb.nonEmpty) {
- 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).
- */
- private def newExceptionHandler(cls: Symbol, pos: Position): ExceptionHandler = {
- handlerCount += 1
- val exh = new ExceptionHandler(method, newTermNameCached("" + handlerCount), cls, pos)
- method.addHandler(exh)
- handlers = exh :: handlers
- debuglog("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
- debuglog("added handler: " + exh)
- }
-
- /** Return a new context for generating code for the given
- * exception handler.
- */
- private def enterExceptionHandler(exh: ExceptionHandler): Context = {
- currentExceptionHandlers ::= exh
- val ctx = newBlock()
- exh.setStartBlock(ctx.bb)
- ctx
- }
-
- def endHandler() {
- currentExceptionHandlers = currentExceptionHandlers.tail
- }
-
- /** 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(unit.freshTermName(name), pos, Flags.SYNTHETIC) setInfo tpe
- 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:
- *
- * ` ctx.Try( ctx => {
- * ctx.bb.emit(...) // protected block
- * }, (ThrowableClass,
- * ctx => {
- * ctx.bb.emit(...); // exception handler
- * }), (AnotherExceptionClass,
- * ctx => {...
- * } ))`
- *
- * The resulting structure will look something like
- *
- * outer:
- * // this 'useless' jump will be removed later,
- * // for now it separates the try body's blocks from previous
- * // code since the try body needs its own exception handlers
- * JUMP body
- *
- * body:
- * [ try body ]
- * JUMP normalExit
- *
- * catch[i]:
- * [ handler[i] body ]
- * JUMP normalExit
- *
- * catchAll:
- * STORE exception
- * [ finally body ]
- * THROW exception
- *
- * normalExit:
- * [ finally body ]
- *
- * each catch[i] will cover body. catchAll will cover both body and each catch[i]
- * Additional finally copies are created on the emission of every RETURN in the try body and exception handlers.
- *
- * This could result in unreachable code which has to be cleaned up later, e.g. if the try and all the exception
- * handlers always end in RETURN then there will be no "normal" flow out of the try/catch/finally.
- * Later reachability analysis will remove unreachable code.
- */
- def Try(body: Context => Context,
- handlers: List[(Symbol, TypeKind, Context => Context)],
- finalizer: Tree,
- tree: Tree) = {
-
- val outerCtx = this.dup // context for generating exception handlers, covered by the catch-all finalizer
- val finalizerCtx = this.dup // context for generating finalizer handler
- val normalExitCtx = outerCtx.newBlock() // context where flow will go on a "normal" (non-return, non-throw) exit from a try or catch handler
- var normalExitReachable = false
- var tmp: Local = null
- val kind = toTypeKind(tree.tpe)
- val guardResult = kind != UNIT && mayCleanStack(finalizer)
- // we need to save bound labels before any code generation is performed on
- // the current context (otherwise, any new labels in the finalizer that need to
- // be duplicated would be incorrectly considered bound -- see #2850).
- val boundLabels: Set[Symbol] = Set.empty ++ labels.keySet
-
- if (guardResult) {
- tmp = this.makeLocal(tree.pos, tree.tpe, "tmp")
- }
-
- def emitFinalizer(ctx: Context): Context = if (!finalizer.isEmpty) {
- val ctx1 = finalizerCtx.dup.newBlock()
- ctx1.bb killIf ctx.bb.ignore
- ctx.bb.closeWith(JUMP(ctx1.bb))
-
- if (guardResult) {
- ctx1.bb.emit(STORE_LOCAL(tmp))
- val ctx2 = genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT)
- ctx2.bb.emit(LOAD_LOCAL(tmp))
- ctx2
- } else
- genLoad(duplicateFinalizer(boundLabels, ctx1, finalizer), ctx1, UNIT)
- } else ctx
-
-
- // Generate the catch-all exception handler that deals with uncaught exceptions coming
- // from the try or exception handlers. It catches the exception, runs the finally code, then rethrows
- // the exception
- if (settings.YdisableUnreachablePrevention || !outerCtx.bb.ignore) {
- if (finalizer != EmptyTree) {
- val exh = outerCtx.newExceptionHandler(NoSymbol, finalizer.pos) // finalizer covers exception handlers
- this.addActiveHandler(exh) // .. and body as well
- val exhStartCtx = finalizerCtx.enterExceptionHandler(exh)
- exhStartCtx.bb killIf outerCtx.bb.ignore
- val exception = exhStartCtx.makeLocal(finalizer.pos, ThrowableTpe, "exc")
- loadException(exhStartCtx, exh, finalizer.pos)
- exhStartCtx.bb.emit(STORE_LOCAL(exception))
- val exhEndCtx = genLoad(finalizer, exhStartCtx, UNIT)
- exhEndCtx.bb.emit(LOAD_LOCAL(exception))
- exhEndCtx.bb.closeWith(THROW(ThrowableClass))
- exhEndCtx.bb.enterIgnoreMode()
- finalizerCtx.endHandler()
- }
-
- // Generate each exception handler
- for ((sym, kind, handler) <- handlers) {
- val exh = this.newExceptionHandler(sym, tree.pos)
- val exhStartCtx = outerCtx.enterExceptionHandler(exh)
- exhStartCtx.bb killIf outerCtx.bb.ignore
- exhStartCtx.addFinalizer(finalizer, finalizerCtx)
- loadException(exhStartCtx, exh, tree.pos)
- val exhEndCtx = handler(exhStartCtx)
- normalExitReachable ||= !exhEndCtx.bb.ignore
- exhEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
- outerCtx.endHandler()
- }
- }
-
- val bodyCtx = this.newBlock()
- bodyCtx.bb killIf outerCtx.bb.ignore
- if (finalizer != EmptyTree)
- bodyCtx.addFinalizer(finalizer, finalizerCtx)
-
- val bodyEndCtx = body(bodyCtx)
-
- outerCtx.bb.closeWith(JUMP(bodyCtx.bb))
-
- normalExitReachable ||= !bodyEndCtx.bb.ignore
- normalExitCtx.bb killUnless normalExitReachable
- bodyEndCtx.bb.closeWith(JUMP(normalExitCtx.bb))
-
- emitFinalizer(normalExitCtx)
- }
- }
- }
-
- /**
- * Represent a label in the current method code. In order
- * to support forward jumps, labels can be created without
- * having a designated 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) {
- val map = mapFrom(toPatch)(patch)
- code.blocks foreach (_ 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 basic 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 = s"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
-
- /** Return all locals that are in scope. */
- def varsInScope: Buffer[Local] = outer.varsInScope.clone() ++= locals
-
- override def toString() = locals.mkString(outer.toString + "[", ", ", "]")
- }
-
- object EmptyScope extends Scope(null) {
- override def toString() = "[]"
- override def varsInScope: Buffer[Local] = new ListBuffer
- }
-}