|
Scala example source code file (GenMSIL.scala)
The Scala GenMSIL.scala source code/* NSC -- new scala compiler * Copyright 2005-2011 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.{Map, HashMap, HashSet, Stack, ListBuffer} 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 abstract class GenMSIL extends SubComponent { import global._ import loaders.clrTypes import clrTypes.{types, constructors, methods, fields} import icodes._ import icodes.opcodes._ /** 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.getClass("scala.cloneable").tpe val TransientAtt = definitions.getClass("scala.transient").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: HashSet[Symbol] = new HashSet() 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 } /* if (settings.debug.value) log("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, ClassTag, 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, ClassTag, 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] if (settings.debug.value) log("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 if (settings.debug.value) log("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) })) { if (settings.debug.value) log("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) dumpMirrorClass(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) { if (settings.debug.value) log("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)) { if (settings.debug.value) log("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: HashMap[BasicBlock, Label] = new HashMap() /* 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]) = { if (settings.debug.value) log("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 = new HashMap[BasicBlock, List[ExceptionHandler]]() // the catch blocks starting / endling at a certain BasicBlock val beginCatchBlock = new HashMap[BasicBlock, ExceptionHandler]() val endExBlock = new 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 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 = new 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 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 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 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 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) { if (settings.debug.value) log(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) { if (settings.debug.value) log(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)) if (settings.debug.value) log("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 ClassTag => 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) => if (settings.debug.value) log("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 if (settings.debug.value) log("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) = atPhase(currentRun.typerPhase) { 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 != defaultTarget) 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) => if (settings.debug.value) log("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 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) { if (settings.debug.value) log("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) { if (settings.debug.value) log("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: String = if (sym.hasModuleFlag && !sym.isMethod && !sym.isImplClass && !sym.isJavaDefined) "$" else "" // 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: HashSet[Symbol] = new HashSet() // 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: HashMap[Local, LocalBuilder] = new HashMap() 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) if (settings.debug.value) log("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 if (settings.debug.value) log("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.code.blocks.last val lastBlock = m.code.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 if (settings.debug.value) log("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) if (settings.debug.value) log("\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 = atPhase (currentRun.refchecksPhase) { 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) { if (settings.debug.value) log("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 = moduleSuffix(csym) val res = if (csym.isNestedClass) nestingAwareFullClassname(csym.owner) + "+" + csym.encodedName else csym.fullName res + suffix } /** cut&pasted from GenJVM */ def moduleSuffix(sym: Symbol) = if (sym.hasFlag(Flags.MODULE) && !sym.isMethod && !sym.isImplClass && !sym.hasFlag(Flags.JAVA)) "$" else ""; /** 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 dumpMirrorClass(sym: Symbol) { val tBuilder = getType(sym) assert(sym.isModuleClass, "Can't generate Mirror-Class for the Non-Module class " + sym) if (settings.debug.value) log("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) { if (settings.debug.value) log(" 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 Other Scala examples (source code examples)Here is a short list of links related to this Scala GenMSIL.scala source code file: |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.