diff options
Diffstat (limited to 'src')
13 files changed, 829 insertions, 194 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala index 27bf9ac29b..d5cafccd30 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/GenICode.scala @@ -167,7 +167,9 @@ abstract class GenICode extends SubComponent { private def genStat(tree: Tree, ctx: Context): Context = tree match { case Assign(lhs @ Select(_, _), rhs) => val isStatic = lhs.symbol.isStaticMember - var ctx1 = if (isStatic) ctx else genLoadQualifier(lhs, ctx) + var ctx1 = if (isStatic) ctx + else if (forMSIL && msil_IsValuetypeInstField(lhs.symbol)) msil_genLoadQualifierAddress(lhs, ctx) + else genLoadQualifier(lhs, ctx) ctx1 = genLoad(rhs, ctx1, toTypeKind(lhs.symbol.info)) ctx1.bb.emit(STORE_FIELD(lhs.symbol, isStatic), tree.pos) @@ -458,6 +460,146 @@ abstract class GenICode extends SubComponent { } /** + * forMSIL + */ + private def msil_IsValuetypeInstMethod(msym: Symbol) = { + val mMSILOpt = loaders.clrTypes.methods.get(msym) + if (mMSILOpt.isEmpty) false + else { + val mMSIL = mMSILOpt.get + val res = mMSIL.IsInstance && mMSIL.DeclaringType.IsValueType + res + } + } + + /** + * forMSIL + */ + private def msil_IsValuetypeInstField(fsym: Symbol) = { + val fMSILOpt = loaders.clrTypes.fields.get(fsym) + if (fMSILOpt.isEmpty) false + else { + val fMSIL = fMSILOpt.get + val res = !fMSIL.IsStatic && fMSIL.DeclaringType.IsValueType + res + } + } + + /** + * forMSIL: Adds a local var, the emitted code requires one more slot on the stack as on entry + */ + private def msil_genLoadZeroOfNonEnumValuetype(ctx: Context, kind: TypeKind, pos: Position, leaveAddressOnStackInstead: Boolean) { + val REFERENCE(clssym) = kind + assert(loaders.clrTypes.isNonEnumValuetype(clssym)) + val local = ctx.makeLocal(pos, clssym.tpe, "tmp") + ctx.method.addLocal(local) + ctx.bb.emit(CIL_LOAD_LOCAL_ADDRESS(local), pos) + ctx.bb.emit(CIL_INITOBJ(kind), pos) + val instr = if (leaveAddressOnStackInstead) + CIL_LOAD_LOCAL_ADDRESS(local) + else + LOAD_LOCAL(local) + ctx.bb.emit(instr, pos) + } + + /** + * forMSIL + */ + private def msil_genLoadAddressOf(tree: Tree, ctx: Context, expectedType: TypeKind, butRawValueIsAlsoGoodEnough: Boolean): Context = { + var generatedType = expectedType + var addressTaken = false + if (settings.debug.value) + log("at line: " + (if (tree.pos.isDefined) tree.pos.line else tree.pos)) + + var resCtx: Context = tree match { + + // emits CIL_LOAD_FIELD_ADDRESS + case Select(qualifier, selector) if (!tree.symbol.isModule) => + addressTaken = true + val sym = tree.symbol + generatedType = toTypeKind(sym.info) + + if (sym.isStaticMember) { + ctx.bb.emit(CIL_LOAD_FIELD_ADDRESS(sym, true), tree.pos) + ctx + } else { + val ctx1 = genLoadQualifier(tree, ctx) + ctx1.bb.emit(CIL_LOAD_FIELD_ADDRESS(sym, false), tree.pos) + ctx1 + } + + // emits CIL_LOAD_LOCAL_ADDRESS + case Ident(name) if (!tree.symbol.isPackage && !tree.symbol.isModule)=> + addressTaken = true + val sym = tree.symbol + try { + val Some(l) = ctx.method.lookupLocal(sym) + ctx.bb.emit(CIL_LOAD_LOCAL_ADDRESS(l), tree.pos) + generatedType = l.kind // actually, should be "V&" but the callsite is aware of this + } catch { + case ex: MatchError => + abort("symbol " + sym + " does not exist in " + ctx.method) + } + ctx + + // emits CIL_LOAD_ARRAY_ITEM_ADDRESS + case Apply(fun, args) => + if (isPrimitive(fun.symbol)) { + + val sym = tree.symbol + val Apply(fun @ Select(receiver, _), args) = tree + val code = scalaPrimitives.getPrimitive(sym, receiver.tpe) + + if (isArrayOp(code)) { + val arrayObj = receiver + val k = toTypeKind(arrayObj.tpe) + val ARRAY(elementType) = k + if (scalaPrimitives.isArrayGet(code)) { + var ctx1 = genLoad(arrayObj, ctx, k) + // load argument on stack + if (settings.debug.value) + assert(args.length == 1, "Too many arguments for array get operation: " + tree); + ctx1 = genLoad(args.head, ctx1, INT) + generatedType = elementType // actually "managed pointer to element type" but the callsite is aware of this + ctx1.bb.emit(CIL_LOAD_ARRAY_ITEM_ADDRESS(elementType), tree.pos) + addressTaken = true + ctx1 + } else null + } else null + } else null + + case This(qual) => + /* TODO: this case handler is a placeholder for the time when Level 2 support for valuetypes is in place, + in particular when invoking other methods on this where this is a valuetype value (boxed or not). + As receiver, a managed pointer is expected, and a plain ldarg.0 achieves just that. */ + addressTaken = true + genLoad(tree, ctx, expectedType) + + case _ => + null /* A method returning ByRef won't pass peverify, so I guess this case handler is dead code. + Even if it's not, the code below to handler !addressTaken below. */ + } + + if(!addressTaken) { + resCtx = genLoad(tree, ctx, expectedType) + if (!butRawValueIsAlsoGoodEnough) { + // raw value on stack (must be an intermediate result, e.g. returned by method call), take address + addressTaken = true + val boxType = expectedType // toTypeKind(expectedType /* TODO FIXME */) + resCtx.bb.emit(BOX(boxType), tree.pos) + resCtx.bb.emit(CIL_UNBOX(boxType), tree.pos) + } + } + + // emit conversion + if (generatedType != expectedType) + abort("Unexpected tree in msil_genLoadAddressOf: " + tree + " at: " + tree.pos) + + resCtx + } + + + /** * Generate code for trees that produce values on the stack * * @param tree The tree to be translated @@ -657,15 +799,31 @@ abstract class GenICode extends SubComponent { if (settings.debug.value) assert(ctor.owner == cls, "Symbol " + ctor.owner.fullName + " is different than " + tpt) - val nw = NEW(rt) - ctx.bb.emit(nw, tree.pos) - ctx.bb.emit(DUP(generatedType)) - val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx) - val init = CALL_METHOD(ctor, Static(true)) - nw.init = init - ctx1.bb.emit(init, tree.pos) - ctx1 + val ctx2 = if (forMSIL && loaders.clrTypes.isNonEnumValuetype(cls)) { + /* parameterful constructors are the only possible custom constructors, + a default constructor can't be defined for valuetypes, CLR dixit */ + val isDefaultConstructor = args.isEmpty + if (isDefaultConstructor) { + msil_genLoadZeroOfNonEnumValuetype(ctx, rt, tree.pos, leaveAddressOnStackInstead = false) + ctx + } else { + val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx) + ctx1.bb.emit(CIL_NEWOBJ(ctor), tree.pos) + ctx1 + } + } else { + val nw = NEW(rt) + ctx.bb.emit(nw, tree.pos) + ctx.bb.emit(DUP(generatedType)) + val ctx1 = genLoadArguments(args, ctor.info.paramTypes, ctx) + + val init = CALL_METHOD(ctor, Static(true)) + nw.init = init + ctx1.bb.emit(init, tree.pos) + ctx1 + } + ctx2 case _ => abort("Cannot instantiate " + tpt + "of kind: " + generatedType) @@ -697,6 +855,13 @@ abstract class GenICode extends SubComponent { ctx1.bb.emit(UNBOX(boxType), expr.pos) ctx1 + case Apply(fun @ _, List(expr)) if (forMSIL && loaders.clrTypes.isAddressOf(fun.symbol)) => + if (settings.debug.value) + log("ADDRESSOF : " + fun.symbol.fullName); + val ctx1 = msil_genLoadAddressOf(expr, ctx, toTypeKind(expr.tpe), butRawValueIsAlsoGoodEnough = false) + generatedType = toTypeKind(fun.symbol.tpe.resultType) + ctx1 + case app @ Apply(fun, args) => val sym = fun.symbol @@ -737,7 +902,11 @@ abstract class GenICode extends SubComponent { Dynamic var ctx1 = - if (invokeStyle.hasInstance) genLoadQualifier(fun, ctx) + if (invokeStyle.hasInstance) + if (forMSIL && !(invokeStyle.isInstanceOf[SuperCall]) && msil_IsValuetypeInstMethod(sym)) + msil_genLoadQualifierAddress(fun, ctx) + else + genLoadQualifier(fun, ctx) else ctx ctx1 = genLoadArguments(args, sym.info.paramTypes, ctx1) @@ -770,6 +939,7 @@ abstract class GenICode extends SubComponent { } case ApplyDynamic(qual, args) => + assert(!forMSIL) ctx.clazz.bootstrapClass = Some("scala.runtime.DynamicDispatch") val ctx1 = genLoad(qual, ctx, ANY_REF_CLASS) genLoadArguments(args, tree.symbol.info.paramTypes, ctx1) @@ -984,6 +1154,15 @@ abstract class GenICode extends SubComponent { abort("Unknown qualifier " + tree) } + /** forMSIL */ + private def msil_genLoadQualifierAddress(tree: Tree, ctx: Context): Context = + tree match { + case Select(qualifier, _) => + msil_genLoadAddressOf(qualifier, ctx, toTypeKind(qualifier.tpe), butRawValueIsAlsoGoodEnough = false) + case _ => + abort("Unknown qualifier " + tree) + } + /** * Generate code that loads args into label parameters. */ diff --git a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala index aa53a22ade..7024687e0e 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/Opcodes.scala @@ -665,5 +665,65 @@ trait Opcodes { self: ICodes => /** Call through super[mix]. */ case class SuperCall(mix: Name) extends InvokeStyle + + // CLR backend + + case class CIL_LOAD_LOCAL_ADDRESS(local: Local) extends Instruction { + /** Returns a string representation of this instruction */ + override def toString(): String = "CIL_LOAD_LOCAL_ADDRESS "+local.toString() //+isArgument?" (argument)":""; + + override def consumed = 0 + override def produced = 1 + + override def producedTypes = List(msil_mgdptr(local.kind)) + } + + case class CIL_LOAD_FIELD_ADDRESS(field: Symbol, isStatic: Boolean) extends Instruction { + /** Returns a string representation of this instruction */ + override def toString(): String = + "CIL_LOAD_FIELD_ADDRESS " + (if (isStatic) field.fullName else field.toString()); + + override def consumed = if (isStatic) 0 else 1 + override def produced = 1 + + override def consumedTypes = if (isStatic) Nil else List(REFERENCE(field.owner)); + override def producedTypes = List(msil_mgdptr(REFERENCE(field.owner))); +} + + case class CIL_LOAD_ARRAY_ITEM_ADDRESS(kind: TypeKind) extends Instruction { + /** Returns a string representation of this instruction */ + override def toString(): String = "CIL_LOAD_ARRAY_ITEM_ADDRESS (" + kind + ")" + + override def consumed = 2 + override def produced = 1 + + override def consumedTypes = List(ARRAY(kind), INT) + override def producedTypes = List(msil_mgdptr(kind)) + } + + case class CIL_UNBOX(valueType: TypeKind) extends Instruction { + override def toString(): String = "CIL_UNBOX " + valueType + override def consumed = 1 + override def consumedTypes = AnyRefReference :: Nil // actually consumes a 'boxed valueType' + override def produced = 1 + override def producedTypes = List(msil_mgdptr(valueType)) + } + + case class CIL_INITOBJ(valueType: TypeKind) extends Instruction { + override def toString(): String = "CIL_INITOBJ " + valueType + override def consumed = 1 + override def consumedTypes = AnyRefReference :: Nil // actually consumes a managed pointer + override def produced = 0 + } + + case class CIL_NEWOBJ(method: Symbol) extends Instruction { + override def toString(): String = "CIL_NEWOBJ " + hostClass.fullName + method.fullName + var hostClass: Symbol = method.owner; + override def consumed = method.tpe.paramTypes.length + override def consumedTypes = method.tpe.paramTypes map toTypeKind + override def produced = 1 + override def producedTypes = List(toTypeKind(method.tpe.resultType)) + } + } } diff --git a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala index 65a5291f36..24e025518e 100644 --- a/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala +++ b/src/compiler/scala/tools/nsc/backend/icode/TypeKinds.scala @@ -488,4 +488,10 @@ trait TypeKinds { self: ICodes => } } + def msil_mgdptr(tk: TypeKind): TypeKind = (tk: @unchecked) match { + case REFERENCE(cls) => REFERENCE(loaders.clrTypes.mdgptrcls4clssym(cls)) + // TODO have ready class-symbols for the by-ref versions of built-in valuetypes + case _ => abort("cannot obtain a managed pointer for " + tk) + } + } diff --git a/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala b/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala index 707cf7cf59..937d1e3d45 100644 --- a/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala +++ b/src/compiler/scala/tools/nsc/backend/msil/GenMSIL.scala @@ -461,8 +461,8 @@ abstract class GenMSIL extends SubComponent { if (settings.debug.value) log("Output path: " + filename) try { - massembly.Save(filename + "\\" + assemName + ".msil") /* use SingleFileILPrinterVisitor */ - // massembly.Save(filename, srcPath.getPath()) /* use MultipleFilesILPrinterVisitor */ + // massembly.Save(filename + "\\" + assemName + ".msil") /* use SingleFileILPrinterVisitor */ + massembly.Save(filename, srcPath.getPath()) /* use MultipleFilesILPrinterVisitor */ } catch { case e:IOException => abort("Could not write to " + filename + ": " + e.getMessage()) } @@ -822,6 +822,76 @@ abstract class GenMSIL extends SubComponent { * @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.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_R4, 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) @@ -927,66 +997,20 @@ abstract class GenMSIL extends SubComponent { // Array[scala.runtime.BoxedUnit] (-> case REFERENCE) } - case LOAD_LOCAL(local) => - if (settings.debug.value) - log("load_local for " + local) - val isArg = local.arg - val i = local.index - if (isArg) - loadArg(mcode)(i) - else - loadLocal(i, local, mcode) + case LOAD_LOCAL(local) => loadLocalOrAddress(local, "load_local", false) - case LOAD_FIELD(field, isStatic) => - if (settings.debug.value) - log("LOAD_FIELD 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.IsLiteral) { - mcode.Emit(if (isStatic) OpCodes.Ldsfld else OpCodes.Ldfld, fieldInfo) - } else { - /* 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_R4, value.asInstanceOf[Double]) - } else { - /* TODO one more case is described in Partition II, 16.2: bytearray(...) */ - abort("Unknown type for static literal field: " + fieldInfo) - } - } - } + 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) @@ -1169,13 +1193,20 @@ abstract class GenMSIL extends SubComponent { 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 ((methodInfo.DeclaringType ne null) && (methodInfo.DeclaringType.IsValueType)) { + 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) @@ -1184,9 +1215,17 @@ abstract class GenMSIL extends SubComponent { } } - case BOX(boxType) => emitBox(mcode, boxType) + case BOX(boxType) => + emitBox(mcode, boxType) + + case UNBOX(boxType) => + emitUnbox(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` @@ -1211,7 +1250,14 @@ abstract class GenMSIL extends SubComponent { // 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(tpe) => mcode.Emit(OpCodes.Castclass, msilType(tpe)) + case CHECK_CAST(tpknd) => + val tMSIL = msilType(tpknd) + if (tMSIL.IsValueType) { + // calling emitUnbox does nothing because there's no unbox method for tMSIL + mcode.Emit(OpCodes.Unbox, tMSIL) + mcode.Emit(OpCodes.Ldobj, tMSIL) + } else + mcode.Emit(OpCodes.Castclass, tMSIL) // no SWITCH is generated when there's // - a default case ("case _ => ...") in the matching expr @@ -1398,7 +1444,14 @@ abstract class GenMSIL extends SubComponent { code.Emit(OpCodes.Ldc_I4, value) } - def loadArg(code: ILGenerator)(i: Int) = i match { + 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) @@ -1409,8 +1462,16 @@ abstract class GenMSIL extends SubComponent { else code.Emit(OpCodes.Ldarg, i) } + } - def loadLocal(i: Int, local: Local, code: ILGenerator) = i match { + 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) @@ -1421,6 +1482,7 @@ abstract class GenMSIL extends SubComponent { else code.Emit(OpCodes.Ldloc, localBuilders(local)) } + } ////////////////////// branches /////////////////////// @@ -1826,7 +1888,9 @@ abstract class GenMSIL extends SubComponent { } def createClassMembers0(iclass: IClass) { + val mtype = getType(iclass.symbol).asInstanceOf[TypeBuilder] + for (ifield <- iclass.fields) { val sym = ifield.symbol if (settings.debug.value) @@ -1836,7 +1900,7 @@ abstract class GenMSIL extends SubComponent { val fBuilder = mtype.DefineField(msilName(sym), msilType(sym.tpe), attributes) fields(sym) = fBuilder addAttributes(fBuilder, sym.annotations) - } + } // all iclass.fields iterated over if (iclass.symbol != definitions.ArrayClass) { for (m: IMethod <- iclass.methods) { @@ -2010,7 +2074,12 @@ abstract class GenMSIL extends SubComponent { val mirrorCode = mirrorMethod.GetILGenerator() mirrorCode.Emit(OpCodes.Ldsfld, getModuleInstanceField(sym)) - 0.until(paramTypes.length) foreach loadArg(mirrorCode) + 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) @@ -2067,7 +2136,7 @@ abstract class GenMSIL extends SubComponent { val dcode: ILGenerator = caller.GetILGenerator() dcode.Emit(OpCodes.Ldsfld, anonfunField) for (i <- 0 until params.length) { - loadArg(dcode)(i) + 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) @@ -2083,7 +2152,7 @@ abstract class GenMSIL extends SubComponent { 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 (definitions.unboxMethod.contains(cls)) => + case REFERENCE(cls) if (definitions.boxMethod.contains(cls)) => code.Emit(OpCodes.Box, (msilType(boxType))) case REFERENCE(_) | ARRAY(_) => () } diff --git a/src/compiler/scala/tools/nsc/symtab/clr/CLRTypes.scala b/src/compiler/scala/tools/nsc/symtab/clr/CLRTypes.scala index f62a42375a..1b18def1a0 100644 --- a/src/compiler/scala/tools/nsc/symtab/clr/CLRTypes.scala +++ b/src/compiler/scala/tools/nsc/symtab/clr/CLRTypes.scala @@ -61,7 +61,19 @@ abstract class CLRTypes { val methods: Map[Symbol,MethodInfo] = new HashMap val fields: Map[Symbol, FieldInfo] = new HashMap val sym2type: Map[Type,Symbol] = new HashMap - + val addressOfViews: HashSet[Symbol] = new HashSet[Symbol] + val mdgptrcls4clssym: Map[ /*cls*/ Symbol, /*cls*/ Symbol] = new HashMap + + def isAddressOf(msym : Symbol) = addressOfViews.contains(msym) + + def isNonEnumValuetype(clssym : Symbol) = { + val msilTOpt = types.get(clssym) + val res = msilTOpt.isDefined && { + val msilT = msilTOpt.get + msilT.IsValueType && !msilT.IsEnum + } + res + } def init() = try { // initialize // the MsilClasspath (nsc/util/Classpath.scala) initializes the msil-library by calling diff --git a/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala b/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala index f661f7bb31..975b6bfc49 100644 --- a/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/clr/TypeParser.scala @@ -13,6 +13,7 @@ import ch.epfl.lamp.compiler.msil.{Type => MSILType, Attribute => MSILAttribute, import scala.collection.mutable.{HashMap, HashSet} import classfile.UnPickler +import ch.epfl.lamp.compiler.msil.Type.TMVarUsage /** * @author Nikolay Mihaylov @@ -64,9 +65,61 @@ abstract class TypeParser { busy = false } + /* the names `classTParams' and `newTParams' stem from the forJVM version (ClassfileParser.sigToType()) + * but there are differences that should be kept in mind. + * forMSIL, a nested class knows nothing about any type-params in the nesting class, + * therefore newTParams is redundant (other than for recording lexical order), + * it always contains the same elements as classTParams.value */ + val classTParams = scala.collection.mutable.Map[Int,Symbol]() // TODO should this be a stack? (i.e., is it possible for >1 invocation to getCLRType on the same TypeParser instance be active ) + val newTParams = new scala.collection.mutable.ListBuffer[Symbol]() + val methodTParams = scala.collection.mutable.Map[Int,Symbol]() + + private def sig2typeBounds(tvarCILDef: GenericParamAndConstraints): Type = { + val ts = new scala.collection.mutable.ListBuffer[Type] + for (cnstrnt <- tvarCILDef.Constraints) { + ts += getCLRType(cnstrnt) // TODO we're definitely not at or after erasure, no need to call objToAny, right? + } + TypeBounds(definitions.NothingClass.tpe, intersectionType(ts.toList, clazz)) + // TODO variance??? + } + + private def createViewFromTo(viewSuffix : String, fromTpe : Type, toTpe : Type, + addToboxMethodMap : Boolean, isAddressOf : Boolean) : Symbol = { + val flags = Flags.JAVA | Flags.STATIC | Flags.IMPLICIT; // todo: static? shouldn't be final instead? + val viewMethodType = (msym: Symbol) => JavaMethodType(msym.newSyntheticValueParams(List(fromTpe)), toTpe) + val vmsym = createMethod(nme.view_ + viewSuffix, flags, viewMethodType, null, true); + if (addToboxMethodMap) definitions.boxMethod(clazz) = vmsym + if (isAddressOf) clrTypes.addressOfViews += vmsym + vmsym + } + + private def createDefaultConstructor(typ: MSILType) { + val attrs = MethodAttributes.Public | MethodAttributes.RTSpecialName | MethodAttributes.SpecialName // TODO instance + val declType= typ + val method = new ConstructorInfo(declType, attrs, Array[MSILType]()) + val flags = Flags.JAVA + val owner = clazz + val methodSym = owner.newMethod(NoPosition, nme.CONSTRUCTOR).setFlag(flags) + val rettype = clazz.tpe + val mtype = methodType(Array[MSILType](), rettype); + val mInfo = mtype(methodSym) + methodSym.setInfo(mInfo) + instanceDefs.enter(methodSym); + clrTypes.constructors(methodSym) = method + } + private def parseClass(typ: MSILType) { + { + val t4c = clrTypes.types.get(clazz) + assert(t4c == None || t4c == Some(typ)) + } clrTypes.types(clazz) = typ + + { + val c4t = clrTypes.sym2type.get(typ) + assert(c4t == None || c4t == Some(clazz)) + } clrTypes.sym2type(typ) = clazz if (typ.IsDefined(clrTypes.SCALA_SYMTAB_ATTR, false)) { @@ -86,29 +139,115 @@ abstract class TypeParser { return } val flags = translateAttributes(typ) - val ifaces: Array[MSILType] = typ.getInterfaces() - val superType = if (typ.BaseType() != null) getCLRType(typ.BaseType()) - else if (typ.IsInterface()) definitions.ObjectClass.tpe - else definitions.AnyClass.tpe; // this is System.Object - val parents = superType :: ifaces.map(getCLRType).toList + + var clazzBoxed : Symbol = NoSymbol + var clazzMgdPtr : Symbol = NoSymbol + + val canBeTakenAddressOf = (typ.IsValueType || typ.IsEnum) && (typ.FullName != "System.Enum") + + if(canBeTakenAddressOf) { + clazzBoxed = clazz.owner.newClass(clazz.name + "Boxed") + clazzMgdPtr = clazz.owner.newClass(clazz.name + "MgdPtr") + clrTypes.mdgptrcls4clssym(clazz) = clazzMgdPtr + /* adding typMgdPtr to clrTypes.sym2type should happen early (before metadata for supertypes is parsed, + before metadata for members are parsed) so that clazzMgdPtr can be found by getClRType. */ + val typMgdPtr = MSILType.mkByRef(typ) + clrTypes.types(clazzMgdPtr) = typMgdPtr + clrTypes.sym2type(typMgdPtr) = clazzMgdPtr + /* clazzMgdPtr but not clazzBoxed is mapped by clrTypes.types into an msil.Type instance, + because there's no metadata-level representation for a "boxed valuetype" */ + val instanceDefsMgdPtr = new Scope + val classInfoMgdPtr = ClassInfoType(definitions.anyvalparam, instanceDefsMgdPtr, clazzMgdPtr) + clazzMgdPtr.setFlag(flags) + clazzMgdPtr.setInfo(classInfoMgdPtr) + } + +/* TODO CLR generics + // first pass + for (tvarCILDef <- typ.getSortedTVars() ) { + val tpname = newTypeName(tvarCILDef.Name.replaceAll("!", "")) // TODO are really all type-params named in all assemblies out there? (NO) + val tpsym = clazz.newTypeParameter(NoPosition, tpname) + classTParams.put(tvarCILDef.Number, tpsym) + newTParams += tpsym + // TODO wouldn't the following also be needed later, i.e. during getCLRType + tpsym.setInfo(definitions.AnyClass.tpe) + } + // second pass + for (tvarCILDef <- typ.getSortedTVars() ) { + val tpsym = classTParams(tvarCILDef.Number) + tpsym.setInfo(sig2typeBounds(tvarCILDef)) // we never skip bounds unlike in forJVM + } +*/ + val ownTypeParams = newTParams.toList +/* TODO CLR generics + if (!ownTypeParams.isEmpty) { + clazz.setInfo(new TypeParamsType(ownTypeParams)) + if(typ.IsValueType && !typ.IsEnum) { + clazzBoxed.setInfo(new TypeParamsType(ownTypeParams)) + } + } +*/ instanceDefs = new Scope staticDefs = new Scope - val classInfo = ClassInfoType(parents, instanceDefs, clazz) + val classInfoAsInMetadata = { + val ifaces: Array[MSILType] = typ.getInterfaces() + val superType = if (typ.BaseType() != null) getCLRType(typ.BaseType()) + else if (typ.IsInterface()) definitions.ObjectClass.tpe + else definitions.AnyClass.tpe; // this branch activates for System.Object only. + // parents (i.e., base type and interfaces) + val parents = new scala.collection.mutable.ListBuffer[Type]() + parents += superType + for (iface <- ifaces) { + parents += getCLRType(iface) // here the variance doesn't matter + } + // methods, properties, events, fields are entered in a moment + if (canBeTakenAddressOf) { + val instanceDefsBoxed = new Scope + ClassInfoType(parents.toList, instanceDefsBoxed, clazzBoxed) + } else + ClassInfoType(parents.toList, instanceDefs, clazz) + } + val staticInfo = ClassInfoType(List(), staticDefs, statics) clazz.setFlag(flags) - clazz.setInfo(classInfo) + + if (canBeTakenAddressOf) { + clazzBoxed.setInfo( if (ownTypeParams.isEmpty) classInfoAsInMetadata + else polyType(ownTypeParams, classInfoAsInMetadata) ) + clazzBoxed.setFlag(flags) + val rawValueInfoType = ClassInfoType(definitions.anyvalparam, instanceDefs, clazz) + clazz.setInfo( if (ownTypeParams.isEmpty) rawValueInfoType + else polyType(ownTypeParams, rawValueInfoType) ) + } else { + clazz.setInfo( if (ownTypeParams.isEmpty) classInfoAsInMetadata + else polyType(ownTypeParams, classInfoAsInMetadata) ) + } + + // TODO I don't remember if statics.setInfo and staticModule.setInfo should also know about type params statics.setFlag(Flags.JAVA) statics.setInfo(staticInfo) staticModule.setFlag(Flags.JAVA) staticModule.setInfo(statics.tpe) + + if (canBeTakenAddressOf) { + // implicit conversions are owned by staticModule.moduleClass + createViewFromTo("2Boxed", clazz.tpe, clazzBoxed.tpe, addToboxMethodMap = true, isAddressOf = false) + // createViewFromTo("2Object", clazz.tpe, definitions.ObjectClass.tpe, addToboxMethodMap = true, isAddressOf = false) + createViewFromTo("2MgdPtr", clazz.tpe, clazzMgdPtr.tpe, addToboxMethodMap = false, isAddressOf = true) + // a return can't have type managed-pointer, thus a dereference-conversion is not needed + // similarly, a method can't declare as return type "boxed valuetype" + if (!typ.IsEnum) { + // a synthetic default constructor for raw-type allows `new X' syntax + createDefaultConstructor(typ) + } + } + // import nested types - for (ntype <- typ.getNestedTypes() if !(ntype.IsNestedPrivate - || ntype.IsNestedAssembly - || ntype.IsNestedFamANDAssem) - || ntype.IsInterface) + for (ntype <- typ.getNestedTypes() if !(ntype.IsNestedPrivate || ntype.IsNestedAssembly || ntype.IsNestedFamANDAssem) + || ntype.IsInterface /* TODO why shouldn't nested ifaces be type-parsed too? */ ) { val loader = new loaders.MSILTypeLoader(ntype) val nclazz = statics.newClass(NoPosition, ntype.Name.toTypeName) @@ -123,13 +262,18 @@ abstract class TypeParser { } val fields = typ.getFields() - for (field <- fields if !(field.IsPrivate() || field.IsAssembly() || field.IsFamilyAndAssembly)) { + for (field <- fields + if !(field.IsPrivate() || field.IsAssembly() || field.IsFamilyAndAssembly) + if (getCLRType(field.FieldType) != null) + ) { + assert (!field.FieldType.IsPointer && !field.FieldType.IsByRef, "CLR requirement") val flags = translateAttributes(field); val name = newTermName(field.Name); val fieldType = - if (field.IsLiteral && !field.FieldType.IsEnum) - ConstantType(getConstant(getCLRType(field.FieldType), field.getValue)) - else getCLRType(field.FieldType); + if (field.IsLiteral && !field.FieldType.IsEnum && isDefinedAtgetConstant(getCLRType(field.FieldType))) + ConstantType(getConstant(getCLRType(field.FieldType), field.getValue)) + else + getCLRType(field.FieldType) val owner = if (field.IsStatic()) statics else clazz; val sym = owner.newValue(NoPosition, name).setFlag(flags).setInfo(fieldType); // TODO: set private within!!! -> look at typechecker/Namers.scala @@ -138,10 +282,10 @@ abstract class TypeParser { } for (constr <- typ.getConstructors() if !constr.IsStatic() && !constr.IsPrivate() && - !constr.IsAssembly() && !constr.IsFamilyAndAssembly()) + !constr.IsAssembly() && !constr.IsFamilyAndAssembly() && !constr.HasPtrParamOrRetType()) createMethod(constr); - // initially also contains getters an setters of properties. + // initially also contains getters and setters of properties. val methodsSet = new HashSet[MethodInfo](); methodsSet ++= typ.getMethods(); @@ -152,7 +296,7 @@ abstract class TypeParser { val setter: MethodInfo = prop.GetSetMethod(true); var gparamsLength: Int = -1; if (!(getter == null || getter.IsPrivate || getter.IsAssembly - || getter.IsFamilyAndAssembly)) + || getter.IsFamilyAndAssembly || getter.HasPtrParamOrRetType)) { assert(prop.PropertyType == getter.ReturnType); val gparams: Array[ParameterInfo] = getter.GetParameters(); @@ -161,7 +305,7 @@ abstract class TypeParser { val flags = translateAttributes(getter); val owner: Symbol = if (getter.IsStatic) statics else clazz; val methodSym = owner.newMethod(NoPosition, name).setFlag(flags) - val mtype: Type = if (gparamsLength == 0) PolyType(List(), propType) + val mtype: Type = if (gparamsLength == 0) PolyType(List(), propType) // .NET properties can't be polymorphic else methodType(getter, getter.ReturnType)(methodSym) methodSym.setInfo(mtype); methodSym.setFlag(Flags.ACCESSOR); @@ -170,7 +314,7 @@ abstract class TypeParser { methodsSet -= getter; } if (!(setter == null || setter.IsPrivate || setter.IsAssembly - || setter.IsFamilyAndAssembly)) + || setter.IsFamilyAndAssembly || setter.HasPtrParamOrRetType)) { val sparams: Array[ParameterInfo] = setter.GetParameters() if(getter != null) @@ -224,8 +368,35 @@ abstract class TypeParser { } } */ +/* Adds view amounting to syntax sugar for a CLR implicit overload. + The long-form syntax can also be supported if "methodsSet -= method" (last statement) is removed. + + /* remember, there's typ.getMethods and type.GetMethods */ + for (method <- typ.getMethods) + if(!method.HasPtrParamOrRetType && + method.IsPublic && method.IsStatic && method.IsSpecialName && + method.Name == "op_Implicit") { + // create a view: typ => method's return type + val viewRetType: Type = getCLRType(method.ReturnType) + val viewParamTypes: List[Type] = method.GetParameters().map(_.ParameterType).map(getCLSType).toList; + /* The spec says "The operator method shall be defined as a static method on either the operand or return type." + * We don't consider the declaring type for the purposes of definitions.functionType, + * instead we regard op_Implicit's argument type and return type as defining the view's signature. + */ + if (viewRetType != null && !viewParamTypes.contains(null)) { + /* The check above applies e.g. to System.Decimal that has a conversion from UInt16, a non-CLS type, whose CLS-mapping returns null */ + val funType: Type = definitions.functionType(viewParamTypes, viewRetType); + val flags = Flags.JAVA | Flags.STATIC | Flags.IMPLICIT; // todo: static? shouldn't be final instead? + val viewMethodType = (msym: Symbol) => JavaMethodType(msym.newSyntheticValueParams(viewParamTypes), funType) + val vmsym = createMethod(nme.view_, flags, viewMethodType, method, true); + methodsSet -= method; + } + } +*/ + for (method <- methodsSet.iterator) - if (!method.IsPrivate() && !method.IsAssembly() && !method.IsFamilyAndAssembly()) + if (!method.IsPrivate() && !method.IsAssembly() && !method.IsFamilyAndAssembly() + && !method.HasPtrParamOrRetType) createMethod(method); // Create methods and views for delegate support @@ -234,19 +405,8 @@ abstract class TypeParser { createDelegateChainers(typ) } - // create the box/unbox methods for value types - if (typ.IsValueType) { - val box = statics.newMethod(NoPosition, nme.box) - box.setInfo(MethodType(box.newSyntheticValueParams(List(clazz.tpe)), definitions.ObjectClass.tpe)) - definitions.boxMethod(clazz) = box - val unbox = statics.newMethod(NoPosition, nme.unbox) - unbox.setInfo(MethodType(unbox.newSyntheticValueParams(List(definitions.ObjectClass.tpe)), clazz.tpe)) - definitions.unboxMethod(clazz) = unbox - //Console.println(typ.FullName + " : " + parents) - } - // for enumerations introduce comparison and bitwise logical operations; - // the backend should recognize and replace them with comparison or + // the backend will recognize them and replace them with comparison or // bitwise logical operations on the primitive underlying type if (typ.IsEnum) { @@ -263,7 +423,7 @@ abstract class TypeParser { for (bitLogName <- ENUM_BIT_LOG_NAMES) { val enumBitLog = clazz.newMethod(NoPosition, bitLogName) - val enumBitLogType = JavaMethodType(enumBitLog.newSyntheticValueParams(List(clazz.tpe)), classInfo) + val enumBitLogType = JavaMethodType(enumBitLog.newSyntheticValueParams(List(clazz.tpe)), clazz.tpe /* was classInfo, infinite typer */) enumBitLog.setFlag(flags).setInfo(enumBitLogType) instanceDefs.enter(enumBitLog) } @@ -271,16 +431,49 @@ abstract class TypeParser { } // parseClass + private def populateMethodTParams(method: MethodBase, methodSym: MethodSymbol) : List[Symbol] = { + if(!method.IsGeneric) Nil + else { + methodTParams.clear + val newMethodTParams = new scala.collection.mutable.ListBuffer[Symbol]() + + // first pass + for (mvarCILDef <- method.getSortedMVars() ) { + val mtpname = newTypeName(mvarCILDef.Name.replaceAll("!", "")) // TODO are really all method-level-type-params named in all assemblies out there? (NO) + val mtpsym = methodSym.newTypeParameter(NoPosition, mtpname) + methodTParams.put(mvarCILDef.Number, mtpsym) + newMethodTParams += mtpsym + // TODO wouldn't the following also be needed later, i.e. during getCLRType + mtpsym.setInfo(definitions.AnyClass.tpe) + } + // second pass + for (mvarCILDef <- method.getSortedMVars() ) { + val mtpsym = methodTParams(mvarCILDef.Number) + mtpsym.setInfo(sig2typeBounds(mvarCILDef)) // we never skip bounds unlike in forJVM + } + + newMethodTParams.toList + } + } + private def createMethod(method: MethodBase) { + + val flags = translateAttributes(method); + val owner = if (method.IsStatic()) statics else clazz; + val methodSym = owner.newMethod(NoPosition, getName(method)).setFlag(flags) + // TODO CLR generics val newMethodTParams = populateMethodTParams(method, methodSym) + val rettype = if (method.IsConstructor()) clazz.tpe else getCLSType(method.asInstanceOf[MethodInfo].ReturnType); if (rettype == null) return; val mtype = methodType(method, rettype); if (mtype == null) return; - val flags = translateAttributes(method); - val owner = if (method.IsStatic()) statics else clazz; - val methodSym = owner.newMethod(NoPosition, getName(method)).setFlag(flags) - methodSym.setInfo(mtype(methodSym)) +/* TODO CLR generics + val mInfo = if (method.IsGeneric) polyType(newMethodTParams, mtype(methodSym)) + else mtype(methodSym) +*/ + val mInfo = mtype(methodSym) + methodSym.setInfo(mInfo) (if (method.IsStatic()) staticDefs else instanceDefs).enter(methodSym); if (method.IsConstructor()) clrTypes.constructors(methodSym) = method.asInstanceOf[ConstructorInfo] @@ -435,7 +628,7 @@ abstract class TypeParser { /** Return a method type for the provided argument types and return type. */ private def methodType(argtypes: Array[MSILType], rettype: Type): Symbol => Type = { def paramType(typ: MSILType): Type = - if (typ eq clrTypes.OBJECT) definitions.AnyClass.tpe + if (typ eq clrTypes.OBJECT) definitions.AnyClass.tpe // TODO a hack to compile scalalib, should be definitions.AnyRefClass.tpe else getCLSType(typ); val ptypes = argtypes.map(paramType).toList; if (ptypes.contains(null)) null @@ -452,21 +645,23 @@ abstract class TypeParser { res } - private def getCLSType(typ: MSILType): Type = { - if (/*type == clrTypes.BYTE ||*/ typ == clrTypes.USHORT - || typ == clrTypes.UINT || typ == clrTypes.ULONG - || typ.IsNotPublic() || typ.IsNestedPrivate() - || typ.IsNestedAssembly() || typ.IsNestedFamANDAssem() - || typ.IsPointer() - || (typ.IsArray() && getCLSType(typ.GetElementType()) == null)) - null; - //Symbol s = clrTypes.getSymbol(type); - //scalac.symtab.Type t = s != null ? make.classType(s) : getCLRType(type); + private def getCLSType(typ: MSILType): Type = { // getCLS returns non-null for types GenMSIL can handle, be they CLS-compliant or not + if (typ.IsTMVarUsage()) + null // TODO after generics: getCLRType(typ) + else if ( /* TODO hack if UBYE, uncommented, "ambiguous reference to overloaded definition" ensues, for example for System.Math.Max(x, y) */ + typ == clrTypes.USHORT || typ == clrTypes.UINT || typ == clrTypes.ULONG + /* || typ == clrTypes.UBYTE */ + || typ.IsNotPublic() || typ.IsNestedPrivate() + || typ.IsNestedAssembly() || typ.IsNestedFamANDAssem() + || typ.IsPointer() + || (typ.IsArray() && getCLRType(typ.GetElementType()) == null) /* TODO hack: getCLR instead of getCLS */ + || (typ.IsByRef() && !typ.GetElementType().CanBeTakenAddressOf())) + null else getCLRType(typ) } - private def getCLRType(typ: MSILType): Type = + private def getCLRTypeIfPrimitiveNullOtherwise(typ: MSILType): Type = if (typ == clrTypes.OBJECT) definitions.ObjectClass.tpe; else if (typ == clrTypes.VALUE_TYPE) @@ -479,32 +674,67 @@ abstract class TypeParser { definitions.BooleanClass.tpe else if (typ == clrTypes.CHAR) definitions.CharClass.tpe - else if (typ == clrTypes.BYTE || typ == clrTypes.UBYTE) + else if ((typ == clrTypes.BYTE) || (typ == clrTypes.UBYTE)) // TODO U... is a hack to compile scalalib definitions.ByteClass.tpe - else if (typ == clrTypes.SHORT || typ == clrTypes.USHORT) + else if ((typ == clrTypes.SHORT) || (typ == clrTypes.SHORT)) // TODO U... is a hack to compile scalalib definitions.ShortClass.tpe - else if (typ == clrTypes.INT || typ == clrTypes.UINT) + else if ((typ == clrTypes.INT) || (typ == clrTypes.UINT)) // TODO U... is a hack to compile scalalib definitions.IntClass.tpe - else if (typ == clrTypes.LONG || typ == clrTypes.ULONG) + else if ((typ == clrTypes.LONG) || (typ == clrTypes.LONG)) // TODO U... is a hack to compile scalalib definitions.LongClass.tpe else if (typ == clrTypes.FLOAT) definitions.FloatClass.tpe else if (typ == clrTypes.DOUBLE) definitions.DoubleClass.tpe - else if (typ.IsArray()) - appliedType(definitions.ArrayClass.tpe, - List(getCLRType(typ.GetElementType()))); - else if (typ.isInstanceOf[ConstructedType]) { - val ct = typ.asInstanceOf[ConstructedType] - getCLRType(ct.instantiatedType) - } else { - val res = clrTypes.sym2type.get (typ) match { - case Some(sym) => sym.tpe - case None => getClassType(typ); - } - assert (res != null, typ) - res - } + else null + + + private def getCLRType(tMSIL: MSILType): Type = { + var res = getCLRTypeIfPrimitiveNullOtherwise(tMSIL) + if (res != null) res + else if (tMSIL.isInstanceOf[ConstructedType]) { + val ct = tMSIL.asInstanceOf[ConstructedType] + /* TODO CLR generics: uncomment next two lines and comment out the hack after them + val cttpArgs = ct.typeArgs.map(tmsil => getCLRType(tmsil)).toList + appliedType(getCLRType(ct.instantiatedType), cttpArgs) + */ + getCLRType(ct.instantiatedType) + } else if (tMSIL.isInstanceOf[TMVarUsage]) { + /* TODO CLR generics: uncomment next lines and comment out the hack after them + val tVarUsage = tMSIL.asInstanceOf[TMVarUsage] + val tVarNumber = tVarUsage.Number + if (tVarUsage.isTVar) classTParams(tVarNumber).typeConstructor // shouldn't fail, just return definitions.AnyClass.tpe at worst + else methodTParams(tVarNumber).typeConstructor // shouldn't fail, just return definitions.AnyClass.tpe at worst + */ + null // definitions.ObjectClass.tpe + } else if (tMSIL.IsArray()) { + var elemtp = getCLRType(tMSIL.GetElementType()) + // cut&pasted from ClassfileParser + // make unbounded Array[T] where T is a type variable into Array[T with Object] + // (this is necessary because such arrays have a representation which is incompatible + // with arrays of primitive types). + // TODO does that incompatibility also apply to .NET? + if (elemtp.typeSymbol.isAbstractType && !(elemtp <:< definitions.ObjectClass.tpe)) + elemtp = intersectionType(List(elemtp, definitions.ObjectClass.tpe)) + appliedType(definitions.ArrayClass.tpe, List(elemtp)) + } else { + res = clrTypes.sym2type.get(tMSIL) match { + case Some(sym) => sym.tpe + case None => if (tMSIL.IsByRef && tMSIL.GetElementType.IsValueType) { + val addressed = getCLRType(tMSIL.GetElementType) + val clasym = addressed.typeSymbolDirect // TODO should be .typeSymbol? + clasym.info.load(clasym) + val secondAttempt = clrTypes.sym2type.get(tMSIL) + secondAttempt match { case Some(sym) => sym.tpe + case None => null + } + } else getClassType(tMSIL) + } + if (res == null) + null // TODO new RuntimeException() + else res + } + } // the values are Java-Box-Classes (e.g. Integer, Boolean, Character) // java.lang.Number to get the value (if a number, not for boolean, character) @@ -533,6 +763,23 @@ abstract class TypeParser { abort("illegal value: " + value + ", class-symbol: " + typeClass) } + def isDefinedAtgetConstant(constType: Type): Boolean = { + val typeClass = constType.typeSymbol + if ( (typeClass == definitions.BooleanClass) + || (typeClass == definitions.ByteClass) + || (typeClass == definitions.ShortClass) + || (typeClass == definitions.CharClass) + || (typeClass == definitions.IntClass) + || (typeClass == definitions.LongClass) + || (typeClass == definitions.FloatClass) + || (typeClass == definitions.DoubleClass) + || (typeClass == definitions.StringClass) + ) + true + else + false + } + private def translateAttributes(typ: MSILType): Long = { var flags: Long = Flags.JAVA; if (typ.IsNotPublic() || typ.IsNestedPrivate() diff --git a/src/msil/ch/epfl/lamp/compiler/msil/ConstructorInfo.java b/src/msil/ch/epfl/lamp/compiler/msil/ConstructorInfo.java index 99e5c5fe69..69f5d6d32a 100644 --- a/src/msil/ch/epfl/lamp/compiler/msil/ConstructorInfo.java +++ b/src/msil/ch/epfl/lamp/compiler/msil/ConstructorInfo.java @@ -24,23 +24,23 @@ public class ConstructorInfo extends MethodBase { protected static final String CTOR = ".ctor"; protected static final String CCTOR = ".cctor"; - protected static final ConstructorInfo[] EMPTY_ARRAY = - new ConstructorInfo[0]; + protected static final ConstructorInfo[] EMPTY_ARRAY = new ConstructorInfo[0]; protected static String getName(int attrs) { - return (attrs & MethodAttributes.Static) == 0 ? CTOR : CCTOR; + return (attrs & MethodAttributes.Static) == 0 ? CTOR : CCTOR; } - /** Protected constructor */ - protected ConstructorInfo(Type declType, int attrs, Type[] paramTypes) { - super(getName(attrs), declType, attrs, paramTypes); - assert declType != null : "Owner can't be 'null' for a constructor!"; + /** Public constructors */ + + public ConstructorInfo(Type declType, int attrs, Type[] paramTypes) { + super(getName(attrs), declType, attrs, paramTypes); + assert declType != null : "Owner can't be 'null' for a constructor!"; } - protected ConstructorInfo(Type declType, int attrs, ParameterInfo[] params) + public ConstructorInfo(Type declType, int attrs, ParameterInfo[] params) { - super(getName(attrs), declType, attrs, params); - assert declType != null : "Owner can't be 'null' for a constructor!"; + super(getName(attrs), declType, attrs, params); + assert declType != null : "Owner can't be 'null' for a constructor!"; } diff --git a/src/msil/ch/epfl/lamp/compiler/msil/MethodBase.java b/src/msil/ch/epfl/lamp/compiler/msil/MethodBase.java index 793ee362e9..fe6404346e 100644 --- a/src/msil/ch/epfl/lamp/compiler/msil/MethodBase.java +++ b/src/msil/ch/epfl/lamp/compiler/msil/MethodBase.java @@ -5,6 +5,8 @@ package ch.epfl.lamp.compiler.msil; +import java.util.Iterator; + /** * The common superclass of MemberInfo and ConstructorInfo * @@ -16,6 +18,34 @@ public abstract class MethodBase extends MemberInfo { //########################################################################## // public interface + private java.util.List /* GenericParamAndConstraints */ mVars = new java.util.LinkedList(); + private GenericParamAndConstraints[] sortedMVars = null; + + public void addMVar(GenericParamAndConstraints tvarAndConstraints) { + sortedMVars = null; + mVars.add(tvarAndConstraints); + } + + public GenericParamAndConstraints[] getSortedMVars() { + if(sortedMVars == null) { + sortedMVars = new GenericParamAndConstraints[mVars.size()]; + for (int i = 0; i < sortedMVars.length; i ++){ + Iterator iter = mVars.iterator(); + while(iter.hasNext()) { + GenericParamAndConstraints tvC = (GenericParamAndConstraints)iter.next(); + if(tvC.Number == i) { + sortedMVars[i] = tvC; + } + } + } + } + return sortedMVars; + } + + public final boolean IsGeneric() { + return mVars.size() > 0; + } + /** The attributes associated with this method/constructor. */ public final short Attributes; @@ -36,6 +66,10 @@ public abstract class MethodBase extends MemberInfo { return (Attributes& MethodAttributes.Virtual) != 0; } + public final boolean IsInstance() { + return !IsStatic() && !IsVirtual(); + } + public final boolean IsStatic() { return (Attributes & MethodAttributes.Static) != 0; } @@ -79,6 +113,26 @@ public abstract class MethodBase extends MemberInfo { == MethodAttributes.FamANDAssem; } + public boolean HasPtrParamOrRetType() { + // the override in MethodInfo checks the return type + ParameterInfo[] ps = GetParameters(); + for (int i = 0; i < ps.length; i++) { + Type pT = ps[i].ParameterType; + if(pT.IsPointer()) { + // Type.mkPtr creates a msil.Type for a pointer type + return true; + } + if(pT.IsByRef() && !pT.GetElementType().CanBeTakenAddressOf()) { + /* TODO Cases where GenMSIL (so far) con't emit good bytecode: + the type being taken address of IsArray(), IsGeneric(), or IsTMVarUsage. + For example, System.Enum declares + public static bool TryParse<TEnum>(string value, out TEnum result) where TEnum : struct, new(); + */ + return true; + } + } + return false; + } /** Returns the parameters of the method/constructor. */ public ParameterInfo[] GetParameters() { diff --git a/src/msil/ch/epfl/lamp/compiler/msil/MethodInfo.java b/src/msil/ch/epfl/lamp/compiler/msil/MethodInfo.java index 8c53a768fc..a415e7551f 100644 --- a/src/msil/ch/epfl/lamp/compiler/msil/MethodInfo.java +++ b/src/msil/ch/epfl/lamp/compiler/msil/MethodInfo.java @@ -15,32 +15,17 @@ import java.util.Iterator; */ public class MethodInfo extends MethodBase { - private java.util.List /* GenericParamAndConstraints */ mVars = new java.util.LinkedList(); - private GenericParamAndConstraints[] sortedMVars = null; - - public void addMVar(GenericParamAndConstraints tvarAndConstraints) { - sortedMVars = null; - mVars.add(tvarAndConstraints); - } - - public GenericParamAndConstraints[] getSortedMVars() { - if(sortedMVars == null) { - sortedMVars = new GenericParamAndConstraints[mVars.size()]; - for (int i = 0; i < sortedMVars.length; i ++){ - Iterator iter = mVars.iterator(); - while(iter.hasNext()) { - GenericParamAndConstraints tvC = (GenericParamAndConstraints)iter.next(); - if(tvC.Number == i) { - sortedMVars[i] = tvC; - } - } - } + public boolean HasPtrParamOrRetType() { + if(ReturnType.IsByRef() && !(ReturnType.GetElementType().IsValueType())) { + /* A method returning ByRef won't pass peverify, so I guess this is dead code. */ + return true; } - return sortedMVars; + if(ReturnType.IsPointer()) { + return true; + } + return super.HasPtrParamOrRetType(); } - - //########################################################################## // public members diff --git a/src/msil/ch/epfl/lamp/compiler/msil/Type.java b/src/msil/ch/epfl/lamp/compiler/msil/Type.java index 78704062b7..a4de4ae11b 100644 --- a/src/msil/ch/epfl/lamp/compiler/msil/Type.java +++ b/src/msil/ch/epfl/lamp/compiler/msil/Type.java @@ -285,6 +285,19 @@ public abstract class Type extends MemberInfo { public final boolean IsEnum() { return BaseType() == ENUM(); } + public boolean CanBeTakenAddressOf() { + /* TODO should be overridden in TMVarUsage, + but there's currently no way to bind a TMVarUsage to its GenericParamAndConstraints definition. Why? + Because of the way the msil library is organized (e.g., mkArray() returns the same !0[] representation + for all !0[] usages, irrespective of the scope of the !0 type-param) + This in turn is so because without generics there's no harm in using a type-def instance + where a type-ref should go (e.g., the ParameterType of a ParameterInfo nowadays may point to a PEType). + The net effect is that this method (CanBeTakenAddressOf) is conservative, it will answer "no" + for example for !0 where !0 refers to a type-param with the isValuetype constraint set. + The whole thing is ok at this point in time, where generics are not supported at the backend. */ + return IsValueType() && (this != ENUM()); + /* ENUM() is a singleton, i.e. System.Enum is not generic */ + } /** IsGeneric, true for a PEType or TypeBuilder (i.e., a type definition) * containing one or more type params. Not to be called on a reference @@ -325,7 +338,8 @@ public abstract class Type extends MemberInfo { public final int Number; public final boolean isTVar; - /** Non-defining reference to either a TVar or an MVar */ + /** Non-defining reference to either a TVar or an MVar. + * An instance of GenericParamAndConstraints represents a TVar or an MVar definition. */ public TMVarUsage(int Number, boolean isTVar) { super(null, 0, ((isTVar ? "!" : "!!") + Number), null, null, null, AuxAttr.None, null); this.Number = Number; @@ -378,7 +392,7 @@ public abstract class Type extends MemberInfo { if (array != null) return array; array = new PrimitiveType(elemType.Module, - TypeAttributes.Public + elemType.Attributes | TypeAttributes.Sealed | TypeAttributes.Serializable, elemType.FullName + arrSig, @@ -393,7 +407,7 @@ public abstract class Type extends MemberInfo { Type type = getType(name); if (type != null) return type; type = new PrimitiveType(elemType.Module, - TypeAttributes.NotPublic, + elemType.Attributes, name, null, EmptyTypes, null, AuxAttr.Pointer, elemType); return addType(type); @@ -405,7 +419,7 @@ public abstract class Type extends MemberInfo { Type type = getType(name); if (type != null) return type; type = new PrimitiveType(elemType.Module, - TypeAttributes.NotPublic, + elemType.Attributes, name, null, EmptyTypes, null, AuxAttr.ByRef, elemType); return addType(type); diff --git a/src/msil/ch/epfl/lamp/compiler/msil/emit/ILPrinterVisitor.scala b/src/msil/ch/epfl/lamp/compiler/msil/emit/ILPrinterVisitor.scala index cc359b813f..66f53b8132 100644 --- a/src/msil/ch/epfl/lamp/compiler/msil/emit/ILPrinterVisitor.scala +++ b/src/msil/ch/epfl/lamp/compiler/msil/emit/ILPrinterVisitor.scala @@ -450,7 +450,7 @@ abstract class ILPrinterVisitor extends Visitor { def caseOpCode(opCode: OpCode) { var opString = opCode.toString() print(opString) - pad(12 - opString.length()) + pad(14 - opString.length()) // switch opcode if (opCode == OpCode.Ldstr) { @@ -484,9 +484,8 @@ abstract class ILPrinterVisitor extends Visitor { print(" \'"); print(loc.name); print("\'") //print("'") print(((LocalBuilder)argument).name) print("'") } else if (opCode == OpCode.Readonly) { - println("readonly. ") + // nothing to do } else if (opCode == OpCode.Constrained) { - print("constrained. ") printReference(argument.asInstanceOf[Type]) } else if (opCode == OpCode.Ldelema) { printReference(argument.asInstanceOf[Type]) diff --git a/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCode.scala b/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCode.scala index bfccc42214..fbcdbf893f 100644 --- a/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCode.scala +++ b/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCode.scala @@ -805,13 +805,13 @@ object OpCode { * constrained prefix */ final val Constrained = new OpCode() -opcode(Constrained, CEE_CONSTRAINED , "constrained" , 0xFFFFFE16, POP_NONE, PUSH_NONE, INLINE_NONE , FLOW_NEXT) +opcode(Constrained, CEE_CONSTRAINED , "constrained." , 0xFFFFFE16, POP_NONE, PUSH_NONE, INLINE_NONE , FLOW_NEXT) /** * readonly prefix */ final val Readonly = new OpCode() -opcode(Readonly, CEE_READONLY , "readonly" , 0xFFFFFE1E, POP_NONE, PUSH_NONE, INLINE_NONE , FLOW_NEXT) +opcode(Readonly, CEE_READONLY , "readonly." , 0xFFFFFE1E, POP_NONE, PUSH_NONE, INLINE_NONE , FLOW_NEXT) /** * Calls the method indicated on the evaluation stack (as a pointer to an entry point) diff --git a/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCodes.scala b/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCodes.scala index db2a6fedc7..d486c31af0 100644 --- a/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCodes.scala +++ b/src/msil/ch/epfl/lamp/compiler/msil/emit/OpCodes.scala @@ -235,6 +235,16 @@ object OpCodes { final val Call = OpCode.Call /** + * constrained. prefix + */ + final val Constrained = OpCode.Constrained + + /** + * readonly. prefix + */ + final val Readonly = OpCode.Readonly + + /** * Calls the method indicated on the evaluation stack (as a pointer to an entry point) * with arguments described by a calling convention. */ |