scala.tools.nsc.backend.msil.GenMSIL.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 Scala Programming Language
/* NSC -- new scala compiler
* Copyright 2005-2013 LAMP/EPFL
* @author Nikolay Mihaylov
*/
package scala.tools.nsc
package backend.msil
import java.io.{File, IOException}
import java.nio.{ByteBuffer, ByteOrder}
import scala.collection.{ mutable, immutable }
import scala.tools.nsc.symtab._
import ch.epfl.lamp.compiler.msil.{Type => MsilType, _}
import ch.epfl.lamp.compiler.msil.emit._
import ch.epfl.lamp.compiler.msil.util.PECustomMod
import scala.language.postfixOps
abstract class GenMSIL extends SubComponent {
import global._
import loaders.clrTypes
import clrTypes.{types, constructors, methods, fields}
import icodes._
import icodes.opcodes._
val x = loaders
/** Create a new phase */
override def newPhase(p: Phase) = new MsilPhase(p)
val phaseName = "msil"
/** MSIL code generation phase
*/
class MsilPhase(prev: Phase) extends GlobalPhase(prev) {
def name = phaseName
override def newFlags = phaseNewFlags
override def erasedTypes = true
override def run() {
if (settings.debug.value) inform("[running phase " + name + " on icode]")
val codeGenerator = new BytecodeGenerator
//classes is ICodes.classes, a HashMap[Symbol, IClass]
classes.values foreach codeGenerator.findEntryPoint
if( opt.showClass.isDefined && (codeGenerator.entryPoint == null) ) { // TODO introduce dedicated setting instead
val entryclass = opt.showClass.get.toString
warning("Couldn't find entry class " + entryclass)
}
codeGenerator.initAssembly
val classesSorted = classes.values.toList.sortBy(c => c.symbol.id) // simplifies comparing cross-compiler vs. .exe output
classesSorted foreach codeGenerator.createTypeBuilder
classesSorted foreach codeGenerator.createClassMembers
try {
classesSorted foreach codeGenerator.genClass
} finally {
codeGenerator.writeAssembly
}
}
override def apply(unit: CompilationUnit) {
abort("MSIL works on icode classes, not on compilation units!")
}
}
/**
* MSIL bytecode generator.
*
*/
class BytecodeGenerator {
val MODULE_INSTANCE_NAME = "MODULE$"
import clrTypes.{VOID => MVOID, BOOLEAN => MBOOL, BYTE => MBYTE, SHORT => MSHORT,
CHAR => MCHAR, INT => MINT, LONG => MLONG, FLOAT => MFLOAT,
DOUBLE => MDOUBLE, OBJECT => MOBJECT, STRING => MSTRING,
STRING_ARRAY => MSTRING_ARRAY,
SYMTAB_CONSTR => SYMTAB_ATTRIBUTE_CONSTRUCTOR,
SYMTAB_DEFAULT_CONSTR => SYMTAB_ATTRIBUTE_EMPTY_CONSTRUCTOR}
val EXCEPTION = clrTypes.getType("System.Exception")
val MBYTE_ARRAY = clrTypes.mkArrayType(MBYTE)
val ICLONEABLE = clrTypes.getType("System.ICloneable")
val MEMBERWISE_CLONE = MOBJECT.GetMethod("MemberwiseClone", MsilType.EmptyTypes)
val MMONITOR = clrTypes.getType("System.Threading.Monitor")
val MMONITOR_ENTER = MMONITOR.GetMethod("Enter", Array(MOBJECT))
val MMONITOR_EXIT = MMONITOR.GetMethod("Exit", Array(MOBJECT))
val MSTRING_BUILDER = clrTypes.getType("System.Text.StringBuilder")
val MSTRING_BUILDER_CONSTR = MSTRING_BUILDER.GetConstructor(MsilType.EmptyTypes)
val MSTRING_BUILDER_TOSTRING = MSTRING_BUILDER.GetMethod("ToString",
MsilType.EmptyTypes)
val TYPE_FROM_HANDLE =
clrTypes.getType("System.Type").GetMethod("GetTypeFromHandle", Array(clrTypes.getType("System.RuntimeTypeHandle")))
val INT_PTR = clrTypes.getType("System.IntPtr")
val JOBJECT = definitions.ObjectClass
val JSTRING = definitions.StringClass
val SystemConvert = clrTypes.getType("System.Convert")
val objParam = Array(MOBJECT)
val toBool: MethodInfo = SystemConvert.GetMethod("ToBoolean", objParam) // see comment in emitUnbox
val toSByte: MethodInfo = SystemConvert.GetMethod("ToSByte", objParam)
val toShort: MethodInfo = SystemConvert.GetMethod("ToInt16", objParam)
val toChar: MethodInfo = SystemConvert.GetMethod("ToChar", objParam)
val toInt: MethodInfo = SystemConvert.GetMethod("ToInt32", objParam)
val toLong: MethodInfo = SystemConvert.GetMethod("ToInt64", objParam)
val toFloat: MethodInfo = SystemConvert.GetMethod("ToSingle", objParam)
val toDouble: MethodInfo = SystemConvert.GetMethod("ToDouble", objParam)
//val boxedUnit: FieldInfo = msilType(definitions.BoxedUnitModule.info).GetField("UNIT")
val boxedUnit: FieldInfo = fields(definitions.BoxedUnit_UNIT)
// Scala attributes
// symtab.Definitions -> object (singleton..)
val SerializableAttr = definitions.SerializableAttr.tpe
val CloneableAttr = definitions.CloneableAttr.tpe
val TransientAtt = definitions.TransientAttr.tpe
// remoting: the architectures are too different, no mapping (no portable code
// possible)
// java instance methods that are mapped to static methods in .net
// these will need to be called with OpCodes.Call (not Callvirt)
val dynToStatMapped = mutable.HashSet[Symbol]()
initMappings()
/** Create the mappings between java and .net classes and methods */
private def initMappings() {
mapType(definitions.AnyClass, MOBJECT)
mapType(definitions.AnyRefClass, MOBJECT)
//mapType(definitions.NullClass, clrTypes.getType("scala.AllRef$"))
//mapType(definitions.NothingClass, clrTypes.getType("scala.All$"))
// FIXME: for some reason the upper two lines map to null
mapType(definitions.NullClass, EXCEPTION)
mapType(definitions.NothingClass, EXCEPTION)
mapType(definitions.BooleanClass, MBOOL)
mapType(definitions.ByteClass, MBYTE)
mapType(definitions.ShortClass, MSHORT)
mapType(definitions.CharClass, MCHAR)
mapType(definitions.IntClass, MINT)
mapType(definitions.LongClass, MLONG)
mapType(definitions.FloatClass, MFLOAT)
mapType(definitions.DoubleClass, MDOUBLE)
}
var clasz: IClass = _
var method: IMethod = _
var massembly: AssemblyBuilder = _
var mmodule: ModuleBuilder = _
var mcode: ILGenerator = _
var assemName: String = _
var firstSourceName = ""
var outDir: File = _
var srcPath: File = _
var moduleName: String = _
def initAssembly() {
assemName = settings.assemname.value
if (assemName == "") {
if (entryPoint != null) {
assemName = msilName(entryPoint.enclClass)
// remove the $ at the end (from module-name)
assemName = assemName.substring(0, assemName.length() - 1)
} else {
// assuming filename of first source file
assert(firstSourceName.endsWith(".scala"), firstSourceName)
assemName = firstSourceName.substring(0, firstSourceName.length() - 6)
}
} else {
if (assemName.endsWith(".msil"))
assemName = assemName.substring(0, assemName.length()-5)
if (assemName.endsWith(".il"))
assemName = assemName.substring(0, assemName.length()-3)
val f: File = new File(assemName)
assemName = f.getName()
}
outDir = new File(settings.outdir.value)
srcPath = new File(settings.sourcedir.value)
val assemblyName = new AssemblyName()
assemblyName.Name = assemName
massembly = AssemblyBuilderFactory.DefineDynamicAssembly(assemblyName)
moduleName = assemName // + (if (entryPoint == null) ".dll" else ".exe")
// filename here: .dll or .exe (in both parameters), second: give absolute-path
mmodule = massembly.DefineDynamicModule(moduleName,
new File(outDir, moduleName).getAbsolutePath())
assert (mmodule != null)
}
/**
* Form of the custom Attribute parameter (Ecma-335.pdf)
* - p. 163 for CustomAttrib Form,
* - p. 164 for FixedArg Form (Array and Element) (if array or not is known!)
* !! least significant byte first if values longer than one byte !!
*
* 1: Prolog (unsigned int16, value 0x0001) -> symtab[0] = 0x01, symtab[1] = 0x00
* 2: FixedArgs (directly the data, get number and types from related constructor)
* 2.1: length of the array (unsigned int32, 4 bytes, least significant first)
* 2.2: the byte array data
* 3: NumNamed (unsigned int16, number of named fields and properties, 0x0000)
*/
def addSymtabAttribute(sym: Symbol, tBuilder: TypeBuilder) {
def addMarker() {
val markerSymtab = new Array[Byte](4)
markerSymtab(0) = 1.toByte
tBuilder.SetCustomAttribute(SYMTAB_ATTRIBUTE_EMPTY_CONSTRUCTOR, markerSymtab)
}
// both conditions are needed (why exactly..?)
if (tBuilder.Name.endsWith("$") || sym.isModuleClass) {
addMarker()
} else {
currentRun.symData.get(sym) match {
case Some(pickle) =>
var size = pickle.writeIndex
val symtab = new Array[Byte](size + 8)
symtab(0) = 1.toByte
for (i <- 2 until 6) {
symtab(i) = (size & 0xff).toByte
size = size >> 8
}
java.lang.System.arraycopy(pickle.bytes, 0, symtab, 6, pickle.writeIndex)
tBuilder.SetCustomAttribute(SYMTAB_ATTRIBUTE_CONSTRUCTOR, symtab)
currentRun.symData -= sym
currentRun.symData -= sym.companionSymbol
case _ =>
addMarker()
}
}
}
/**
* Mutates `member` adding CLR attributes (if any) based on sym.annotations.
* Please notice that CLR custom modifiers are a different beast (see customModifiers below)
* and thus shouldn't be added by this method.
*/
def addAttributes(member: ICustomAttributeSetter, annotations: List[AnnotationInfo]) {
val attributes = annotations.map(_.atp.typeSymbol).collect {
case definitions.TransientAttr => null // TODO this is just an example
}
return // TODO: implement at some point
}
/**
* What's a CLR custom modifier? Intro available as source comments in compiler.msil.CustomModifier.
* It's basically a marker associated with a location (think of FieldInfo, ParameterInfo, and PropertyInfo)
* and thus that marker (be it optional or required) becomes part of the signature of that location.
* Some annotations will become CLR attributes (see addAttributes above), others custom modifiers (this method).
*/
def customModifiers(annotations: List[AnnotationInfo]): Array[CustomModifier] = {
annotations.map(_.atp.typeSymbol).collect {
case definitions.VolatileAttr => new CustomModifier(true, CustomModifier.VolatileMarker)
} toArray
}
/*
debuglog("creating annotations: " + annotations + " for member : " + member)
for (annot@ AnnotationInfo(typ, annArgs, nvPairs) <- annotations ;
if annot.isConstant)
//!typ.typeSymbol.isJavaDefined
{
// assert(consts.length <= 1,
// "too many constant arguments for annotations; "+consts.toString())
// Problem / TODO having the symbol of the annotations type would be nicer
// (i hope that type.typeSymbol is the same as the one in types2create)
// AND: this will crash if the annotations Type is already compiled (-> not a typeBuilder)
// when this is solved, types2create will be the same as icodes.classes, thus superfluous
val annType: TypeBuilder = getType(typ.typeSymbol).asInstanceOf[TypeBuilder]
// val annType: MsilType = getType(typ.typeSymbol)
// Problem / TODO: i have no idea which constructor is used. This
// information should be available in AnnotationInfo.
annType.CreateType() // else, GetConstructors can't be used
val constr: ConstructorInfo = annType.GetConstructors()(0)
// prevent a second call of CreateType, only needed because there's no
// other way than GetConstructors()(0) to get the constructor, if there's
// no constructor symbol available.
val args: Array[Byte] =
getAttributeArgs(
annArgs map (_.constant.get),
(for((n,v) <- nvPairs) yield (n, v.constant.get)))
member.SetCustomAttribute(constr, args)
}
} */
/* def getAttributeArgs(consts: List[Constant], nvPairs: List[(Name, Constant)]): Array[Byte] = {
val buf = ByteBuffer.allocate(2048) // FIXME: this may be not enough!
buf.order(ByteOrder.LITTLE_ENDIAN)
buf.putShort(1.toShort) // signature
def emitSerString(str: String) = {
// this is wrong, it has to be the length of the UTF-8 byte array, which
// may be longer (see clr-book on page 302)
// val length: Int = str.length
val strBytes: Array[Byte] = try {
str.getBytes("UTF-8")
} catch {
case _: Error => abort("could not get byte-array for string: " + str)
}
val length: Int = strBytes.length //this length is stored big-endian
if (length < 128)
buf.put(length.toByte)
else if (length < (1<<14)) {
buf.put(((length >> 8) | 0x80).toByte) // the bits 14 and 15 of length are '0'
buf.put((length | 0xff).toByte)
} else if (length < (1 << 29)) {
buf.put(((length >> 24) | 0xc0).toByte)
buf.put(((length >> 16) & 0xff).toByte)
buf.put(((length >> 8) & 0xff).toByte)
buf.put(((length ) & 0xff).toByte)
} else
abort("string too long for attribute parameter: " + length)
buf.put(strBytes)
}
def emitConst(const: Constant): Unit = const.tag match {
case BooleanTag => buf.put((if (const.booleanValue) 1 else 0).toByte)
case ByteTag => buf.put(const.byteValue)
case ShortTag => buf.putShort(const.shortValue)
case CharTag => buf.putChar(const.charValue)
case IntTag => buf.putInt(const.intValue)
case LongTag => buf.putLong(const.longValue)
case FloatTag => buf.putFloat(const.floatValue)
case DoubleTag => buf.putDouble(const.doubleValue)
case StringTag =>
val str: String = const.stringValue
if (str == null) {
buf.put(0xff.toByte)
} else {
emitSerString(str)
}
case ArrayTag =>
val arr: Array[Constant] = const.arrayValue
if (arr == null) {
buf.putInt(0xffffffff)
} else {
buf.putInt(arr.length)
arr.foreach(emitConst)
}
// TODO: other Tags: NoTag, UnitTag, ClazzTag, EnumTag, ArrayTag ???
case _ => abort("could not handle attribute argument: " + const)
}
consts foreach emitConst
buf.putShort(nvPairs.length.toShort)
def emitNamedArg(nvPair: (Name, Constant)) {
// the named argument is a property of the attribute (it can't be a field, since
// all fields in scala are private)
buf.put(0x54.toByte)
def emitType(c: Constant) = c.tag match { // type of the constant, Ecma-335.pdf, page 151
case BooleanTag => buf.put(0x02.toByte)
case ByteTag => buf.put(0x05.toByte)
case ShortTag => buf.put(0x06.toByte)
case CharTag => buf.put(0x07.toByte)
case IntTag => buf.put(0x08.toByte)
case LongTag => buf.put(0x0a.toByte)
case FloatTag => buf.put(0x0c.toByte)
case DoubleTag => buf.put(0x0d.toByte)
case StringTag => buf.put(0x0e.toByte)
// TODO: other Tags: NoTag, UnitTag, ClazzTag, EnumTag ???
// ArrayTag falls in here
case _ => abort("could not handle attribute argument: " + c)
}
val cnst: Constant = nvPair._2
if (cnst.tag == ArrayTag) {
buf.put(0x1d.toByte)
emitType(cnst.arrayValue(0)) // FIXME: will crash if array length = 0
} else if (cnst.tag == EnumTag) {
buf.put(0x55.toByte)
// TODO: put a SerString (don't know what exactly, names of the enums somehow..)
} else {
buf.put(0x51.toByte)
emitType(cnst)
}
emitSerString(nvPair._1.toString)
emitConst(nvPair._2)
}
val length = buf.position()
buf.array().slice(0, length)
} */
def writeAssembly() {
if (entryPoint != null) {
assert(entryPoint.enclClass.isModuleClass, entryPoint.enclClass)
val mainMethod = methods(entryPoint)
val stringArrayTypes: Array[MsilType] = Array(MSTRING_ARRAY)
val globalMain = mmodule.DefineGlobalMethod(
"Main", MethodAttributes.Public | MethodAttributes.Static,
MVOID, stringArrayTypes)
globalMain.DefineParameter(0, ParameterAttributes.None, "args")
massembly.SetEntryPoint(globalMain)
val code = globalMain.GetILGenerator()
val moduleField = getModuleInstanceField(entryPoint.enclClass)
code.Emit(OpCodes.Ldsfld, moduleField)
code.Emit(OpCodes.Ldarg_0)
code.Emit(OpCodes.Callvirt, mainMethod)
code.Emit(OpCodes.Ret)
}
createTypes()
var outDirName: String = null
try {
if (settings.Ygenjavap.isDefault) { // we reuse the JVM-sounding setting because it's conceptually similar
outDirName = outDir.getPath()
massembly.Save(outDirName + "\\" + assemName + ".msil") /* use SingleFileILPrinterVisitor */
} else {
outDirName = srcPath.getPath()
massembly.Save(settings.Ygenjavap.value, outDirName) /* use MultipleFilesILPrinterVisitor */
}
} catch {
case e:IOException => abort("Could not write to " + outDirName + ": " + e.getMessage())
}
}
private def createTypes() {
for (sym <- classes.keys) {
val iclass = classes(sym)
val tBuilder = types(sym).asInstanceOf[TypeBuilder]
debuglog("Calling CreatType for " + sym + ", " + tBuilder.toString)
tBuilder.CreateType()
tBuilder.setSourceFilepath(iclass.cunit.source.file.path)
}
}
private[GenMSIL] def ilasmFileName(iclass: IClass) : String = {
// method.sourceFile contains just the filename
iclass.cunit.source.file.toString.replace("\\", "\\\\")
}
private[GenMSIL] def genClass(iclass: IClass) {
val sym = iclass.symbol
debuglog("Generating class " + sym + " flags: " + Flags.flagsToString(sym.flags))
clasz = iclass
val tBuilder = getType(sym).asInstanceOf[TypeBuilder]
if (isCloneable(sym)) {
// FIXME: why there's no nme.clone_ ?
// "Clone": if the code is non-portable, "Clone" is defined, not "clone"
// TODO: improve condition (should override AnyRef.clone)
if (iclass.methods.forall(m => {
!((m.symbol.name.toString != "clone" || m.symbol.name.toString != "Clone") &&
m.symbol.tpe.paramTypes.length != 0)
})) {
debuglog("auto-generating cloneable method for " + sym)
val attrs: Short = (MethodAttributes.Public | MethodAttributes.Virtual |
MethodAttributes.HideBySig).toShort
val cloneMethod = tBuilder.DefineMethod("Clone", attrs, MOBJECT,
MsilType.EmptyTypes)
val clCode = cloneMethod.GetILGenerator()
clCode.Emit(OpCodes.Ldarg_0)
clCode.Emit(OpCodes.Call, MEMBERWISE_CLONE)
clCode.Emit(OpCodes.Ret)
}
}
val line = sym.pos.line
tBuilder.setPosition(line, ilasmFileName(iclass))
if (isTopLevelModule(sym)) {
if (sym.companionClass == NoSymbol)
generateMirrorClass(sym)
else
log("No mirror class for module with linked class: " +
sym.fullName)
}
addSymtabAttribute(sym, tBuilder)
addAttributes(tBuilder, sym.annotations)
if (iclass.symbol != definitions.ArrayClass)
iclass.methods foreach genMethod
} //genClass
private def genMethod(m: IMethod) {
debuglog("Generating method " + m.symbol + " flags: " + Flags.flagsToString(m.symbol.flags) +
" owner: " + m.symbol.owner)
method = m
localBuilders.clear
computeLocalVarsIndex(m)
if (m.symbol.isClassConstructor) {
mcode = constructors(m.symbol).asInstanceOf[ConstructorBuilder].GetILGenerator()
} else {
val mBuilder = methods(m.symbol).asInstanceOf[MethodBuilder]
if (!mBuilder.IsAbstract())
try {
mcode = mBuilder.GetILGenerator()
} catch {
case e: Exception =>
java.lang.System.out.println("m.symbol = " + Flags.flagsToString(m.symbol.flags) + " " + m.symbol)
java.lang.System.out.println("m.symbol.owner = " + Flags.flagsToString(m.symbol.owner.flags) + " " + m.symbol.owner)
java.lang.System.out.println("mBuilder = " + mBuilder)
java.lang.System.out.println("mBuilder.DeclaringType = " +
TypeAttributes.toString(mBuilder.DeclaringType.Attributes) +
"::" + mBuilder.DeclaringType)
throw e
}
else
mcode = null
}
if (mcode != null) {
for (local <- m.locals ; if !(m.params contains local)) {
debuglog("add local var: " + local + ", of kind " + local.kind)
val t: MsilType = msilType(local.kind)
val localBuilder = mcode.DeclareLocal(t)
localBuilder.SetLocalSymInfo(msilName(local.sym))
localBuilders(local) = localBuilder
}
genCode(m)
}
}
/** Special linearizer for methods with at least one exception handler. This
* linearizer brings all basic blocks in the right order so that nested
* try-catch and try-finally blocks can be emitted.
*/
val msilLinearizer = new MSILLinearizer()
val labels = mutable.HashMap[BasicBlock, Label]()
/* when emitting .line, it's enough to include the full filename just once per method, thus reducing filesize.
* this scheme relies on the fact that the entry block is emitted first. */
var dbFilenameSeen = false
def genCode(m: IMethod) {
def makeLabels(blocks: List[BasicBlock]) = {
debuglog("Making labels for: " + method)
for (bb <- blocks) labels(bb) = mcode.DefineLabel()
}
labels.clear
var linearization = if(!m.exh.isEmpty) msilLinearizer.linearize(m)
else linearizer.linearize(m)
if (!m.exh.isEmpty)
linearization = computeExceptionMaps(linearization, m)
makeLabels(linearization)
// debug val blocksInM = m.code.blocks.toList.sortBy(bb => bb.label)
// debug val blocksInL = linearization.sortBy(bb => bb.label)
// debug val MButNotL = (blocksInM.toSet) diff (blocksInL.toSet) // if non-empty, a jump to B fails to find a label for B (case CJUMP, case CZJUMP)
// debug if(!MButNotL.isEmpty) { }
dbFilenameSeen = false
genBlocks(linearization)
// RETURN inside exception blocks are replaced by Leave. The target of the
// leave is a `Ret` outside any exception block (generated here).
if (handlerReturnMethod == m) {
mcode.MarkLabel(handlerReturnLabel)
if (handlerReturnKind != UNIT)
mcode.Emit(OpCodes.Ldloc, handlerReturnLocal)
mcode.Emit(OpCodes.Ret)
}
beginExBlock.clear()
beginCatchBlock.clear()
endExBlock.clear()
endFinallyLabels.clear()
}
def genBlocks(blocks: List[BasicBlock], previous: BasicBlock = null) {
blocks match {
case Nil => ()
case x :: Nil => genBlock(x, prev = previous, next = null)
case x :: y :: ys => genBlock(x, prev = previous, next = y); genBlocks(y :: ys, previous = x)
}
}
// the try blocks starting at a certain BasicBlock
val beginExBlock = mutable.HashMap[BasicBlock, List[ExceptionHandler]]()
// the catch blocks starting / endling at a certain BasicBlock
val beginCatchBlock = mutable.HashMap[BasicBlock, ExceptionHandler]()
val endExBlock = mutable.HashMap[BasicBlock, List[ExceptionHandler]]()
/** When emitting the code (genBlock), the number of currently active try / catch
* blocks. When seeing a `RETURN` inside a try / catch, we need to
* - store the result in a local (if it's not UNIT)
* - emit `Leave handlerReturnLabel` instead of the Return
* - emit code at the end: load the local and return its value
*/
var currentHandlers = new mutable.Stack[ExceptionHandler]
// The IMethod the Local/Label/Kind below belong to
var handlerReturnMethod: IMethod = _
// Stores the result when returning inside an exception block
var handlerReturnLocal: LocalBuilder = _
// Label for a return instruction outside any exception block
var handlerReturnLabel: Label = _
// The result kind.
var handlerReturnKind: TypeKind = _
def returnFromHandler(kind: TypeKind): (LocalBuilder, Label) = {
if (handlerReturnMethod != method) {
handlerReturnMethod = method
if (kind != UNIT) {
handlerReturnLocal = mcode.DeclareLocal(msilType(kind))
handlerReturnLocal.SetLocalSymInfo("$handlerReturn")
}
handlerReturnLabel = mcode.DefineLabel()
handlerReturnKind = kind
}
(handlerReturnLocal, handlerReturnLabel)
}
/** For try/catch nested inside a finally, we can't use `Leave OutsideFinally`, the
* Leave target has to be inside the finally (and it has to be the `endfinally` instruction).
* So for every finalizer, we have a label which marks the place of the `endfinally`,
* nested try/catch blocks will leave there.
*/
val endFinallyLabels = mutable.HashMap[ExceptionHandler, Label]()
/** Computes which blocks are the beginning / end of a try or catch block */
private def computeExceptionMaps(blocks: List[BasicBlock], m: IMethod): List[BasicBlock] = {
val visitedBlocks = new mutable.HashSet[BasicBlock]()
// handlers which have not been introduced so far
var openHandlers = m.exh
/** Example
* try {
* try {
* // *1*
* } catch {
* case h1 =>
* }
* } catch {
* case h2 =>
* case h3 =>
* try {
*
* } catch {
* case h4 => // *2*
* case h5 =>
* }
* }
*/
// Stack of nested try blocks. Each bloc has a List of ExceptionHandler (multiple
// catch statements). Example *1*: Stack(List(h2, h3), List(h1))
val currentTryHandlers = new mutable.Stack[List[ExceptionHandler]]()
// Stack of nested catch blocks. The head of the list is the current catch block. The
// tail is all following catch blocks. Example *2*: Stack(List(h3), List(h4, h5))
val currentCatchHandlers = new mutable.Stack[List[ExceptionHandler]]()
for (b <- blocks) {
// are we past the current catch blocks?
def endHandlers(): List[ExceptionHandler] = {
var res: List[ExceptionHandler] = Nil
if (!currentCatchHandlers.isEmpty) {
val handler = currentCatchHandlers.top.head
if (!handler.blocks.contains(b)) {
// all blocks of the handler are either visited, or not part of the linearization (i.e. dead)
assert(handler.blocks.forall(b => visitedBlocks.contains(b) || !blocks.contains(b)),
"Bad linearization of basic blocks inside catch. Found block not part of the handler\n"+
b.fullString +"\nwhile in catch-part of\n"+ handler)
val rest = currentCatchHandlers.pop.tail
if (rest.isEmpty) {
// all catch blocks of that exception handler are covered
res = handler :: endHandlers()
} else {
// there are more catch blocks for that try (handlers covering the same)
currentCatchHandlers.push(rest)
beginCatchBlock(b) = rest.head
}
}
}
res
}
val end = endHandlers()
if (!end.isEmpty) endExBlock(b) = end
// are we past the current try block?
if (!currentTryHandlers.isEmpty) {
val handler = currentTryHandlers.top.head
if (!handler.covers(b)) {
// all of the covered blocks are visited, or not part of the linearization
assert(handler.covered.forall(b => visitedBlocks.contains(b) || !blocks.contains(b)),
"Bad linearization of basic blocks inside try. Found non-covered block\n"+
b.fullString +"\nwhile in try-part of\n"+ handler)
assert(handler.startBlock == b,
"Bad linearization of basic blocks. The entry block of a catch does not directly follow the try\n"+
b.fullString +"\n"+ handler)
val handlers = currentTryHandlers.pop
currentCatchHandlers.push(handlers)
beginCatchBlock(b) = handler
}
}
// are there try blocks starting at b?
val (newHandlers, stillOpen) = openHandlers.partition(_.covers(b))
openHandlers = stillOpen
val newHandlersBySize = newHandlers.groupBy(_.covered.size)
// big handlers first, smaller ones are nested inside the try of the big one
// (checked by the assertions below)
val sizes = newHandlersBySize.keys.toList.sortWith(_ > _)
val beginHandlers = new mutable.ListBuffer[ExceptionHandler]
for (s <- sizes) {
val sHandlers = newHandlersBySize(s)
for (h <- sHandlers) {
assert(h.covered == sHandlers.head.covered,
"bad nesting of exception handlers. same size, but not covering same blocks\n"+
h +"\n"+ sHandlers.head)
assert(h.resultKind == sHandlers.head.resultKind,
"bad nesting of exception handlers. same size, but the same resultKind\n"+
h +"\n"+ sHandlers.head)
}
for (bigger <- beginHandlers; h <- sHandlers) {
assert(h.covered.subsetOf(bigger.covered),
"bad nesting of exception handlers. try blocks of smaller handler are not nested in bigger one.\n"+
h +"\n"+ bigger)
assert(h.blocks.toSet.subsetOf(bigger.covered),
"bad nesting of exception handlers. catch blocks of smaller handler are not nested in bigger one.\n"+
h +"\n"+ bigger)
}
beginHandlers += sHandlers.head
currentTryHandlers.push(sHandlers)
}
beginExBlock(b) = beginHandlers.toList
visitedBlocks += b
}
// if there handlers left (i.e. handlers covering nothing, or a
// non-existent (dead) block), remove their catch-blocks.
val liveBlocks = if (openHandlers.isEmpty) blocks else {
blocks.filter(b => openHandlers.forall(h => !h.blocks.contains(b)))
}
/** There might be open handlers, but no more blocks. happens when try/catch end
* with `throw` or `return`
* def foo() { try { .. throw } catch { _ => .. throw } }
*
* In this case we need some code after the catch block for the auto-generated
* `leave` instruction. So we're adding a (dead) `throw new Exception`.
*/
val rest = currentCatchHandlers.map(handlers => {
assert(handlers.length == 1, handlers)
handlers.head
}).toList
if (rest.isEmpty) {
liveBlocks
} else {
val b = m.code.newBlock
b.emit(Seq(
NEW(REFERENCE(definitions.ThrowableClass)),
DUP(REFERENCE(definitions.ObjectClass)),
CALL_METHOD(definitions.ThrowableClass.primaryConstructor, Static(true)),
THROW(definitions.ThrowableClass)
))
b.close
endExBlock(b) = rest
liveBlocks ::: List(b)
}
}
/**
* @param block the BasicBlock to emit code for
* @param next the following BasicBlock, `null` if `block` is the last one
*/
def genBlock(block: BasicBlock, prev: BasicBlock, next: BasicBlock) {
def loadLocalOrAddress(local: Local, msg : String , loadAddr : Boolean) {
debuglog(msg + " for " + local)
val isArg = local.arg
val i = local.index
if (isArg)
loadArg(mcode, loadAddr)(i)
else
loadLocal(i, local, mcode, loadAddr)
}
def loadFieldOrAddress(field: Symbol, isStatic: Boolean, msg: String, loadAddr : Boolean) {
debuglog(msg + " with owner: " + field.owner +
" flags: " + Flags.flagsToString(field.owner.flags))
var fieldInfo = fields.get(field) match {
case Some(fInfo) => fInfo
case None =>
val fInfo = getType(field.owner).GetField(msilName(field))
fields(field) = fInfo
fInfo
}
if (fieldInfo.IsVolatile) {
mcode.Emit(OpCodes.Volatile)
}
if (!fieldInfo.IsLiteral) {
if (loadAddr) {
mcode.Emit(if (isStatic) OpCodes.Ldsflda else OpCodes.Ldflda, fieldInfo)
} else {
mcode.Emit(if (isStatic) OpCodes.Ldsfld else OpCodes.Ldfld, fieldInfo)
}
} else {
assert(!loadAddr, "can't take AddressOf a literal field (not even with readonly. prefix) because no memory was allocated to such field ...")
// TODO the above can be overcome by loading the value, boxing, and finally unboxing. An address to a copy of the raw value will be on the stack.
/* We perform `field inlining' as required by CLR.
* Emit as for a CONSTANT ICode stmt, with the twist that the constant value is available
* as a java.lang.Object and its .NET type allows constant initialization in CLR, i.e. that type
* is one of I1, I2, I4, I8, R4, R8, CHAR, BOOLEAN, STRING, or CLASS (in this last case,
* only accepting nullref as value). See Table 9-1 in Lidin's book on ILAsm. */
val value = fieldInfo.getValue()
if (value == null) {
mcode.Emit(OpCodes.Ldnull)
} else {
val typ = if (fieldInfo.FieldType.IsEnum) fieldInfo.FieldType.getUnderlyingType
else fieldInfo.FieldType
if (typ == clrTypes.STRING) {
mcode.Emit(OpCodes.Ldstr, value.asInstanceOf[String])
} else if (typ == clrTypes.BOOLEAN) {
mcode.Emit(if (value.asInstanceOf[Boolean]) OpCodes.Ldc_I4_1
else OpCodes.Ldc_I4_0)
} else if (typ == clrTypes.BYTE || typ == clrTypes.UBYTE) {
loadI4(value.asInstanceOf[Byte], mcode)
} else if (typ == clrTypes.SHORT || typ == clrTypes.USHORT) {
loadI4(value.asInstanceOf[Int], mcode)
} else if (typ == clrTypes.CHAR) {
loadI4(value.asInstanceOf[Char], mcode)
} else if (typ == clrTypes.INT || typ == clrTypes.UINT) {
loadI4(value.asInstanceOf[Int], mcode)
} else if (typ == clrTypes.LONG || typ == clrTypes.ULONG) {
mcode.Emit(OpCodes.Ldc_I8, value.asInstanceOf[Long])
} else if (typ == clrTypes.FLOAT) {
mcode.Emit(OpCodes.Ldc_R4, value.asInstanceOf[Float])
} else if (typ == clrTypes.DOUBLE) {
mcode.Emit(OpCodes.Ldc_R8, value.asInstanceOf[Double])
} else {
/* TODO one more case is described in Partition II, 16.2: bytearray(...) */
abort("Unknown type for static literal field: " + fieldInfo)
}
}
}
}
/** Creating objects works differently on .NET. On the JVM
* - NEW(type) => reference on Stack
* - DUP, load arguments, CALL_METHOD(constructor)
*
* On .NET, the NEW and DUP are ignored, but we emit a special method call
* - load arguments
* - NewObj(constructor) => reference on stack
*
* This variable tells whether the previous instruction was a NEW,
* we expect a DUP which is not emitted. */
var previousWasNEW = false
var lastLineNr: Int = 0
var lastPos: Position = NoPosition
// EndExceptionBlock must happen before MarkLabel because it adds the
// Leave instruction. Otherwise, labels(block) points to the Leave
// (inside the catch) instead of the instruction afterwards.
for (handlers <- endExBlock.get(block); exh <- handlers) {
currentHandlers.pop()
for (l <- endFinallyLabels.get(exh))
mcode.MarkLabel(l)
mcode.EndExceptionBlock()
}
mcode.MarkLabel(labels(block))
debuglog("Generating code for block: " + block)
for (handler <- beginCatchBlock.get(block)) {
if (!currentHandlers.isEmpty && currentHandlers.top.covered == handler.covered) {
currentHandlers.pop()
currentHandlers.push(handler)
}
if (handler.cls == NoSymbol) {
// `finally` blocks are represented the same as `catch`, but with no catch-type
mcode.BeginFinallyBlock()
} else {
val t = getType(handler.cls)
mcode.BeginCatchBlock(t)
}
}
for (handlers <- beginExBlock.get(block); exh <- handlers) {
currentHandlers.push(exh)
mcode.BeginExceptionBlock()
}
for (instr <- block) {
try {
val currentLineNr = instr.pos.line
val skip = if(instr.pos.isRange) instr.pos.sameRange(lastPos) else (currentLineNr == lastLineNr);
if(!skip || !dbFilenameSeen) {
val fileName = if(dbFilenameSeen) "" else {dbFilenameSeen = true; ilasmFileName(clasz)};
if(instr.pos.isRange) {
val startLine = instr.pos.focusStart.line
val endLine = instr.pos.focusEnd.line
val startCol = instr.pos.focusStart.column
val endCol = instr.pos.focusEnd.column
mcode.setPosition(startLine, endLine, startCol, endCol, fileName)
} else {
mcode.setPosition(instr.pos.line, fileName)
}
lastLineNr = currentLineNr
lastPos = instr.pos
}
} catch { case _: UnsupportedOperationException => () }
if (previousWasNEW)
assert(instr.isInstanceOf[DUP], block)
instr match {
case THIS(clasz) =>
mcode.Emit(OpCodes.Ldarg_0)
case CONSTANT(const) =>
const.tag match {
case UnitTag => ()
case BooleanTag => mcode.Emit(if (const.booleanValue) OpCodes.Ldc_I4_1
else OpCodes.Ldc_I4_0)
case ByteTag => loadI4(const.byteValue, mcode)
case ShortTag => loadI4(const.shortValue, mcode)
case CharTag => loadI4(const.charValue, mcode)
case IntTag => loadI4(const.intValue, mcode)
case LongTag => mcode.Emit(OpCodes.Ldc_I8, const.longValue)
case FloatTag => mcode.Emit(OpCodes.Ldc_R4, const.floatValue)
case DoubleTag => mcode.Emit(OpCodes.Ldc_R8, const.doubleValue)
case StringTag => mcode.Emit(OpCodes.Ldstr, const.stringValue)
case NullTag => mcode.Emit(OpCodes.Ldnull)
case ClazzTag =>
mcode.Emit(OpCodes.Ldtoken, msilType(const.typeValue))
mcode.Emit(OpCodes.Call, TYPE_FROM_HANDLE)
case _ => abort("Unknown constant value: " + const)
}
case LOAD_ARRAY_ITEM(kind) =>
(kind: @unchecked) match {
case BOOL => mcode.Emit(OpCodes.Ldelem_I1)
case BYTE => mcode.Emit(OpCodes.Ldelem_I1) // I1 for System.SByte, i.e. a scala.Byte
case SHORT => mcode.Emit(OpCodes.Ldelem_I2)
case CHAR => mcode.Emit(OpCodes.Ldelem_U2)
case INT => mcode.Emit(OpCodes.Ldelem_I4)
case LONG => mcode.Emit(OpCodes.Ldelem_I8)
case FLOAT => mcode.Emit(OpCodes.Ldelem_R4)
case DOUBLE => mcode.Emit(OpCodes.Ldelem_R8)
case REFERENCE(cls) => mcode.Emit(OpCodes.Ldelem_Ref)
case ARRAY(elem) => mcode.Emit(OpCodes.Ldelem_Ref)
// case UNIT is not possible: an Array[Unit] will be an
// Array[scala.runtime.BoxedUnit] (-> case REFERENCE)
}
case LOAD_LOCAL(local) => loadLocalOrAddress(local, "load_local", false)
case CIL_LOAD_LOCAL_ADDRESS(local) => loadLocalOrAddress(local, "cil_load_local_address", true)
case LOAD_FIELD(field, isStatic) => loadFieldOrAddress(field, isStatic, "load_field", false)
case CIL_LOAD_FIELD_ADDRESS(field, isStatic) => loadFieldOrAddress(field, isStatic, "cil_load_field_address", true)
case CIL_LOAD_ARRAY_ITEM_ADDRESS(kind) => mcode.Emit(OpCodes.Ldelema, msilType(kind))
case CIL_NEWOBJ(msym) =>
assert(msym.isClassConstructor)
val constructorInfo: ConstructorInfo = getConstructor(msym)
mcode.Emit(OpCodes.Newobj, constructorInfo)
case LOAD_MODULE(module) =>
debuglog("Generating LOAD_MODULE for: " + showsym(module))
mcode.Emit(OpCodes.Ldsfld, getModuleInstanceField(module))
case STORE_ARRAY_ITEM(kind) =>
(kind: @unchecked) match {
case BOOL => mcode.Emit(OpCodes.Stelem_I1)
case BYTE => mcode.Emit(OpCodes.Stelem_I1)
case SHORT => mcode.Emit(OpCodes.Stelem_I2)
case CHAR => mcode.Emit(OpCodes.Stelem_I2)
case INT => mcode.Emit(OpCodes.Stelem_I4)
case LONG => mcode.Emit(OpCodes.Stelem_I8)
case FLOAT => mcode.Emit(OpCodes.Stelem_R4)
case DOUBLE => mcode.Emit(OpCodes.Stelem_R8)
case REFERENCE(cls) => mcode.Emit(OpCodes.Stelem_Ref)
case ARRAY(elem) => mcode.Emit(OpCodes.Stelem_Ref) // @TODO: test this! (occurs when calling a Array[Object]* vararg param method)
// case UNIT not possible (see comment at LOAD_ARRAY_ITEM)
}
case STORE_LOCAL(local) =>
val isArg = local.arg
val i = local.index
debuglog("store_local for " + local + ", index " + i)
// there are some locals defined by the compiler that
// are isArg and are need to be stored.
if (isArg) {
if (i >= -128 && i <= 127)
mcode.Emit(OpCodes.Starg_S, i)
else
mcode.Emit(OpCodes.Starg, i)
} else {
i match {
case 0 => mcode.Emit(OpCodes.Stloc_0)
case 1 => mcode.Emit(OpCodes.Stloc_1)
case 2 => mcode.Emit(OpCodes.Stloc_2)
case 3 => mcode.Emit(OpCodes.Stloc_3)
case _ =>
if (i >= -128 && i <= 127)
mcode.Emit(OpCodes.Stloc_S, localBuilders(local))
else
mcode.Emit(OpCodes.Stloc, localBuilders(local))
}
}
case STORE_THIS(_) =>
// this only works for impl classes because the self parameter comes first
// in the method signature. If that changes, this code has to be revisited.
mcode.Emit(OpCodes.Starg_S, 0)
case STORE_FIELD(field, isStatic) =>
val fieldInfo = fields.get(field) match {
case Some(fInfo) => fInfo
case None =>
val fInfo = getType(field.owner).GetField(msilName(field))
fields(field) = fInfo
fInfo
}
mcode.Emit(if (isStatic) OpCodes.Stsfld else OpCodes.Stfld, fieldInfo)
case CALL_PRIMITIVE(primitive) =>
genPrimitive(primitive, instr.pos)
case CALL_METHOD(msym, style) =>
if (msym.isClassConstructor) {
val constructorInfo: ConstructorInfo = getConstructor(msym)
(style: @unchecked) match {
// normal constructor calls are Static..
case Static(_) =>
if (method.symbol.isClassConstructor && method.symbol.owner == msym.owner)
// we're generating a constructor (method: IMethod is a constructor), and we're
// calling another constructor of the same class.
// @LUC TODO: this can probably break, namely when having: class A { def this() { new A() } }
// instead, we should instruct the CALL_METHOD with additional information, know whether it's
// an instance creation constructor call or not.
mcode.Emit(OpCodes.Call, constructorInfo)
else
mcode.Emit(OpCodes.Newobj, constructorInfo)
case SuperCall(_) =>
mcode.Emit(OpCodes.Call, constructorInfo)
if (isStaticModule(clasz.symbol) &&
notInitializedModules.contains(clasz.symbol) &&
method.symbol.isClassConstructor)
{
notInitializedModules -= clasz.symbol
mcode.Emit(OpCodes.Ldarg_0)
mcode.Emit(OpCodes.Stsfld, getModuleInstanceField(clasz.symbol))
}
}
} else {
var doEmit = true
getTypeOpt(msym.owner) match {
case Some(typ) if (typ.IsEnum) => {
def negBool() = {
mcode.Emit(OpCodes.Ldc_I4_0)
mcode.Emit(OpCodes.Ceq)
}
doEmit = false
val name = msym.name
if (name eq nme.EQ) { mcode.Emit(OpCodes.Ceq) }
else if (name eq nme.NE) { mcode.Emit(OpCodes.Ceq); negBool }
else if (name eq nme.LT) { mcode.Emit(OpCodes.Clt) }
else if (name eq nme.LE) { mcode.Emit(OpCodes.Cgt); negBool }
else if (name eq nme.GT) { mcode.Emit(OpCodes.Cgt) }
else if (name eq nme.GE) { mcode.Emit(OpCodes.Clt); negBool }
else if (name eq nme.OR) { mcode.Emit(OpCodes.Or) }
else if (name eq nme.AND) { mcode.Emit(OpCodes.And) }
else if (name eq nme.XOR) { mcode.Emit(OpCodes.Xor) }
else
doEmit = true
}
case _ => ()
}
// method: implicit view(FunctionX[PType0, PType1, ...,PTypeN, ResType]):DelegateType
val (isDelegateView, paramType, resType) = beforeTyper {
msym.tpe match {
case MethodType(params, resultType)
if (params.length == 1 && msym.name == nme.view_) =>
val paramType = params(0).tpe
val isDel = definitions.isCorrespondingDelegate(resultType, paramType)
(isDel, paramType, resultType)
case _ => (false, null, null)
}
}
if (doEmit && isDelegateView) {
doEmit = false
createDelegateCaller(paramType, resType)
}
if (doEmit &&
(msym.name == nme.PLUS || msym.name == nme.MINUS)
&& clrTypes.isDelegateType(msilType(msym.owner.tpe)))
{
doEmit = false
val methodInfo: MethodInfo = getMethod(msym)
// call it as a static method, even if the compiler (symbol) thinks it's virtual
mcode.Emit(OpCodes.Call, methodInfo)
mcode.Emit(OpCodes.Castclass, msilType(msym.owner.tpe))
}
if (doEmit && definitions.Delegate_scalaCallers.contains(msym)) {
doEmit = false
val methodSym: Symbol = definitions.Delegate_scalaCallerTargets(msym)
val delegateType: Type = msym.tpe match {
case MethodType(_, retType) => retType
case _ => abort("not a method type: " + msym.tpe)
}
val methodInfo: MethodInfo = getMethod(methodSym)
val delegCtor = msilType(delegateType).GetConstructor(Array(MOBJECT, INT_PTR))
if (methodSym.isStatic) {
mcode.Emit(OpCodes.Ldftn, methodInfo)
} else {
mcode.Emit(OpCodes.Dup)
mcode.Emit(OpCodes.Ldvirtftn, methodInfo)
}
mcode.Emit(OpCodes.Newobj, delegCtor)
}
if (doEmit) {
val methodInfo: MethodInfo = getMethod(msym)
(style: @unchecked) match {
case SuperCall(_) =>
mcode.Emit(OpCodes.Call, methodInfo)
case Dynamic =>
// methodInfo.DeclaringType is null for global methods
val isValuetypeMethod = (methodInfo.DeclaringType ne null) && (methodInfo.DeclaringType.IsValueType)
val isValuetypeVirtualMethod = isValuetypeMethod && (methodInfo.IsVirtual)
if (dynToStatMapped(msym)) {
mcode.Emit(OpCodes.Call, methodInfo)
} else if (isValuetypeVirtualMethod) {
mcode.Emit(OpCodes.Constrained, methodInfo.DeclaringType)
mcode.Emit(OpCodes.Callvirt, methodInfo)
} else if (isValuetypeMethod) {
// otherwise error "Callvirt on a value type method" ensues
mcode.Emit(OpCodes.Call, methodInfo)
} else {
mcode.Emit(OpCodes.Callvirt, methodInfo)
}
case Static(_) =>
if(methodInfo.IsVirtual && !mcode.Ldarg0WasJustEmitted) {
mcode.Emit(OpCodes.Callvirt, methodInfo)
} else mcode.Emit(OpCodes.Call, methodInfo)
}
}
}
case BOX(boxType) =>
emitBox(mcode, boxType)
case UNBOX(boxType) =>
emitUnbox(mcode, boxType)
case CIL_UNBOX(boxType) =>
mcode.Emit(OpCodes.Unbox, msilType(boxType))
case CIL_INITOBJ(valueType) =>
mcode.Emit(OpCodes.Initobj, msilType(valueType))
case NEW(REFERENCE(cls)) =>
// the next instruction must be a DUP, see comment on `var previousWasNEW`
previousWasNEW = true
// works also for arrays and reference-types
case CREATE_ARRAY(elem, dims) =>
// TODO: handle multi dimensional arrays
assert(dims == 1, "Can't handle multi dimensional arrays")
mcode.Emit(OpCodes.Newarr, msilType(elem))
// works for arrays and reference-types
case IS_INSTANCE(tpe) =>
mcode.Emit(OpCodes.Isinst, msilType(tpe))
mcode.Emit(OpCodes.Ldnull)
mcode.Emit(OpCodes.Ceq)
mcode.Emit(OpCodes.Ldc_I4_0)
mcode.Emit(OpCodes.Ceq)
// works for arrays and reference-types
// part from the scala reference: "S <: T does not imply
// Array[S] <: Array[T] in Scala. However, it is possible
// to cast an array of S to an array of T if such a cast
// is permitted in the host environment."
case CHECK_CAST(tpknd) =>
val tMSIL = msilType(tpknd)
mcode.Emit(OpCodes.Castclass, tMSIL)
// no SWITCH is generated when there's
// - a default case ("case _ => ...") in the matching expr
// - OR is used ("case 1 | 2 => ...")
case SWITCH(tags, branches) =>
// tags is List[List[Int]]; a list of integers for every label.
// if the int on stack is 4, and 4 is in the second list => jump
// to second label
// branches is List[BasicBlock]
// the labels to jump to (the last one is the default one)
val switchLocal = mcode.DeclareLocal(MINT)
// several switch variables will appear with the same name in the
// assembly code, but this makes no truble
switchLocal.SetLocalSymInfo("$switch_var")
mcode.Emit(OpCodes.Stloc, switchLocal)
var i = 0
for (l <- tags) {
var targetLabel = labels(branches(i))
for (i <- l) {
mcode.Emit(OpCodes.Ldloc, switchLocal)
loadI4(i, mcode)
mcode.Emit(OpCodes.Beq, targetLabel)
}
i += 1
}
val defaultTarget = labels(branches(i))
if (next != branches(i))
mcode.Emit(OpCodes.Br, defaultTarget)
case JUMP(whereto) =>
val (leaveHandler, leaveFinally, lfTarget) = leavesHandler(block, whereto)
if (leaveHandler) {
if (leaveFinally) {
if (lfTarget.isDefined) mcode.Emit(OpCodes.Leave, lfTarget.get)
else mcode.Emit(OpCodes.Endfinally)
} else
mcode.Emit(OpCodes.Leave, labels(whereto))
} else if (next != whereto)
mcode.Emit(OpCodes.Br, labels(whereto))
case CJUMP(success, failure, cond, kind) =>
// cond is TestOp (see Primitives.scala), and can take
// values EQ, NE, LT, GE LE, GT
// kind is TypeKind
val isFloat = kind == FLOAT || kind == DOUBLE
val emit = (c: TestOp, l: Label) => emitBr(c, l, isFloat)
emitCondBr(block, cond, success, failure, next, emit)
case CZJUMP(success, failure, cond, kind) =>
emitCondBr(block, cond, success, failure, next, emitBrBool(_, _))
case RETURN(kind) =>
if (currentHandlers.isEmpty)
mcode.Emit(OpCodes.Ret)
else {
val (local, label) = returnFromHandler(kind)
if (kind != UNIT)
mcode.Emit(OpCodes.Stloc, local)
mcode.Emit(OpCodes.Leave, label)
}
case THROW(_) =>
mcode.Emit(OpCodes.Throw)
case DROP(kind) =>
mcode.Emit(OpCodes.Pop)
case DUP(kind) =>
// see comment on `var previousWasNEW`
if (!previousWasNEW)
mcode.Emit(OpCodes.Dup)
else
previousWasNEW = false
case MONITOR_ENTER() =>
mcode.Emit(OpCodes.Call, MMONITOR_ENTER)
case MONITOR_EXIT() =>
mcode.Emit(OpCodes.Call, MMONITOR_EXIT)
case SCOPE_ENTER(_) | SCOPE_EXIT(_) | LOAD_EXCEPTION(_) =>
()
}
} // end for (instr <- b) { .. }
} // end genBlock
def genPrimitive(primitive: Primitive, pos: Position) {
primitive match {
case Negation(kind) =>
kind match {
// CHECK: is ist possible to get this for BOOL? in this case, verify.
case BOOL | BYTE | CHAR | SHORT | INT | LONG | FLOAT | DOUBLE =>
mcode.Emit(OpCodes.Neg)
case _ => abort("Impossible to negate a " + kind)
}
case Arithmetic(op, kind) =>
op match {
case ADD => mcode.Emit(OpCodes.Add)
case SUB => mcode.Emit(OpCodes.Sub)
case MUL => mcode.Emit(OpCodes.Mul)
case DIV => mcode.Emit(OpCodes.Div)
case REM => mcode.Emit(OpCodes.Rem)
case NOT => mcode.Emit(OpCodes.Not) //bitwise complement (one's complement)
case _ => abort("Unknown arithmetic primitive " + primitive )
}
case Logical(op, kind) => op match {
case AND => mcode.Emit(OpCodes.And)
case OR => mcode.Emit(OpCodes.Or)
case XOR => mcode.Emit(OpCodes.Xor)
}
case Shift(op, kind) => op match {
case LSL => mcode.Emit(OpCodes.Shl)
case ASR => mcode.Emit(OpCodes.Shr)
case LSR => mcode.Emit(OpCodes.Shr_Un)
}
case Conversion(src, dst) =>
debuglog("Converting from: " + src + " to: " + dst)
dst match {
case BYTE => mcode.Emit(OpCodes.Conv_I1) // I1 for System.SByte, i.e. a scala.Byte
case SHORT => mcode.Emit(OpCodes.Conv_I2)
case CHAR => mcode.Emit(OpCodes.Conv_U2)
case INT => mcode.Emit(OpCodes.Conv_I4)
case LONG => mcode.Emit(OpCodes.Conv_I8)
case FLOAT => mcode.Emit(OpCodes.Conv_R4)
case DOUBLE => mcode.Emit(OpCodes.Conv_R8)
case _ =>
Console.println("Illegal conversion at: " + clasz +
" at: " + pos.source + ":" + pos.line)
}
case ArrayLength(_) =>
mcode.Emit(OpCodes.Ldlen)
case StartConcat =>
mcode.Emit(OpCodes.Newobj, MSTRING_BUILDER_CONSTR)
case StringConcat(el) =>
val elemType : MsilType = el match {
case REFERENCE(_) | ARRAY(_) => MOBJECT
case _ => msilType(el)
}
val argTypes:Array[MsilType] = Array(elemType)
val stringBuilderAppend = MSTRING_BUILDER.GetMethod("Append", argTypes )
mcode.Emit(OpCodes.Callvirt, stringBuilderAppend)
case EndConcat =>
mcode.Emit(OpCodes.Callvirt, MSTRING_BUILDER_TOSTRING)
case _ =>
abort("Unimplemented primitive " + primitive)
}
} // end genPrimitive
////////////////////// loading ///////////////////////
def loadI4(value: Int, code: ILGenerator): Unit = value match {
case -1 => code.Emit(OpCodes.Ldc_I4_M1)
case 0 => code.Emit(OpCodes.Ldc_I4_0)
case 1 => code.Emit(OpCodes.Ldc_I4_1)
case 2 => code.Emit(OpCodes.Ldc_I4_2)
case 3 => code.Emit(OpCodes.Ldc_I4_3)
case 4 => code.Emit(OpCodes.Ldc_I4_4)
case 5 => code.Emit(OpCodes.Ldc_I4_5)
case 6 => code.Emit(OpCodes.Ldc_I4_6)
case 7 => code.Emit(OpCodes.Ldc_I4_7)
case 8 => code.Emit(OpCodes.Ldc_I4_8)
case _ =>
if (value >= -128 && value <= 127)
code.Emit(OpCodes.Ldc_I4_S, value)
else
code.Emit(OpCodes.Ldc_I4, value)
}
def loadArg(code: ILGenerator, loadAddr: Boolean)(i: Int) =
if (loadAddr) {
if (i >= -128 && i <= 127)
code.Emit(OpCodes.Ldarga_S, i)
else
code.Emit(OpCodes.Ldarga, i)
} else {
i match {
case 0 => code.Emit(OpCodes.Ldarg_0)
case 1 => code.Emit(OpCodes.Ldarg_1)
case 2 => code.Emit(OpCodes.Ldarg_2)
case 3 => code.Emit(OpCodes.Ldarg_3)
case _ =>
if (i >= -128 && i <= 127)
code.Emit(OpCodes.Ldarg_S, i)
else
code.Emit(OpCodes.Ldarg, i)
}
}
def loadLocal(i: Int, local: Local, code: ILGenerator, loadAddr: Boolean) =
if (loadAddr) {
if (i >= -128 && i <= 127)
code.Emit(OpCodes.Ldloca_S, localBuilders(local))
else
code.Emit(OpCodes.Ldloca, localBuilders(local))
} else {
i match {
case 0 => code.Emit(OpCodes.Ldloc_0)
case 1 => code.Emit(OpCodes.Ldloc_1)
case 2 => code.Emit(OpCodes.Ldloc_2)
case 3 => code.Emit(OpCodes.Ldloc_3)
case _ =>
if (i >= -128 && i <= 127)
code.Emit(OpCodes.Ldloc_S, localBuilders(local))
else
code.Emit(OpCodes.Ldloc, localBuilders(local))
}
}
////////////////////// branches ///////////////////////
/** Returns a Triple (Boolean, Boolean, Option[Label])
* - whether the jump leaves some exception block (try / catch / finally)
* - whether it leaves a finally handler (finally block, but not it's try / catch)
* - a label where to jump for leaving the finally handler
* . None to leave directly using `endfinally`
* . Some(label) to emit `leave label` (for try / catch inside a finally handler)
*/
def leavesHandler(from: BasicBlock, to: BasicBlock): (Boolean, Boolean, Option[Label]) =
if (currentHandlers.isEmpty) (false, false, None)
else {
val h = currentHandlers.head
val leaveHead = { h.covers(from) != h.covers(to) ||
h.blocks.contains(from) != h.blocks.contains(to) }
if (leaveHead) {
// we leave the innermost exception block.
// find out if we also leave som e `finally` handler
currentHandlers.find(e => {
e.cls == NoSymbol && e.blocks.contains(from) != e.blocks.contains(to)
}) match {
case Some(finallyHandler) =>
if (h == finallyHandler) {
// the finally handler is the innermost, so we can emit `endfinally` directly
(true, true, None)
} else {
// we need to `Leave` to the `endfinally` of the next outer finally handler
val l = endFinallyLabels.getOrElseUpdate(finallyHandler, mcode.DefineLabel())
(true, true, Some(l))
}
case None =>
(true, false, None)
}
} else (false, false, None)
}
def emitCondBr(block: BasicBlock, cond: TestOp, success: BasicBlock, failure: BasicBlock,
next: BasicBlock, emitBrFun: (TestOp, Label) => Unit) {
val (sLeaveHandler, sLeaveFinally, slfTarget) = leavesHandler(block, success)
val (fLeaveHandler, fLeaveFinally, flfTarget) = leavesHandler(block, failure)
if (sLeaveHandler || fLeaveHandler) {
val sLabelOpt = if (sLeaveHandler) {
val leaveSLabel = mcode.DefineLabel()
emitBrFun(cond, leaveSLabel)
Some(leaveSLabel)
} else {
emitBrFun(cond, labels(success))
None
}
if (fLeaveHandler) {
if (fLeaveFinally) {
if (flfTarget.isDefined) mcode.Emit(OpCodes.Leave, flfTarget.get)
else mcode.Emit(OpCodes.Endfinally)
} else
mcode.Emit(OpCodes.Leave, labels(failure))
} else
mcode.Emit(OpCodes.Br, labels(failure))
sLabelOpt.map(l => {
mcode.MarkLabel(l)
if (sLeaveFinally) {
if (slfTarget.isDefined) mcode.Emit(OpCodes.Leave, slfTarget.get)
else mcode.Emit(OpCodes.Endfinally)
} else
mcode.Emit(OpCodes.Leave, labels(success))
})
} else {
if (next == success) {
emitBrFun(cond.negate, labels(failure))
} else {
emitBrFun(cond, labels(success))
if (next != failure) {
mcode.Emit(OpCodes.Br, labels(failure))
}
}
}
}
def emitBr(condition: TestOp, dest: Label, isFloat: Boolean) {
condition match {
case EQ => mcode.Emit(OpCodes.Beq, dest)
case NE => mcode.Emit(OpCodes.Bne_Un, dest)
case LT => mcode.Emit(if (isFloat) OpCodes.Blt_Un else OpCodes.Blt, dest)
case GE => mcode.Emit(if (isFloat) OpCodes.Bge_Un else OpCodes.Bge, dest)
case LE => mcode.Emit(if (isFloat) OpCodes.Ble_Un else OpCodes.Ble, dest)
case GT => mcode.Emit(if (isFloat) OpCodes.Bgt_Un else OpCodes.Bgt, dest)
}
}
def emitBrBool(cond: TestOp, dest: Label) {
(cond: @unchecked) match {
// EQ -> Brfalse, NE -> Brtrue; this is because we come from
// a CZJUMP. If the value on the stack is 0 (e.g. a boolean
// method returned false), and we are in the case EQ, then
// we need to emit Brfalse (EQ Zero means false). vice versa
case EQ => mcode.Emit(OpCodes.Brfalse, dest)
case NE => mcode.Emit(OpCodes.Brtrue, dest)
}
}
////////////////////// local vars ///////////////////////
/**
* Compute the indexes of each local variable of the given
* method.
*/
def computeLocalVarsIndex(m: IMethod) {
var idx = if (m.symbol.isStaticMember) 0 else 1
val params = m.params
for (l <- params) {
debuglog("Index value for parameter " + l + ": " + idx)
l.index = idx
idx += 1 // sizeOf(l.kind)
}
val locvars = m.locals filterNot (params contains)
idx = 0
for (l <- locvars) {
debuglog("Index value for local variable " + l + ": " + idx)
l.index = idx
idx += 1 // sizeOf(l.kind)
}
}
////////////////////// Utilities ////////////////////////
/** Return the a name of this symbol that can be used on the .NET
* platform. It removes spaces from names.
*
* Special handling: scala.All and scala.AllRef are 'erased' to
* scala.All$ and scala.AllRef$. This is needed because they are
* not real classes, and they mean 'abrupt termination upon evaluation
* of that expression' or 'null' respectively. This handling is
* done already in GenICode, but here we need to remove references
* from method signatures to these types, because such classes can
* not exist in the classpath: the type checker will be very confused.
*/
def msilName(sym: Symbol): String = {
val suffix = sym.moduleSuffix
// Flags.JAVA: "symbol was not defined by a scala-class" (java, or .net-class)
if (sym == definitions.NothingClass)
return "scala.runtime.Nothing$"
else if (sym == definitions.NullClass)
return "scala.runtime.Null$"
(if (sym.isClass || (sym.isModule && !sym.isMethod)) {
if (sym.isNestedClass) sym.simpleName
else sym.fullName
} else
sym.simpleName.toString.trim()) + suffix
}
////////////////////// flags ///////////////////////
def msilTypeFlags(sym: Symbol): Int = {
var mf: Int = TypeAttributes.AutoLayout | TypeAttributes.AnsiClass
if(sym.isNestedClass) {
mf = mf | (if (sym hasFlag Flags.PRIVATE) TypeAttributes.NestedPrivate else TypeAttributes.NestedPublic)
} else {
mf = mf | (if (sym hasFlag Flags.PRIVATE) TypeAttributes.NotPublic else TypeAttributes.Public)
}
mf = mf | (if (sym hasFlag Flags.ABSTRACT) TypeAttributes.Abstract else 0)
mf = mf | (if (sym.isTrait && !sym.isImplClass) TypeAttributes.Interface else TypeAttributes.Class)
mf = mf | (if (sym isFinal) TypeAttributes.Sealed else 0)
sym.annotations foreach { a => a match {
case AnnotationInfo(SerializableAttr, _, _) =>
// TODO: add the Serializable TypeAttribute also if the annotation
// System.SerializableAttribute is present (.net annotation, not scala)
// Best way to do it: compare with
// definitions.getClass("System.SerializableAttribute").tpe
// when frontend available
mf = mf | TypeAttributes.Serializable
case _ => ()
}}
mf
// static: not possible (or?)
}
def msilMethodFlags(sym: Symbol): Short = {
var mf: Int = MethodAttributes.HideBySig |
(if (sym hasFlag Flags.PRIVATE) MethodAttributes.Private
else MethodAttributes.Public)
if (!sym.isClassConstructor) {
if (sym.isStaticMember)
mf = mf | FieldAttributes.Static // coincidentally, same value as for MethodAttributes.Static ...
else {
mf = mf | MethodAttributes.Virtual
if (sym.isFinal && !getType(sym.owner).IsInterface)
mf = mf | MethodAttributes.Final
if (sym.isDeferred || getType(sym.owner).IsInterface)
mf = mf | MethodAttributes.Abstract
}
}
if (sym.isStaticMember) {
mf = mf | MethodAttributes.Static
}
// constructors of module classes should be private
if (sym.isPrimaryConstructor && isTopLevelModule(sym.owner)) {
mf |= MethodAttributes.Private
mf &= ~(MethodAttributes.Public)
}
mf.toShort
}
def msilFieldFlags(sym: Symbol): Short = {
var mf: Int =
if (sym hasFlag Flags.PRIVATE) FieldAttributes.Private
else if (sym hasFlag Flags.PROTECTED) FieldAttributes.FamORAssem
else FieldAttributes.Public
if (sym hasFlag Flags.FINAL)
mf = mf | FieldAttributes.InitOnly
if (sym.isStaticMember)
mf = mf | FieldAttributes.Static
// TRANSIENT: "not serialized", VOLATILE: doesn't exist on .net
// TODO: add this annotation also if the class has the custom attribute
// System.NotSerializedAttribute
sym.annotations.foreach( a => a match {
case AnnotationInfo(TransientAtt, _, _) =>
mf = mf | FieldAttributes.NotSerialized
case _ => ()
})
mf.toShort
}
////////////////////// builders, types ///////////////////////
var entryPoint: Symbol = _
val notInitializedModules = mutable.HashSet[Symbol]()
// TODO: create fields also in def createType, and not in genClass,
// add a getField method (it only works as it is because fields never
// accessed from outside a class)
val localBuilders = mutable.HashMap[Local, LocalBuilder]()
private[GenMSIL] def findEntryPoint(cls: IClass) {
def isEntryPoint(sym: Symbol):Boolean = {
if (isStaticModule(sym.owner) && msilName(sym) == "main")
if (sym.tpe.paramTypes.length == 1) {
toTypeKind(sym.tpe.paramTypes(0)) match {
case ARRAY(elem) =>
if (elem.toType.typeSymbol == definitions.StringClass) {
return true
}
case _ => ()
}
}
false
}
if((entryPoint == null) && opt.showClass.isDefined) { // TODO introduce dedicated setting instead
val entryclass = opt.showClass.get.toString
val cfn = cls.symbol.fullName
if(cfn == entryclass) {
for (m <- cls.methods; if isEntryPoint(m.symbol)) { entryPoint = m.symbol }
if(entryPoint == null) { warning("Couldn't find main method in class " + cfn) }
}
}
if (firstSourceName == "")
if (cls.symbol.sourceFile != null) // is null for nested classes
firstSourceName = cls.symbol.sourceFile.name
}
// #####################################################################
// get and create types
private def msilType(t: TypeKind): MsilType = (t: @unchecked) match {
case UNIT => MVOID
case BOOL => MBOOL
case BYTE => MBYTE
case SHORT => MSHORT
case CHAR => MCHAR
case INT => MINT
case LONG => MLONG
case FLOAT => MFLOAT
case DOUBLE => MDOUBLE
case REFERENCE(cls) => getType(cls)
case ARRAY(elem) =>
msilType(elem) match {
// For type builders, cannot call "clrTypes.mkArrayType" because this looks up
// the type "tp" in the assembly (not in the HashMap "types" of the backend).
// This can fail for nested types because the builders are not complete yet.
case tb: TypeBuilder => tb.MakeArrayType()
case tp: MsilType => clrTypes.mkArrayType(tp)
}
}
private def msilType(tpe: Type): MsilType = msilType(toTypeKind(tpe))
private def msilParamTypes(sym: Symbol): Array[MsilType] = {
sym.tpe.paramTypes.map(msilType).toArray
}
def getType(sym: Symbol) = getTypeOpt(sym).getOrElse(abort(showsym(sym)))
/**
* Get an MSIL type from a symbol. First look in the clrTypes.types map, then
* lookup the name using clrTypes.getType
*/
def getTypeOpt(sym: Symbol): Option[MsilType] = {
val tmp = types.get(sym)
tmp match {
case typ @ Some(_) => typ
case None =>
def typeString(sym: Symbol): String = {
val s = if (sym.isNestedClass) typeString(sym.owner) +"+"+ sym.simpleName
else sym.fullName
if (sym.isModuleClass && !sym.isTrait) s + "$" else s
}
val name = typeString(sym)
val typ = clrTypes.getType(name)
if (typ == null)
None
else {
types(sym) = typ
Some(typ)
}
}
}
def mapType(sym: Symbol, mType: MsilType) {
assert(mType != null, showsym(sym))
types(sym) = mType
}
def createTypeBuilder(iclass: IClass) {
/**
* First look in the clrTypes.types map, if that fails check if it's a class being compiled, otherwise
* lookup by name (clrTypes.getType calls the static method msil.Type.GetType(fullname)).
*/
def msilTypeFromSym(sym: Symbol): MsilType = {
types.get(sym).getOrElse {
classes.get(sym) match {
case Some(iclass) =>
msilTypeBuilderFromSym(sym)
case None =>
getType(sym)
}
}
}
def msilTypeBuilderFromSym(sym: Symbol): TypeBuilder = {
if(!(types.contains(sym) && types(sym).isInstanceOf[TypeBuilder])){
val iclass = classes(sym)
assert(iclass != null)
createTypeBuilder(iclass)
}
types(sym).asInstanceOf[TypeBuilder]
}
val sym = iclass.symbol
if (types.contains(sym) && types(sym).isInstanceOf[TypeBuilder])
return
def isInterface(s: Symbol) = s.isTrait && !s.isImplClass
val parents: List[Type] =
if (sym.info.parents.isEmpty) List(definitions.ObjectClass.tpe)
else sym.info.parents.distinct
val superType : MsilType = if (isInterface(sym)) null else msilTypeFromSym(parents.head.typeSymbol)
debuglog("super type: " + parents(0).typeSymbol + ", msil type: " + superType)
val interfaces: Array[MsilType] =
parents.tail.map(p => msilTypeFromSym(p.typeSymbol)).toArray
if (parents.length > 1) {
if (settings.debug.value) {
log("interfaces:")
for (i <- 0.until(interfaces.length)) {
log(" type: " + parents(i + 1).typeSymbol + ", msil type: " + interfaces(i))
}
}
}
val tBuilder = if (sym.isNestedClass) {
val ownerT = msilTypeBuilderFromSym(sym.owner).asInstanceOf[TypeBuilder]
ownerT.DefineNestedType(msilName(sym), msilTypeFlags(sym), superType, interfaces)
} else {
mmodule.DefineType(msilName(sym), msilTypeFlags(sym), superType, interfaces)
}
mapType(sym, tBuilder)
} // createTypeBuilder
def createClassMembers(iclass: IClass) {
try {
createClassMembers0(iclass)
}
catch {
case e: Throwable =>
java.lang.System.err.println(showsym(iclass.symbol))
java.lang.System.err.println("with methods = " + iclass.methods)
throw e
}
}
def createClassMembers0(iclass: IClass) {
val mtype = getType(iclass.symbol).asInstanceOf[TypeBuilder]
for (ifield <- iclass.fields) {
val sym = ifield.symbol
debuglog("Adding field: " + sym.fullName)
var attributes = msilFieldFlags(sym)
val fieldTypeWithCustomMods =
new PECustomMod(msilType(sym.tpe),
customModifiers(sym.annotations))
val fBuilder = mtype.DefineField(msilName(sym),
fieldTypeWithCustomMods,
attributes)
fields(sym) = fBuilder
addAttributes(fBuilder, sym.annotations)
} // all iclass.fields iterated over
if (isStaticModule(iclass.symbol)) {
val sc = iclass.lookupStaticCtor
if (sc.isDefined) {
val m = sc.get
val oldLastBlock = m.lastBlock
val lastBlock = m.newBlock()
oldLastBlock.replaceInstruction(oldLastBlock.length - 1, JUMP(lastBlock))
// call object's private ctor from static ctor
lastBlock.emit(CIL_NEWOBJ(iclass.symbol.primaryConstructor))
lastBlock.emit(DROP(toTypeKind(iclass.symbol.tpe)))
lastBlock emit RETURN(UNIT)
lastBlock.close
}
}
if (iclass.symbol != definitions.ArrayClass) {
for (m: IMethod <- iclass.methods) {
val sym = m.symbol
debuglog("Creating MethodBuilder for " + Flags.flagsToString(sym.flags) + " " +
sym.owner.fullName + "::" + sym.name)
val ownerType = getType(sym.enclClass).asInstanceOf[TypeBuilder]
assert(mtype == ownerType, "mtype = " + mtype + "; ownerType = " + ownerType)
var paramTypes = msilParamTypes(sym)
val attr = msilMethodFlags(sym)
if (m.symbol.isClassConstructor) {
val constr =
ownerType.DefineConstructor(attr, CallingConventions.Standard, paramTypes)
for (i <- 0.until(paramTypes.length)) {
constr.DefineParameter(i, ParameterAttributes.None, msilName(m.params(i).sym))
}
mapConstructor(sym, constr)
addAttributes(constr, sym.annotations)
} else {
var resType = msilType(m.returnType)
val method =
ownerType.DefineMethod(msilName(sym), attr, resType, paramTypes)
for (i <- 0.until(paramTypes.length)) {
method.DefineParameter(i, ParameterAttributes.None, msilName(m.params(i).sym))
}
if (!methods.contains(sym))
mapMethod(sym, method)
addAttributes(method, sym.annotations)
debuglog("\t created MethodBuilder " + method)
}
}
} // method builders created for non-array iclass
if (isStaticModule(iclass.symbol)) {
addModuleInstanceField(iclass.symbol)
notInitializedModules += iclass.symbol
if (iclass.lookupStaticCtor.isEmpty) {
addStaticInit(iclass.symbol)
}
}
} // createClassMembers0
private def isTopLevelModule(sym: Symbol): Boolean =
beforeRefchecks {
sym.isModuleClass && !sym.isImplClass && !sym.isNestedClass
}
// if the module is lifted it does not need to be initialized in
// its static constructor, and the MODULE$ field is not required.
// the outer class will care about it.
private def isStaticModule(sym: Symbol): Boolean = {
// .net inner classes: removed '!sym.hasFlag(Flags.LIFTED)', added
// 'sym.isStatic'. -> no longer compatible without skipping flatten!
sym.isModuleClass && sym.isStatic && !sym.isImplClass
}
private def isCloneable(sym: Symbol): Boolean = {
!sym.annotations.forall( a => a match {
case AnnotationInfo(CloneableAttr, _, _) => false
case _ => true
})
}
private def addModuleInstanceField(sym: Symbol) {
debuglog("Adding Module-Instance Field for " + showsym(sym))
val tBuilder = getType(sym).asInstanceOf[TypeBuilder]
val fb = tBuilder.DefineField(MODULE_INSTANCE_NAME,
tBuilder,
(FieldAttributes.Public |
//FieldAttributes.InitOnly |
FieldAttributes.Static).toShort)
fields(sym) = fb
}
// the symbol may be a object-symbol (module-symbol), or a module-class-symbol
private def getModuleInstanceField(sym: Symbol): FieldInfo = {
assert(sym.isModule || sym.isModuleClass, "Expected module: " + showsym(sym))
// when called by LOAD_MODULE, the corresponding type maybe doesn't
// exist yet -> make a getType
val moduleClassSym = if (sym.isModule) sym.moduleClass else sym
// TODO: get module field for modules not defined in the
// source currently compiling (e.g. Console)
fields get moduleClassSym match {
case Some(sym) => sym
case None =>
//val mclass = types(moduleClassSym)
val nameInMetadata = nestingAwareFullClassname(moduleClassSym)
val mClass = clrTypes.getType(nameInMetadata)
val mfield = mClass.GetField("MODULE$")
assert(mfield ne null, "module not found " + showsym(moduleClassSym))
fields(moduleClassSym) = mfield
mfield
}
//fields(moduleClassSym)
}
def nestingAwareFullClassname(csym: Symbol) : String = {
val suffix = csym.moduleSuffix
val res = if (csym.isNestedClass)
nestingAwareFullClassname(csym.owner) + "+" + csym.encodedName
else
csym.fullName
res + suffix
}
/** Adds a static initializer which creates an instance of the module
* class (calls the primary constructor). A special primary constructor
* will be generated (notInitializedModules) which stores the new instance
* in the MODULE$ field right after the super call.
*/
private def addStaticInit(sym: Symbol) {
val tBuilder = getType(sym).asInstanceOf[TypeBuilder]
val staticInit = tBuilder.DefineConstructor(
(MethodAttributes.Static | MethodAttributes.Public).toShort,
CallingConventions.Standard,
MsilType.EmptyTypes)
val sicode = staticInit.GetILGenerator()
val instanceConstructor = constructors(sym.primaryConstructor)
// there are no constructor parameters. assuming the constructor takes no parameter
// is fine: we call (in the static constructor) the constructor of the module class,
// which takes no arguments - an object definition cannot take constructor arguments.
sicode.Emit(OpCodes.Newobj, instanceConstructor)
// the stsfld is done in the instance constructor, just after the super call.
sicode.Emit(OpCodes.Pop)
sicode.Emit(OpCodes.Ret)
}
private def generateMirrorClass(sym: Symbol) {
val tBuilder = getType(sym)
assert(sym.isModuleClass, "Can't generate Mirror-Class for the Non-Module class " + sym)
debuglog("Dumping mirror class for object: " + sym)
val moduleName = msilName(sym)
val mirrorName = moduleName.substring(0, moduleName.length() - 1)
val mirrorTypeBuilder = mmodule.DefineType(mirrorName,
TypeAttributes.Class |
TypeAttributes.Public |
TypeAttributes.Sealed,
MOBJECT,
MsilType.EmptyTypes)
val iclass = classes(sym)
for (m <- sym.tpe.nonPrivateMembers
if m.owner != definitions.ObjectClass && !m.isProtected &&
m.isMethod && !m.isClassConstructor && !m.isStaticMember && !m.isCase &&
!m.isDeferred)
{
debuglog(" Mirroring method: " + m)
val paramTypes = msilParamTypes(m)
val paramNames: Array[String] = new Array[String](paramTypes.length)
for (i <- 0 until paramTypes.length)
paramNames(i) = "x_" + i
// CHECK: verify if getMethodName is better than msilName
val mirrorMethod = mirrorTypeBuilder.DefineMethod(msilName(m),
(MethodAttributes.Public |
MethodAttributes.Static).toShort,
msilType(m.tpe.resultType),
paramTypes)
var i = 0
while (i < paramTypes.length) {
mirrorMethod.DefineParameter(i, ParameterAttributes.None, paramNames(i))
i += 1
}
val mirrorCode = mirrorMethod.GetILGenerator()
mirrorCode.Emit(OpCodes.Ldsfld, getModuleInstanceField(sym))
val mInfo = getMethod(m)
for (paramidx <- 0.until(paramTypes.length)) {
val mInfoParams = mInfo.GetParameters
val loadAddr = mInfoParams(paramidx).ParameterType.IsByRef
loadArg(mirrorCode, loadAddr)(paramidx)
}
mirrorCode.Emit(OpCodes.Callvirt, getMethod(m))
mirrorCode.Emit(OpCodes.Ret)
}
addSymtabAttribute(sym.sourceModule, mirrorTypeBuilder)
mirrorTypeBuilder.CreateType()
mirrorTypeBuilder.setSourceFilepath(iclass.cunit.source.file.path)
}
// #####################################################################
// delegate callers
var delegateCallers: TypeBuilder = _
var nbDelegateCallers: Int = 0
private def initDelegateCallers() = {
delegateCallers = mmodule.DefineType("$DelegateCallers", TypeAttributes.Public |
TypeAttributes.Sealed)
}
private def createDelegateCaller(functionType: Type, delegateType: Type) = {
if (delegateCallers == null)
initDelegateCallers()
// create a field an store the function-object
val mFunctionType: MsilType = msilType(functionType)
val anonfunField: FieldBuilder = delegateCallers.DefineField(
"$anonfunField$$" + nbDelegateCallers, mFunctionType,
(FieldAttributes.InitOnly | FieldAttributes.Public | FieldAttributes.Static).toShort)
mcode.Emit(OpCodes.Stsfld, anonfunField)
// create the static caller method and the delegate object
val (params, returnType) = delegateType.member(nme.apply).tpe match {
case MethodType(delParams, delReturn) => (delParams, delReturn)
case _ => abort("not a delegate type: " + delegateType)
}
val caller: MethodBuilder = delegateCallers.DefineMethod(
"$delegateCaller$$" + nbDelegateCallers,
(MethodAttributes.Final | MethodAttributes.Public | MethodAttributes.Static).toShort,
msilType(returnType), (params map (_.tpe)).map(msilType).toArray)
for (i <- 0 until params.length)
caller.DefineParameter(i, ParameterAttributes.None, "arg" + i) // FIXME: use name of parameter symbol
val delegCtor = msilType(delegateType).GetConstructor(Array(MOBJECT, INT_PTR))
mcode.Emit(OpCodes.Ldnull)
mcode.Emit(OpCodes.Ldftn, caller)
mcode.Emit(OpCodes.Newobj, delegCtor)
// create the static caller method body
val functionApply: MethodInfo = getMethod(functionType.member(nme.apply))
val dcode: ILGenerator = caller.GetILGenerator()
dcode.Emit(OpCodes.Ldsfld, anonfunField)
for (i <- 0 until params.length) {
loadArg(dcode, false /* TODO confirm whether passing actual as-is to formal is correct wrt the ByRef attribute of the param */)(i)
emitBox(dcode, toTypeKind(params(i).tpe))
}
dcode.Emit(OpCodes.Callvirt, functionApply)
emitUnbox(dcode, toTypeKind(returnType))
dcode.Emit(OpCodes.Ret)
nbDelegateCallers = nbDelegateCallers + 1
} //def createDelegateCaller
def emitBox(code: ILGenerator, boxType: TypeKind) = (boxType: @unchecked) match {
// doesn't make sense, unit as parameter..
case UNIT => code.Emit(OpCodes.Ldsfld, boxedUnit)
case BOOL | BYTE | SHORT | CHAR | INT | LONG | FLOAT | DOUBLE =>
code.Emit(OpCodes.Box, msilType(boxType))
case REFERENCE(cls) if clrTypes.isValueType(cls) =>
code.Emit(OpCodes.Box, (msilType(boxType)))
case REFERENCE(_) | ARRAY(_) =>
warning("Tried to BOX a non-valuetype.")
()
}
def emitUnbox(code: ILGenerator, boxType: TypeKind) = (boxType: @unchecked) match {
case UNIT => code.Emit(OpCodes.Pop)
/* (1) it's essential to keep the code emitted here (as of now plain calls to System.Convert.ToBlaBla methods)
behaviorally.equiv.wrt. BoxesRunTime.unboxToBlaBla methods
(case null: that's easy, case boxed: track changes to unboxBlaBla)
(2) See also: asInstanceOf to cast from Any to number,
tracked in http://lampsvn.epfl.ch/trac/scala/ticket/4437 */
case BOOL => code.Emit(OpCodes.Call, toBool)
case BYTE => code.Emit(OpCodes.Call, toSByte)
case SHORT => code.Emit(OpCodes.Call, toShort)
case CHAR => code.Emit(OpCodes.Call, toChar)
case INT => code.Emit(OpCodes.Call, toInt)
case LONG => code.Emit(OpCodes.Call, toLong)
case FLOAT => code.Emit(OpCodes.Call, toFloat)
case DOUBLE => code.Emit(OpCodes.Call, toDouble)
case REFERENCE(cls) if clrTypes.isValueType(cls) =>
code.Emit(OpCodes.Unbox, msilType(boxType))
code.Emit(OpCodes.Ldobj, msilType(boxType))
case REFERENCE(_) | ARRAY(_) =>
warning("Tried to UNBOX a non-valuetype.")
()
}
// #####################################################################
// get and create methods / constructors
def getConstructor(sym: Symbol): ConstructorInfo = constructors.get(sym) match {
case Some(constr) => constr
case None =>
val mClass = getType(sym.owner)
val constr = mClass.GetConstructor(msilParamTypes(sym))
if (constr eq null) {
java.lang.System.out.println("Cannot find constructor " + sym.owner + "::" + sym.name)
java.lang.System.out.println("scope = " + sym.owner.tpe.decls)
abort(sym.fullName)
}
else {
mapConstructor(sym, constr)
constr
}
}
def mapConstructor(sym: Symbol, cInfo: ConstructorInfo) = {
constructors(sym) = cInfo
}
private def getMethod(sym: Symbol): MethodInfo = {
methods.get(sym) match {
case Some(method) => method
case None =>
val mClass = getType(sym.owner)
try {
val method = mClass.GetMethod(msilName(sym), msilParamTypes(sym),
msilType(sym.tpe.resultType))
if (method eq null) {
java.lang.System.out.println("Cannot find method " + sym.owner + "::" + msilName(sym))
java.lang.System.out.println("scope = " + sym.owner.tpe.decls)
abort(sym.fullName)
}
else {
mapMethod(sym, method)
method
}
}
catch {
case e: Exception =>
Console.println("While looking up " + mClass + "::" + sym.nameString)
Console.println("\t" + showsym(sym))
throw e
}
}
}
/*
* add a mapping between sym and mInfo
*/
private def mapMethod(sym: Symbol, mInfo: MethodInfo) {
assert (mInfo != null, mInfo)
methods(sym) = mInfo
}
/*
* add mapping between sym and method with newName, paramTypes of newClass
*/
private def mapMethod(sym: Symbol, newClass: MsilType, newName: String, paramTypes: Array[MsilType]) {
val methodInfo = newClass.GetMethod(newName, paramTypes)
assert(methodInfo != null, "Can't find mapping for " + sym + " -> " +
newName + "(" + paramTypes + ")")
mapMethod(sym, methodInfo)
if (methodInfo.IsStatic)
dynToStatMapped += sym
}
/*
* add mapping between method with name and paramTypes of clazz to
* method with newName and newParamTypes of newClass (used for instance
* for "wait")
*/
private def mapMethod(
clazz: Symbol, name: Name, paramTypes: Array[Type],
newClass: MsilType, newName: String, newParamTypes: Array[MsilType]) {
val methodSym = lookupMethod(clazz, name, paramTypes)
assert(methodSym != null, "cannot find method " + name + "(" +
paramTypes + ")" + " in class " + clazz)
mapMethod(methodSym, newClass, newName, newParamTypes)
}
/*
* add mapping for member with name and paramTypes to member
* newName of newClass (same parameters)
*/
private def mapMethod(
clazz: Symbol, name: Name, paramTypes: Array[Type],
newClass: MsilType, newName: String) {
mapMethod(clazz, name, paramTypes, newClass, newName, paramTypes map msilType)
}
/*
* add mapping for all methods with name of clazz to the corresponding
* method (same parameters) with newName of newClass
*/
private def mapMethod(
clazz: Symbol, name: Name,
newClass: MsilType, newName: String) {
val memberSym: Symbol = clazz.tpe.member(name)
memberSym.tpe match {
// alternatives: List[Symbol]
case OverloadedType(_, alternatives) =>
alternatives.foreach(s => mapMethod(s, newClass, newName, msilParamTypes(s)))
// paramTypes: List[Type], resType: Type
case MethodType(params, resType) =>
mapMethod(memberSym, newClass, newName, msilParamTypes(memberSym))
case _ =>
abort("member not found: " + clazz + ", " + name)
}
}
/*
* find the method in clazz with name and paramTypes
*/
private def lookupMethod(clazz: Symbol, name: Name, paramTypes: Array[Type]): Symbol = {
val memberSym = clazz.tpe.member(name)
memberSym.tpe match {
case OverloadedType(_, alternatives) =>
alternatives.find(s => {
var i: Int = 0
var typesOK: Boolean = true
if (paramTypes.length == s.tpe.paramTypes.length) {
while(i < paramTypes.length) {
if (paramTypes(i) != s.tpe.paramTypes(i))
typesOK = false
i += 1
}
} else {
typesOK = false
}
typesOK
}) match {
case Some(sym) => sym
case None => abort("member of " + clazz + ", " + name + "(" +
paramTypes + ") not found")
}
case MethodType(_, _) => memberSym
case _ => abort("member not found: " + name + " of " + clazz)
}
}
private def showsym(sym: Symbol): String = (sym.toString +
"\n symbol = " + Flags.flagsToString(sym.flags) + " " + sym +
"\n owner = " + Flags.flagsToString(sym.owner.flags) + " " + sym.owner
)
} // class BytecodeGenerator
} // class GenMSIL