scala.tools.nsc.backend.icode.GenICode.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scala-compiler Show documentation
Show all versions of scala-compiler Show documentation
Compiler for the SubScript extension of the Scala Programming Language
The newest version!
/* 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, 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 {
try {
val Some(l) = ctx.method.lookupLocal(sym)
ctx.bb.emit(LOAD_LOCAL(l), tree.pos)
generatedType = l.kind
} catch {
case ex: MatchError =>
abort("symbol " + sym + " does not exist in " + ctx.method)
}
}
}
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 emitt 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 primtive -> 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 unreacahble 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 aswell
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 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) {
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
}
}