From 3914e2bca4f28fd565a24506980b3261b4588328 Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Thu, 22 May 2014 16:40:03 +0200 Subject: Rewrite BType to a type hierarchy. This makes BTypes more type safe. It fixes at least one bug: the old BTypes are not actually hash-consed. This worked because the equals method used to have a fallback that would compare the represented string character-by-character. --- .../tools/nsc/backend/jvm/BCodeBodyBuilder.scala | 90 ++- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 70 +- .../tools/nsc/backend/jvm/BCodeIdiomatic.scala | 129 ++- .../tools/nsc/backend/jvm/BCodeSkelBuilder.scala | 24 +- .../tools/nsc/backend/jvm/BCodeSyncAndTry.scala | 16 +- .../scala/tools/nsc/backend/jvm/BCodeTypes.scala | 142 ++-- .../scala/tools/nsc/backend/jvm/BTypes.scala | 884 +++++++-------------- src/compiler/scala/tools/nsc/transform/Mixin.scala | 2 +- 8 files changed, 508 insertions(+), 849 deletions(-) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 4af1b55316..1285ca6862 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -47,16 +47,16 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def emit(opc: Int) { mnode.visitInsn(opc) } def emitZeroOf(tk: BType) { - (tk.sort: @switch) match { - case asm.Type.BOOLEAN => bc.boolconst(false) - case asm.Type.BYTE | - asm.Type.SHORT | - asm.Type.CHAR | - asm.Type.INT => bc.iconst(0) - case asm.Type.LONG => bc.lconst(0) - case asm.Type.FLOAT => bc.fconst(0) - case asm.Type.DOUBLE => bc.dconst(0) - case asm.Type.VOID => () + tk match { + case BOOL => bc.boolconst(false) + case BYTE | + SHORT | + CHAR | + INT => bc.iconst(0) + case LONG => bc.lconst(0) + case FLOAT => bc.fconst(0) + case DOUBLE => bc.dconst(0) + case UNIT => () case _ => emit(asm.Opcodes.ACONST_NULL) } } @@ -167,7 +167,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { // load argument on stack assert(args.length == 1, s"Too many arguments for array get operation: $tree"); genLoad(args.head, INT) - generatedType = k.getComponentType + generatedType = k.asArrayBType.componentType bc.aload(elementType) } else if (scalaPrimitives.isArraySet(code)) { @@ -321,7 +321,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) generatedType = if (tree.symbol == ArrayClass) ObjectReference - else brefType(thisName) // inner class (if any) for claszSymbol already tracked. + else ClassBType(thisName) // inner class (if any) for claszSymbol already tracked. } case Select(Ident(nme.EMPTY_PACKAGE_NAME), module) => @@ -419,7 +419,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (hostClass == null) internalName(field.owner) else internalName(hostClass) val fieldJName = field.javaSimpleName.toString - val fieldDescr = symInfoTK(field).getDescriptor + val fieldDescr = symInfoTK(field).descriptor val isStatic = field.isStaticMember val opc = if (isLoad) { if (isStatic) asm.Opcodes.GETSTATIC else asm.Opcodes.GETFIELD } @@ -460,7 +460,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case ClazzTag => val toPush: BType = { val kind = toTypeKind(const.typeValue) - if (kind.isValueType) classLiteral(kind) + if (kind.isPrimitive) classLiteral(kind) else kind } mnode.visitLdcInsn(toPush.toASMType) @@ -469,7 +469,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val sym = const.symbolValue val ownerName = internalName(sym.owner) val fieldName = sym.javaSimpleName.toString - val fieldDesc = toTypeKind(sym.tpe.underlying).getDescriptor + val fieldDesc = toTypeKind(sym.tpe.underlying).descriptor mnode.visitFieldInsn( asm.Opcodes.GETSTATIC, ownerName, @@ -541,26 +541,28 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { def genTypeApply(): BType = { genLoadQualifier(fun) - if (l.isValueType && r.isValueType) + // TODO @lry make pattern match + if (l.isPrimitive && r.isPrimitive) genConversion(l, r, cast) - else if (l.isValueType) { + else if (l.isPrimitive) { bc drop l if (cast) { - mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.getInternalName) + mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.internalName) bc dup ObjectReference emit(asm.Opcodes.ATHROW) } else { bc boolconst false } } - else if (r.isValueType && cast) { + else if (r.isPrimitive && cast) { abort(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $app") } - else if (r.isValueType) { + else if (r.isPrimitive) { bc isInstance classLiteral(r) } else { - genCast(r, cast) + assert(r.isRef, r) // ensure that it's not a method + genCast(r.asRefBType, cast) } if (cast) r else BOOL @@ -580,7 +582,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) genLoadArguments(args, paramTKs(app)) genCallMethod(fun.symbol, invokeStyle, pos = app.pos) - generatedType = asmMethodType(fun.symbol).getReturnType + generatedType = asmMethodType(fun.symbol).returnType // 'new' constructor call: Note: since constructors are // thought to return an instance of what they construct, @@ -591,13 +593,13 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { assert(ctor.isClassConstructor, s"'new' call to non-constructor: ${ctor.name}") generatedType = tpeTK(tpt) - assert(generatedType.isRefOrArrayType, s"Non reference type cannot be instantiated: $generatedType") + assert(generatedType.isRef, s"Non reference type cannot be instantiated: $generatedType") generatedType match { - case arr if generatedType.isArray => + case arr @ ArrayBType(componentType) => genLoadArguments(args, paramTKs(app)) - val dims = arr.getDimensions - var elemKind = arr.getElementType + val dims = arr.dimension + var elemKind = arr.elementType val argsSize = args.length if (argsSize > dims) { cunit.error(app.pos, s"too many arguments for array constructor: found ${args.length} but array has only $dims dimension(s)") @@ -607,18 +609,18 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { * elemKind = new BType(BType.ARRAY, arr.off + argsSize, arr.len - argsSize) * however the above does not enter a TypeName for each nested arrays in chrs. */ - for (i <- args.length until dims) elemKind = arrayOf(elemKind) + for (i <- args.length until dims) elemKind = ArrayBType(elemKind) } (argsSize : @switch) match { case 1 => bc newarray elemKind case _ => - val descr = ('[' * argsSize) + elemKind.getDescriptor // denotes the same as: arrayN(elemKind, argsSize).getDescriptor + val descr = ('[' * argsSize) + elemKind.descriptor // denotes the same as: arrayN(elemKind, argsSize).descriptor mnode.visitMultiANewArrayInsn(descr, argsSize) } - case rt if generatedType.hasObjectSort => + case rt: ClassBType => assert(exemplar(ctor.owner).c == rt, s"Symbol ${ctor.owner.fullName} is different from $rt") - mnode.visitTypeInsn(asm.Opcodes.NEW, rt.getInternalName) + mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName) bc dup generatedType genLoadArguments(args, paramTKs(app)) genCallMethod(ctor, icodes.opcodes.Static(onInstance = true)) @@ -631,7 +633,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) val MethodNameAndType(mname, mdesc) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.getInternalName, mname, mdesc) + bc.invokestatic(BoxesRunTime.internalName, mname, mdesc) generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) case Apply(fun @ _, List(expr)) if currentRun.runDefinitions.isUnbox(fun.symbol) => @@ -639,7 +641,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType val MethodNameAndType(mname, mdesc) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.getInternalName, mname, mdesc) + bc.invokestatic(BoxesRunTime.internalName, mname, mdesc) case app @ Apply(fun, args) => val sym = fun.symbol @@ -684,7 +686,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case _ => } if ((targetTypeKind != null) && (sym == definitions.Array_clone) && invokeStyle.isDynamic) { - val target: String = targetTypeKind.getInternalName + val target: String = targetTypeKind.asClassBType.internalName bc.invokevirtual(target, "clone", "()Ljava/lang/Object;") } else { @@ -695,7 +697,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { genNormalMethodCall() - generatedType = asmMethodType(sym).getReturnType + generatedType = asmMethodType(sym).returnType } } @@ -707,7 +709,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { val ArrayValue(tpt @ TypeTree(), elems) = av val elmKind = tpeTK(tpt) - val generatedType = arrayOf(elmKind) + val generatedType = ArrayBType(elmKind) lineNumber(av) bc iconst elems.length @@ -877,12 +879,12 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { if (claszSymbol == module.moduleClass && jMethodName != "readResolve" && !inStaticMethod) { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) } else { - val mbt = symInfoTK(module) + val mbt = symInfoTK(module).asClassBType mnode.visitFieldInsn( asm.Opcodes.GETSTATIC, - mbt.getInternalName /* + "$" */ , + mbt.internalName /* + "$" */ , strMODULE_INSTANCE_FIELD, - mbt.getDescriptor // for nostalgics: toTypeKind(module.tpe).getDescriptor + mbt.descriptor // for nostalgics: toTypeKind(module.tpe).descriptor ) } } @@ -895,7 +897,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { } } - def genCast(to: BType, cast: Boolean) { + def genCast(to: RefBType, cast: Boolean) { if (cast) { bc checkCast to } else { bc isInstance to } } @@ -960,10 +962,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { ) val receiver = if (useMethodOwner) methodOwner else hostSymbol val bmOwner = asmClassType(receiver) - val jowner = bmOwner.getInternalName + val jowner = bmOwner.internalName val jname = method.javaSimpleName.toString val bmType = asmMethodType(method) - val mdescr = bmType.getDescriptor + val mdescr = bmType.descriptor def initModule() { // we initialize the MODULE$ field immediately after the super ctor @@ -1026,7 +1028,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { private def genCJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT bc.emitIF_ICMP(op, success) - } else if (tk.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) bc.emitIF_ACMP(op, success) } else { (tk: @unchecked) match { @@ -1047,7 +1049,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { private def genCZJUMP(success: asm.Label, failure: asm.Label, op: TestOp, tk: BType) { if (tk.isIntSizedType) { // BOOL, BYTE, CHAR, SHORT, or INT bc.emitIF(op, success) - } else if (tk.isRefOrArrayType) { // REFERENCE(_) | ARRAY(_) + } else if (tk.isRef) { // REFERENCE(_) | ARRAY(_) // @unchecked because references aren't compared with GT, GE, LT, LE. (op : @unchecked) match { case icodes.EQ => bc emitIFNULL success @@ -1132,7 +1134,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case ZOR => genZandOrZor(and = false) case code => // TODO !!!!!!!!!! isReferenceType, in the sense of TypeKind? (ie non-array, non-boxed, non-nothing, may be null) - if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).hasObjectSort) { + if (scalaPrimitives.isUniversalEqualityOp(code) && tpeTK(lhs).isClass) { // `lhs` has reference type if (code == EQ) genEqEqPrimitive(lhs, rhs, success, failure) else genEqEqPrimitive(lhs, rhs, failure, success) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index d812f6b58d..51bfdf0027 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -56,7 +56,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { /* * can-multi-thread */ - def firstCommonSuffix(as: List[Tracked], bs: List[Tracked]): BType = { + def firstCommonSuffix(as: List[Tracked], bs: List[Tracked]): ClassBType = { var chainA = as var chainB = bs var fcs: Tracked = null @@ -85,10 +85,10 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * can-multi-thread */ override def getCommonSuperClass(inameA: String, inameB: String): String = { - val a = brefType(lookupTypeName(inameA.toCharArray)) - val b = brefType(lookupTypeName(inameB.toCharArray)) + val a = ClassBType(lookupTypeName(inameA.toCharArray)) + val b = ClassBType(lookupTypeName(inameB.toCharArray)) val lca = jvmWiseLUB(a, b) - val lcaName = lca.getInternalName // don't call javaName because that side-effects innerClassBuffer. + val lcaName = lca.internalName // don't call javaName because that side-effects innerClassBuffer. assert(lcaName != "scala/Any") lcaName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. @@ -106,7 +106,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * can-multi-thread */ - def jvmWiseLUB(a: BType, b: BType): BType = { + def jvmWiseLUB(a: ClassBType, b: ClassBType): ClassBType = { assert(a.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $a") assert(b.isNonSpecial, s"jvmWiseLUB() received a non-plain-class $b") @@ -403,14 +403,14 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * must-single-thread */ - final def internalName(sym: Symbol): String = asmClassType(sym).getInternalName + final def internalName(sym: Symbol): String = asmClassType(sym).internalName /* * Tracks (if needed) the inner class given by `sym`. * * must-single-thread */ - final def asmClassType(sym: Symbol): BType = { + final def asmClassType(sym: Symbol): ClassBType = { assert( hasInternalName(sym), { @@ -516,17 +516,18 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { case _: ConstantType => toTypeKind(t.underlying) case TypeRef(_, sym, args) => - if (sym == ArrayClass) arrayOf(toTypeKind(args.head)) + if (sym == ArrayClass) ArrayBType(toTypeKind(args.head)) else primitiveOrRefType2(sym) case ClassInfoType(_, _, sym) => assert(sym != ArrayClass, "ClassInfoType to ArrayClass!") primitiveOrRefType(sym) + // TODO @lry check below comments / todo's // !!! Iulian says types which make no sense after erasure should not reach here, which includes the ExistentialType, AnnotatedType, RefinedType. case ExistentialType(_, t) => toTypeKind(t) // TODO shouldn't get here but the following does: akka-actor/src/main/scala/akka/util/WildcardTree.scala case AnnotatedType(_, w) => toTypeKind(w) // TODO test/files/jvm/annotations.scala causes an AnnotatedType to reach here. - case RefinedType(parents, _) => parents map toTypeKind reduceLeft jvmWiseLUB + case RefinedType(parents, _) => parents.map(toTypeKind(_).asClassBType) reduceLeft jvmWiseLUB // For sure WildcardTypes shouldn't reach here either, but when debugging such situations this may come in handy. // case WildcardType => REFERENCE(ObjectClass) @@ -540,12 +541,12 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { /* * must-single-thread */ - def asmMethodType(msym: Symbol): BType = { + def asmMethodType(msym: Symbol): MethodBType = { assert(msym.isMethod, s"not a method-symbol: $msym") val resT: BType = - if (msym.isClassConstructor || msym.isConstructor) BType.VOID_TYPE - else toTypeKind(msym.tpe.resultType); - BType.getMethodType( resT, mkArray(msym.tpe.paramTypes map toTypeKind) ) + if (msym.isClassConstructor || msym.isConstructor) UNIT + else toTypeKind(msym.tpe.resultType) + MethodBType(msym.tpe.paramTypes map toTypeKind, resT) } /* @@ -579,14 +580,14 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { * * must-single-thread */ - final def descriptor(t: Type): String = { toTypeKind(t).getDescriptor } + final def descriptor(t: Type): String = { toTypeKind(t).descriptor } /* * Tracks (if needed) the inner class given by `sym`. * * must-single-thread */ - final def descriptor(sym: Symbol): String = { asmClassType(sym).getDescriptor } + final def descriptor(sym: Symbol): String = { asmClassType(sym).descriptor } } // end of trait BCInnerClassGen @@ -802,7 +803,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { val thrownExceptions: List[String] = getExceptions(throws) val jReturnType = toTypeKind(methodInfo.resultType) - val mdesc = BType.getMethodType(jReturnType, mkArray(paramJavaTypes)).getDescriptor + val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor val mirrorMethodName = m.javaSimpleName.toString val mirrorMethod: asm.MethodVisitor = jclass.visitMethod( flags, @@ -821,13 +822,13 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { var index = 0 for(jparamType <- paramJavaTypes) { - mirrorMethod.visitVarInsn(jparamType.getOpcode(asm.Opcodes.ILOAD), index) - assert(jparamType.sort != BType.METHOD, jparamType) - index += jparamType.getSize + mirrorMethod.visitVarInsn(jparamType.typedOpcode(asm.Opcodes.ILOAD), index) + assert(!jparamType.isInstanceOf[MethodBType], jparamType) + index += jparamType.size } - mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).getDescriptor, false) - mirrorMethod.visitInsn(jReturnType.getOpcode(asm.Opcodes.IRETURN)) + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, asmMethodType(m).descriptor, false) + mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN)) mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments mirrorMethod.visitEnd() @@ -995,7 +996,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { flags, mirrorName, null /* no java-generic-signature */, - JAVA_LANG_OBJECT.getInternalName, + JAVA_LANG_OBJECT.internalName, EMPTY_STRING_ARRAY ) @@ -1087,12 +1088,11 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { EMPTY_STRING_ARRAY // no throwable exceptions ) - val stringArrayJType: BType = arrayOf(JAVA_LANG_STRING) - val conJType: BType = - BType.getMethodType( - BType.VOID_TYPE, - Array(exemplar(definitions.ClassClass).c, stringArrayJType, stringArrayJType) - ) + val stringArrayJType: BType = ArrayBType(JAVA_LANG_STRING) + val conJType: BType = MethodBType( + exemplar(definitions.ClassClass).c :: stringArrayJType :: stringArrayJType :: Nil, + UNIT + ) def push(lst: List[String]) { var fi = 0 @@ -1101,7 +1101,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { constructor.visitLdcInsn(new java.lang.Integer(fi)) if (f == null) { constructor.visitInsn(asm.Opcodes.ACONST_NULL) } else { constructor.visitLdcInsn(f) } - constructor.visitInsn(JAVA_LANG_STRING.getOpcode(asm.Opcodes.IASTORE)) + constructor.visitInsn(JAVA_LANG_STRING.typedOpcode(asm.Opcodes.IASTORE)) fi += 1 } } @@ -1114,17 +1114,17 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { // push the string array of field information constructor.visitLdcInsn(new java.lang.Integer(fieldList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.internalName) push(fieldList) // push the string array of method information constructor.visitLdcInsn(new java.lang.Integer(methodList.length)) - constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.getInternalName) + constructor.visitTypeInsn(asm.Opcodes.ANEWARRAY, JAVA_LANG_STRING.internalName) push(methodList) // invoke the superclass constructor, which will do the // necessary java reflection and create Method objects. - constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.getDescriptor, false) + constructor.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, "scala/beans/ScalaBeanInfo", INSTANCE_CONSTRUCTOR_NAME, conJType.descriptor, false) constructor.visitInsn(asm.Opcodes.RETURN) constructor.visitMaxs(0, 0) // just to follow protocol, dummy arguments @@ -1163,7 +1163,7 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String) { // this tracks the inner class in innerClassBufferASM, if needed. val androidCreatorType = asmClassType(AndroidCreatorClass) - val tdesc_creator = androidCreatorType.getDescriptor + val tdesc_creator = androidCreatorType.descriptor cnode.visitField( PublicStaticFinal, @@ -1184,12 +1184,12 @@ abstract class BCodeHelpers extends BCodeTypes with BytecodeWriters { ) // INVOKEVIRTUAL `moduleName`.CREATOR() : android.os.Parcelable$Creator; - val bt = BType.getMethodType(androidCreatorType, Array.empty[BType]) + val bt = MethodBType(Nil, androidCreatorType) clinit.visitMethodInsn( asm.Opcodes.INVOKEVIRTUAL, moduleName, "CREATOR", - bt.getDescriptor, + bt.descriptor, false ) diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala index e83453f51e..9b7c975960 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeIdiomatic.scala @@ -9,8 +9,7 @@ package backend.jvm import scala.tools.asm import scala.annotation.switch -import scala.collection.{ immutable, mutable } -import collection.convert.Wrappers.JListWrapper +import scala.collection.mutable /* * A high-level facade to the ASM API for bytecode generation. @@ -52,12 +51,12 @@ abstract class BCodeIdiomatic extends SubComponent { val CLASS_CONSTRUCTOR_NAME = "" val INSTANCE_CONSTRUCTOR_NAME = "" - val ObjectReference = brefType("java/lang/Object") + val ObjectReference = ClassBType("java/lang/Object") val AnyRefReference = ObjectReference - val objArrayReference = arrayOf(ObjectReference) + val objArrayReference = ArrayBType(ObjectReference) val JAVA_LANG_OBJECT = ObjectReference - val JAVA_LANG_STRING = brefType("java/lang/String") + val JAVA_LANG_STRING = ClassBType("java/lang/String") var StringBuilderReference: BType = null @@ -116,17 +115,6 @@ abstract class BCodeIdiomatic extends SubComponent { a } - /* - * The type of 1-dimensional arrays of `elem` type. - * The invoker is responsible for tracking (if needed) the inner class given by the elem BType. - * - * must-single-thread - */ - final def arrayOf(elem: BType): BType = { - assert(!(elem.isUnitType), s"The element type of an array can't be: $elem") - brefType("[" + elem.getDescriptor) - } - /* Just a namespace for utilities that encapsulate MethodVisitor idioms. * In the ASM world, org.objectweb.asm.commons.InstructionAdapter plays a similar role, * but the methods here allow choosing when to transition from ICode to ASM types @@ -250,12 +238,12 @@ abstract class BCodeIdiomatic extends SubComponent { final def genStringConcat(el: BType) { val jtype = - if (el.isArray || el.hasObjectSort) JAVA_LANG_OBJECT - else el; + if (el.isArray || el.isClass) JAVA_LANG_OBJECT + else el - val bt = BType.getMethodType(StringBuilderReference, Array(jtype)) + val bt = MethodBType(List(jtype), StringBuilderReference) - invokevirtual(StringBuilderClassName, "append", bt.getDescriptor) + invokevirtual(StringBuilderClassName, "append", bt.descriptor) } /* @@ -276,7 +264,7 @@ abstract class BCodeIdiomatic extends SubComponent { final def emitT2T(from: BType, to: BType) { assert( - from.isNonUnitValueType && to.isNonUnitValueType, + from.isNonVoidPrimitiveType && to.isNonVoidPrimitiveType, s"Cannot emit primitive conversion from $from to $to" ) @@ -298,37 +286,37 @@ abstract class BCodeIdiomatic extends SubComponent { assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") // We're done with BOOL already - (from.sort: @switch) match { + from match { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" - case asm.Type.BYTE => pickOne(JCodeMethodN.fromByteT2T) - case asm.Type.SHORT => pickOne(JCodeMethodN.fromShortT2T) - case asm.Type.CHAR => pickOne(JCodeMethodN.fromCharT2T) - case asm.Type.INT => pickOne(JCodeMethodN.fromIntT2T) + case BYTE => pickOne(JCodeMethodN.fromByteT2T) + case SHORT => pickOne(JCodeMethodN.fromShortT2T) + case CHAR => pickOne(JCodeMethodN.fromCharT2T) + case INT => pickOne(JCodeMethodN.fromIntT2T) - case asm.Type.FLOAT => + case FLOAT => import asm.Opcodes.{ F2L, F2D, F2I } - (to.sort: @switch) match { - case asm.Type.LONG => emit(F2L) - case asm.Type.DOUBLE => emit(F2D) - case _ => emit(F2I); emitT2T(INT, to) + to match { + case LONG => emit(F2L) + case DOUBLE => emit(F2D) + case _ => emit(F2I); emitT2T(INT, to) } - case asm.Type.LONG => + case LONG => import asm.Opcodes.{ L2F, L2D, L2I } - (to.sort: @switch) match { - case asm.Type.FLOAT => emit(L2F) - case asm.Type.DOUBLE => emit(L2D) - case _ => emit(L2I); emitT2T(INT, to) + to match { + case FLOAT => emit(L2F) + case DOUBLE => emit(L2D) + case _ => emit(L2I); emitT2T(INT, to) } - case asm.Type.DOUBLE => + case DOUBLE => import asm.Opcodes.{ D2L, D2F, D2I } - (to.sort: @switch) match { - case asm.Type.FLOAT => emit(D2F) - case asm.Type.LONG => emit(D2L) - case _ => emit(D2I); emitT2T(INT, to) + to match { + case FLOAT => emit(D2F) + case LONG => emit(D2L) + case _ => emit(D2I); emitT2T(INT, to) } } } // end of emitT2T() @@ -380,24 +368,26 @@ abstract class BCodeIdiomatic extends SubComponent { // can-multi-thread final def newarray(elem: BType) { - if (elem.isRefOrArrayType || elem.isPhantomType ) { - /* phantom type at play in `Array(null)`, SI-1513. On the other hand, Array(()) has element type `scala.runtime.BoxedUnit` which hasObjectSort. */ - jmethod.visitTypeInsn(Opcodes.ANEWARRAY, elem.getInternalName) - } else { - val rand = { - // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" - (elem.sort: @switch) match { - case asm.Type.BOOLEAN => Opcodes.T_BOOLEAN - case asm.Type.BYTE => Opcodes.T_BYTE - case asm.Type.SHORT => Opcodes.T_SHORT - case asm.Type.CHAR => Opcodes.T_CHAR - case asm.Type.INT => Opcodes.T_INT - case asm.Type.LONG => Opcodes.T_LONG - case asm.Type.FLOAT => Opcodes.T_FLOAT - case asm.Type.DOUBLE => Opcodes.T_DOUBLE + elem match { + case c: RefBType => + /* phantom type at play in `Array(null)`, SI-1513. On the other hand, Array(()) has element type `scala.runtime.BoxedUnit` which isObject. */ + jmethod.visitTypeInsn(Opcodes.ANEWARRAY, c.classOrArrayType) + case _ => + assert(elem.isNonVoidPrimitiveType) + val rand = { + // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" + elem match { + case BOOL => Opcodes.T_BOOLEAN + case BYTE => Opcodes.T_BYTE + case SHORT => Opcodes.T_SHORT + case CHAR => Opcodes.T_CHAR + case INT => Opcodes.T_INT + case LONG => Opcodes.T_LONG + case FLOAT => Opcodes.T_FLOAT + case DOUBLE => Opcodes.T_DOUBLE + } } - } - jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) + jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) } } @@ -537,7 +527,7 @@ abstract class BCodeIdiomatic extends SubComponent { // can-multi-thread final def emitVarInsn(opc: Int, idx: Int, tk: BType) { assert((opc == Opcodes.ILOAD) || (opc == Opcodes.ISTORE), opc) - jmethod.visitVarInsn(tk.getOpcode(opc), idx) + jmethod.visitVarInsn(tk.typedOpcode(opc), idx) } // ---------------- array load and store ---------------- @@ -546,7 +536,7 @@ abstract class BCodeIdiomatic extends SubComponent { final def emitTypeBased(opcs: Array[Int], tk: BType) { assert(tk != UNIT, tk) val opc = { - if (tk.isRefOrArrayType) { opcs(0) } + if (tk.isRef) { opcs(0) } else if (tk.isIntSizedType) { (tk: @unchecked) match { case BOOL | BYTE => opcs(1) @@ -571,11 +561,11 @@ abstract class BCodeIdiomatic extends SubComponent { final def emitPrimitive(opcs: Array[Int], tk: BType) { val opc = { // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" - (tk.sort: @switch) match { - case asm.Type.LONG => opcs(1) - case asm.Type.FLOAT => opcs(2) - case asm.Type.DOUBLE => opcs(3) - case _ => opcs(0) + tk match { + case LONG => opcs(1) + case FLOAT => opcs(2) + case DOUBLE => opcs(3) + case _ => opcs(0) } } emit(opc) @@ -590,15 +580,14 @@ abstract class BCodeIdiomatic extends SubComponent { // ---------------- type checks and casts ---------------- // can-multi-thread - final def isInstance(tk: BType) { - jmethod.visitTypeInsn(Opcodes.INSTANCEOF, tk.getInternalName) + final def isInstance(tk: RefBType): Unit = { + jmethod.visitTypeInsn(Opcodes.INSTANCEOF, tk.classOrArrayType) } // can-multi-thread - final def checkCast(tk: BType) { - assert(tk.isRefOrArrayType, s"checkcast on primitive type: $tk") + final def checkCast(tk: RefBType): Unit = { // TODO ICode also requires: but that's too much, right? assert(!isBoxedType(tk), "checkcast on boxed type: " + tk) - jmethod.visitTypeInsn(Opcodes.CHECKCAST, tk.getInternalName) + jmethod.visitTypeInsn(Opcodes.CHECKCAST, tk.classOrArrayType) } } // end of class JCodeMethodN diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala index a9ba1945d2..effc68c5e3 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSkelBuilder.scala @@ -134,7 +134,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { private def initJClass(jclass: asm.ClassVisitor) { val ps = claszSymbol.info.parents - val superClass: String = if (ps.isEmpty) JAVA_LANG_OBJECT.getInternalName else internalName(ps.head.typeSymbol); + val superClass: String = if (ps.isEmpty) JAVA_LANG_OBJECT.internalName else internalName(ps.head.typeSymbol); val ifaces: Array[String] = { val arrIfacesTr: Array[Tracked] = exemplar(claszSymbol).ifaces val arrIfaces = new Array[String](arrIfacesTr.length) @@ -143,7 +143,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val ifaceTr = arrIfacesTr(i) val bt = ifaceTr.c if (ifaceTr.isInnerClass) { innerClassBufferASM += bt } - arrIfaces(i) = bt.getInternalName + arrIfaces(i) = bt.internalName i += 1 } arrIfaces @@ -167,7 +167,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val enclM = getEnclosingMethodAttribute(claszSymbol) if (enclM != null) { val EnclMethodEntry(className, methodName, methodType) = enclM - cnode.visitOuterClass(className, methodName, methodType.getDescriptor) + cnode.visitOuterClass(className, methodName, methodType.descriptor) } val ssa = getAnnotPickle(thisName, claszSymbol) @@ -262,7 +262,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val jfield = new asm.tree.FieldNode( flags, f.javaSimpleName.toString, - symInfoTK(f).getDescriptor, + symInfoTK(f).descriptor, javagensig, null // no initial value ) @@ -398,8 +398,8 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { assert(nxtIdx != -1, "not a valid start index") val loc = Local(tk, sym.javaSimpleName.toString, nxtIdx, sym.isSynthetic) slots += (sym -> loc) - assert(tk.getSize > 0, "makeLocal called for a symbol whose type is Unit.") - nxtIdx += tk.getSize + assert(tk.size > 0, "makeLocal called for a symbol whose type is Unit.") + nxtIdx += tk.size loc } @@ -532,7 +532,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME else jMethodName - val mdesc = asmMethodType(methSymbol).getDescriptor + val mdesc = asmMethodType(methSymbol).descriptor mnode = cnode.visitMethod( flags, bytecodeName, @@ -556,7 +556,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { methSymbol = dd.symbol jMethodName = methSymbol.javaSimpleName.toString - returnType = asmMethodType(dd.symbol).getReturnType + returnType = asmMethodType(dd.symbol).returnType isMethSymStaticCtor = methSymbol.isStaticConstructor resetMethodBookkeeping(dd) @@ -686,7 +686,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val callee = methSymbol.enclClass.primaryConstructor val jname = callee.javaSimpleName.toString val jowner = internalName(callee.owner) - val jtype = asmMethodType(callee).getDescriptor + val jtype = asmMethodType(callee).descriptor insnModB = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESPECIAL, jowner, jname, jtype, false) } @@ -695,7 +695,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = asmClassType(AndroidCreatorClass).getDescriptor + val andrFieldDescr = asmClassType(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", @@ -707,7 +707,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { val callee = definitions.getMember(claszSymbol.companionModule, androidFieldName) val jowner = internalName(callee.owner) val jname = callee.javaSimpleName.toString - val jtype = asmMethodType(callee).getDescriptor + val jtype = asmMethodType(callee).descriptor insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false) // PUTSTATIC `thisName`.CREATOR; insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr) @@ -724,7 +724,7 @@ abstract class BCodeSkelBuilder extends BCodeHelpers { def emitLocalVarScope(sym: Symbol, start: asm.Label, end: asm.Label, force: Boolean = false) { val Local(tk, name, idx, isSynth) = locals(sym) if (force || !isSynth) { - mnode.visitLocalVariable(name, tk.getDescriptor, null, start, end, idx) + mnode.visitLocalVariable(name, tk.descriptor, null, start, end, idx) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala index 5801b5e09c..c271e7b129 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeSyncAndTry.scala @@ -9,9 +9,7 @@ package tools.nsc package backend package jvm -import scala.collection.{ mutable, immutable } -import scala.annotation.switch - +import scala.collection.immutable import scala.tools.asm /* @@ -185,7 +183,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { val caseHandlers: List[EHClause] = for (CaseDef(pat, _, caseBody) <- catches) yield { pat match { - case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt), caseBody) + case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt).asClassBType, caseBody) case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody) case Bind(_, _) => BoundEH (pat.symbol, caseBody) } @@ -251,7 +249,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { // (2.a) emit case clause proper val startHandler = currProgramPoint() var endHandler: asm.Label = null - var excType: BType = null + var excType: ClassBType = null registerCleanup(finCleanup) ch match { case NamelessEH(typeToDrop, caseBody) => @@ -270,7 +268,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { nopIfNeeded(startHandler) endHandler = currProgramPoint() emitLocalVarScope(patSymbol, startHandler, endHandler) - excType = patTK + excType = patTK.asClassBType } unregisterCleanup(finCleanup) // (2.b) mark the try-body as protected by this case clause. @@ -358,10 +356,10 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { } } - def protect(start: asm.Label, end: asm.Label, handler: asm.Label, excType: BType) { + def protect(start: asm.Label, end: asm.Label, handler: asm.Label, excType: ClassBType) { val excInternalName: String = if (excType == null) null - else excType.getInternalName + else excType.internalName assert(start != end, "protecting a range of zero instructions leads to illegal class format. Solution: add a NOP to that range.") mnode.visitTryCatchBlock(start, end, handler, excInternalName) } @@ -388,7 +386,7 @@ abstract class BCodeSyncAndTry extends BCodeBodyBuilder { def mayCleanStack(tree: Tree): Boolean = tree exists { t => t.isInstanceOf[Try] } trait EHClause - case class NamelessEH(typeToDrop: BType, caseBody: Tree) extends EHClause + case class NamelessEH(typeToDrop: ClassBType, caseBody: Tree) extends EHClause case class BoundEH (patSymbol: Symbol, caseBody: Tree) extends EHClause } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala index 20c2c52103..62dfb4917d 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeTypes.scala @@ -25,18 +25,18 @@ abstract class BCodeTypes extends BCodeIdiomatic { val isCompilingStdLib = !(settings.sourcepath.isDefault) // special names - var StringReference : BType = null - var ThrowableReference : BType = null - var jlCloneableReference : BType = null // java/lang/Cloneable - var jlNPEReference : BType = null // java/lang/NullPointerException - var jioSerializableReference : BType = null // java/io/Serializable - var scalaSerializableReference : BType = null // scala/Serializable - var classCastExceptionReference : BType = null // java/lang/ClassCastException + var StringReference : ClassBType = null + var ThrowableReference : ClassBType = null + var jlCloneableReference : ClassBType = null // java/lang/Cloneable + var jlNPEReference : ClassBType = null // java/lang/NullPointerException + var jioSerializableReference : ClassBType = null // java/io/Serializable + var scalaSerializableReference : ClassBType = null // scala/Serializable + var classCastExceptionReference : ClassBType = null // java/lang/ClassCastException /* A map from scala primitive type-symbols to BTypes */ var primitiveTypeMap: Map[Symbol, BType] = null /* A map from scala type-symbols for Nothing and Null to (runtime version) BTypes */ - var phantomTypeMap: Map[Symbol, BType] = null + var phantomTypeMap: Map[Symbol, ClassBType] = null /* Maps the method symbol for a box method to the boxed type of the result. * For example, the method symbol for `Byte.box()`) is mapped to the BType `Ljava/lang/Integer;`. */ var boxResultType: Map[Symbol, BType] = null @@ -61,10 +61,10 @@ abstract class BCodeTypes extends BCodeIdiomatic { val AbstractFunctionReference = new Array[Tracked](definitions.MaxFunctionArity + 1) val abstractFunctionArityMap = mutable.Map.empty[BType, Int] - var PartialFunctionReference: BType = null // scala.PartialFunction - var AbstractPartialFunctionReference: BType = null // scala.runtime.AbstractPartialFunction + var PartialFunctionReference: ClassBType = null // scala.PartialFunction + var AbstractPartialFunctionReference: ClassBType = null // scala.runtime.AbstractPartialFunction - var BoxesRunTime: BType = null + var BoxesRunTime: ClassBType = null /* * must-single-thread @@ -105,7 +105,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { // Other than that, they aren't needed there (e.g., `isSubtypeOf()` special-cases boxed classes, similarly for others). val boxedClasses = List(BoxedBooleanClass, BoxedCharacterClass, BoxedByteClass, BoxedShortClass, BoxedIntClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass) for(csym <- boxedClasses) { - val key = brefType(csym.javaBinaryName.toTypeName) + val key = ClassBType(csym.javaBinaryName.toTypeName) val tr = buildExemplar(key, csym) symExemplars.put(csym, tr) exemplars.put(tr.c, tr) @@ -145,16 +145,6 @@ abstract class BCodeTypes extends BCodeIdiomatic { scalaSerializableReference = exemplar(SerializableClass).c classCastExceptionReference = exemplar(ClassCastExceptionClass).c - /* - * The bytecode emitter special-cases String concatenation, in that three methods of `JCodeMethodN` - * ( `genStartConcat()` , `genStringConcat()` , and `genEndConcat()` ) - * don't obtain the method descriptor of the callee via `asmMethodType()` (as normally done) - * but directly emit callsites on StringBuilder using literal constant for method descriptors. - * In order to make sure those method descriptors are available as BTypes, they are initialized here. - */ - BType.getMethodType("()V") // necessary for JCodeMethodN.genStartConcat - BType.getMethodType("()Ljava/lang/String;") // necessary for JCodeMethodN.genEndConcat - PartialFunctionReference = exemplar(PartialFunctionClass).c for(idx <- 0 to definitions.MaxFunctionArity) { FunctionReference(idx) = exemplar(FunctionClass(idx)) @@ -163,12 +153,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { AbstractPartialFunctionReference = exemplar(AbstractPartialFunctionClass).c } - // later a few analyses (e.g. refreshInnerClasses) will look up BTypes based on descriptors in instructions - // we make sure those BTypes can be found via lookup as opposed to creating them on the fly. - BoxesRunTime = brefType("scala/runtime/BoxesRunTime") - asmBoxTo.values foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) } - asmUnboxTo.values foreach { mnat: MethodNameAndType => BType.getMethodType(mnat.mdesc) } - + BoxesRunTime = ClassBType("scala/runtime/BoxesRunTime") } /* @@ -189,28 +174,41 @@ abstract class BCodeTypes extends BCodeIdiomatic { // allowing answering `conforms()` without resorting to typer. // ------------------------------------------------ - val exemplars = new java.util.concurrent.ConcurrentHashMap[BType, Tracked] - val symExemplars = new java.util.concurrent.ConcurrentHashMap[Symbol, Tracked] + /** + * TODO @lry should probably be a map form ClassBType to Tracked + */ + val exemplars = new java.util.concurrent.ConcurrentHashMap[BType, Tracked] - /* - * Typically, a question about a BType can be answered only by using the BType as lookup key in one or more maps. - * A `Tracked` object saves time by holding together information required to answer those questions: + /** + * Maps class symbols to their corresponding `Tracked` instance. + */ + val symExemplars = new java.util.concurrent.ConcurrentHashMap[Symbol, Tracked] + + /** + * A `Tracked` instance stores information about a BType. This allows ansering type questions + * without resolving to the compiler, in a thread-safe manner, in particular isSubtypeOf. * - * - `sc` denotes the bytecode-level superclass if any, null otherwise + * @param c the BType described by this `Tracked` + * @param flags the java flags for the type, computed by BCodeTypes#javaFlags + * @param sc the bytecode-level superclass if any, null otherwise + * @param ifaces the interfaces explicitly declared. Not included are those transitively + * supported, but the utility method `allLeafIfaces()` can be used for that. + * @param innersChain the containing classes for a non-package-level class `c`, null otherwise. * - * - `ifaces` denotes the interfaces explicitly declared. - * Not included are those transitively supported, but the utility method `allLeafIfaces()` can be used for that. + * Note: the optimizer may inline anonymous closures, thus eliding those inner classes (no + * physical class file is emitted for elided classes). Before committing `innersChain` to + * bytecode, cross-check with the list of elided classes (SI-6546). * - * - `innersChain` denotes the containing classes for a non-package-level class `c`, null otherwise. - * Note: the optimizer may inline anonymous closures, thus eliding those inner classes - * (no physical class file is emitted for elided classes). - * Before committing `innersChain` to bytecode, cross-check with the list of elided classes (SI-6546). + * All methods of this class can-multi-thread * - * All methods of this class can-multi-thread + * TODO @lry c: ClassBType. rename to ClassBTypeInfo */ - case class Tracked(c: BType, flags: Int, sc: Tracked, ifaces: Array[Tracked], innersChain: Array[InnerClassEntry]) { + case class Tracked(c: ClassBType, flags: Int, sc: Tracked, ifaces: Array[Tracked], innersChain: Array[InnerClassEntry]) { // not a case-field because we initialize it only for JVM classes we emit. + // TODO @lry make it an Option[List[BType]] + // TODO: this is currently not used. a commit in the optimizer branch uses this field to + // re-compute inner classes (ee4c185). leaving it in for now. private var _directMemberClasses: List[BType] = null def directMemberClasses: List[BType] = { @@ -221,9 +219,9 @@ abstract class BCodeTypes extends BCodeIdiomatic { def directMemberClasses_=(bs: List[BType]) { if (_directMemberClasses != null) { // TODO we enter here when both mirror class and plain class are emitted for the same ModuleClassSymbol. - assert(_directMemberClasses == bs.sortBy(_.off)) + assert(_directMemberClasses.sameElements(bs)) } - _directMemberClasses = bs.sortBy(_.off) + _directMemberClasses = bs } /* `isCompilingStdLib` saves the day when compiling: @@ -245,7 +243,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { def isInnerClass = { innersChain != null } def isLambda = { // ie isLCC || isTraditionalClosureClass - isFinal && (c.getSimpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) + isFinal && (c.simpleName.contains(tpnme.ANON_FUN_NAME.toString)) && isFunctionType(c) } /* can-multi-thread */ @@ -378,8 +376,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { if (opt != null) { return opt } - - val key = brefType(csym.javaBinaryName.toTypeName) + val key = new ClassBType(csym.javaBinaryName.toTypeName) assert(key.isNonSpecial || isCompilingStdLib, s"Not a class to track: ${csym.fullName}") // TODO accomodate the fix for SI-5031 of https://github.com/scala/scala/commit/0527b2549bcada2fda2201daa630369b377d0877 @@ -396,7 +393,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { /* * must-single-thread */ - private def buildExemplar(key: BType, csym: Symbol): Tracked = { + private def buildExemplar(key: ClassBType, csym: Symbol): Tracked = { val sc = if (csym.isImplClass) definitions.ObjectClass else csym.superClass @@ -465,29 +462,30 @@ abstract class BCodeTypes extends BCodeIdiomatic { if ((b == jlCloneableReference) || (b == jioSerializableReference) || (b == AnyRefReference)) { true } - else if (b.isArray) { conforms(a.getComponentType, b.getComponentType) } + else if (b.isArray) { conforms(a.asArrayBType.componentType, // TODO @lry change to pattern match, get rid of casts + b.asArrayBType.componentType) } else { false } } else if (a.isBoxed) { // may be null if (b.isBoxed) { a == b } else if (b == AnyRefReference) { true } - else if (!(b.hasObjectSort)) { false } + else if (!(b.isClass)) { false } else { exemplars.get(a).isSubtypeOf(b) } // e.g., java/lang/Double conforms to java/lang/Number } else if (a.isNullType) { // known to be null if (b.isNothingType) { false } - else if (b.isValueType) { false } + else if (b.isPrimitive) { false } else { true } } else if (a.isNothingType) { // known to be Nothing true } - else if (a.isUnitType) { - b.isUnitType + else if (a == UNIT) { + b == UNIT } - else if (a.hasObjectSort) { // may be null + else if (a.isClass) { // may be null if (a.isNothingType) { true } - else if (b.hasObjectSort) { exemplars.get(a).isSubtypeOf(b) } + else if (b.isClass) { exemplars.get(a).isSubtypeOf(b) } else if (b.isArray) { a.isNullType } // documentation only, because `if(a.isNullType)` (above) covers this case already. else { false } } @@ -495,8 +493,8 @@ abstract class BCodeTypes extends BCodeIdiomatic { def msg = s"(a: $a, b: $b)" - assert(a.isNonUnitValueType, s"a isn't a non-Unit value type. $msg") - assert(b.isValueType, s"b isn't a value type. $msg") + assert(a.isNonVoidPrimitiveType, s"a isn't a non-Unit value type. $msg") + assert(b.isPrimitive, s"b isn't a value type. $msg") (a eq b) || (a match { case BOOL | BYTE | SHORT | CHAR => b == INT || b == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). @@ -510,7 +508,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { * can-multi-thread */ def maxValueType(a: BType, other: BType): BType = { - assert(a.isValueType, "maxValueType() is defined only for 1st arg valuetypes (2nd arg doesn't matter).") + assert(a.isPrimitive, "maxValueType() is defined only for 1st arg valuetypes (2nd arg doesn't matter).") def uncomparable: Nothing = { abort(s"Uncomparable BTypes: $a with $other") @@ -526,30 +524,30 @@ abstract class BCodeTypes extends BCodeIdiomatic { case BOOL => uncomparable case BYTE => - if (other == CHAR) INT + if (other == CHAR) INT else if (other.isNumericType) other else uncomparable case SHORT => other match { - case BYTE => SHORT - case CHAR => INT + case BYTE => SHORT + case CHAR => INT case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable + case _ => uncomparable } case CHAR => other match { - case BYTE | SHORT => INT + case BYTE | SHORT => INT case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable + case _ => uncomparable } case INT => other match { case BYTE | SHORT | CHAR => INT case LONG | FLOAT | DOUBLE => other - case _ => uncomparable + case _ => uncomparable } case LONG => @@ -558,7 +556,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { else uncomparable case FLOAT => - if (other == DOUBLE) DOUBLE + if (other == DOUBLE) DOUBLE else if (other.isNumericType) FLOAT else uncomparable @@ -575,18 +573,18 @@ abstract class BCodeTypes extends BCodeIdiomatic { * can-multi-thread */ final def maxType(a: BType, other: BType): BType = { - if (a.isValueType) { maxValueType(a, other) } + if (a.isPrimitive) { maxValueType(a, other) } else { if (a.isNothingType) return other; if (other.isNothingType) return a; if (a == other) return a; // Approximate `lub`. The common type of two references is always AnyRef. // For 'real' least upper bound wrt to subclassing use method 'lub'. - assert(a.isArray || a.isBoxed || a.hasObjectSort, s"This is not a valuetype and it's not something else, what is it? $a") + assert(a.isArray || a.isBoxed || a.isClass, s"This is not a valuetype and it's not something else, what is it? $a") // TODO For some reason, ICode thinks `REFERENCE(...).maxType(BOXED(whatever))` is `uncomparable`. Here, that has maxType AnyRefReference. // BTW, when swapping arguments, ICode says BOXED(whatever).maxType(REFERENCE(...)) == AnyRefReference, so I guess the above was an oversight in REFERENCE.maxType() - if (other.isRefOrArrayType) { AnyRefReference } - else { abort(s"Uncomparable BTypes: $a with $other") } + if (other.isRef) { AnyRefReference } + else { abort(s"Uncomparable BTypes: $a with $other") } } } @@ -598,7 +596,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { * can-multi-thread */ def isPartialFunctionType(t: BType): Boolean = { - (t.hasObjectSort) && exemplars.get(t).isSubtypeOf(PartialFunctionReference) + (t.isClass) && exemplars.get(t).isSubtypeOf(PartialFunctionReference) } /* @@ -607,7 +605,7 @@ abstract class BCodeTypes extends BCodeIdiomatic { * can-multi-thread */ def isFunctionType(t: BType): Boolean = { - if (!t.hasObjectSort) return false + if (!t.isClass) return false var idx = 0 val et: Tracked = exemplars.get(t) while (idx <= definitions.MaxFunctionArity) { diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 0c5dcd2908..e6b2136be2 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -4,6 +4,8 @@ package backend.jvm import scala.collection.immutable import scala.annotation.switch import scala.tools.asm +import asm.Opcodes +import scala.collection.mutable.ListBuffer /** * BTypes is a backend component that defines the class BType, a number of basic instances and @@ -30,675 +32,346 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { */ def createNewName(s: String): BTypeName - /** - * Creates a BType for the class, interface or array descriptor `iname`. - * - * must-single-thread - */ - def brefType(iname: String): BType = brefType(createNewName(iname)) - - /** - * Creates a BType for the class, interface or array descriptor `iname`. - * - * can-multi-thread - */ - def brefType(iname: BTypeName): BType = BType.getObjectType(iname.start, iname.length) - - /** - * TODO @lry : make members private to BType, move those used outside into the BTypes component - */ - object BType { - // ------------- sorts ------------- - - final val VOID = asm.Type.VOID - final val BOOLEAN = asm.Type.BOOLEAN - final val CHAR = asm.Type.CHAR - final val BYTE = asm.Type.BYTE - final val SHORT = asm.Type.SHORT - final val INT = asm.Type.INT - final val FLOAT = asm.Type.FLOAT - final val LONG = asm.Type.LONG - final val DOUBLE = asm.Type.DOUBLE - final val ARRAY = asm.Type.ARRAY - final val OBJECT = asm.Type.OBJECT - final val METHOD = asm.Type.METHOD - - // ------------- primitive types ------------- - - // magic shifted numbers: see comment on class BType. This has been copied 1:1 from asm.Type. - - val VOID_TYPE = new BType(VOID, ('V' << 24) | (5 << 16) | (0 << 8) | 0, 1) - val BOOLEAN_TYPE = new BType(BOOLEAN, ('Z' << 24) | (0 << 16) | (5 << 8) | 1, 1) - val CHAR_TYPE = new BType(CHAR, ('C' << 24) | (0 << 16) | (6 << 8) | 1, 1) - val BYTE_TYPE = new BType(BYTE, ('B' << 24) | (0 << 16) | (5 << 8) | 1, 1) - val SHORT_TYPE = new BType(SHORT, ('S' << 24) | (0 << 16) | (7 << 8) | 1, 1) - val INT_TYPE = new BType(INT, ('I' << 24) | (0 << 16) | (0 << 8) | 1, 1) - val FLOAT_TYPE = new BType(FLOAT, ('F' << 24) | (2 << 16) | (2 << 8) | 1, 1) - val LONG_TYPE = new BType(LONG, ('J' << 24) | (1 << 16) | (1 << 8) | 2, 1) - val DOUBLE_TYPE = new BType(DOUBLE, ('D' << 24) | (3 << 16) | (3 << 8) | 2, 1) - - /* - * Returns the Java type corresponding to the given type descriptor. - * - * @param off the offset of this descriptor in the chrs buffer. - * @return the Java type corresponding to the given type descriptor. - * - * can-multi-thread - */ - def getType(off: Int): BType = { - var len = 0 - chrs(off) match { - case 'V' => VOID_TYPE - case 'Z' => BOOLEAN_TYPE - case 'C' => CHAR_TYPE - case 'B' => BYTE_TYPE - case 'S' => SHORT_TYPE - case 'I' => INT_TYPE - case 'F' => FLOAT_TYPE - case 'J' => LONG_TYPE - case 'D' => DOUBLE_TYPE - case '[' => - len = 1 - while (chrs(off + len) == '[') { - len += 1 - } - if (chrs(off + len) == 'L') { - len += 1 - while (chrs(off + len) != ';') { - len += 1 - } - } - new BType(ARRAY, off, len + 1) - case 'L' => - len = 1 - while (chrs(off + len) != ';') { - len += 1 - } - new BType(OBJECT, off + 1, len - 1) - // case '(': - case _ => - assert(chrs(off) == '(') - var resPos = off + 1 - while (chrs(resPos) != ')') { resPos += 1 } - val resType = getType(resPos + 1) - val len = resPos - off + 1 + resType.len - new BType( - METHOD, - off, - if (resType.hasObjectSort) { - len + 2 // "+ 2" accounts for the "L ... ;" in a descriptor for a non-array reference. - } else { - len - } - ) - } + /*sealed*/ trait BType { // Not sealed for now due to SI-8546 + final override def toString: String = this match { + case UNIT => "V" + case BOOL => "Z" + case CHAR => "C" + case BYTE => "B" + case SHORT => "S" + case INT => "I" + case FLOAT => "F" + case LONG => "J" + case DOUBLE => "D" + case c @ ClassBType(_, _) => "L" + c.internalName + ";" + case ArrayBType(component) => "[" + component + case MethodBType(args, res) => "(" + args.mkString + ")" + res } - /* Params denote an internal name. - * can-multi-thread + /** + * @return The Java descriptor of this type. Examples: + * - int: I + * - java.lang.String: Ljava/lang/String; + * - int[]: [I + * - Object m(String s, double d): (Ljava/lang/String;D)Ljava/lang/Object; */ - def getObjectType(index: Int, length: Int): BType = { - val sort = if (chrs(index) == '[') ARRAY else OBJECT - new BType(sort, index, length) - } + final def descriptor = toString - /* - * @param methodDescriptor a method descriptor. - * - * must-single-thread + /** + * @return 0 for void, 2 for long and double, 1 otherwise */ - def getMethodType(methodDescriptor: String): BType = { - val n = createNewName(methodDescriptor) - new BType(BType.METHOD, n.start, n.length) // TODO assert isValidMethodDescriptor + final def size: Int = this match { + case UNIT => 0 + case LONG | DOUBLE => 2 + case _ => 1 } - /* - * Returns the Java method type corresponding to the given argument and return types. - * - * @param returnType the return type of the method. - * @param argumentTypes the argument types of the method. - * @return the Java type corresponding to the given argument and return types. - * - * must-single-thread - */ - def getMethodType(returnType: BType, argumentTypes: Array[BType]): BType = { - val n = createNewName(getMethodDescriptor(returnType, argumentTypes)) - new BType(BType.METHOD, n.start, n.length) + final def isPrimitive: Boolean = this.isInstanceOf[PrimitiveBType] + final def isRef: Boolean = this.isInstanceOf[RefBType] + final def isArray: Boolean = this.isInstanceOf[ArrayBType] + final def isClass: Boolean = this.isInstanceOf[ClassBType] + final def isMethod: Boolean = this.isInstanceOf[MethodBType] + + final def isNonVoidPrimitiveType = isPrimitive && this != UNIT + // TODO @lry should also include !isMethod in isNonSpecial? in this case it would be equivalent to isClass, so we could get rid of it. + final def isNonSpecial = !isPrimitive && !isArray && !isPhantomType + final def isNullType = this == RT_NULL || this == CT_NULL + final def isNothingType = this == RT_NOTHING || this == CT_NOTHING + final def isPhantomType = isNullType || isNothingType + + final def isBoxed = this match { + case BOXED_UNIT | BOXED_BOOLEAN | BOXED_CHAR | + BOXED_BYTE | BOXED_SHORT | BOXED_INT | + BOXED_FLOAT | BOXED_LONG | BOXED_DOUBLE => true + case _ => false } - /* - * Returns the Java types corresponding to the argument types of method descriptor whose first argument starts at idx0. - * - * @param idx0 index into chrs of the first argument (after the '('). - * @return the Java types corresponding to the argument types of the given method descriptor. - * - * can-multi-thread + final def isIntSizedType = this == BOOL || this == CHAR || this == BYTE || + this == SHORT || this == INT + final def isIntegralType = this == INT || this == BYTE || this == LONG || + this == CHAR || this == SHORT + final def isRealType = this == FLOAT || this == DOUBLE + final def isNumericType = isIntegralType || isRealType + final def isWideType = size == 2 + + /** + * See documentation of [[typedOpcode]]. + * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 8. */ - private def getArgumentTypes(idx0: Int): Array[BType] = { - assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.") - val args = new Array[BType](getArgumentCount(idx0)) - var off = idx0 - var size = 0 - while (chrs(off) != ')') { - args(size) = getType(off) - off += args(size).len - if (args(size).sort == OBJECT) { off += 2 } // account for 'L' and ';' - // debug: assert("LVZBSCIJFD[)".contains(chrs(off))) - size += 1 - } - // debug: var check = 0; while (check < args.length) { assert(args(check) != null); check += 1 } - args + private def loadStoreOpcodeOffset: Int = this match { + case UNIT | INT => 0 + case BOOL | BYTE => 5 + case CHAR => 6 + case SHORT => 7 + case FLOAT => 2 + case LONG => 1 + case DOUBLE => 3 + case _ => 4 } - /* - * Returns the number of argument types of this method type, whose first argument starts at idx0. - * - * @param idx0 index into chrs of the first argument. - * @return the number of argument types of this method type. - * - * can-multi-thread + /** + * See documentation of [[typedOpcode]]. + * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 16. */ - private def getArgumentCount(idx0: Int): Int = { - assert(chrs(idx0 - 1) == '(', "doesn't look like a method descriptor.") - var off = idx0 - var size = 0 - var keepGoing = true - while (keepGoing) { - val car = chrs(off) - off += 1 - if (car == ')') { - keepGoing = false - } else if (car == 'L') { - while (chrs(off) != ';') { off += 1 } - off += 1 - size += 1 - } else if (car != '[') { - size += 1 - } - } - - size + private def typedOpcodeOffset: Int = this match { + case UNIT => 5 + case BOOL | CHAR | BYTE | SHORT | INT => 0 + case FLOAT => 2 + case LONG => 1 + case DOUBLE => 3 + case _ => 4 } - /* - * Returns the Java type corresponding to the return type of the given - * method descriptor. - * - * @param methodDescriptor a method descriptor. - * @return the Java type corresponding to the return type of the given method descriptor. + /** + * Some JVM opcodes have typed variants. This method returns the correct opcode according to + * the type. * - * must-single-thread + * @param opcode A JVM instruction opcode. This opcode must be one of ILOAD, ISTORE, IALOAD, + * IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, ISHR, IUSHR, IAND, IOR + * IXOR and IRETURN. + * @return The opcode adapted to this java type. For example, if this type is `float` and + * `opcode` is `IRETURN`, this method returns `FRETURN`. */ - def getReturnType(methodDescriptor: String): BType = { - val n = createNewName(methodDescriptor) - val delta = n.pos(')') // `delta` is relative to the Name's zero-based start position, not a valid index into chrs. - assert(delta < n.length, s"not a valid method descriptor: $methodDescriptor") - getType(n.start + delta + 1) + final def typedOpcode(opcode: Int): Int = { + if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) + opcode + loadStoreOpcodeOffset + else + opcode + typedOpcodeOffset } - /* - * Returns the descriptor corresponding to the given argument and return types. - * Note: no BType is created here for the resulting method descriptor, - * if that's desired the invoker is responsible for that. + /** + * The asm.Type corresponding to this BType. * - * @param returnType the return type of the method. - * @param argumentTypes the argument types of the method. - * @return the descriptor corresponding to the given argument and return types. + * Note about asm.Type.getObjectType (*): For class types, the method expects the internal + * name, i.e. without the surrounding 'L' and ';'. For array types on the other hand, the + * method expects a full descriptor, for example "[Ljava/lang/String;". * - * can-multi-thread + * See method asm.Type.getType that creates a asm.Type from a type descriptor + * - for an OBJECT type, the 'L' and ';' are not part of the range of the created Type + * - for an ARRAY type, the full descriptor is part of the range */ - def getMethodDescriptor( - returnType: BType, - argumentTypes: Array[BType]): String = - { - val buf = new StringBuffer() - buf.append('(') - var i = 0 - while (i < argumentTypes.length) { - argumentTypes(i).getDescriptor(buf) - i += 1 - } - buf.append(')') - returnType.getDescriptor(buf) - buf.toString() + def toASMType: asm.Type = this match { + case UNIT => asm.Type.VOID_TYPE + case BOOL => asm.Type.BOOLEAN_TYPE + case CHAR => asm.Type.CHAR_TYPE + case BYTE => asm.Type.BYTE_TYPE + case SHORT => asm.Type.SHORT_TYPE + case INT => asm.Type.INT_TYPE + case FLOAT => asm.Type.FLOAT_TYPE + case LONG => asm.Type.LONG_TYPE + case DOUBLE => asm.Type.DOUBLE_TYPE + case c @ ClassBType(_, _) => asm.Type.getObjectType(c.internalName) // (*) + case a @ ArrayBType(_) => asm.Type.getObjectType(a.descriptor) + case m @ MethodBType(_, _) => asm.Type.getMethodType(m.descriptor) } - } // end of object BType - - /** - * Based on ASM's Type class. Namer's chrs is used in this class for the same purposes as the - * `buf` char array in asm.Type. - * - * @param sort One of BType.VOID ... BType.METHOD - * - * @param off For array, object and method types, the offset of the type description in chrs. - * For primitive types, the `off` field contains - * - at byte 0 (& 0xff): the lenght, 0 for void, 2 for long/double, 1 otherwise - * - at byte 1 (& 0xff00): the Opcode offset for the corresponding xALOAD / xSTORE - * instruction. Example: Opcodes.IALOAD is 46, to load a boolean or a byte the - * necessary Opcode.BALOAD is 51, therefore the offset value is 5. - * - at byte 2 (& 0xff0000): the Opcode offset for other instructions (xLOAD, - * xSTORE, xADD, etc). See method BType#getOpcode. - * - at byte 3 (& 0xff000000): the descriptor ('V' for void, etc) - * For array types, the description starts with one or more '[' - * - * @param len The length of the type description - * - 1 for primitive types - * - For array, object and method types, the number of characters in chrs. - * Note: for array and method types, '[' and '(' and ')' are stored in the array - * and included in the length, for example "[Ljava/lang/Object;" or "(I)L...;" - * For object types, the leading 'L' and trailing ';' are not stored in the array - * and therefore excluded from the length, for example "java/lang/Object". - * - * All methods of this classs can-multi-thread - */ - final class BType(val sort: Int, val off: Int, val len: Int) { - /* - * can-multi-thread - */ - def toASMType: asm.Type = (sort: @switch) match { - case BType.VOID => asm.Type.VOID_TYPE - case BType.BOOLEAN => asm.Type.BOOLEAN_TYPE - case BType.CHAR => asm.Type.CHAR_TYPE - case BType.BYTE => asm.Type.BYTE_TYPE - case BType.SHORT => asm.Type.SHORT_TYPE - case BType.INT => asm.Type.INT_TYPE - case BType.FLOAT => asm.Type.FLOAT_TYPE - case BType.LONG => asm.Type.LONG_TYPE - case BType.DOUBLE => asm.Type.DOUBLE_TYPE - case BType.ARRAY | - BType.OBJECT => asm.Type.getObjectType(getInternalName) - case BType.METHOD => asm.Type.getMethodType(getDescriptor) - } + def asRefBType : RefBType = this.asInstanceOf[RefBType] + def asArrayBType: ArrayBType = this.asInstanceOf[ArrayBType] + def asClassBType: ClassBType = this.asInstanceOf[ClassBType] + } - /* - * Returns the number of dimensions of this array type. This method should - * only be used for an array type. - * - * @return the number of dimensions of this array type. - * - * can-multi-thread + object BType { + /** + * @param chars The character array containing the descriptor + * @param start The position where the descriptor starts + * @return The BType and the index of the first character after the consumed descriptor */ - def getDimensions: Int = { - assert(isArray, s"getDimensions on non-array type $this") - var i = 1 - while (chrs(off + i) == '[') { - i += 1 + private[BTypes] def fromNonMethodDescriptor(chars: Array[Char], start: Int): (BType, Int) = { + chars(start) match { + case 'L' => + var i = start + while (chars(i) != ';') { i += 1 } + // Example: chars = "IILpkg/Cls;I" + // ^ ^ + // start=2 i=10 + // `start + 1` to exclude the 'L', `i - start - 1` excludes the ';' + (new ClassBType(new String(chars, start + 1, i - start - 1)), i + 1) + case '[' => + val (res, next) = fromNonMethodDescriptor(chars, start + 1) + (ArrayBType(res), next) + case 'V' => (UNIT, start + 1) + case 'Z' => (BOOL, start + 1) + case 'C' => (CHAR, start + 1) + case 'B' => (BYTE, start + 1) + case 'S' => (SHORT, start + 1) + case 'I' => (INT, start + 1) + case 'F' => (FLOAT, start + 1) + case 'J' => (LONG, start + 1) + case 'D' => (DOUBLE, start + 1) } - i - } - - /* - * Returns the (ultimate) element type of this array type. - * This method should only be used for an array type. - * - * @return Returns the type of the elements of this array type. - * - * can-multi-thread - */ - def getElementType: BType = { - assert(isArray, s"getElementType on non-array type $this") - BType.getType(off + getDimensions) } + } - /* - * Element vs. Component type of an array: - * Quoting from the JVMS, Sec. 2.4 "Reference Types and Values" - * - * An array type consists of a component type with a single dimension (whose - * length is not given by the type). The component type of an array type may itself be - * an array type. If, starting from any array type, one considers its component type, - * and then (if that is also an array type) the component type of that type, and so on, - * eventually one must reach a component type that is not an array type; this is called - * the element type of the array type. The element type of an array type is necessarily - * either a primitive type, or a class type, or an interface type. - * - */ + sealed trait PrimitiveBType extends BType - /* The type of items this array holds. - * - * can-multi-thread - */ - def getComponentType: BType = { - assert(isArray, s"Asked for the component type of a non-array type: $this") - BType.getType(off + 1) - } + case object UNIT extends PrimitiveBType + case object BOOL extends PrimitiveBType + case object CHAR extends PrimitiveBType + case object BYTE extends PrimitiveBType + case object SHORT extends PrimitiveBType + case object INT extends PrimitiveBType + case object FLOAT extends PrimitiveBType + case object LONG extends PrimitiveBType + case object DOUBLE extends PrimitiveBType - /* - * Returns the internal name of the class corresponding to this object or - * array type. The internal name of a class is its fully qualified name (as - * returned by Class.getName(), where '.' are replaced by '/'. This method - * should only be used for an object or array type. + sealed trait RefBType extends BType { + /** + * The class or array type of this reference type. Used for ANEWARRAY, MULTIANEWARRAY, + * INSTANCEOF and CHECKCAST instructions. * - * @return the internal name of the class corresponding to this object type. + * In contrast to the descriptor, this string does not contain the surrounding 'L' and ';' for + * class types, for example "java/lang/String". + * However, for array types, the full descriptor is used, for example "[Ljava/lang/String;". * - * can-multi-thread + * This can be verified for example using javap or ASMifier. */ - def getInternalName: String = { - assert(isRefOrArrayType, s"getInternalName on non-object, non-array type $this") - new String(chrs, off, len) - } - - /* - * @return the suffix of the internal name until the last '/' (if '/' present), internal name otherwise. - * - * can-multi-thread - */ - def getSimpleName: String = { - assert(hasObjectSort, s"getSimpleName on non-object $this") - val iname = getInternalName - val idx = iname.lastIndexOf('/') - if (idx == -1) iname - else iname.substring(idx + 1) - } - - /* - * Returns the argument types of methods of this type. - * This method should only be used for method types. - * - * @return the argument types of methods of this type. - * - * can-multi-thread - */ - def getArgumentTypes: Array[BType] = { - assert(sort == BType.METHOD, s"getArgumentTypes on non-method $this") - BType.getArgumentTypes(off + 1) - } - - /* - * Returns the return type of methods of this type. - * This method should only be used for method types. - * - * @return the return type of methods of this type. - * - * can-multi-thread - */ - def getReturnType: BType = { - assert(sort == BType.METHOD, s"getReturnType on non-method $this") - var resPos = off + 1 - while (chrs(resPos) != ')') { resPos += 1 } - BType.getType(resPos + 1) - } - - // ------------------------------------------------------------------------ - // Inspector methods - // ------------------------------------------------------------------------ - - def isPrimitiveOrVoid = (sort < BType.ARRAY) // can-multi-thread - def isValueType = (sort < BType.ARRAY) // can-multi-thread - def isArray = (sort == BType.ARRAY) // can-multi-thread - def isUnitType = (sort == BType.VOID) // can-multi-thread - - /* - * Unlike for ICode's REFERENCE, isBoxedType(t) implies isReferenceType(t) - * Also, `isReferenceType(RT_NOTHING) == true` , similarly for RT_NULL. - * Use isNullType() , isNothingType() to detect Nothing and Null. - * - * can-multi-thread - */ - def hasObjectSort = (sort == BType.OBJECT) - - def isRefOrArrayType = { hasObjectSort || isArray } // can-multi-thread - def isNonUnitValueType = { isValueType && !isUnitType } // can-multi-thread - - def isNonSpecial = { !isValueType && !isArray && !isPhantomType } // can-multi-thread - def isNothingType = { (this == RT_NOTHING) || (this == CT_NOTHING) } // can-multi-thread - def isNullType = { (this == RT_NULL) || (this == CT_NULL) } // can-multi-thread - def isPhantomType = { isNothingType || isNullType } // can-multi-thread - - /* - * can-multi-thread - */ - def isBoxed = { - this match { - case BOXED_UNIT | BOXED_BOOLEAN | BOXED_CHAR | - BOXED_BYTE | BOXED_SHORT | BOXED_INT | - BOXED_FLOAT | BOXED_LONG | BOXED_DOUBLE - => true - case _ - => false - } + def classOrArrayType: String = this match { + case c: ClassBType => c.internalName + case a: ArrayBType => a.descriptor } + } - /* On the JVM, - * BOOL, BYTE, CHAR, SHORT, and INT - * are like Ints for the purpose of lub calculation. + /** + * Class or Interface type. + * + * Classes are represented using their name as a slice of the `chrs` array. This representation is + * efficient because the JVM class name is initially created using `classSymbol.javaBinaryName`. + * This already adds the necessary string to the `chrs` array, so it makes sense to reuse the same + * name table in the backend. + * + * Not a case class because that would expose the (Int, Int) constructor (didn't find a way to + * make it private, also the factory in the companion). + */ + class ClassBType private(val offset: Int, val length: Int) extends RefBType { + /** + * Construct a ClassBType for a given (intenred) class name. * - * can-multi-thread + * @param n The class name as a slice of the `chrs` array, without the surrounding 'L' and ';'. + * Note that `classSymbol.javaBinaryName` returns exactly such a name. */ - def isIntSizedType = { - (sort : @switch) match { - case BType.BOOLEAN | BType.CHAR | - BType.BYTE | BType.SHORT | BType.INT - => true - case _ - => false - } - } + def this(n: BTypeName) = this(n.start, n.length) - /* On the JVM, similar to isIntSizedType except that BOOL isn't integral while LONG is. + /** + * Construct a ClassBType for a given java class name. * - * can-multi-thread + * @param s A class name of the form "java/lang/String", without the surrounding 'L' and ';'. */ - def isIntegralType = { - (sort : @switch) match { - case BType.CHAR | - BType.BYTE | BType.SHORT | BType.INT | - BType.LONG - => true - case _ - => false - } - } + def this(s: String) = this(createNewName(s)) - /* On the JVM, FLOAT and DOUBLE. - * - * can-multi-thread + /** + * The internal name of a class is the string returned by java.lang.Class.getName, with all '.' + * replaced by '/'. For example "java/lang/String". */ - def isRealType = { (sort == BType.FLOAT ) || (sort == BType.DOUBLE) } + def internalName: String = new String(chrs, offset, length) - def isNumericType = (isIntegralType || isRealType) // can-multi-thread - - /* Is this type a category 2 type in JVM terms? (ie, is it LONG or DOUBLE?) - * - * can-multi-thread + /** + * @return The class name without the package prefix */ - def isWideType = (getSize == 2) + def simpleName: String = internalName.split("/").last - // ------------------------------------------------------------------------ - // Conversion to type descriptors - // ------------------------------------------------------------------------ - - /* - * @return the descriptor corresponding to this Java type. - * - * can-multi-thread + /** + * Custom equals / hashCode are needed because this is not a case class. */ - def getDescriptor: String = { - val buf = new StringBuffer() - getDescriptor(buf) - buf.toString() + override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { + case ClassBType(`offset`, `length`) => true + case _ => false + }) + + override def hashCode: Int = { + import scala.runtime.Statics + var acc: Int = -889275714 + acc = Statics.mix(acc, offset) + acc = Statics.mix(acc, length) + Statics.finalizeHash(acc, 2) } + } - /* - * Appends the descriptor corresponding to this Java type to the given string buffer. - * - * @param buf the string buffer to which the descriptor must be appended. - * - * can-multi-thread - */ - private def getDescriptor(buf: StringBuffer) { - if (isPrimitiveOrVoid) { - // descriptor is in byte 3 of 'off' for primitive types (buf == null) - buf.append(((off & 0xFF000000) >>> 24).asInstanceOf[Char]) - } else if (sort == BType.OBJECT) { - buf.append('L') - buf.append(chrs, off, len) - buf.append(';') - } else { // sort == ARRAY || sort == METHOD - buf.append(chrs, off, len) - } - } + object ClassBType { + def apply(n: BTypeName): ClassBType = new ClassBType(n) + def apply(s: String): ClassBType = new ClassBType(s) - // ------------------------------------------------------------------------ - // Corresponding size and opcodes - // ------------------------------------------------------------------------ + def unapply(c: ClassBType): Option[(Int, Int)] = + if (c == null) None + else Some((c.offset, c.length)) + } - /* - * Returns the size of values of this type. - * This method must not be used for method types. - * - * @return the size of values of this type, i.e., 2 for long and - * double, 0 for void and 1 otherwise. - * - * can-multi-thread - */ - def getSize: Int = { - // the size is in byte 0 of 'off' for primitive types (buf == null) - if (isPrimitiveOrVoid) (off & 0xFF) else 1 + case class ArrayBType(componentType: BType) extends RefBType { + def dimension: Int = componentType match { + case a: ArrayBType => 1 + a.dimension + case _ => 1 } - /* - * Returns a JVM instruction opcode adapted to this Java type. This method - * must not be used for method types. - * - * @param opcode a JVM instruction opcode. This opcode must be one of ILOAD, - * ISTORE, IALOAD, IASTORE, IADD, ISUB, IMUL, IDIV, IREM, INEG, ISHL, - * ISHR, IUSHR, IAND, IOR, IXOR and IRETURN. - * @return an opcode that is similar to the given opcode, but adapted to - * this Java type. For example, if this type is float and - * opcode is IRETURN, this method returns FRETURN. - * - * can-multi-thread - */ - def getOpcode(opcode: Int): Int = { - import asm.Opcodes - if (opcode == Opcodes.IALOAD || opcode == Opcodes.IASTORE) { - // the offset for IALOAD or IASTORE is in byte 1 of 'off' for - // primitive types (buf == null) - opcode + (if (isPrimitiveOrVoid) (off & 0xFF00) >> 8 else 4) - } else { - // the offset for other instructions is in byte 2 of 'off' for - // primitive types (buf == null) - opcode + (if (isPrimitiveOrVoid) (off & 0xFF0000) >> 16 else 4) - } + def elementType: BType = componentType match { + case a: ArrayBType => a.elementType + case t => t } + } - // ------------------------------------------------------------------------ - // Equals, hashCode and toString - // ------------------------------------------------------------------------ + case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType { + private def this(types: (List[BType], BType)) = this(types._1, types._2) + def this(descriptor: String) = this(MethodBType.decomposeMethodDescriptor(descriptor)) + } - /* - * Tests if the given object is equal to this type. - * - * @param o the object to be compared to this type. - * @return true if the given object is equal to this type. - * - * can-multi-thread - */ - override def equals(o: Any): Boolean = { - if (!(o.isInstanceOf[BType])) { - return false - } - val t = o.asInstanceOf[BType] - if (this eq t) { - return true - } - if (sort != t.sort) { - return false - } - if (sort >= BType.ARRAY) { - if (len != t.len) { - return false - } - // sort checked already - if (off == t.off) { - return true - } - var i = 0 - while (i < len) { - if (chrs(off + i) != chrs(t.off + i)) { - return false - } - i += 1 - } - // If we reach here, we could update the largest of (this.off, t.off) to match the other, so as to simplify future == comparisons. - // But that would require a var rather than val. + object MethodBType { + private def decomposeMethodDescriptor(descriptor: String): (List[BType], BType) = { + val chars = descriptor.toCharArray + assert(chars(0) == '(', s"Not a valid method descriptor: $descriptor") + var i = 1 + val argTypes = new ListBuffer[BType] + while (chars(i) != ')') { + val (argType, next) = BType.fromNonMethodDescriptor(chars, i) + argTypes += argType + i = next } - true + val (resType, _) = BType.fromNonMethodDescriptor(chars, i + 1) // `i + 1` to skip the ')' + (argTypes.toList, resType) } - - /* - * @return a hash code value for this type. - * - * can-multi-thread - */ - override def hashCode(): Int = { - var hc = 13 * sort - if (sort >= BType.ARRAY) { - var i = off - val end = i + len - while (i < end) { - hc = 17 * (hc + chrs(i)) - i += 1 - } - } - hc + def apply(descriptor: String) = { + val (argTypes, resType) = decomposeMethodDescriptor(descriptor) + new MethodBType(argTypes, resType) } - - /* - * @return the descriptor of this type. - * - * can-multi-thread - */ - override def toString: String = getDescriptor } - val UNIT = BType.VOID_TYPE - val BOOL = BType.BOOLEAN_TYPE - val CHAR = BType.CHAR_TYPE - val BYTE = BType.BYTE_TYPE - val SHORT = BType.SHORT_TYPE - val INT = BType.INT_TYPE - val LONG = BType.LONG_TYPE - val FLOAT = BType.FLOAT_TYPE - val DOUBLE = BType.DOUBLE_TYPE - - val BOXED_UNIT = brefType("java/lang/Void") - val BOXED_BOOLEAN = brefType("java/lang/Boolean") - val BOXED_BYTE = brefType("java/lang/Byte") - val BOXED_SHORT = brefType("java/lang/Short") - val BOXED_CHAR = brefType("java/lang/Character") - val BOXED_INT = brefType("java/lang/Integer") - val BOXED_LONG = brefType("java/lang/Long") - val BOXED_FLOAT = brefType("java/lang/Float") - val BOXED_DOUBLE = brefType("java/lang/Double") + val BOXED_UNIT = ClassBType("java/lang/Void") + val BOXED_BOOLEAN = ClassBType("java/lang/Boolean") + val BOXED_BYTE = ClassBType("java/lang/Byte") + val BOXED_SHORT = ClassBType("java/lang/Short") + val BOXED_CHAR = ClassBType("java/lang/Character") + val BOXED_INT = ClassBType("java/lang/Integer") + val BOXED_LONG = ClassBType("java/lang/Long") + val BOXED_FLOAT = ClassBType("java/lang/Float") + val BOXED_DOUBLE = ClassBType("java/lang/Double") /* - * RT_NOTHING and RT_NULL exist at run-time only. - * They are the bytecode-level manifestation (in method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. - * Therefore, when RT_NOTHING or RT_NULL are to be emitted, - * a mapping is needed: the internal names of NothingClass and NullClass can't be emitted as-is. + * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in + * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * + * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal + * names of NothingClass and NullClass can't be emitted as-is. */ - val RT_NOTHING = brefType("scala/runtime/Nothing$") - val RT_NULL = brefType("scala/runtime/Null$") - val CT_NOTHING = brefType("scala/Nothing") - val CT_NULL = brefType("scala/Null") - - val srBooleanRef = brefType("scala/runtime/BooleanRef") - val srByteRef = brefType("scala/runtime/ByteRef") - val srCharRef = brefType("scala/runtime/CharRef") - val srIntRef = brefType("scala/runtime/IntRef") - val srLongRef = brefType("scala/runtime/LongRef") - val srFloatRef = brefType("scala/runtime/FloatRef") - val srDoubleRef = brefType("scala/runtime/DoubleRef") - - /* Map from type kinds to the Java reference types. - * Useful when pushing class literals onto the operand stack (ldc instruction taking a class literal). - * @see Predef.classOf - * @see genConstant() + val RT_NOTHING = ClassBType("scala/runtime/Nothing$") + val RT_NULL = ClassBType("scala/runtime/Null$") + val CT_NOTHING = ClassBType("scala/Nothing") + val CT_NULL = ClassBType("scala/Null") + + val srBooleanRef = ClassBType("scala/runtime/BooleanRef") + val srByteRef = ClassBType("scala/runtime/ByteRef") + val srCharRef = ClassBType("scala/runtime/CharRef") + val srIntRef = ClassBType("scala/runtime/IntRef") + val srLongRef = ClassBType("scala/runtime/LongRef") + val srFloatRef = ClassBType("scala/runtime/FloatRef") + val srDoubleRef = ClassBType("scala/runtime/DoubleRef") + + /** + * Map from type kinds to the Java reference types. + * Useful when pushing class literals onto the operand stack (ldc instruction taking a class + * literal). + * @see Predef.classOf + * @see genConstant() + * + * TODO @lry rename to "boxedClassOfPrimitive" or so, check usages */ - val classLiteral = immutable.Map[BType, BType]( + val classLiteral = immutable.Map[BType, ClassBType]( UNIT -> BOXED_UNIT, BOOL -> BOXED_BOOLEAN, BYTE -> BOXED_BYTE, @@ -710,7 +383,7 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { DOUBLE -> BOXED_DOUBLE ) - case class MethodNameAndType(mname: String, mdesc: String) + case class MethodNameAndType(name: String, descriptor: String) val asmBoxTo: immutable.Map[BType, MethodNameAndType] = { Map( @@ -738,4 +411,3 @@ abstract class BTypes[G <: Global](val __global_dont_use: G) { ) } } - diff --git a/src/compiler/scala/tools/nsc/transform/Mixin.scala b/src/compiler/scala/tools/nsc/transform/Mixin.scala index 3ef85728da..313d0a6e61 100644 --- a/src/compiler/scala/tools/nsc/transform/Mixin.scala +++ b/src/compiler/scala/tools/nsc/transform/Mixin.scala @@ -391,7 +391,7 @@ abstract class Mixin extends InfoTransform with ast.TreeDSL { else { sourceModule setPos sym.pos if (sourceModule.flags != MODULE) { - log("!!! Directly setting sourceModule flags for $sourceModule from %s to MODULE".format(sourceModule.flagString)) + log(s"!!! Directly setting sourceModule flags for $sourceModule from ${sourceModule.flagString} to MODULE") sourceModule.flags = MODULE } } -- cgit v1.2.3