diff --git a/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala b/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala index 8a71a09aa7ab..4c6d130b2ee4 100644 --- a/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/AsmUtils.scala @@ -2,7 +2,7 @@ package dotty.tools package backend package jvm -import scala.tools.asm.tree.{AbstractInsnNode} +import scala.tools.asm.tree.AbstractInsnNode import java.io.PrintWriter import scala.tools.asm.util.{TraceClassVisitor, TraceMethodVisitor, Textifier} import scala.tools.asm.ClassReader diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala b/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala index 5b1a8e1683f0..9c96dd5c2919 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala @@ -2,17 +2,18 @@ package dotty.tools package backend package jvm +import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.report +import DottyBackendInterface.symExtensions + /** - * This trait contains code shared between GenBCode and GenASM that depends on types defined in + * Code shared between GenBCode and GenASM that depends on types defined in * the compiler cake (Global). */ -final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { - import interface.given - import DottyBackendInterface.symExtensions +object BCodeAsmCommon { /** * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a @@ -20,7 +21,7 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { * It is also used to decide whether the "owner" field in the InnerClass attribute should be * null. */ - def isAnonymousOrLocalClass(classSym: Symbol): Boolean = { + def isAnonymousOrLocalClass(classSym: Symbol)(using ctx: Context): Boolean = { assert(classSym.isClass, s"not a class: $classSym") // Here used to be an `assert(!classSym.isDelambdafyFunction)`: delambdafy lambda classes are // always top-level. However, SI-8900 shows an example where the weak name-based implementation @@ -54,7 +55,7 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { * The EnclosingMethod attribute needs to be added to non-member classes (see doc in BTypes). * This is a source-level property, so we need to use the originalOwner chain to reconstruct it. */ - private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol): Option[Symbol] = { + private def enclosingMethodForEnclosingMethodAttribute(classSym: Symbol)(using ctx: Context): Option[Symbol] = { assert(classSym.isClass, classSym) def enclosingMethod(sym: Symbol): Option[Symbol] = { if (sym.isClass || sym == NoSymbol) None @@ -68,7 +69,7 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { * The enclosing class for emitting the EnclosingMethod attribute. Since this is a source-level * property, this method looks at the originalOwner chain. See doc in BTypes. */ - private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol): Symbol = { + private def enclosingClassForEnclosingMethodAttribute(classSym: Symbol)(using ctx: Context): Symbol = { assert(classSym.isClass, classSym) def enclosingClass(sym: Symbol): Symbol = { if (sym.isClass) sym @@ -84,10 +85,10 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { * an anonymous or local class). See doc in BTypes. * * The class is parametrized by two functions to obtain a bytecode class descriptor for a class - * symbol, and to obtain a method signature descriptor fro a method symbol. These function depend + * symbol, and to obtain a method signature descriptor from a method symbol. These function depend * on the implementation of GenASM / GenBCode, so they need to be passed in. */ - def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String): Option[EnclosingMethodEntry] = { + def enclosingMethodAttribute(classSym: Symbol, classDesc: Symbol => String, methodDesc: Symbol => String)(using ctx: Context): Option[EnclosingMethodEntry] = { if (isAnonymousOrLocalClass(classSym)) { val methodOpt = enclosingMethodForEnclosingMethodAttribute(classSym) report.debuglog(s"enclosing method for $classSym is $methodOpt (in ${methodOpt.map(_.enclosingClass)})") @@ -99,10 +100,8 @@ final class BCodeAsmCommon[I <: DottyBackendInterface](val interface: I) { None } } -} -object BCodeAsmCommon{ - def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { + private def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { val ca = new Array[Char](bytes.length) var idx = 0 while(idx < bytes.length) { @@ -115,16 +114,16 @@ object BCodeAsmCommon{ ca } - final def arrEncode(bSeven: Array[Byte]): Array[String] = { + def arrEncode(bSeven: Array[Byte]): Array[String] = { var strs: List[String] = Nil // chop into slices of at most 65535 bytes, counting 0x00 as taking two bytes (as per JVMS 4.4.7 The CONSTANT_Utf8_info Structure) var prevOffset = 0 var offset = 0 var encLength = 0 while(offset < bSeven.length) { - val deltaEncLength = (if(bSeven(offset) == 0) 2 else 1) + val deltaEncLength = if(bSeven(offset) == 0) 2 else 1 val newEncLength = encLength.toLong + deltaEncLength - if(newEncLength >= 65535) { + if (newEncLength >= 65535) { val ba = bSeven.slice(prevOffset, offset) strs ::= new java.lang.String(ubytesToCharArray(ba)) encLength = 0 @@ -143,7 +142,6 @@ object BCodeAsmCommon{ strs.reverse.toArray } - def strEncode(bSeven: Array[Byte]): String = { val ca = ubytesToCharArray(bSeven) new java.lang.String(ca) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index b822e0fdf278..7afb5b9f38df 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -23,8 +23,13 @@ import dotty.tools.dotc.util.Spans.* import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Phases.* import dotty.tools.dotc.core.Decorators.em +import dotty.tools.dotc.core.Names.* import dotty.tools.dotc.report import dotty.tools.dotc.ast.Trees.SyntheticUnit +import dotty.tools.dotc.ast.Positioned + +import tpd.* +import DottyBackendInterface.symExtensions /* * @@ -32,16 +37,7 @@ import dotty.tools.dotc.ast.Trees.SyntheticUnit * @version 1.0 * */ -trait BCodeBodyBuilder extends BCodeSkelBuilder { - // import global.* - // import definitions.* - import tpd.* - import int.{_, given} - import DottyBackendInterface.symExtensions - import bTypes.* - import coreBTypes.* - - protected val primitives: DottyPrimitives +trait BCodeBodyBuilder(val primitives: DottyPrimitives)(using ctx: Context) extends BCodeSkelBuilder { /* * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. @@ -50,6 +46,31 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { import Primitives.TestOp + private object DesugaredSelect { + private val desugared = new java.util.IdentityHashMap[Type, tpd.Select] + + def cached(i: Ident): Option[tpd.Select] = { + var found = desugared.get(i.tpe) + if (found == null) { + tpd.desugarIdent(i) match { + case sel: tpd.Select => + desugared.put(i.tpe, sel) + found = sel + case _ => + } + } + Option(found) + } + + def unapply(s: tpd.Tree): Option[(Tree, Name)] = { + s match { + case t: tpd.Select => Some((t.qualifier, t.name)) + case t: Ident => cached(t).map(c => (c.qualifier, c.name)) + case _ => None + } + } + } + /* ---------------- helper utils for generating methods and code ---------------- */ def emit(opc: Int): Unit = { mnode.visitInsn(opc) } @@ -142,7 +163,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { // binary operation case rarg :: Nil => val isShift = isShiftOp(code) - resKind = tpeTK(larg).maxType(if (isShift) INT else tpeTK(rarg)) + resKind = tpeTK(larg).maxType(if (isShift) INT else tpeTK(rarg), ts) if (isShift || isBitwiseOp(code)) { assert(resKind.isIntegralType || (resKind == BOOL), @@ -182,7 +203,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { import ScalaPrimitivesOps.* val k = tpeTK(arrayObj) genLoad(arrayObj, k) - val elementType = typeOfArrayOp.getOrElse[bTypes.BType](code, abort(s"Unknown operation on arrays: $tree code: $code")) + val elementType = ts.typeOfArrayOp.getOrElse[BType](code, abort(s"Unknown operation on arrays: $tree code: $code")) var generatedType = expectedType @@ -369,7 +390,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { // load receiver of non-static implementation of lambda // darkdimius: I haven't found in spec `this` reference should go - // but I was able to derrive it by reading + // but I was able to derive it by reading // AbstractValidatingLambdaMetafactory.validateMetafactoryArgs val DesugaredSelect(prefix, _) = fun: @unchecked @@ -398,7 +419,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { // the generatedType to `Array` below, the call to adapt at the end would fail. The situation is // similar for primitives (`I` vs `Int`). if (tree.symbol != defn.ArrayClass && !tree.symbol.isPrimitiveValueClass) { - generatedType = classBTypeFromSymbol(claszSymbol) + generatedType = ts.classBTypeFromSymbol(claszSymbol) } } @@ -431,22 +452,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val tk = symInfoTK(sym) generatedType = tk - val desugared = cachedDesugarIdent(t) - desugared match { - case None => - if (!sym.is(Package)) { - if (sym.is(Module)) genLoadModule(sym) - else locals.load(sym) - } - case Some(t) => - genLoad(t, generatedType) + if (!sym.is(Package)) { + if (sym.is(Module)) genLoadModule(sym) + else locals.load(sym) } case Literal(value) => if (value.tag != UnitTag) (value.tag, expectedType) match { case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE - case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = srNullRef + case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = ts.srNullRef case _ => genConstant(value); generatedType = tpeTK(tree) } @@ -469,7 +484,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { generatedType = UNIT genStat(tree) - case av @ ArrayValue(_, _) => + case av: tpd.JavaSeqLiteral => generatedType = genArrayValue(av) case mtch @ Match(_, _) => @@ -513,9 +528,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { bc.emitRETURN(returnType) case LoadDestination.Throw => val thrownType = expectedType - // `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable. - // Similarly for scala.Nothing (again, as defined in src/libray-aux). - assert(thrownType.isNullType || thrownType.isNothingType || thrownType.asClassBType.isSubtypeOf(jlThrowableRef)) + // `throw null` is valid although scala.Null (as defined in src/library-aux) isn't a subtype of Throwable. + // Similarly for scala.Nothing (again, as defined in src/library-aux). + assert(thrownType.isNullType || thrownType.isNothingType || thrownType.asClassBType.isSubtypeOf(ts.jlThrowableRef).getOrElse(false)) emit(asm.Opcodes.ATHROW) end genAdaptAndSendToDest @@ -580,12 +595,12 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { case ClazzTag => val tp = toTypeKind(const.typeValue) if tp.isPrimitive then - val boxedClass = boxedClassOfPrimitive(tp.asPrimitiveBType) + val boxedClass = ts.boxedClassOfPrimitive(tp.asPrimitiveBType) mnode.visitFieldInsn( asm.Opcodes.GETSTATIC, boxedClass.internalName, "TYPE", // field name - jlClassRef.descriptor + ts.jlClassRef.descriptor ) else mnode.visitLdcInsn(tp.toASMType) @@ -699,8 +714,8 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { else if (l.isPrimitive) { bc.drop(l) if (cast) { - mnode.visitTypeInsn(asm.Opcodes.NEW, jlClassCastExceptionRef.internalName) - bc.dup(ObjectRef) + mnode.visitTypeInsn(asm.Opcodes.NEW, ts.jlClassCastExceptionRef.internalName) + bc.dup(ts.ObjectRef) emit(asm.Opcodes.ATHROW) } else { bc.boolconst(false) @@ -710,7 +725,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { abort(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $t") } else if (r.isPrimitive) { - bc.isInstance(boxedClassOfPrimitive(r.asPrimitiveBType)) + bc.isInstance(ts.boxedClassOfPrimitive(r.asPrimitiveBType)) } else { assert(r.isRef, r) // ensure that it's not a method @@ -750,10 +765,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { lineNumber(app) app match { case Apply(_, args) if app.symbol eq defn.newArrayMethod => - val List(elemClaz, Literal(c: Constant), ArrayValue(_, dims)) = args: @unchecked + val List(elemClaz, Literal(c: Constant), av: tpd.JavaSeqLiteral) = args: @unchecked generatedType = toTypeKind(c.typeValue) - mkArrayConstructorCall(generatedType.asArrayBType, app, dims) + mkArrayConstructorCall(generatedType.asArrayBType, app, av.elems) case Apply(t :TypeApply, _) => generatedType = if (t.symbol ne defn.Object_synchronized) genTypeApply(t) @@ -790,7 +805,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { mkArrayConstructorCall(arr, app, args) case rt: ClassBType => - assert(classBTypeFromSymbol(ctor.owner) == rt, s"Symbol ${ctor.owner.showFullName} is different from $rt") + assert(ts.classBTypeFromSymbol(ctor.owner) == rt, s"Symbol ${ctor.owner.showFullName} is different from $rt") mnode.visitTypeInsn(asm.Opcodes.NEW, rt.internalName) bc.dup(generatedType) stack.push(rt) @@ -806,16 +821,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { case Apply(fun, List(expr)) if Erasure.Boxing.isBox(fun.symbol) && fun.symbol.denot.owner != defn.UnitModuleClass => val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) - val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) - bc.invokestatic(srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false) - generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) + val MethodNameAndType(mname, methodType) = ts.asmBoxTo(nativeKind) + bc.invokestatic(ts.srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false) + generatedType = ts.boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) case Apply(fun, List(expr)) if Erasure.Boxing.isUnbox(fun.symbol) && fun.symbol.denot.owner != defn.UnitModuleClass => genLoad(expr) - val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) + val boxType = ts.unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType - val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) - bc.invokestatic(srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false) + val MethodNameAndType(mname, methodType) = ts.asmUnboxTo(boxType) + bc.invokestatic(ts.srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false) case app @ Apply(fun, args) => val sym = fun.symbol @@ -878,10 +893,15 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } // end of genApply() private def genArrayValue(av: tpd.JavaSeqLiteral): BType = { - val ArrayValue(tpt, elems) = av: @unchecked + val tpt = av.tpe match { + case JavaArrayType(elem) => elem + case _ => + report.error(em"JavaSeqArray with type ${av.tpe} reached backend: $av", ctx.source.atSpan(av.span)) + UnspecifiedErrorType + } lineNumber(av) - genArray(elems, tpt) + genArray(av.elems, tpt) } private def genArray(elems: List[Tree], elemType: Type): BType = { @@ -1109,12 +1129,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { end emitLocalVarScopes def adapt(from: BType, to: BType): Unit = { - if (!from.conformsTo(to)) { - to match { - case UNIT => bc.drop(from) - case _ => bc.emitT2T(from, to) - } - } else if (from.isNothingType) { + if (from.isNothingType) { /* There are two possibilities for from.isNothingType: emitting a "throw e" expressions and * loading a (phantom) value of type Nothing. * @@ -1175,12 +1190,16 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { */ if (lastInsn.getOpcode != asm.Opcodes.ACONST_NULL) { bc.drop(from) - emit(asm.Opcodes.ACONST_NULL) + if (to != UNIT) + emit(asm.Opcodes.ACONST_NULL) + } else if (to == UNIT) { + bc.drop(from) + } + } else if (!from.conformsTo(to).get) { + to match { + case UNIT => bc.drop(from) + case _ => bc.emitT2T(from, to) } - } - else (from, to) match { - case (BYTE, LONG) | (SHORT, LONG) | (CHAR, LONG) | (INT, LONG) => bc.emitT2T(INT, LONG) - case _ => () } } @@ -1190,7 +1209,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { tree match { case DesugaredSelect(qualifier, _) => genLoad(qualifier) case t: Ident => // dotty specific - cachedDesugarIdent(t) match { + DesugaredSelect.cached(t) match { case Some(sel) => genLoadQualifier(sel) case None => assert(t.symbol.owner == this.claszSymbol) @@ -1284,7 +1303,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { liftStringConcat(tree) match { // Optimization for expressions of the form "" + x case List(Literal(Constant("")), arg) => - genLoad(arg, ObjectRef) + genLoad(arg, ts.ObjectRef) genCallMethod(defn.String_valueOf_Object, InvokeStyle.Static) case concatenations => @@ -1325,8 +1344,8 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { if (totalArgSlots + elemSlots >= MaxIndySlots) { stack.restoreSize(savedStackSize) for _ <- 0 until countConcats do - stack.push(StringRef) - bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result()) + stack.push(ts.StringRef) + bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result(), ts) countConcats += 1 totalArgSlots = 0 recipe.setLength(0) @@ -1354,18 +1373,19 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } } stack.restoreSize(savedStackSize) - bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result()) + bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result(), ts) // If we spilled, generate one final concat if (countConcats > 1) { bc.genIndyStringConcat( TagArg.toString * countConcats, - Seq.fill(countConcats)(StringRef.toASMType), - Seq.empty + Seq.fill(countConcats)(ts.StringRef.toASMType), + Seq.empty, + ts ) } } - StringRef + ts.StringRef } /** @@ -1443,7 +1463,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { /* Generate the scala ## method. */ def genScalaHash(tree: Tree): BType = { - genLoad(tree, ObjectRef) + genLoad(tree, ts.ObjectRef) genCallMethod(NoSymbol, InvokeStyle.Static) // used to dispatch ## on primitives to ScalaRuntime.hash. Should be implemented by a miniphase } @@ -1542,10 +1562,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val nonNullSide = if (ScalaPrimitivesOps.isReferenceEqualityOp(code)) ifOneIsNull(l, r) else null if (nonNullSide != null) { // special-case reference (in)equality test for null (null eq x, x eq null) - genLoad(nonNullSide, ObjectRef) - genCZJUMP(success, failure, op, ObjectRef, targetIfNoJump) + genLoad(nonNullSide, ts.ObjectRef) + genCZJUMP(success, failure, op, ts.ObjectRef, targetIfNoJump) } else { - val tk = tpeTK(l).maxType(tpeTK(r)) + val tk = tpeTK(l).maxType(tpeTK(r), ts) genLoad(l, tk) stack.push(tk) genLoad(r, tk) @@ -1663,9 +1683,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } else defn.BoxesRunTimeModule_externalEquals } - genLoad(l, ObjectRef) - stack.push(ObjectRef) - genLoad(r, ObjectRef) + genLoad(l, ts.ObjectRef) + stack.push(ts.ObjectRef) + genLoad(r, ts.ObjectRef) stack.pop() genCallMethod(equalsMethod, InvokeStyle.Static) genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump) @@ -1673,38 +1693,38 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { else { if (isNull(l)) { // null == expr -> expr eq null - genLoad(r, ObjectRef) - genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump) + genLoad(r, ts.ObjectRef) + genCZJUMP(success, failure, Primitives.EQ, ts.ObjectRef, targetIfNoJump) } else if (isNull(r)) { // expr == null -> expr eq null - genLoad(l, ObjectRef) - genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump) + genLoad(l, ts.ObjectRef) + genCZJUMP(success, failure, Primitives.EQ, ts.ObjectRef, targetIfNoJump) } else if (isNonNullExpr(l)) { // SI-7852 Avoid null check if L is statically non-null. - genLoad(l, ObjectRef) - stack.push(ObjectRef) - genLoad(r, ObjectRef) + genLoad(l, ts.ObjectRef) + stack.push(ts.ObjectRef) + genLoad(r, ts.ObjectRef) stack.pop() genCallMethod(defn.Any_equals, InvokeStyle.Virtual) genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump) } else { // l == r -> if (l eq null) r eq null else l.equals(r) - val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.mangledString, defn.ObjectType, r.span) + val eqEqTempLocal = locals.makeLocal(ts.ObjectRef, nme.EQEQ_LOCAL_VAR.mangledString, defn.ObjectType, r.span) val lNull = new asm.Label val lNonNull = new asm.Label - genLoad(l, ObjectRef) - stack.push(ObjectRef) - genLoad(r, ObjectRef) + genLoad(l, ts.ObjectRef) + stack.push(ts.ObjectRef) + genLoad(r, ts.ObjectRef) stack.pop() locals.store(eqEqTempLocal) - bc.dup(ObjectRef) - genCZJUMP(lNull, lNonNull, Primitives.EQ, ObjectRef, targetIfNoJump = lNull) + bc.dup(ts.ObjectRef) + genCZJUMP(lNull, lNonNull, Primitives.EQ, ts.ObjectRef, targetIfNoJump = lNull) markProgramPoint(lNull) - bc.drop(ObjectRef) + bc.drop(ts.ObjectRef) locals.load(eqEqTempLocal) - genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump = lNonNull) + genCZJUMP(success, failure, Primitives.EQ, ts.ObjectRef, targetIfNoJump = lNonNull) markProgramPoint(lNonNull) locals.load(eqEqTempLocal) @@ -1722,7 +1742,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { import java.lang.invoke.LambdaMetafactory.{FLAG_BRIDGES, FLAG_SERIALIZABLE} report.debuglog(s"Using invokedynamic rather than `new ${ctor.owner}`") - val generatedType = classBTypeFromSymbol(functionalInterface) + val generatedType = ts.classBTypeFromSymbol(functionalInterface) // Lambdas should be serializable if they implement a SAM that extends Serializable or if they // implement a scala.Function* class. val isSerializable = functionalInterface.isSerializable || defn.isFunctionClass(functionalInterface) @@ -1735,7 +1755,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val targetHandle = new asm.Handle(invokeStyle, - classBTypeFromSymbol(lambdaTarget.owner).internalName, + ts.classBTypeFromSymbol(lambdaTarget.owner).internalName, lambdaTarget.javaSimpleName, asmMethodType(lambdaTarget).descriptor, /* itf = */ isInterface) @@ -1766,7 +1786,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { def boxInstantiated(instantiatedType: BType, samType: BType): BType = if(!samType.isPrimitive && instantiatedType.isPrimitive) - boxedClassOfPrimitive(instantiatedType.asPrimitiveBType) + ts.boxedClassOfPrimitive(instantiatedType.asPrimitiveBType) else instantiatedType // TODO specialization val instantiatedMethodBType = new MethodBType( @@ -1807,9 +1827,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val metafactory = if (flags != 0) - jliLambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed + ts.jliLambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed else - jliLambdaMetaFactoryMetafactoryHandle + ts.jliLambdaMetaFactoryMetafactoryHandle bc.jmethod.visitInvokeDynamicInsn(methodName, desc, metafactory, bsmArgs*) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index 614d4dc79948..3ce5b3e30877 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -25,7 +25,7 @@ import dotty.tools.dotc.core.NameKinds.ExpandedName import dotty.tools.dotc.core.Signature import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.NameKinds -import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Symbols.{requiredClass => _, *} import dotty.tools.dotc.core.Types import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.TypeErasure @@ -35,40 +35,30 @@ import dotty.tools.dotc.transform.Mixin import dotty.tools.io.AbstractFile import dotty.tools.dotc.report -import dotty.tools.backend.jvm.DottyBackendInterface.symExtensions +import tpd.* +import DottyBackendInterface.{*, given} /* - * Traits encapsulating functionality to convert Scala AST Trees into ASM ClassNodes. + * Encapsulates functionality to convert Scala AST Trees into ASM ClassNodes. * * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded * @version 1.0 * */ -trait BCodeHelpers extends BCodeIdiomatic { - // for some reason singleton types aren't allowed in constructor calls. will need several casts in code to enforce - //import global.* - import bTypes.* - import tpd.* - import coreBTypes.* - import int.{_, given} - import DottyBackendInterface.* +trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends BCodeIdiomatic { - // We need to access GenBCode phase to get access to post-processor components. - // At this point it should always be initialized already. - protected lazy val backendUtils = genBCodePhase.asInstanceOf[GenBCode].postProcessor.backendUtils + val ts: CoreBTypesFromSymbols - def ScalaATTRName: String = "Scala" - def ScalaSignatureATTRName: String = "ScalaSig" + private def ScalaATTRName: String = "Scala" + private def ScalaSignatureATTRName: String = "ScalaSig" - @threadUnsafe lazy val AnnotationRetentionAttr: ClassSymbol = requiredClass("java.lang.annotation.Retention") - @threadUnsafe lazy val AnnotationRetentionSourceAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("SOURCE") - @threadUnsafe lazy val AnnotationRetentionClassAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("CLASS") - @threadUnsafe lazy val AnnotationRetentionRuntimeAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("RUNTIME") - - val bCodeAsmCommon: BCodeAsmCommon[int.type] = new BCodeAsmCommon(int) + @threadUnsafe private lazy val AnnotationRetentionAttr: ClassSymbol = requiredClass("java.lang.annotation.Retention") + @threadUnsafe private lazy val AnnotationRetentionSourceAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("SOURCE") + @threadUnsafe private lazy val AnnotationRetentionClassAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("CLASS") + @threadUnsafe private lazy val AnnotationRetentionRuntimeAttr: TermSymbol = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("RUNTIME") final def traitSuperAccessorName(sym: Symbol): String = { - val nameString = sym.javaSimpleName.toString + val nameString = sym.javaSimpleName if (sym.name == nme.TRAIT_CONSTRUCTOR) nameString else nameString + "$" } @@ -104,7 +94,7 @@ trait BCodeHelpers extends BCodeIdiomatic { import dotty.tools.dotc.core.unpickleScala2.{ PickleFormat, PickleBuffer } - val versionPickle = { + private val versionPickle = { val vp = new PickleBuffer(new Array[Byte](16), -1, 0) assert(vp.writeIndex == 0, vp) vp.writeNat(PickleFormat.MajorVersion) @@ -130,7 +120,7 @@ trait BCodeHelpers extends BCodeIdiomatic { trait BCInnerClassGen { - def debugLevel = 3 // 0 -> no debug info; 1-> filename; 2-> lines; 3-> varnames + private def debugLevel = 3 // 0 -> no debug info; 1-> filename; 2-> lines; 3-> varnames final val emitSource = debugLevel >= 1 final val emitLines = debugLevel >= 2 @@ -155,7 +145,7 @@ trait BCodeHelpers extends BCodeIdiomatic { private def assertClassNotArrayNotPrimitive(sym: Symbol): Unit = { assertClassNotArray(sym) - assert(!primitiveTypeMap.contains(sym) || isCompilingPrimitive, sym) + assert(!ts.primitiveTypeMap.contains(sym) || compilingPrimitive, sym) } /** @@ -174,9 +164,9 @@ trait BCodeHelpers extends BCodeIdiomatic { final def getClassBType(sym: Symbol): ClassBType = { assertClassNotArrayNotPrimitive(sym) - if (sym == defn.NothingClass) srNothingRef - else if (sym == defn.NullClass) srNullRef - else classBTypeFromSymbol(sym) + if (sym == defn.NothingClass) ts.srNothingRef + else if (sym == defn.NullClass) ts.srNullRef + else ts.classBTypeFromSymbol(sym) } /* @@ -187,7 +177,7 @@ trait BCodeHelpers extends BCodeIdiomatic { val resT: BType = if (msym.isClassConstructor || msym.isConstructor) UNIT else toTypeKind(msym.info.resultType) - MethodBType(msym.info.firstParamTypes map toTypeKind, resT) + MethodBType(msym.info.firstParamTypes.map(toTypeKind), resT) } /** @@ -200,7 +190,7 @@ trait BCodeHelpers extends BCodeIdiomatic { */ final def symDescriptor(sym: Symbol): String = getClassBType(sym).descriptor - final def toTypeKind(tp: Type): BType = typeToTypeKind(tp)(BCodeHelpers.this)(this) + final def toTypeKind(tp: Type): BType = typeToTypeKind(tp)(this) } // end of trait BCInnerClassGen @@ -214,7 +204,7 @@ trait BCodeHelpers extends BCodeIdiomatic { val typ = annot.tree.tpe val assocs = assocsFromApply(annot.tree) val av = cw.visitAnnotation(typeDescriptor(typ), isRuntimeVisible(annot)) - emitAssocs(av, assocs, BCodeHelpers.this)(this) + emitAssocs(av, assocs)(this) } /* @@ -225,7 +215,7 @@ trait BCodeHelpers extends BCodeIdiomatic { val typ = annot.tree.tpe val assocs = assocsFromApply(annot.tree) val av = mw.visitAnnotation(typeDescriptor(typ), isRuntimeVisible(annot)) - emitAssocs(av, assocs, BCodeHelpers.this)(this) + emitAssocs(av, assocs)(this) } /* @@ -236,13 +226,13 @@ trait BCodeHelpers extends BCodeIdiomatic { val typ = annot.tree.tpe val assocs = assocsFromApply(annot.tree) val av = fw.visitAnnotation(typeDescriptor(typ), isRuntimeVisible(annot)) - emitAssocs(av, assocs, BCodeHelpers.this)(this) + emitAssocs(av, assocs)(this) } /* * must-single-thread */ - def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]) = + def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]): Unit = for param <- params do var access = asm.Opcodes.ACC_FINAL if param.is(Artifact) then access |= asm.Opcodes.ACC_SYNTHETIC @@ -252,14 +242,13 @@ trait BCodeHelpers extends BCodeIdiomatic { * must-single-thread */ def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[Annotation]]): Unit = - val annotationss = pannotss map (_ filter shouldEmitAnnotation) - if (annotationss forall (_.isEmpty)) return - for ((annots, idx) <- annotationss.zipWithIndex; - annot <- annots) { + val annotationss = pannotss.map(_.filter(shouldEmitAnnotation)) + if (annotationss.forall(_.isEmpty)) return + for ((annots, idx) <- annotationss.zipWithIndex; annot <- annots) { val typ = annot.tree.tpe val assocs = assocsFromApply(annot.tree) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, typeDescriptor(typ.asInstanceOf[Type]), isRuntimeVisible(annot)) - emitAssocs(pannVisitor, assocs, BCodeHelpers.this)(this) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, typeDescriptor(typ), isRuntimeVisible(annot)) + emitAssocs(pannVisitor, assocs)(this) } @@ -268,16 +257,15 @@ trait BCodeHelpers extends BCodeIdiomatic { retentionPolicyOf(annot) != AnnotationRetentionSourceAttr } - private def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, Object)], bcodeStore: BCodeHelpers) - (innerClasesStore: bcodeStore.BCInnerClassGen) = { + private def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, Object)])(innerClasesStore: BCInnerClassGen): Unit = { for ((name, value) <- assocs) - emitArgument(av, name.mangledString, value.asInstanceOf[Tree], bcodeStore)(innerClasesStore) + emitArgument(av, name.mangledString, value.asInstanceOf[Tree])(innerClasesStore) av.visitEnd() } private def emitArgument(av: AnnotationVisitor, name: String, - arg: Tree, bcodeStore: BCodeHelpers)(innerClasesStore: bcodeStore.BCInnerClassGen): Unit = { + arg: Tree)(innerClasesStore: BCInnerClassGen): Unit = { val narg = normalizeArgument(arg) // Transformation phases are not run on annotation trees, so we need to run // `constToLiteral` at this point. @@ -289,7 +277,7 @@ trait BCodeHelpers extends BCodeIdiomatic { case StringTag => assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag - case ClazzTag => av.visit(name, typeToTypeKind(TypeErasure.erasure(const.typeValue))(bcodeStore)(innerClasesStore).toASMType) + case ClazzTag => av.visit(name, typeToTypeKind(TypeErasure.erasure(const.typeValue))(innerClasesStore).toASMType) } case Ident(nme.WILDCARD) => // An underscore argument indicates that we want to use the default value for this parameter, so do not emit anything @@ -310,14 +298,14 @@ trait BCodeHelpers extends BCodeIdiomatic { av.visitEnum(name, edesc, evalue) case t: SeqLiteral => val arrAnnotV: AnnotationVisitor = av.visitArray(name) - for (arg <- t.elems) { emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore) } + for (arg <- t.elems) { emitArgument(arrAnnotV, null, arg)(innerClasesStore) } arrAnnotV.visitEnd() case Apply(fun, args) if fun.symbol == defn.ArrayClass.primaryConstructor || toDenot(fun.symbol).owner == defn.ArrayClass.linkedClass && fun.symbol.name == nme.apply => val arrAnnotV: AnnotationVisitor = av.visitArray(name) - var actualArgs = if (fun.tpe.isImplicitMethod) { + val actualArgs = if (fun.tpe.isImplicitMethod) { // generic array method, need to get implicit argument out of the way fun.asInstanceOf[Apply].args } else args @@ -329,7 +317,7 @@ trait BCodeHelpers extends BCodeIdiomatic { } } for arg <- flatArgs do - emitArgument(arrAnnotV, null, arg, bcodeStore)(innerClasesStore) + emitArgument(arrAnnotV, null, arg)(innerClasesStore) arrAnnotV.visitEnd() /* case sb @ ScalaSigBytes(bytes) => @@ -348,10 +336,10 @@ trait BCodeHelpers extends BCodeIdiomatic { val assocs = assocsFromApply(t) val desc = innerClasesStore.typeDescriptor(typ) // the class descriptor of the nested annotation class val nestedVisitor = av.visitAnnotation(name, desc) - emitAssocs(nestedVisitor, assocs, bcodeStore)(innerClasesStore) + emitAssocs(nestedVisitor, assocs)(innerClasesStore) case Inlined(_, _, expansion) => - emitArgument(av, name, arg = expansion, bcodeStore)(innerClasesStore) + emitArgument(av, name, arg = expansion)(innerClasesStore) case t => report.error(em"Annotation argument is not a constant", t.sourcePos) @@ -384,8 +372,8 @@ trait BCodeHelpers extends BCodeIdiomatic { case Apply(fun, args) => fun.tpe.widen match { case MethodType(names) => - (names zip args).filter { - case (_, t: tpd.Ident) if (t.tpe.normalizedPrefix eq NoPrefix) => false + names.zip(args).filter { + case (_, t: tpd.Ident) if t.tpe.normalizedPrefix eq NoPrefix => false case _ => true } } @@ -394,7 +382,6 @@ trait BCodeHelpers extends BCodeIdiomatic { } // end of trait BCAnnotGen trait BCJGenSigGen { - import int.given def getCurrentCUnit(): CompilationUnit @@ -438,8 +425,8 @@ trait BCodeHelpers extends BCodeIdiomatic { private def addForwarder(jclass: asm.ClassVisitor, module: Symbol, m: Symbol, isSynthetic: Boolean): Unit = { val moduleName = internalName(module) val methodInfo = module.thisType.memberInfo(m) - val paramJavaTypes: List[BType] = methodInfo.firstParamTypes map toTypeKind - // val paramNames = 0 until paramJavaTypes.length map ("x_" + _) + val paramJavaTypes: List[BType] = methodInfo.firstParamTypes.map(toTypeKind) + // val paramNames = 0 until paramJavaTypes.length.map("x_" + _) /* Forwarders must not be marked final, * as the JVM will not allow redefinition of a final static method, @@ -507,9 +494,8 @@ trait BCodeHelpers extends BCodeIdiomatic { report.debuglog(s"Dumping mirror class for object: $moduleClass") val linkedClass = moduleClass.companionClass - lazy val conflictingNames: Set[Name] = { - (linkedClass.info.allMembers.collect { case d if d.name.isTermName => d.name }).toSet - } + lazy val conflictingNames: Set[Name] = + linkedClass.info.allMembers.collect { case d if d.name.isTermName => d.name }.toSet report.debuglog(s"Potentially conflicting names for forwarders: $conflictingNames") for (m0 <- sortedMembersBasedOnFlags(moduleClass.info, required = Method, excluded = ExcludedForwarder)) { @@ -600,7 +586,7 @@ trait BCodeHelpers extends BCodeIdiomatic { class JMirrorBuilder extends JCommonBuilder { private var cunit: CompilationUnit = uninitialized - def getCurrentCUnit(): CompilationUnit = cunit; + def getCurrentCUnit(): CompilationUnit = cunit /* Generate a mirror class for a top-level module. A mirror class is a class * containing only static methods that forward to the corresponding method @@ -614,17 +600,16 @@ trait BCodeHelpers extends BCodeIdiomatic { assert(moduleClass.is(ModuleClass)) assert(moduleClass.companionClass == NoSymbol, moduleClass) this.cunit = cunit - val bType = mirrorClassBTypeFromSymbol(moduleClass) + val bType = ts.mirrorClassBTypeFromSymbol(moduleClass) val moduleName = internalName(moduleClass) // + "$" val mirrorName = bType.internalName - val mirrorClass = new asm.tree.ClassNode mirrorClass.visit( backendUtils.classfileVersion, - bType.info.flags, + bType.info.get.flags, mirrorName, null /* no java-generic-signature */, - ObjectRef.internalName, + ts.ObjectRef.internalName, EMPTY_STRING_ARRAY ) @@ -640,7 +625,7 @@ trait BCodeHelpers extends BCodeIdiomatic { addForwarders(mirrorClass, mirrorName, moduleClass) mirrorClass.visitEnd() - moduleClass.name // this side-effect is necessary, really. + moduleClass.name // this side effect is necessary, really. mirrorClass } @@ -682,7 +667,7 @@ trait BCodeHelpers extends BCodeIdiomatic { null // no initial value ).visitEnd() - val moduleName = (thisName + "$") + val moduleName = thisName + "$" // GETSTATIC `moduleName`.MODULE$ : `moduleName`; clinit.visitFieldInsn( @@ -722,10 +707,8 @@ trait BCodeHelpers extends BCodeIdiomatic { * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation * classes. */ - private def typeToTypeKind(tp: Type)(ct: BCodeHelpers)(storage: ct.BCInnerClassGen): ct.bTypes.BType = { - import ct.bTypes.* + private def typeToTypeKind(tp: Type)(storage: BCInnerClassGen): BType = { val defn = ctx.definitions - import coreBTypes.* import Types.* /** * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. @@ -734,7 +717,7 @@ trait BCodeHelpers extends BCodeIdiomatic { def primitiveOrClassToBType(sym: Symbol): BType = { assert(sym.isClass, sym) assert(sym != defn.ArrayClass || compilingArray, sym) - primitiveTypeMap.getOrElse(sym, storage.getClassBType(sym)).asInstanceOf[BType] + ts.primitiveTypeMap.getOrElse(sym, storage.getClassBType(sym)) } /** @@ -743,11 +726,11 @@ trait BCodeHelpers extends BCodeIdiomatic { */ def nonClassTypeRefToBType(sym: Symbol): ClassBType = { assert(sym.isType && compilingArray, sym) - ObjectRef.asInstanceOf[ct.bTypes.ClassBType] + ts.ObjectRef } tp.widenDealias match { - case JavaArrayType(el) =>ArrayBType(typeToTypeKind(el)(ct)(storage)) // Array type such as Array[Int] (kept by erasure) + case JavaArrayType(el) =>ArrayBType(typeToTypeKind(el)(storage)) // Array type such as Array[Int] (kept by erasure) case t: TypeRef => t.info match { @@ -757,13 +740,13 @@ trait BCodeHelpers extends BCodeIdiomatic { } case Types.ClassInfo(_, sym, _, _, _) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes toTypeKind(moduleClassSymbol.info) - /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for + /* AnnotatedType should (probably) be eliminated by erasure. However, we know it happens for * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. */ case a @ AnnotatedType(t, _) => report.debuglog(s"typeKind of annotated type $a") - typeToTypeKind(t)(ct)(storage) + typeToTypeKind(t)(storage) /* The cases below should probably never occur. They are kept for now to avoid introducing * new compiler crashes, but we added a warning. The compiler / library bootstrap and the @@ -776,11 +759,11 @@ trait BCodeHelpers extends BCodeIdiomatic { "If possible, please file a bug on https://github.com/scala/scala3/issues") tp match { - case tp: ThisType if tp.cls == defn.ArrayClass => ObjectRef.asInstanceOf[ct.bTypes.ClassBType] // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test + case tp: ThisType if tp.cls == defn.ArrayClass => ts.ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test case tp: ThisType => storage.getClassBType(tp.cls) // case t: SingletonType => primitiveOrClassToBType(t.classSymbol) - case t: SingletonType => typeToTypeKind(t.underlying)(ct)(storage) - case t: RefinedType => typeToTypeKind(t.parent)(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) + case t: SingletonType => typeToTypeKind(t.underlying)(storage) + case t: RefinedType => typeToTypeKind(t.parent)(storage) } } } @@ -869,6 +852,21 @@ trait BCodeHelpers extends BCodeIdiomatic { private def compilingArray(using Context) = ctx.compilationUnit.source.file.name == "Array.scala" + + private val primitiveCompilationUnits = Set( + "Unit.scala", + "Boolean.scala", + "Char.scala", + "Byte.scala", + "Short.scala", + "Int.scala", + "Float.scala", + "Long.scala", + "Double.scala" + ) + + private def compilingPrimitive(using Context) = + primitiveCompilationUnits(ctx.compilationUnit.source.file.name) } object BCodeHelpers { @@ -880,7 +878,7 @@ object BCodeHelpers { def isSpecial: Boolean = this == Special def isSuper : Boolean = this == Super - def hasInstance = this != Static + def hasInstance: Boolean = this != Static } object InvokeStyle { @@ -897,4 +895,15 @@ object BCodeHelpers { */ val UseInvokeSpecial = new dotc.util.Property.Key[Unit] + /** + * Valid flags for InnerClass attribute entry. + * See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 + */ + val INNER_CLASSES_FLAGS = { + asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | + asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | + asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | + asm.Opcodes.ACC_ENUM + } + } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index ca8c372ae289..72d53131237a 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -8,6 +8,7 @@ import scala.tools.asm import scala.annotation.switch import Primitives.{NE, EQ, TestOp, ArithmeticOp} import scala.tools.asm.tree.MethodInsnNode +import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.report /* @@ -17,14 +18,7 @@ import dotty.tools.dotc.report * @version 1.0 * */ -trait BCodeIdiomatic { - val int: DottyBackendInterface - val bTypes: BTypesFromSymbols[int.type] - - import int.{_, given} - import bTypes.* - import coreBTypes.* - +trait BCodeIdiomatic(using Context) { val CLASS_CONSTRUCTOR_NAME = "" @@ -60,7 +54,7 @@ trait BCodeIdiomatic { val a = new Array[String](len) var i = len - 1 var rest = xs - while (!rest.isEmpty) { + while (rest.nonEmpty) { a(i) = rest.head rest = rest.tail i -= 1 @@ -77,7 +71,7 @@ trait BCodeIdiomatic { val a = new Array[Int](len) var i = len - 1 var rest = xs - while (!rest.isEmpty) { + while (rest.nonEmpty) { a(i) = rest.head rest = rest.tail i -= 1 @@ -196,12 +190,13 @@ trait BCodeIdiomatic { final def genIndyStringConcat( recipe: String, argTypes: Seq[asm.Type], - constants: Seq[String] + constants: Seq[String], + ts: CoreBTypes ): Unit = { jmethod.visitInvokeDynamicInsn( "makeConcatWithConstants", - asm.Type.getMethodDescriptor(StringRef.toASMType, argTypes*), - coreBTypes.jliStringConcatFactoryMakeConcatWithConstantsHandle, + asm.Type.getMethodDescriptor(ts.StringRef.toASMType, argTypes*), + ts.jliStringConcatFactoryMakeConcatWithConstantsHandle, (recipe +: constants)* ) } @@ -214,33 +209,31 @@ trait BCodeIdiomatic { * * can-multi-thread */ - final def emitT2T(from: BType, to: BType): Unit = { - + final def emitT2T(from: BType, to: BType): Unit = assert( from.isNonVoidPrimitiveType && to.isNonVoidPrimitiveType, s"Cannot emit primitive conversion from $from to $to" ) - def pickOne(opcs: Array[Int]): Unit = { // TODO index on to.sort - val chosen = (to: @unchecked) match { - case BYTE => opcs(0) - case SHORT => opcs(1) - case CHAR => opcs(2) - case INT => opcs(3) - case LONG => opcs(4) - case FLOAT => opcs(5) - case DOUBLE => opcs(6) - } - if (chosen != -1) { emit(chosen) } - } + def pickOne(opcs: Array[Int]): Unit = { // TODO index on to.sort + val chosen = (to: @unchecked) match { + case BYTE => opcs(0) + case SHORT => opcs(1) + case CHAR => opcs(2) + case INT => opcs(3) + case LONG => opcs(4) + case FLOAT => opcs(5) + case DOUBLE => opcs(6) + } + if (chosen != -1) { emit(chosen) } + } if (from == to) { return } // the only conversion involving BOOL that is allowed is (BOOL -> BOOL) assert(from != BOOL && to != BOOL, s"inconvertible types : $from -> $to") - // We're done with BOOL already + // we're done with BOOL already from match { - // using `asm.Type.SHORT` instead of `BType.SHORT` because otherwise "warning: could not emit switch for @switch annotated match" case BYTE => pickOne(JCodeMethodN.fromByteT2T) @@ -271,8 +264,10 @@ trait BCodeIdiomatic { case LONG => emit(D2L) case _ => emit(D2I); emitT2T(INT, to) } + + case _ => throw new IllegalArgumentException("Unsupported from type for T2T: " + from) } - } // end of emitT2T() + end emitT2T // can-multi-thread final def boolconst(b: Boolean): Unit = { iconst(if (b) 1 else 0) } @@ -338,6 +333,7 @@ trait BCodeIdiomatic { case LONG => Opcodes.T_LONG case FLOAT => Opcodes.T_FLOAT case DOUBLE => Opcodes.T_DOUBLE + case _ => throw new IllegalArgumentException("Invalid array element type: " + elem) } } jmethod.visitIntInsn(Opcodes.NEWARRAY, rand) @@ -391,7 +387,7 @@ trait BCodeIdiomatic { // can-multi-thread final def emitIF_ACMP(cond: TestOp, label: asm.Label): Unit = { assert((cond == EQ) || (cond == NE), cond) - val opc = (if (cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE) + val opc = if (cond == EQ) Opcodes.IF_ACMPEQ else Opcodes.IF_ACMPNE jmethod.visitJumpInsn(opc, label) } // can-multi-thread @@ -405,7 +401,7 @@ trait BCodeIdiomatic { else { emitTypeBased(JCodeMethodN.returnOpcodes, tk) } } - /* Emits one of tableswitch or lookoupswitch. + /* Emits one of tableswitch or lookupswitch. * * can-multi-thread */ @@ -453,19 +449,19 @@ trait BCodeIdiomatic { /* Calculate in long to guard against overflow. TODO what overflow? */ val keyRangeD: Double = (keyMax.asInstanceOf[Long] - keyMin + 1).asInstanceOf[Double] val klenD: Double = keys.length - val kdensity: Double = (klenD / keyRangeD) + val kdensity: Double = klenD / keyRangeD kdensity >= minDensity } if (isDenseEnough) { // use a table in which holes are filled with defaultBranch. - val keyRange = (keyMax - keyMin + 1) + val keyRange = keyMax - keyMin + 1 val newBranches = new Array[asm.Label](keyRange) var oldPos = 0 var i = 0 while (i < keyRange) { - val key = keyMin + i; + val key = keyMin + i if (keys(oldPos) == key) { newBranches(i) = branches(oldPos) oldPos += 1 diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index f8185098390a..6338ec5dd0e3 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -15,12 +15,14 @@ import dotty.tools.dotc.core.Flags.* import dotty.tools.dotc.core.StdNames.* import dotty.tools.dotc.core.NameKinds.* import dotty.tools.dotc.core.Names.TermName -import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.core.Symbols.{requiredClass => _, *} import dotty.tools.dotc.core.Types.* import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.util.Spans.* import dotty.tools.dotc.report +import DottyBackendInterface.{symExtensions, *} +import tpd.* /* * @@ -28,13 +30,7 @@ import dotty.tools.dotc.report * @version 1.0 * */ -trait BCodeSkelBuilder extends BCodeHelpers { - import int.{_, given} - import DottyBackendInterface.{symExtensions, _} - import tpd.* - import bTypes.* - import coreBTypes.* - import bCodeAsmCommon.* +trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { lazy val NativeAttr: Symbol = requiredClass[scala.native] @@ -156,7 +152,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { def paramTKs(app: Apply, take: Int = -1): List[BType] = app match { case Apply(fun, _) => val funSym = fun.symbol - (funSym.info.firstParamTypes map toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM) + funSym.info.firstParamTypes.map(toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM) } def symInfoTK(sym: Symbol): BType = { @@ -306,8 +302,9 @@ trait BCodeSkelBuilder extends BCodeHelpers { private def initJClass(jclass: asm.ClassVisitor): Unit = { val ps = claszSymbol.info.parents - val superClass: String = if (ps.isEmpty) ObjectRef.internalName else internalName(ps.head.typeSymbol) - val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces.map(_.internalName) + val superClass: String = if (ps.isEmpty) ts.ObjectRef.internalName else internalName(ps.head.typeSymbol) + val Right(info) = ts.classBTypeFromSymbol(claszSymbol).info.runtimeChecked + val interfaceNames0 = info.interfaces.map(_.internalName) /* To avoid deadlocks when combining objects, lambdas and multi-threading, * lambdas in objects are compiled to instance methods of the module class * instead of static methods (see tests/run/deadlock.scala and @@ -328,7 +325,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { else interfaceNames0 - val flags = javaFlags(claszSymbol) + val flags = BCodeUtils.javaFlags(claszSymbol) val thisSignature = getGenericSignature(claszSymbol, claszSymbol.owner) cnode.visit(backendUtils.classfileVersion, flags, @@ -339,8 +336,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { cnode.visitSource(cunit.source.file.name, null /* SourceDebugExtension */) } - enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { - case Some(EnclosingMethodEntry(className, methodName, methodDescriptor)) => + BCodeAsmCommon.enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { + case Some(BCodeAsmCommon.EnclosingMethodEntry(className, methodName, methodDescriptor)) => cnode.visitOuterClass(className, methodName, methodDescriptor) case _ => () } @@ -390,6 +387,18 @@ trait BCodeSkelBuilder extends BCodeHelpers { clinit.visitMaxs(0, 0) // just to follow protocol, dummy arguments clinit.visitEnd() } + + private lazy val TransientAttr = requiredClass[scala.transient] + private lazy val VolatileAttr = requiredClass[scala.volatile] + + private def javaFieldFlags(sym: Symbol) = { + import asm.Opcodes.* + import GenBCodeOps.addFlagIf + BCodeUtils.javaFlags(sym) + .addFlagIf(sym.hasAnnotation(TransientAttr), ACC_TRANSIENT) + .addFlagIf(sym.hasAnnotation(VolatileAttr), ACC_VOLATILE) + .addFlagIf(!sym.is(Mutable), ACC_FINAL) + } def addClassFields(): Unit = { /* Non-method term members are fields, except for module members. Module @@ -702,7 +711,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { val body = if (tree.constr.rhs.isEmpty) tree.body else tree.constr :: tree.body - body foreach gen + body.foreach(gen) case _ => abort(s"Illegal tree in gen: $tree") } @@ -730,7 +739,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { mkArrayS(thrownExceptions) ).asInstanceOf[MethodNode1] - // TODO param names: (m.params map (p => javaName(p.sym))) + // TODO param names: (m.params.map(p => javaName(p.sym))) emitAnnotations(mnode, others) emitParamNames(mnode, params) @@ -845,7 +854,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { val isAbstractMethod = (methSymbol.is(Deferred) || (methSymbol.owner.isInterface && ((methSymbol.is(Deferred)) || methSymbol.isClassConstructor))) val flags = import GenBCodeOps.addFlagIf - javaFlags(methSymbol) + BCodeUtils.javaFlags(methSymbol) .addFlagIf(isAbstractMethod, asm.Opcodes.ACC_ABSTRACT) .addFlagIf(false /*methSymbol.isStrictFP*/, asm.Opcodes.ACC_STRICT) .addFlagIf(isNative, asm.Opcodes.ACC_NATIVE) // native methods of objects are generated in mirror classes @@ -958,7 +967,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor + val andrFieldDescr = ts.classBTypeFromSymbol(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala index aca16707fd0f..dd783a4cb7fd 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala @@ -3,14 +3,14 @@ package backend package jvm import scala.language.unsafeNulls - import scala.collection.immutable import scala.tools.asm - import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.core.StdNames.nme import dotty.tools.dotc.core.Symbols.* import dotty.tools.dotc.ast.tpd +import dotty.tools.dotc.core.Contexts.Context +import tpd.* /* * @@ -18,29 +18,25 @@ import dotty.tools.dotc.ast.tpd * @version 1.0 * */ -trait BCodeSyncAndTry extends BCodeBodyBuilder { - import int.given - import tpd.* - import bTypes.* - import coreBTypes.* +trait BCodeSyncAndTry(using ctx: Context) extends BCodeBodyBuilder { /* * Functionality to lower `synchronized` and `try` expressions. */ - abstract class SyncAndTryBuilder(cunit: CompilationUnit) extends PlainBodyBuilder(cunit) { + class SyncAndTryBuilder(cunit: CompilationUnit) extends PlainBodyBuilder(cunit) { def genSynchronized(tree: Apply, expectedType: BType): BType = (tree: @unchecked) match { case Apply(TypeApply(fun, _), args) => - val monitor = locals.makeLocal(ObjectRef, "monitor", defn.ObjectType, tree.span) + val monitor = locals.makeLocal(ts.ObjectRef, "monitor", defn.ObjectType, tree.span) val monCleanup = new asm.Label // if the synchronized block returns a result, store it in a local variable. // Just leaving it on the stack is not valid in MSIL (stack is cleaned when leaving try-blocks). - val hasResult = (expectedType != UNIT) + val hasResult = expectedType != UNIT val monitorResult: Symbol = if (hasResult) locals.makeLocal(tpeTK(args.head), "monitorResult", defn.ObjectType, tree.span) else null /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ genLoadQualifier(fun) - bc.dup(ObjectRef) + bc.dup(ts.ObjectRef) locals.store(monitor) emit(asm.Opcodes.MONITORENTER) @@ -111,7 +107,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { * Useful to avoid emitting an empty try-block being protected by exception handlers, * which results in "java.lang.ClassFormatError: Illegal exception table range". See SI-6102. */ - def nopIfNeeded(lbl: asm.Label): Unit = { + private def nopIfNeeded(lbl: asm.Label): Unit = { val noInstructionEmitted = isAtProgramPoint(lbl) if (noInstructionEmitted) { emit(asm.Opcodes.NOP) } } @@ -120,7 +116,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { * Emitting try-catch is easy, emitting try-catch-finally not quite so. * * For a try-catch, the only thing we need to care about is to stash the stack away - * in local variables and load them back in afterwards, in case the incoming stack + * in local variables and load them back in afterward, in case the incoming stack * is not empty. * * A finally-block (which always has type Unit, thus leaving the operand stack unchanged) @@ -129,7 +125,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { * (a) `return` statement: * * First, the value to return (if any) is evaluated. - * Afterwards, all enclosing finally-blocks are run, from innermost to outermost. + * Afterward, all enclosing finally-blocks are run, from innermost to outermost. * Only then is the return value (if any) returned. * * Some terminology: @@ -190,7 +186,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { for (CaseDef(pat, _, caseBody) <- catches) yield { pat match { case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt).asClassBType, caseBody) - case Ident(nme.WILDCARD) => NamelessEH(jlThrowableRef, caseBody) + case Ident(nme.WILDCARD) => NamelessEH(ts.jlThrowableRef, caseBody) case Bind(_, _) => BoundEH (pat.symbol, caseBody) } } @@ -209,13 +205,13 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { val postHandlers = new asm.Label // stack stash - val needStackStash = !stack.isEmpty && !caseHandlers.isEmpty + val needStackStash = !stack.isEmpty && caseHandlers.nonEmpty val acquiredStack = if needStackStash then stack.acquireFullStack() else null val stashLocals = if acquiredStack == null then null else acquiredStack.uncheckedNN.filter(_ != UNIT).map(btpe => locals.makeTempLocal(btpe)) - val hasFinally = (finalizer != tpd.EmptyTree) + val hasFinally = finalizer != tpd.EmptyTree /* * used in the finally-clause reached via fall-through from try-catch, if any. @@ -281,7 +277,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { * here makes sure that `shouldEmitCleanup` is only propagated outwards, not inwards to * nested `finally` blocks. */ - def withFreshCleanupScope(body: => Unit) = { + def withFreshCleanupScope(body: => Unit): Unit = { val savedShouldEmitCleanup = shouldEmitCleanup shouldEmitCleanup = false body @@ -347,7 +343,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { nopIfNeeded(startTryBody) val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception. protect(startTryBody, finalHandler, finalHandler, null) - val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(jlThrowableRef, "exc", defn.ThrowableType, finalizer.span)) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ts.jlThrowableRef, "exc", defn.ThrowableType, finalizer.span)) bc.store(eIdx, eTK) emitFinalizer(finalizer, null, isDuplicate = true) bc.load(eIdx, eTK) @@ -426,7 +422,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { kind } // end of genLoadTry() - /* if no more pending cleanups, all that remains to do is return. Otherwise jump to the next (outer) pending cleanup. */ + /* if no more pending cleanups, all that remains to do is return. Otherwise, jump to the next (outer) pending cleanup. */ private def pendingCleanups(): Unit = { cleanups match { case Nil => @@ -443,7 +439,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { } } - def protect(start: asm.Label, end: asm.Label, handler: asm.Label, excType: ClassBType): Unit = { + private def protect(start: asm.Label, end: asm.Label, handler: asm.Label, excType: ClassBType): Unit = { val excInternalName: String = if (excType == null) null else excType.internalName @@ -452,7 +448,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { } /* `tmp` (if non-null) is the symbol of the local-var used to preserve the result of the try-body, see `guardResult` */ - def emitFinalizer(finalizer: Tree, tmp: Symbol, isDuplicate: Boolean): Unit = { + private def emitFinalizer(finalizer: Tree, tmp: Symbol, isDuplicate: Boolean): Unit = { var saved: immutable.Map[ /* Labeled */ Symbol, (BType, LoadDestination) ] = null if (isDuplicate) { saved = jumpDest @@ -467,15 +463,14 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { } /* Does this tree have a try-catch block? */ - def mayCleanStack(tree: Tree): Boolean = tree.find { t => t match { // TODO: use existsSubTree - case Try(_, _, _) => true - case _ => false - } + private def mayCleanStack(tree: Tree): Boolean = tree.find { // TODO: use existsSubTree + case Try(_, _, _) => true + case _ => false }.isDefined - trait EHClause - case class NamelessEH(typeToDrop: ClassBType, caseBody: Tree) extends EHClause - case class BoundEH (patSymbol: Symbol, caseBody: Tree) extends EHClause + private trait EHClause + private case class NamelessEH(typeToDrop: ClassBType, caseBody: Tree) extends EHClause + private case class BoundEH (patSymbol: Symbol, caseBody: Tree) extends EHClause } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala b/compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala new file mode 100644 index 000000000000..f63eed9310ef --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala @@ -0,0 +1,79 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package dotty.tools.backend.jvm + +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.Flags.{AbstractOrTrait, Artifact, Bridge, Deferred, Enum, Final, JavaEnum, JavaVarargs, Mutable, Private, Synchronized, Trait} +import dotty.tools.dotc.core.Symbols.* + +import scala.tools.asm + +// TODO this object will be filled (a _lot_) when porting the optimizer +// (one can argue we should reuse one of the other existing helper objects, but that's for a later cleanup) + +object BCodeUtils { + + /** + * Return the Java modifiers for the given symbol. + * Java modifiers for classes: + * - public, abstract, final, strictfp (not used) + * for interfaces: + * - the same as for classes, without 'final' + * for fields: + * - public, private (*) + * - static, final + * for methods: + * - the same as for fields, plus: + * - abstract, synchronized (not used), strictfp (not used), native (not used) + * for all: + * - deprecated + * + * (*) protected cannot be used, since inner classes 'see' protected members, + * and they would fail verification after lifted. + */ + final def javaFlags(sym: Symbol)(using Context): Int = { + import DottyBackendInterface.symExtensions + + // Classes are always emitted as public. This matches the behavior of Scala 2 + // and is necessary for object deserialization to work properly, otherwise + // ModuleSerializationProxy may fail with an accessiblity error (see + // tests/run/serialize.scala and https://github.com/typelevel/cats-effect/pull/2360). + val privateFlag = !sym.isClass && (sym.is(Private) || (sym.isPrimaryConstructor && sym.owner.isTopLevelModuleClass)) + + val finalFlag = sym.is(Final) && !toDenot(sym).isClassConstructor && !sym.isMutableVar && !sym.enclosingClass.is(Trait) + + import asm.Opcodes.* + import GenBCodeOps.addFlagIf + 0 .addFlagIf(privateFlag, ACC_PRIVATE) + .addFlagIf(!privateFlag, ACC_PUBLIC) + .addFlagIf(sym.is(Deferred) || sym.isOneOf(AbstractOrTrait), ACC_ABSTRACT) + .addFlagIf(sym.isInterface, ACC_INTERFACE) + .addFlagIf(finalFlag + // Primitives are "abstract final" to prohibit instantiation + // without having to provide any implementations, but that is an + // illegal combination of modifiers at the bytecode level so + // suppress final if abstract if present. + && !sym.isOneOf(AbstractOrTrait) + // Bridges can be final, but final bridges confuse some frameworks + && !sym.is(Bridge), ACC_FINAL) + .addFlagIf(sym.isStaticMember, ACC_STATIC) + .addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC) + .addFlagIf(sym.is(Artifact), ACC_SYNTHETIC) + .addFlagIf(sym.isClass && !sym.isInterface, ACC_SUPER) + .addFlagIf(sym.isAllOf(JavaEnum), ACC_ENUM) + .addFlagIf(sym.is(JavaVarargs), ACC_VARARGS) + .addFlagIf(sym.is(Synchronized), ACC_SYNCHRONIZED) + .addFlagIf(sym.isDeprecated, ACC_DEPRECATED) + .addFlagIf(sym.is(Enum), ACC_ENUM) + } +} diff --git a/compiler/src/dotty/tools/backend/jvm/BTypes.scala b/compiler/src/dotty/tools/backend/jvm/BTypes.scala index dff7353b761e..0452452bceea 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypes.scala @@ -3,563 +3,613 @@ package backend package jvm import scala.language.unsafeNulls - +import java.util.concurrent.ConcurrentHashMap import scala.tools.asm +import dotty.tools.backend.jvm.PostProcessorFrontendAccess.Lazy +import dotty.tools.backend.jvm.BTypes.InternalName +import dotty.tools.backend.jvm.BackendReporting.* + +import scala.tools.asm.Opcodes +import scala.tools.asm.Opcodes.{ACC_PRIVATE, ACC_PROTECTED, ACC_PUBLIC, ACC_STATIC} + /** * The BTypes component defines The BType class hierarchy. BTypes encapsulates all type information - * that is required after building the ASM nodes. This includes optimizations, geneartion of + * that is required after building the ASM nodes. This includes optimizations, generation of * InnerClass attributes and generation of stack map frames. * * This representation is immutable and independent of the compiler data structures, hence it can * be queried by concurrent threads. */ -abstract class BTypes { self => - val frontendAccess: PostProcessorFrontendAccess - val int: DottyBackendInterface - import int.given - /** - * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its - * construction. - * - * This map is used when computing stack map frames. The asm.ClassWriter invokes the method - * `getCommonSuperClass`. In this method we need to obtain the ClassBType for a given internal - * name. The method assumes that every class type that appears in the bytecode exists in the map. - * - * Concurrent because stack map frames are computed when in the class writer, which might run - * on multiple classes concurrently. - */ - protected lazy val classBTypeFromInternalNameMap: collection.concurrent.Map[String, ClassBType] + +/** + * A BType is either a primitve type, a ClassBType, an ArrayBType of one of these, or a MethodType + * referring to BTypes. + */ +sealed trait BType { + 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 ClassBType(internalName) => "L" + internalName + ";" + case ArrayBType(component) => "[" + component + case MethodBType(args, res) => args.mkString("(", "", ")" + res) + } /** - * Obtain a previously constructed ClassBType for a given internal name. + * @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 classBTypeFromInternalName(internalName: String) = classBTypeFromInternalNameMap(internalName) - - val coreBTypes: CoreBTypes { val bTypes: self.type} - import coreBTypes.* + final def descriptor: String = toString /** - * A BType is either a primitve type, a ClassBType, an ArrayBType of one of these, or a MethodType - * referring to BTypes. + * @return 0 for void, 2 for long and double, 1 otherwise */ - /*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 ClassBType(internalName) => "L" + internalName + ";" - case ArrayBType(component) => "[" + component - case MethodBType(args, res) => args.mkString("(", "", ")" + res) - } - - /** - * @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; - */ - final def descriptor = toString - - /** - * @return 0 for void, 2 for long and double, 1 otherwise - */ - final def size: Int = this match { - case UNIT => 0 - case LONG | DOUBLE => 2 - case _ => 1 - } + final def size: Int = this match { + case UNIT => 0 + case LONG | DOUBLE => 2 + case _ => 1 + } - 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 - - final def isNullType = this == srNullRef - final def isNothingType = this == srNothingRef - - final def isBoxed = this.isClass && boxedClasses(this.asClassBType) - - 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 - - /* - * Subtype check `this <:< other` on BTypes that takes into account the JVM built-in numeric - * promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the - * Java bytecode type hierarchy. - */ - final def conformsTo(other: BType): Boolean = { - assert(isRef || isPrimitive, s"conformsTo cannot handle $this") - assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other") - - this match { - case ArrayBType(component) => - if (other == ObjectRef || other == jlCloneableRef || other == jiSerializableRef) true - else other match { - case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent) - case _ => false - } - - case classType: ClassBType => - if (isBoxed) { - if (other.isBoxed) this == other - else if (other == ObjectRef) true - else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number - case _ => false - } - } else if (isNullType) { - if (other.isNothingType) false - else if (other.isPrimitive) false - else true // Null conforms to all classes (except Nothing) and arrays. - } else if (isNothingType) { - true - } else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) - // case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case - case _ => - // isNothingType || // documentation only, because `if (isNothingType)` above covers this case - false - } - - case UNIT => - other == UNIT - case BOOL | BYTE | SHORT | CHAR => - this == other || other == INT || other == LONG // TODO Actually, BOOL does NOT conform to LONG. Even with adapt(). - case _ => - assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other") - this == other - } - } + 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: Boolean = isPrimitive && this != UNIT + + def isObjectType: Boolean + def isJlCloneableType: Boolean + def isJiSerializableType: Boolean + def isNullType: Boolean + def isNothingType: Boolean + def isBoxed: Boolean + + final def isIntSizedType: Boolean = this == BOOL || this == CHAR || this == BYTE || + this == SHORT || this == INT + final def isIntegralType: Boolean = this == INT || this == BYTE || this == LONG || + this == CHAR || this == SHORT + final def isNumericType: Boolean = isIntegralType || this == FLOAT || this == DOUBLE + final def isWideType: Boolean = size == 2 + + /* + * Subtype check `this <:< other` on BTypes that takes into account the JVM built-in numeric + * promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the + * Java bytecode type hierarchy. + */ + final def conformsTo(other: BType): Either[NoClassBTypeInfo, Boolean] = tryEither(Right({ + assert(isRef || isPrimitive, s"conformsTo cannot handle $this") + assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other") + + this match { + case ArrayBType(component) => + if (other.isObjectType || other.isJlCloneableType || other.isJiSerializableType) true + else other match { + case ArrayBType(otherComponent) => + // Array[Short]().isInstanceOf[Array[Int]] is false + // but Array[String]().isInstanceOf[Array[Object]] is true + if (component.isPrimitive || otherComponent.isPrimitive) component == otherComponent + else component.conformsTo(otherComponent).orThrow + case _ => false + } - /** - * Compute the upper bound of two types. - * Takes promotions of numeric primitives into account. - */ - final def maxType(other: BType): BType = this match { - case pt: PrimitiveBType => pt.maxValueType(other) - - case _: ArrayBType | _: ClassBType => - if (isNothingType) return other - if (other.isNothingType) return this - if (this == other) return this - - assert(other.isRef, s"Cannot compute maxType: $this, $other") - // Approximate `lub`. The common type of two references is always ObjectReference. - ObjectRef + case classType: ClassBType => + // Quick test for Object to make a common case fast + other.isObjectType || (other match { + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow + case _ => false + }) + + case _ => + // there are no bool/byte/short/char primitives at runtime, they are represented as ints. + // instructions like i2s are used to truncate, the result is again an int. conformsTo + // returns true for conversions that don't need a truncating instruction. see also emitT2T. + // note that for primitive arrays, Array[Short]().isInstanceOf[Array[Int]] is false. + this == other || ((this, other) match { + case (BOOL, BYTE | SHORT | INT) => true + case (BYTE, SHORT | INT) => true + case (SHORT, INT) => true + case (CHAR, INT) => true + case _ => false + }) } + })) - /** - * See documentation of [[typedOpcode]]. - * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 8. - */ - 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 - } + /** + * Compute the upper bound of two types. + * Takes promotions of numeric primitives into account. + */ + final def maxType(other: BType, ts: CoreBTypes): BType = this match { + case pt: PrimitiveBType => pt.maxValueType(other) - /** - * See documentation of [[typedOpcode]]. - * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 16. - */ - 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 - } + case _: ArrayBType | _: ClassBType => + if isNothingType then return other + if other.isNothingType then return this + if this == other then return this - /** - * Some JVM opcodes have typed variants. This method returns the correct opcode according to - * the type. - * - * @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`. - */ - final def typedOpcode(opcode: Int): Int = { - if (opcode == asm.Opcodes.IALOAD || opcode == asm.Opcodes.IASTORE) - opcode + loadStoreOpcodeOffset - else - opcode + typedOpcodeOffset - } + assert(other.isRef, s"Cannot compute maxType: $this, $other") + // Approximate `lub`. The common type of two references is always ObjectReference. + ts.ObjectRef - /** - * The asm.Type corresponding to this BType. - * - * 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;". - * - * 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 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 ClassBType(internalName) => asm.Type.getObjectType(internalName) // see (*) above - case a: ArrayBType => asm.Type.getObjectType(a.descriptor) - case m: MethodBType => asm.Type.getMethodType(m.descriptor) - } + case _: MethodBType => throw new IllegalArgumentException("Cannot take the max of a method type") + } - def asRefBType : RefBType = this.asInstanceOf[RefBType] - def asArrayBType : ArrayBType = this.asInstanceOf[ArrayBType] - def asClassBType : ClassBType = this.asInstanceOf[ClassBType] - def asPrimitiveBType : PrimitiveBType = this.asInstanceOf[PrimitiveBType] + /** + * See documentation of [[typedOpcode]]. + * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 8. + */ + 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 } - sealed trait PrimitiveBType extends BType { - - /** - * The upper bound of two primitive types. The `other` type has to be either a primitive - * type or Nothing. - * - * The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative - * values of Byte and Short. See ticket #2087. - */ - final def maxValueType(other: BType): BType = { - - def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other") - - if (!other.isPrimitive && !other.isNothingType) uncomparable - - if (other.isNothingType) return this - if (this == other) return this - - this match { - case BYTE => - if (other == CHAR) INT - else if (other.isNumericType) other - else uncomparable - - case SHORT => - other match { - case BYTE => SHORT - case CHAR => INT - case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case CHAR => - other match { - case BYTE | SHORT => INT - case INT | LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case INT => - other match { - case BYTE | SHORT | CHAR => INT - case LONG | FLOAT | DOUBLE => other - case _ => uncomparable - } - - case LONG => - other match { - case INT | BYTE | LONG | CHAR | SHORT => LONG - case DOUBLE => DOUBLE - case FLOAT => FLOAT - case _ => uncomparable - } - - case FLOAT => - if (other == DOUBLE) DOUBLE - else if (other.isNumericType) FLOAT - else uncomparable - - case DOUBLE => - if (other.isNumericType) DOUBLE - else uncomparable - - case UNIT | BOOL => uncomparable - } - } + /** + * See documentation of [[typedOpcode]]. + * The numbers are taken from asm.Type.VOID_TYPE ff., the values are those shifted by << 16. + */ + 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 } - 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 - - sealed trait RefBType extends BType { - /** - * The class or array type of this reference type. Used for ANEWARRAY, MULTIANEWARRAY, - * INSTANCEOF and CHECKCAST instructions. Also used for emitting invokevirtual calls to - * (a: Array[T]).clone() for any T, see genApply. - * - * 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;". - * - * This can be verified for example using javap or ASMifier. - */ - def classOrArrayType: String = this match { - case ClassBType(internalName) => internalName - case a: ArrayBType => a.descriptor - } + /** + * Some JVM opcodes have typed variants. This method returns the correct opcode according to + * the type. + * + * @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`. + */ + final def typedOpcode(opcode: Int): Int = { + if (opcode == asm.Opcodes.IALOAD || opcode == asm.Opcodes.IASTORE) + opcode + loadStoreOpcodeOffset + else + opcode + typedOpcodeOffset } /** - * InnerClass and EnclosingMethod attributes (EnclosingMethod is displayed as OUTERCLASS in asm). - * - * In this summary, "class" means "class or interface". - * - * JLS: http://docs.oracle.com/javase/specs/jls/se8/html/index.html - * JVMS: http://docs.oracle.com/javase/specs/jvms/se8/html/index.html - * - * Terminology - * ----------- - * - * - Nested class (JLS 8): class whose declaration occurs within the body of another class - * - * - Top-level class (JLS 8): non-nested class - * - * - Inner class (JLS 8.1.3): nested class that is not (explicitly or implicitly) static - * - * - Member class (JLS 8.5): class directly enclosed in the body of a class (and not, for - * example, defined in a method). Member classes cannot be anonymous. May be static. - * - * - Local class (JLS 14.3): nested, non-anonymous class that is not a member of a class - * - cannot be static (therefore they are "inner" classes) - * - can be defined in a method, a constructor or in an initializer block - * - * - Initializer block (JLS 8.6 / 8.7): block of statements in a java class - * - static initializer: executed before constructor body - * - instance initializer: executed when class is initialized (instance creation, static - * field access, ...) - * - * - A static nested class can be defined as - * - a static member class (explicitly static), or - * - a member class of an interface (implicitly static) - * - local classes are never static, even if they are defined in a static method. - * - * Note: it is NOT the case that all inner classes (non-static) have an outer pointer. Example: - * class C { static void foo { class D {} } } - * The class D is an inner class (non-static), but javac does not add an outer pointer to it. - * - * InnerClass - * ---------- - * - * The JVMS 4.7.6 requires an entry for every class mentioned in a CONSTANT_Class_info in the - * constant pool (CP) that is not a member of a package (JLS 7.1). - * - * The JLS 13.1, points 9. / 10. requires: a class must reference (in the CP) - * - its immediately enclosing class - * - all of its member classes - * - all local and anonymous classes that are referenced (or declared) elsewhere (method, - * constructor, initializer block, field initializer) - * - * In a comment, the 4.7.6 spec says: this implies an entry in the InnerClass attribute for - * - All enclosing classes (except the outermost, which is top-level) - * - My comment: not sure how this is implied, below (*) a Java counter-example. - * In any case, the Java compiler seems to add all enclosing classes, even if they are not - * otherwise mentioned in the CP. So we should do the same. - * - All nested classes (including anonymous and local, but not transitively) - * - * Fields in the InnerClass entries: - * - inner class: the (nested) class C we are talking about - * - outer class: the class of which C is a member. Has to be null for non-members, i.e. for - * local and anonymous classes. NOTE: this co-incides with the presence of an - * EnclosingMethod attribute (see below) - * - inner name: A string with the simple name of the inner class. Null for anonymous classes. - * - flags: access property flags, details in JVMS, table in 4.7.6. Static flag: see - * discussion below. - * - * - * Note 1: when a nested class is present in the InnerClass attribute, all of its enclosing - * classes have to be present as well (by the rules above). Example: - * - * class Outer { class I1 { class I2 { } } } - * class User { Outer.I1.I2 foo() { } } - * - * The return type "Outer.I1.I2" puts "Outer$I1$I2" in the CP, therefore the class is added to the - * InnerClass attribute. For this entry, the "outer class" field will be "Outer$I1". This in turn - * adds "Outer$I1" to the CP, which requires adding that class to the InnerClass attribute. - * (For local / anonymous classes this would not be the case, since the "outer class" attribute - * would be empty. However, no class (other than the enclosing class) can refer to them, as they - * have no name.) - * - * In the current implementation of the Scala compiler, when adding a class to the InnerClass - * attribute, all of its enclosing classes will be added as well. Javac seems to do the same, - * see (*). - * - * - * Note 2: If a class name is mentioned only in a CONSTANT_Utf8_info, but not in a - * CONSTANT_Class_info, the JVMS does not require an entry in the InnerClass attribute. However, - * the Java compiler seems to add such classes anyway. For example, when using an annotation, the - * annotation class is stored as a CONSTANT_Utf8_info in the CP: - * - * @O.Ann void foo() { } - * - * adds "const #13 = Asciz LO$Ann;;" in the constant pool. The "RuntimeInvisibleAnnotations" - * attribute refers to that constant pool entry. Even though there is no other reference to - * `O.Ann`, the java compiler adds an entry for that class to the InnerClass attribute (which - * entails adding a CONSTANT_Class_info for the class). - * - * - * - * EnclosingMethod - * --------------- - * - * JVMS 4.7.7: the attribute must be present "if and only if it represents a local class - * or an anonymous class" (i.e. not for member classes). - * - * The attribute is mis-named, it should be called "EnclosingClass". It has to be defined for all - * local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the - * "class" field (see below) must be always defined, while the "method" field may be null. - * - * NOTE: When a EnclosingMethod attribute is required (local and anonymous classes), the "outer" - * field in the InnerClass table must be null. + * The asm.Type corresponding to this BType. * - * Fields: - * - class: the enclosing class - * - method: the enclosing method (or constructor). Null if the class is not enclosed by a - * method, i.e. for - * - local or anonymous classes defined in (static or non-static) initializer blocks - * - anonymous classes defined in initializer blocks or field initializers + * 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;". * - * Note: the field is required for anonymous classes defined within local variable - * initializers (within a method), Java example below (**). - * - * For local and anonymous classes in initializer blocks or field initializers, and - * class-level anonymous classes, the scala compiler sets the "method" field to null. - * - * - * (*) - * public class Test { - * void foo() { - * class Foo1 { - * // constructor statement block - * { - * class Foo2 { - * class Foo3 { } - * } - * } - * } - * } - * } - * - * The class file Test$1Foo1$1Foo2$Foo3 has no reference to the class Test$1Foo1, however it - * still contains an InnerClass attribute for Test$1Foo1. - * Maybe this is just because the Java compiler follows the JVMS comment ("InnerClasses - * information for each enclosing class"). - * - * - * (**) - * void foo() { - * // anonymous class defined in local variable initializer expression. - * Runnable x = true ? (new Runnable() { - * public void run() { return; } - * }) : null; - * } - * - * The EnclosingMethod attribute of the anonymous class mentions "foo" in the "method" field. - * - * - * Java Compatibility - * ------------------ - * - * In the InnerClass entry for classes in top-level modules, the "outer class" is emitted as the - * mirror class (or the existing companion class), i.e. C1 is nested in T (not T$). - * For classes nested in a nested object, the "outer class" is the module class: C2 is nested in T$N$ - * object T { - * class C1 - * object N { class C2 } - * } - * - * Reason: java compat. It's a "best effort" "solution". If you want to use "C1" from Java, you - * can write "T.C1", and the Java compiler will translate that to the classfile T$C1. - * - * If we would emit the "outer class" of C1 as "T$", then in Java you'd need to write "T$.C1" - * because the java compiler looks at the InnerClass attribute to find if an inner class exists. - * However, the Java compiler would then translate the '.' to '$' and you'd get the class name - * "T$$C1". This class file obviously does not exist. - * - * Directly using the encoded class name "T$C1" in Java does not work: since the classfile - * describes a nested class, the Java compiler hides it from the classpath and will report - * "cannot find symbol T$C1". This means that the class T.N.C2 cannot be referenced from a - * Java source file in any way. - * - * - * STATIC flag - * ----------- - * - * Java: static member classes have the static flag in the InnerClass attribute, for example B in - * class A { static class B { } } - * - * The spec is not very clear about when the static flag should be emitted. It says: "Marked or - * implicitly static in source." - * - * The presence of the static flag does NOT coincide with the absence of an "outer" field in the - * class. The java compiler never puts the static flag for local classes, even if they don't have - * an outer pointer: - * - * class A { - * void f() { class B {} } - * static void g() { calss C {} } - * } - * - * B has an outer pointer, C doesn't. Both B and C are NOT marked static in the InnerClass table. - * - * It seems sane to follow the same principle in the Scala compiler. So: + * See method asm.Type.getType that creates an 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 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 ClassBType(internalName) => asm.Type.getObjectType(internalName) // see (*) above + case a: ArrayBType => asm.Type.getObjectType(a.descriptor) + case m: MethodBType => asm.Type.getMethodType(m.descriptor) + } + + def asRefBType : RefBType = this.asInstanceOf[RefBType] + def asArrayBType : ArrayBType = this.asInstanceOf[ArrayBType] + def asClassBType : ClassBType = this.asInstanceOf[ClassBType] + def asPrimitiveBType : PrimitiveBType = this.asInstanceOf[PrimitiveBType] +} + +sealed trait PrimitiveBType extends BType { + + override def isObjectType: Boolean = false + override def isJlCloneableType: Boolean = false + override def isJiSerializableType: Boolean = false + override def isNullType: Boolean = false + override def isNothingType: Boolean = false + override def isBoxed: Boolean = false + + + /** + * The upper bound of two primitive types. The `other` type has to be either a primitive + * type or Nothing. * - * package p - * object O1 { - * class C1 // static inner class - * object O2 { // static inner module - * def f = { - * class C2 { // non-static inner class, even though there's no outer pointer - * class C3 // non-static, has an outer pointer - * } - * } - * } - * } + * The maxValueType of (Char, Byte) and of (Char, Short) is Int, to encompass the negative + * values of Byte and Short. See ticket #2087. + */ + final def maxValueType(other: BType): BType = { + + def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other") + + if !other.isPrimitive && !other.isNothingType then uncomparable + + if other.isNothingType then return this + if this == other then return this + + this match { + case BYTE => + if (other == CHAR) INT + else if (other.isNumericType) other + else uncomparable + + case SHORT => + other match { + case BYTE => SHORT + case CHAR => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case CHAR => + other match { + case BYTE | SHORT => INT + case INT | LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case INT => + other match { + case BYTE | SHORT | CHAR => INT + case LONG | FLOAT | DOUBLE => other + case _ => uncomparable + } + + case LONG => + other match { + case INT | BYTE | LONG | CHAR | SHORT => LONG + case DOUBLE => DOUBLE + case FLOAT => FLOAT + case _ => uncomparable + } + + case FLOAT => + if (other == DOUBLE) DOUBLE + else if (other.isNumericType) FLOAT + else uncomparable + + case DOUBLE => + if (other.isNumericType) DOUBLE + else uncomparable + + case UNIT | BOOL => uncomparable + } + } +} + +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 + +sealed trait RefBType extends BType { + + override def isObjectType: Boolean = false + override def isJlCloneableType: Boolean = false + override def isJiSerializableType: Boolean = false + override def isNullType: Boolean = false + override def isNothingType: Boolean = false + override def isBoxed: Boolean = false + + + /** + * The class or array type of this reference type. Used for ANEWARRAY, MULTIANEWARRAY, + * INSTANCEOF and CHECKCAST instructions. Also used for emitting invokevirtual calls to + * (a: Array[T]).clone() for any T, see genApply. * - * Mirror Classes - * -------------- + * 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;". * - * TODO: innerclass attributes on mirror class + * This can be verified for example using javap or ASMifier. */ + def classOrArrayType: String = this match { + case ClassBType(internalName) => internalName + case a: ArrayBType => a.descriptor + } +} - /** +/** + * InnerClass and EnclosingMethod attributes (EnclosingMethod is displayed as OUTERCLASS in asm). + * + * In this summary, "class" means "class or interface". + * + * JLS: http://docs.oracle.com/javase/specs/jls/se8/html/index.html + * JVMS: http://docs.oracle.com/javase/specs/jvms/se8/html/index.html + * + * Terminology + * ----------- + * + * - Nested class (JLS 8): class whose declaration occurs within the body of another class + * + * - Top-level class (JLS 8): non-nested class + * + * - Inner class (JLS 8.1.3): nested class that is not (explicitly or implicitly) static + * + * - Member class (JLS 8.5): class directly enclosed in the body of a class (and not, for + * example, defined in a method). Member classes cannot be anonymous. May be static. + * + * - Local class (JLS 14.3): nested, non-anonymous class that is not a member of a class + * - cannot be static (therefore they are "inner" classes) + * - can be defined in a method, a constructor or in an initializer block + * + * - Initializer block (JLS 8.6 / 8.7): block of statements in a java class + * - static initializer: executed before constructor body + * - instance initializer: executed when class is initialized (instance creation, static + * field access, ...) + * + * - A static nested class can be defined as + * - a static member class (explicitly static), or + * - a member class of an interface (implicitly static) + * - local classes are never static, even if they are defined in a static method. + * + * Note: it is NOT the case that all inner classes (non-static) have an outer pointer. Example: + * class C { static void foo { class D {} } } + * The class D is an inner class (non-static), but javac does not add an outer pointer to it. + * + * InnerClass + * ---------- + * + * The JVMS 4.7.6 requires an entry for every class mentioned in a CONSTANT_Class_info in the + * constant pool (CP) that is not a member of a package (JLS 7.1). + * + * The JLS 13.1, points 9. / 10. requires: a class must reference (in the CP) + * - its immediately enclosing class + * - all of its member classes + * - all local and anonymous classes that are referenced (or declared) elsewhere (method, + * constructor, initializer block, field initializer) + * + * In a comment, the 4.7.6 spec says: this implies an entry in the InnerClass attribute for + * - All enclosing classes (except the outermost, which is top-level) + * - My comment: not sure how this is implied, below (*) a Java counter-example. + * In any case, the Java compiler seems to add all enclosing classes, even if they are not + * otherwise mentioned in the CP. So we should do the same. + * - All nested classes (including anonymous and local, but not transitively) + * + * Fields in the InnerClass entries: + * - inner class: the (nested) class C we are talking about + * - outer class: the class of which C is a member. Has to be null for non-members, i.e. for + * local and anonymous classes. NOTE: this co-incides with the presence of an + * EnclosingMethod attribute (see below) + * - inner name: A string with the simple name of the inner class. Null for anonymous classes. + * - flags: access property flags, details in JVMS, table in 4.7.6. Static flag: see + * discussion below. + * + * + * Note 1: when a nested class is present in the InnerClass attribute, all of its enclosing + * classes have to be present as well (by the rules above). Example: + * + * class Outer { class I1 { class I2 { } } } + * class User { Outer.I1.I2 foo() { } } + * + * The return type "Outer.I1.I2" puts "Outer$I1$I2" in the CP, therefore the class is added to the + * InnerClass attribute. For this entry, the "outer class" field will be "Outer$I1". This in turn + * adds "Outer$I1" to the CP, which requires adding that class to the InnerClass attribute. + * (For local / anonymous classes this would not be the case, since the "outer class" attribute + * would be empty. However, no class (other than the enclosing class) can refer to them, as they + * have no name.) + * + * In the current implementation of the Scala compiler, when adding a class to the InnerClass + * attribute, all of its enclosing classes will be added as well. Javac seems to do the same, + * see (*). + * + * + * Note 2: If a class name is mentioned only in a CONSTANT_Utf8_info, but not in a + * CONSTANT_Class_info, the JVMS does not require an entry in the InnerClass attribute. However, + * the Java compiler seems to add such classes anyway. For example, when using an annotation, the + * annotation class is stored as a CONSTANT_Utf8_info in the CP: + * + * `@O.Ann void foo() { }` + * + * adds "const #13 = Asciz LO$Ann;;" in the constant pool. The "RuntimeInvisibleAnnotations" + * attribute refers to that constant pool entry. Even though there is no other reference to + * `O.Ann`, the java compiler adds an entry for that class to the InnerClass attribute (which + * entails adding a CONSTANT_Class_info for the class). + * + * + * + * EnclosingMethod + * --------------- + * + * JVMS 4.7.7: the attribute must be present "if and only if it represents a local class + * or an anonymous class" (i.e. not for member classes). + * + * The attribute is mis-named, it should be called "EnclosingClass". It has to be defined for all + * local and anonymous classes, no matter if there is an enclosing method or not. Accordingly, the + * "class" field (see below) must be always defined, while the "method" field may be null. + * + * NOTE: When a EnclosingMethod attribute is required (local and anonymous classes), the "outer" + * field in the InnerClass table must be null. + * + * Fields: + * - class: the enclosing class + * - method: the enclosing method (or constructor). Null if the class is not enclosed by a + * method, i.e. for + * - local or anonymous classes defined in (static or non-static) initializer blocks + * - anonymous classes defined in initializer blocks or field initializers + * + * Note: the field is required for anonymous classes defined within local variable + * initializers (within a method), Java example below (**). + * + * For local and anonymous classes in initializer blocks or field initializers, and + * class-level anonymous classes, the scala compiler sets the "method" field to null. + * + * + * (*) + * public class Test { + * void foo() { + * class Foo1 { + * // constructor statement block + * { + * class Foo2 { + * class Foo3 { } + * } + * } + * } + * } + * } + * + * The class file Test$1Foo1$1Foo2$Foo3 has no reference to the class Test$1Foo1, however it + * still contains an InnerClass attribute for Test$1Foo1. + * Maybe this is just because the Java compiler follows the JVMS comment ("InnerClasses + * information for each enclosing class"). + * + * + * (**) + * void foo() { + * // anonymous class defined in local variable initializer expression. + * Runnable x = true ? (new Runnable() { + * public void run() { return; } + * }) : null; + * } + * + * The EnclosingMethod attribute of the anonymous class mentions "foo" in the "method" field. + * + * + * Java Compatibility + * ------------------ + * + * In the InnerClass entry for classes in top-level modules, the "outer class" is emitted as the + * mirror class (or the existing companion class), i.e. C1 is nested in T (not T$). + * For classes nested in a nested object, the "outer class" is the module class: C2 is nested in T$N$ + * object T { + * class C1 + * object N { class C2 } + * } + * + * Reason: java compatibility. It's a "best effort" "solution". If you want to use "C1" from Java, you + * can write "T.C1", and the Java compiler will translate that to the classfile T$C1. + * + * If we would emit the "outer class" of C1 as "T$", then in Java you'd need to write "T$.C1" + * because the java compiler looks at the InnerClass attribute to find if an inner class exists. + * However, the Java compiler would then translate the '.' to '$' and you'd get the class name + * "T$$C1". This class file obviously does not exist. + * + * Directly using the encoded class name "T$C1" in Java does not work: since the classfile + * describes a nested class, the Java compiler hides it from the classpath and will report + * "cannot find symbol T$C1". This means that the class T.N.C2 cannot be referenced from a + * Java source file in any way. + * + * + * STATIC flag + * ----------- + * + * Java: static member classes have the static flag in the InnerClass attribute, for example B in + * class A { static class B { } } + * + * The spec is not very clear about when the static flag should be emitted. It says: "Marked or + * implicitly static in source." + * + * The presence of the static flag does NOT coincide with the absence of an "outer" field in the + * class. The java compiler never puts the static flag for local classes, even if they don't have + * an outer pointer: + * + * class A { + * void f() { class B {} } + * static void g() { class C {} } + * } + * + * B has an outer pointer, C doesn't. Both B and C are NOT marked static in the InnerClass table. + * + * It seems sane to follow the same principle in the Scala compiler. So: + * + * package p + * object O1 { + * class C1 // static inner class + * object O2 { // static inner module + * def f = { + * class C2 { // non-static inner class, even though there's no outer pointer + * class C3 // non-static, has an outer pointer + * } + * } + * } + * } + * + * Mirror Classes + * -------------- + * + * TODO: innerclass attributes on mirror class + */ + +/** + * The type info for a class. Used for symboltable-independent subtype checks in the backend. + * + * @param superClass The super class, not defined for class java/lang/Object. + * @param interfaces All transitively implemented interfaces, except for those inherited + * through the superclass. + * + * @param flags The java flags, obtained through `javaFlags`. Used also to derive + * the flags for InnerClass entries. + * + * @param nestedClasses Classes nested in this class. Those need to be added to the + * InnerClass table, see the InnerClass spec summary above. + * + * @param nestedInfo If this describes a nested class, information for the InnerClass table. + */ +final case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, + nestedClasses: Lazy[List[ClassBType]], nestedInfo: Lazy[Option[NestedInfo]]) + +/** + * Information required to add a class to an InnerClass table. + * The spec summary above explains what information is required for the InnerClass entry. + * + * @param enclosingClass The enclosing class, if it is also nested. When adding a class + * to the InnerClass table, enclosing nested classes are also added. + * + * @param outerName The outerName field in the InnerClass entry, may be None. + * @param innerName The innerName field, may be None. + * @param isStaticNestedClass True if this is a static nested class (not inner class) (*) + * + * (*) Note that the STATIC flag in ClassInfo.flags, obtained through javaFlags(classSym), is not + * correct for the InnerClass entry, see javaFlags. The static flag in the InnerClass describes + * a source-level propety: if the class is in a static context (does not have an outer pointer). + * This is checked when building the NestedInfo. + */ +case class NestedInfo(enclosingClass: ClassBType, + outerName: Option[String], + innerName: Option[String], + isStaticNestedClass: Boolean) + +/** + * This class holds the data for an entry in the InnerClass table. See the InnerClass summary + * above in this file. + * + * There's some overlap with the class NestedInfo, but it's not exactly the same and cleaner to + * keep separate. + * + * @param name The internal name of the class. + * @param outerName The internal name of the outer class, may be null. + * @param innerName The simple name of the inner class, may be null. + * @param flags The flags for this class in the InnerClass entry. + */ +case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) + +/** * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. * @@ -577,286 +627,294 @@ abstract class BTypes { self => * ClassBType is not a case class because we want a custom equals method, and because the * extractor extracts the internalName, which is what you typically need. */ - final class ClassBType(val internalName: String) extends RefBType { - /** - * Write-once variable allows initializing a cyclic graph of infos. This is required for - * nested classes. Example: for the definition `class A { class B }` we have - * - * B.info.nestedInfo.outerClass == A - * A.info.memberClasses contains B - */ - private var _info: ClassInfo = null - - def info: ClassInfo = { - assert(_info != null, s"ClassBType.info not yet assigned: $this") - _info - } +final class ClassBType private(val internalName: String, val fromSymbol: Boolean, private val ts: CoreBTypes) extends RefBType { + /** + * Write-once variable allows initializing a cyclic graph of infos. This is required for + * nested classes. Example: for the definition `class A { class B }` we have + * + * B.info.nestedInfo.outerClass == A + * A.info.memberClasses contains B + */ + private var _info: Either[NoClassBTypeInfo, ClassInfo] = null - def info_=(i: ClassInfo): Unit = { - assert(_info == null, s"Cannot set ClassBType.info multiple times: $this") - _info = i - checkInfoConsistency() - } + def info: Either[NoClassBTypeInfo, ClassInfo] = { + assert(_info != null, s"ClassBType.info not yet assigned: $this") + _info + } - classBTypeFromInternalNameMap(internalName) = this + def info_=(i: Either[NoClassBTypeInfo, ClassInfo]): Unit = { + assert(_info == null, s"Cannot set ClassBType.info multiple times: $this") + _info = i + checkInfoConsistency() + } - private def checkInfoConsistency(): Unit = { - // we assert some properties. however, some of the linked ClassBType (members, superClass, - // interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a - // best-effort verification. - def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) + override def isObjectType: Boolean = this == ts.ObjectRef + override def isJlCloneableType: Boolean = this == ts.jlCloneableRef + override def isJiSerializableType: Boolean = this == ts.jiSerializableRef + override def isNullType: Boolean = this == ts.srNullRef + override def isNothingType: Boolean = this == ts.srNothingRef + override def isBoxed: Boolean = this.isClass && ts.boxedClasses(this.asClassBType) - def isJLO(t: ClassBType) = t.internalName == "java/lang/Object" + private def checkInfoConsistency(): Unit = { + // we assert some properties. however, some of the linked ClassBType (members, superClass, + // interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a + // best-effort verification. + def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) - assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") + def isJLO(t: ClassBType) = t.internalName == "java/lang/Object" - assert( - if (info.superClass.isEmpty) { isJLO(this) || (DottyBackendInterface.isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } - else if (isInterface) isJLO(info.superClass.get) - else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface), - s"Invalid superClass in $this: ${info.superClass}" - ) - assert( - info.interfaces.forall(c => ifInit(c)(_.isInterface)), - s"Invalid interfaces in $this: ${info.interfaces}" - ) + assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") - assert(info.memberClasses.forall(c => ifInit(c)(_.isNestedClass)), info.memberClasses) - } + assert( + if (info.get.superClass.isEmpty) { isJLO(this) || ClassBType.hasNoSuper(internalName) } + else if (isInterface.get) isJLO(info.get.superClass.get) + else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get), + s"Invalid superClass in $this: ${info.get.superClass}" + ) + assert( + info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)), + s"Invalid interfaces in $this: ${info.get.interfaces}" + ) - /** - * 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 internalName: String = internalNameString(offset, length) + assert(info.get.nestedClasses.get.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses.get) + } + + /** + * 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 internalName: String = internalNameString(offset, length) - /** - * @return The class name without the package prefix - */ - def simpleName: String = internalName.split("/").last + /** + * @return The class name without the package prefix + */ + def simpleName: String = internalName.split("/").last - def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0 + def isInterface: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_INTERFACE) != 0) - def superClassesTransitive: List[ClassBType] = info.superClass match { - case None => Nil - case Some(sc) => sc :: sc.superClassesTransitive + /** The super class chain of this type, starting with Object, ending with `this`. */ + def superClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = try { + var res = List(this) + var sc = info.orThrow.superClass + while (sc.nonEmpty) { + res ::= sc.get + sc = sc.get.info.orThrow.superClass } + Right(res) + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => Left(noInfo) + } - def isNestedClass = info.nestedInfo.isDefined - - def enclosingNestedClassesChain: List[ClassBType] = - if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain - else Nil - - def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { - case NestedInfo(_, outerName, innerName, isStaticNestedClass) => - import GenBCodeOps.addFlagIf - InnerClassEntry( - internalName, - outerName.orNull, - innerName.orNull, - info.flags.addFlagIf(isStaticNestedClass, asm.Opcodes.ACC_STATIC) - & ClassBType.INNER_CLASSES_FLAGS - ) + /** + * The prefix of the internal name until the last '/', or the empty string. + */ + def packageInternalName: String = { + val name = internalName + name.lastIndexOf('/') match { + case -1 => "" + case i => name.substring(0, i) } + } - def isSubtypeOf(other: ClassBType): Boolean = { - if (this == other) return true - - if (isInterface) { - if (other == ObjectRef) return true // interfaces conform to Object - if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. - // else: this and other are both interfaces. continue to (*) - } else { - val sc = info.superClass - if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other - if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform - // else: this is a class, the other is an interface. continue to (*) - } + def isPublic: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_PUBLIC) != 0) - // (*) check if some interface of this class conforms to other. - info.interfaces.exists(_.isSubtypeOf(other)) + def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.get.isDefined) + + def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = { + isNestedClass.flatMap(isNested => { + // if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined. + if (isNested) info.get.nestedInfo.get.get.enclosingClass.enclosingNestedClassesChain.map(this :: _) + else Right(Nil) + }) + } + + def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo.get map { + case NestedInfo(_, outerName, innerName, isStaticNestedClass) => + // the static flag in the InnerClass table has a special meaning, see InnerClass comment + def adjustStatic(flags: Int): Int = (flags & ~Opcodes.ACC_STATIC | + (if (isStaticNestedClass) Opcodes.ACC_STATIC else 0) + ) & BCodeHelpers.INNER_CLASSES_FLAGS + InnerClassEntry( + internalName, + outerName.orNull, + innerName.orNull, + flags = adjustStatic(i.flags) + ) + }) + + def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try { + if (this == other) return Right(true) + if (isInterface.orThrow) { + if (other == ts.ObjectRef) return Right(true) // interfaces conform to Object + if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. + // else: this and other are both interfaces. continue to (*) + } else { + val sc = info.orThrow.superClass + if (sc.isDefined && sc.get.isSubtypeOf(other).orThrow) return Right(true) // the superclass of this class conforms to other + if (!other.isInterface.orThrow) return Right(false) // this and other are both classes, and the superclass of this does not conform + // else: this is a class, the other is an interface. continue to (*) } - /** - * Finding the least upper bound in agreement with the bytecode verifier - * Background: - * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf - * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 - * https://issues.scala-lang.org/browse/SI-3872 - */ - def jvmWiseLUB(other: ClassBType): ClassBType = { - def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType - assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other") - - val res: ClassBType = (this.isInterface, other.isInterface) match { + // (*) check if some interface of this class conforms to other. + Right(info.orThrow.interfaces.exists(_.isSubtypeOf(other).orThrow)) + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => Left(noInfo) + } + + /** + * Finding the least upper bound in agreement with the bytecode verifier + * Background: + * http://gallium.inria.fr/~xleroy/publi/bytecode-verification-JAR.pdf + * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 + * https://issues.scala-lang.org/browse/SI-3872 + */ + def jvmWiseLUB(other: ClassBType): Either[NoClassBTypeInfo, ClassBType] = { + def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType + assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLUB for null or nothing: $this - $other") + + tryEither { + val res: ClassBType = (this.isInterface.orThrow, other.isInterface.orThrow) match { case (true, true) => // exercised by test/files/run/t4761.scala - if (other.isSubtypeOf(this)) this - else if (this.isSubtypeOf(other)) other - else ObjectRef + if (other.isSubtypeOf(this).orThrow) this + else if (this.isSubtypeOf(other).orThrow) other + else ts.ObjectRef case (true, false) => - if (other.isSubtypeOf(this)) this else ObjectRef + if (other.isSubtypeOf(this).orThrow) this else ts.ObjectRef case (false, true) => - if (this.isSubtypeOf(other)) other else ObjectRef + if (this.isSubtypeOf(other).orThrow) other else ts.ObjectRef case _ => - // TODO @lry I don't really understand the reasoning here. - // Both this and other are classes. The code takes (transitively) all superclasses and - // finds the first common one. - // MOST LIKELY the answer can be found here, see the comments and links by Miguel: - // - https://issues.scala-lang.org/browse/SI-3872 - firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive) + firstCommonSuffix(superClassesChain.orThrow, other.superClassesChain.orThrow) } - assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") - res - } - - private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { - var chainA = as - var chainB = bs - var fcs: ClassBType = null - while { - if (chainB contains chainA.head) fcs = chainA.head - else if (chainA contains chainB.head) fcs = chainB.head - else { - chainA = chainA.tail - chainB = chainB.tail - } - fcs == null - } do () - fcs - } - - /** - * Custom equals / hashCode: we only compare the name (offset / length) - */ - override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { - case c: ClassBType @unchecked => c.internalName == this.internalName - case _ => false - }) - - override def hashCode: Int = { - import scala.runtime.Statics - var acc: Int = -889275714 - acc = Statics.mix(acc, internalName.hashCode) - Statics.finalizeHash(acc, 2) + assert(isNotNullOrNothing(res), s"jvmWiseLUB computed: $res") + Right(res) } } - object ClassBType { - /** - * Pattern matching on a ClassBType extracts the `internalName` of the class. - */ - def unapply(c: ClassBType): Some[String] = Some(c.internalName) - - /** - * Valid flags for InnerClass attribute entry. - * See http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.6 - */ - private val INNER_CLASSES_FLAGS = { - asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_PRIVATE | asm.Opcodes.ACC_PROTECTED | - asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL | asm.Opcodes.ACC_INTERFACE | - asm.Opcodes.ACC_ABSTRACT | asm.Opcodes.ACC_SYNTHETIC | asm.Opcodes.ACC_ANNOTATION | - asm.Opcodes.ACC_ENUM + private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { + var chainA = as.tail + var chainB = bs.tail + var fcs = ts.ObjectRef + while (chainA.nonEmpty && chainB.nonEmpty && chainA.head == chainB.head) { + fcs = chainA.head + chainA = chainA.tail + chainB = chainB.tail } - - // Primitive classes have no super class. A ClassBType for those is only created when - // they are actually being compiled (e.g., when compiling scala/Boolean.scala). - private val hasNoSuper = Set( - "scala/Unit", - "scala/Boolean", - "scala/Char", - "scala/Byte", - "scala/Short", - "scala/Int", - "scala/Float", - "scala/Long", - "scala/Double" - ) - - private val isInternalPhantomType = Set( - "scala/Null", - "scala/Nothing" - ) + fcs } /** - * The type info for a class. Used for symboltable-independent subtype checks in the backend. - * - * @param superClass The super class, not defined for class java/lang/Object. - * @param interfaces All transitively implemented interfaces, except for those inherited - * through the superclass. - * @param flags The java flags, obtained through `javaFlags`. Used also to derive - * the flags for InnerClass entries. - * @param memberClasses Classes nested in this class. Those need to be added to the - * InnerClass table, see the InnerClass spec summary above. - * @param nestedInfo If this describes a nested class, information for the InnerClass table. + * Custom equals / hashCode: we only compare the name (offset / length) */ - case class ClassInfo(superClass: Option[ClassBType], interfaces: List[ClassBType], flags: Int, - memberClasses: List[ClassBType], nestedInfo: Option[NestedInfo]) + override def equals(o: Any): Boolean = (this eq o.asInstanceOf[Object]) || (o match { + case c: ClassBType @unchecked => c.internalName == this.internalName + case _ => false + }) + + override def hashCode: Int = { + import scala.runtime.Statics + var acc: Int = -889275714 + acc = Statics.mix(acc, internalName.hashCode) + Statics.finalizeHash(acc, 2) + } +} + +object ClassBType { /** - * Information required to add a class to an InnerClass table. - * The spec summary above explains what information is required for the InnerClass entry. - * - * @param enclosingClass The enclosing class, if it is also nested. When adding a class - * to the InnerClass table, enclosing nested classes are also added. - * @param outerName The outerName field in the InnerClass entry, may be None. - * @param innerName The innerName field, may be None. - * @param isStaticNestedClass True if this is a static nested class (not inner class) (*) - * - * (*) Note that the STATIC flag in ClassInfo.flags, obtained through javaFlags(classSym), is not - * correct for the InnerClass entry, see javaFlags. The static flag in the InnerClass describes - * a source-level propety: if the class is in a static context (does not have an outer pointer). - * This is checked when building the NestedInfo. + * Retrieve the `ClassBType` for the class with the given internal name, creating the entry if it doesn't + * already exist in the cache + * + * @param internalName The name of the class + * @param t A value that will be passed to the `init` function. For efficiency, callers should use this + * value rather than capturing it in the `init` lambda, allowing that lambda to be hoisted. + * @param fromSymbol Is this type being initialized from a `Symbol`, rather than from byte code? + * @param ts The core types associated with the compilation + * @param cache The cache to use. If you're wondering what to pass here, you're in the wrong place and should not be directly calling this. + * @param init Function to initialize the info of this `BType`. During execution of this function, + * code _may_ reenter into `apply(internalName, ...)` and retrieve the initializing + * `ClassBType`. + * @tparam T The type of the state that will be threaded into the `init` function. + * @return The `ClassBType` */ - case class NestedInfo(enclosingClass: ClassBType, - outerName: Option[String], - innerName: Option[String], - isStaticNestedClass: Boolean) + final def apply[T](internalName: InternalName, t: T, fromSymbol: Boolean, ts: CoreBTypes, cache: ConcurrentHashMap[InternalName, ClassBType])(init: (ClassBType, T) => Either[NoClassBTypeInfo, ClassInfo]): ClassBType = { + val cached = cache.get(internalName) + if cached ne null then cached + else { + val newRes = new ClassBType(internalName, fromSymbol, ts) + // synchronized is required to ensure proper initialization of info. + // see comment on def info + newRes.synchronized { + cache.putIfAbsent(internalName, newRes) match { + case null => + newRes._info = init(newRes, t) + newRes.checkInfoConsistency() + newRes + case old => + old + } + } + } + } /** - * This class holds the data for an entry in the InnerClass table. See the InnerClass summary - * above in this file. - * - * There's some overlap with the class NestedInfo, but it's not exactly the same and cleaner to - * keep separate. - * @param name The internal name of the class. - * @param outerName The internal name of the outer class, may be null. - * @param innerName The simple name of the inner class, may be null. - * @param flags The flags for this class in the InnerClass entry. + * Pattern matching on a ClassBType extracts the `internalName` of the class. */ - case class InnerClassEntry(name: String, outerName: String, innerName: String, flags: Int) + def unapply(c: ClassBType): Some[String] = Some(c.internalName) + + // Primitive classes have no super class. A ClassBType for those is only created when + // they are actually being compiled (e.g., when compiling scala/Boolean.scala). + private val hasNoSuper = Set( + "scala/Unit", + "scala/Boolean", + "scala/Char", + "scala/Byte", + "scala/Short", + "scala/Int", + "scala/Float", + "scala/Long", + "scala/Double" + ) + + private val isInternalPhantomType = Set( + "scala/Null", + "scala/Nothing" + ) +} - case class ArrayBType(componentType: BType) extends RefBType { - def dimension: Int = componentType match { - case a: ArrayBType => 1 + a.dimension - case _ => 1 - } +case class ArrayBType(componentType: BType) extends RefBType { - def elementType: BType = componentType match { - case a: ArrayBType => a.elementType - case t => t - } + override def isObjectType: Boolean = false + override def isJlCloneableType: Boolean = false + override def isJiSerializableType: Boolean = false + override def isNullType: Boolean = false + override def isNothingType: Boolean = false + override def isBoxed: Boolean = false + + def dimension: Int = componentType match { + case a: ArrayBType => 1 + a.dimension + case _ => 1 } - case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType + def elementType: BType = componentType match { + case a: ArrayBType => a.elementType + case t => t + } +} - /* Some definitions that are required for the implementation of BTypes. They are abstract because - * initializing them requires information from types / symbols, which is not accessible here in - * BTypes. - * - * They are defs (not vals) because they are implemented using vars (see comment on CoreBTypes). - */ +case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType { + + override def isObjectType: Boolean = false + override def isJlCloneableType: Boolean = false + override def isJiSerializableType: Boolean = false + override def isNullType: Boolean = false + override def isNothingType: Boolean = false + override def isBoxed: Boolean = false - /** - * Just a named pair, used in CoreBTypes.asmBoxTo/asmUnboxTo. - */ - /*final*/ case class MethodNameAndType(name: String, methodType: MethodBType) } object BTypes { diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala deleted file mode 100644 index 68c6add4ef13..000000000000 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ /dev/null @@ -1,323 +0,0 @@ -package dotty.tools -package backend -package jvm - -import scala.tools.asm -import scala.annotation.threadUnsafe -import scala.collection.mutable -import scala.collection.mutable.Clearable - -import dotty.tools.dotc.core.Flags.* -import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Phases.* -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.core.Phases.Phase - -import dotty.tools.dotc.core.StdNames -import dotty.tools.dotc.core.Phases - -/** - * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary - * information from a symbol and its type to create the corresponding ClassBType. It requires - * access to the compiler (global parameter). - */ -class BTypesFromSymbols[I <: DottyBackendInterface](val int: I, val frontendAccess: PostProcessorFrontendAccess) extends BTypes { - import int.{_, given} - import DottyBackendInterface.{symExtensions, _} - - lazy val TransientAttr = requiredClass[scala.transient] - lazy val VolatileAttr = requiredClass[scala.volatile] - - val bCodeAsmCommon: BCodeAsmCommon[int.type ] = new BCodeAsmCommon(int) - import bCodeAsmCommon.* - - val coreBTypes = new CoreBTypesFromSymbols[I]{ - val bTypes: BTypesFromSymbols.this.type = BTypesFromSymbols.this - } - import coreBTypes.* - - @threadUnsafe protected lazy val classBTypeFromInternalNameMap = - collection.concurrent.TrieMap.empty[String, ClassBType] - - /** - * Cache for the method classBTypeFromSymbol. - */ - @threadUnsafe private lazy val convertedClasses = collection.mutable.HashMap.empty[Symbol, ClassBType] - - /** - * The ClassBType for a class symbol `sym`. - */ - final def classBTypeFromSymbol(classSym: Symbol): ClassBType = { - assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") - assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") - assert( - (!primitiveTypeMap.contains(classSym) || isCompilingPrimitive) && - (classSym != defn.NothingClass && classSym != defn.NullClass), - s"Cannot create ClassBType for special class symbol ${classSym.showFullName}") - - convertedClasses.synchronized: - convertedClasses.getOrElse(classSym, { - val internalName = classSym.javaBinaryName - // We first create and add the ClassBType to the hash map before computing its info. This - // allows initializing cylic dependencies, see the comment on variable ClassBType._info. - val classBType = new ClassBType(internalName) - convertedClasses(classSym) = classBType - setClassInfo(classSym, classBType) - }) - } - - final def mirrorClassBTypeFromSymbol(moduleClassSym: Symbol): ClassBType = { - assert(moduleClassSym.isTopLevelModuleClass, s"not a top-level module class: $moduleClassSym") - val internalName = moduleClassSym.javaBinaryName.stripSuffix(StdNames.str.MODULE_SUFFIX) - val bType = ClassBType(internalName) - bType.info = ClassInfo( - superClass = Some(ObjectRef), - interfaces = Nil, - flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, - memberClasses = getMemberClasses(moduleClassSym).map(classBTypeFromSymbol), - nestedInfo = None - ) - bType - } - - private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { - val superClassSym: Symbol = { - val t = classSym.asClass.superClass - if (t.exists) t - else if (classSym.is(ModuleClass)) { - // workaround #371 - - println(s"Warning: mocking up superclass for $classSym") - defn.ObjectClass - } - else t - } - assert( - if (classSym == defn.ObjectClass) - superClassSym == NoSymbol - else if (classSym.isInterface) - superClassSym == defn.ObjectClass - else - // A ClassBType for a primitive class (scala.Boolean et al) is only created when compiling these classes. - ((superClassSym != NoSymbol) && !superClassSym.isInterface) || (isCompilingPrimitive && primitiveTypeMap.contains(classSym)), - s"Bad superClass for $classSym: $superClassSym" - ) - val superClass = if (superClassSym == NoSymbol) None - else Some(classBTypeFromSymbol(superClassSym)) - - /** - * All interfaces implemented by a class, except for those inherited through the superclass. - * Redundant interfaces are removed unless there is a super call to them. - */ - extension (sym: Symbol) def superInterfaces: List[Symbol] = { - val directlyInheritedTraits = sym.directlyInheritedTraits - val directlyInheritedTraitsSet = directlyInheritedTraits.toSet - val allBaseClasses = directlyInheritedTraits.iterator.flatMap(_.asClass.baseClasses.drop(1)).toSet - val superCalls = superCallsMap.getOrElse(sym, List.empty) - val superCallsSet = superCalls.toSet - val additional = superCalls.filter(t => !directlyInheritedTraitsSet(t) && t.is(Trait)) -// if (additional.nonEmpty) -// println(s"$fullName: adding supertraits $additional") - directlyInheritedTraits.filter(t => !allBaseClasses(t) || superCallsSet(t)) ++ additional - } - - val interfaces = classSym.superInterfaces.map(classBTypeFromSymbol) - - val flags = javaFlags(classSym) - - /* The InnerClass table of a class C must contain all nested classes of C, even if they are only - * declared but not otherwise referenced in C (from the bytecode or a method / field signature). - * We collect them here. - */ - val nestedClassSymbols = { - // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect - // member classes right after lambdalift, we obtain all nested classes, including local and - // anonymous ones. - val nestedClasses = getNestedClasses(classSym) - - // If this is a top-level class, and it has a companion object, the member classes of the - // companion are added as members of the class. For example: - // class C { } - // object C { - // class D - // def f = { class E } - // } - // The class D is added as a member of class C. The reason is that the InnerClass attribute - // for D will containt class "C" and NOT the module class "C$" as the outer class of D. - // This is done by buildNestedInfo, the reason is Java compatibility, see comment in BTypes. - // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks - // like D is a member of C, not C$. - val linkedClass = classSym.linkedClass - val companionModuleMembers = { - if (classSym.linkedClass.isTopLevelModuleClass) getMemberClasses(classSym.linkedClass) - else Nil - } - - nestedClasses ++ companionModuleMembers - } - - /** - * For nested java classes, the scala compiler creates both a class and a module (and therefore - * a module class) symbol. For example, in `class A { class B {} }`, the nestedClassSymbols - * for A contain both the class B and the module class B. - * Here we get rid of the module class B, making sure that the class B is present. - */ - val nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => { - if (s.is(JavaDefined) && s.is(ModuleClass)) { - // We could also search in nestedClassSymbols for s.linkedClassOfClass, but sometimes that - // returns NoSymbol, so it doesn't work. - val nb = nestedClassSymbols.count(mc => mc.name == s.name && mc.owner == s.owner) - // this assertion is specific to how ScalaC works. It doesn't apply to dotty, as n dotty there will be B & B$ - // assert(nb == 2, s"Java member module without member class: $s - $nestedClassSymbols") - false - } else true - }) - - val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) - - val nestedInfo = buildNestedInfo(classSym) - - classBType.info = ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo) - classBType - } - - /** For currently compiled classes: All locally defined classes including local classes. - * The empty list for classes that are not currently compiled. - */ - private def getNestedClasses(sym: Symbol): List[Symbol] = definedClasses(sym, flattenPhase) - - /** For currently compiled classes: All classes that are declared as members of this class - * (but not inherited ones). The empty list for classes that are not currently compiled. - */ - private def getMemberClasses(sym: Symbol): List[Symbol] = definedClasses(sym, lambdaLiftPhase) - - private def definedClasses(sym: Symbol, phase: Phase) = - if (sym.isDefinedInCurrentRun) - atPhase(phase) { - toDenot(sym).info.decls.filter(sym => sym.isClass && !sym.isEffectivelyErased) - } - else Nil - - private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = { - assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") - - val isNested = !innerClassSym.originalOwner.originalLexicallyEnclosingClass.is(PackageClass) - if (!isNested) None - else { - // See comment in BTypes, when is a class marked static in the InnerClass table. - val isStaticNestedClass = innerClassSym.originalOwner.originalLexicallyEnclosingClass.isOriginallyStaticOwner - - // After lambdalift (which is where we are), the rawowoner field contains the enclosing class. - val enclosingClassSym = { - if (innerClassSym.isClass) { - atPhase(flattenPhase.prev) { - toDenot(innerClassSym).owner.enclosingClass - } - } - else atPhase(flattenPhase.prev)(innerClassSym.enclosingClass) - } //todo is handled specially for JavaDefined symbols in scalac - - val enclosingClass: ClassBType = classBTypeFromSymbol(enclosingClassSym) - - val outerName: Option[String] = { - if (isAnonymousOrLocalClass(innerClassSym)) { - None - } else { - val outerName = innerClassSym.originalOwner.originalLexicallyEnclosingClass.javaBinaryName - def dropModule(str: String): String = - if (!str.isEmpty && str.last == '$') str.take(str.length - 1) else str - // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. - val outerNameModule = - if (innerClassSym.originalOwner.originalLexicallyEnclosingClass.isTopLevelModuleClass) dropModule(outerName) - else outerName - Some(outerNameModule.toString) - } - } - - val innerName: Option[String] = { - if (innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction) None - else { - val original = innerClassSym.initial - Some(atPhase(original.validFor.phaseId)(innerClassSym.name).mangledString) // moduleSuffix for module classes - } - } - - Some(NestedInfo(enclosingClass, outerName, innerName, isStaticNestedClass)) - } - } - - /** - * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. - * - * The problem is that we are interested in a source-level property. Various phases changed the - * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. - * Therefore, `sym.isStatic` is not what we want. For example, in - * object T { def f { object U } } - * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. - */ - extension (sym: Symbol) - private def isOriginallyStaticOwner: Boolean = - sym.is(PackageClass) || sym.is(ModuleClass) && sym.originalOwner.originalLexicallyEnclosingClass.isOriginallyStaticOwner - - /** - * Return the Java modifiers for the given symbol. - * Java modifiers for classes: - * - public, abstract, final, strictfp (not used) - * for interfaces: - * - the same as for classes, without 'final' - * for fields: - * - public, private (*) - * - static, final - * for methods: - * - the same as for fields, plus: - * - abstract, synchronized (not used), strictfp (not used), native (not used) - * for all: - * - deprecated - * - * (*) protected cannot be used, since inner classes 'see' protected members, - * and they would fail verification after lifted. - */ - final def javaFlags(sym: Symbol): Int = { - - // Classes are always emitted as public. This matches the behavior of Scala 2 - // and is necessary for object deserialization to work properly, otherwise - // ModuleSerializationProxy may fail with an accessiblity error (see - // tests/run/serialize.scala and https://github.com/typelevel/cats-effect/pull/2360). - val privateFlag = !sym.isClass && (sym.is(Private) || (sym.isPrimaryConstructor && sym.owner.isTopLevelModuleClass)) - - val finalFlag = sym.is(Final) && !toDenot(sym).isClassConstructor && !sym.isMutableVar && !sym.enclosingClass.is(Trait) - - import asm.Opcodes.* - import GenBCodeOps.addFlagIf - 0 .addFlagIf(privateFlag, ACC_PRIVATE) - .addFlagIf(!privateFlag, ACC_PUBLIC) - .addFlagIf(sym.is(Deferred) || sym.isOneOf(AbstractOrTrait), ACC_ABSTRACT) - .addFlagIf(sym.isInterface, ACC_INTERFACE) - .addFlagIf(finalFlag - // Primitives are "abstract final" to prohibit instantiation - // without having to provide any implementations, but that is an - // illegal combination of modifiers at the bytecode level so - // suppress final if abstract if present. - && !sym.isOneOf(AbstractOrTrait) - // Bridges can be final, but final bridges confuse some frameworks - && !sym.is(Bridge), ACC_FINAL) - .addFlagIf(sym.isStaticMember, ACC_STATIC) - .addFlagIf(sym.is(Bridge), ACC_BRIDGE | ACC_SYNTHETIC) - .addFlagIf(sym.is(Artifact), ACC_SYNTHETIC) - .addFlagIf(sym.isClass && !sym.isInterface, ACC_SUPER) - .addFlagIf(sym.isAllOf(JavaEnum), ACC_ENUM) - .addFlagIf(sym.is(JavaVarargs), ACC_VARARGS) - .addFlagIf(sym.is(Synchronized), ACC_SYNCHRONIZED) - .addFlagIf(sym.isDeprecated, ACC_DEPRECATED) - .addFlagIf(sym.is(Enum), ACC_ENUM) - } - - def javaFieldFlags(sym: Symbol) = { - import asm.Opcodes.* - import GenBCodeOps.addFlagIf - javaFlags(sym) - .addFlagIf(sym.hasAnnotation(TransientAttr), ACC_TRANSIENT) - .addFlagIf(sym.hasAnnotation(VolatileAttr), ACC_VOLATILE) - .addFlagIf(!sym.is(Mutable), ACC_FINAL) - } -} diff --git a/compiler/src/dotty/tools/backend/jvm/BackendReporting.scala b/compiler/src/dotty/tools/backend/jvm/BackendReporting.scala new file mode 100644 index 000000000000..1d2eba91b0f5 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/BackendReporting.scala @@ -0,0 +1,151 @@ +/* + * Scala (https://www.scala-lang.org) + * + * Copyright EPFL and Lightbend, Inc. dba Akka + * + * Licensed under Apache License 2.0 + * (http://www.apache.org/licenses/LICENSE-2.0). + * + * See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + */ + +package dotty.tools +package backend.jvm + +import scala.tools.asm.tree.{AbstractInsnNode, MethodNode} +import dotty.tools.backend.jvm.BTypes.InternalName +import dotty.tools.backend.jvm.PostProcessorFrontendAccess.CompilerSettings +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.report +import dotty.tools.dotc.reporting.Message + +import scala.util.control.ControlThrowable +import dotty.tools.dotc.util.{NoSourcePosition, SourcePosition, SrcPos} +import dotty.tools.io.FileWriters.{BufferingReporter, Report} + + +sealed trait BackendReporting { + def error(message: Context ?=> Message, position: SourcePosition): Unit + + def warning(message: Context ?=> Message, position: SourcePosition): Unit + + def optimizerWarning(message: Context ?=> Message, site: String, position: SourcePosition): Unit + + def log(message: String): Unit + + def error(message: Context ?=> Message): Unit = error(message, NoSourcePosition) + + def warning(message: Context ?=> Message): Unit = warning(message, NoSourcePosition) + + def siteString(owner: String, method: String): String = { + val c = owner.replace('/', '.').replaceAll("\\$+", ".").replaceAll("\\.$", "") + if (method.isEmpty) c + else s"$c.$method" + } + + def relay(r: Report)(using ctx: Context): Unit = r match { + case Report.Error(m, pos) => error(m(ctx), pos) + case Report.Warning(m, pos) => warning(m(ctx), pos) + case Report.OptimizerWarning(m, s, pos) => warning(m(ctx), pos) // TODO use the site + case Report.Log(m) => log(m) + } + + /** Should only be called from main compiler thread. */ + def relayReports(fromReporting: BufferingReporter)(using Context): Unit = + val reports = fromReporting.resetReports() + if reports.nonEmpty then + reports.reverse.foreach(relay) +} + +final class BufferingBackendReporting(using Context) extends BackendReporting { + // We optimize access to the buffered reports for the common case - that there are no warning/errors to report + // We could use a listBuffer etc. - but that would be extra allocation in the common case + // Note - all access is externally synchronized, as this allow the reports to be generated in on thread and + // consumed in another + private var bufferedReports = List.empty[Report] + + def error(message: Context ?=> Message, position: SourcePosition): Unit = synchronized: + bufferedReports ::= Report.Error({case given Context => message}, position) + + def warning(message: Context ?=> Message, position: SourcePosition): Unit = synchronized: + bufferedReports ::= Report.Warning({case given Context => message}, position) + + def optimizerWarning(message: Context ?=> Message, site: String, position: SourcePosition): Unit = synchronized: + bufferedReports ::= Report.OptimizerWarning({case given Context => message}, site, position) + + def log(message: String): Unit = synchronized: + bufferedReports ::= Report.Log(message) + + def relayReports(toReporting: BackendReporting): Unit = synchronized: + if bufferedReports.nonEmpty then + bufferedReports.reverse.foreach(toReporting.relay) + bufferedReports = Nil +} + +final class DirectBackendReporting(ppa: PostProcessorFrontendAccess)(using Context) extends BackendReporting { + def error(message: Context ?=> Message, position: SourcePosition): Unit = ppa.frontendSynch(report.error(message, position)) + def warning(message: Context ?=> Message, position: SourcePosition): Unit = ppa.frontendSynch(report.warning(message, position)) + def optimizerWarning(message: Context ?=> Message, site: String, position: SourcePosition): Unit = ppa.frontendSynch(report.warning(message, position, site)) + def log(message: String): Unit = ppa.frontendSynch(report.log(message)) +} + +/** + * Utilities for error reporting. + * + * Defines some utility methods to make error reporting with Either easier. + */ +object BackendReporting { + def methodSignature(classInternalName: InternalName, name: String, desc: String) = { + classInternalName + "::" + name + desc + } + + def methodSignature(classInternalName: InternalName, method: MethodNode): String = { + methodSignature(classInternalName, method.name, method.desc) + } + + def assertionError(message: String): Nothing = throw new AssertionError(message) + + implicit class RightBiasedEither[A, B](val v: Either[A, B]) extends AnyVal { + def withFilter(f: B => Boolean)(implicit empty: A): Either[A, B] = v.filterOrElse(f, empty) + + /** Get the value, fail with an assertion if this is an error. */ + def get: B = v.fold(a => assertionError(s"$a"), identity) + + /** + * Get the right value of an `Either` by throwing a potential error message. Can simplify the + * implementation of methods that act on multiple `Either` instances. Instead of flat-mapping, + * the first error can be collected as + * + * tryEither { + * eitherOne.orThrow .... eitherTwo.orThrow ... eitherThree.orThrow + * } + */ + def orThrow: B = v.fold(a => throw Invalid(a), identity) + } + + case class Invalid[A](e: A) extends ControlThrowable + + /** + * See documentation of orThrow above. + */ + def tryEither[A, B](op: => Either[A, B]): Either[A, B] = try { op } catch { case Invalid(e) => Left(e.asInstanceOf[A]) } + + sealed trait OptimizerWarning { + def emitWarning(settings: CompilerSettings): Boolean + } + + object OptimizerWarning { + // Method withFilter in RightBiasedEither requires an implicit empty value. Taking the value here + // in scope allows for-comprehensions that desugar into withFilter calls (for example when using a + // tuple de-constructor). + implicit val emptyOptimizerWarning: OptimizerWarning = new OptimizerWarning { + def emitWarning(settings: CompilerSettings): Boolean = false + } + } + + // TODO will be implemented along with optimizer -- depends on new compiler settings, etc. + sealed trait NoClassBTypeInfo extends OptimizerWarning { + def emitWarning(settings: CompilerSettings): Boolean = false + } +} diff --git a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala index c2b4fdf24565..c62a9fce2d99 100644 --- a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala @@ -1,30 +1,38 @@ -package dotty.tools.backend.jvm +package dotty.tools +package backend.jvm import scala.tools.asm -import scala.tools.asm.Handle -import scala.tools.asm.tree.InvokeDynamicInsnNode +import scala.tools.asm.{Handle, Type} +import scala.tools.asm.tree.* import asm.tree.ClassNode import scala.collection.mutable +import scala.collection.BitSet import scala.jdk.CollectionConverters.* import dotty.tools.dotc.report +import dotty.tools.dotc.core.Contexts.Context +import scala.language.unsafeNulls + +import scala.tools.asm.Opcodes +import dotty.tools.backend.jvm.BTypes.InternalName + +import scala.annotation.switch +import java.util.concurrent.ConcurrentHashMap +import PostProcessorFrontendAccess.Lazy +import dotty.tools.dotc.util.SourcePosition /** - * This component hosts tools and utilities used in the backend that require access to a `BTypes` + * This component hosts tools and utilities used in the backend that require access to a `CoreBTypes` * instance. */ -class BackendUtils(val postProcessor: PostProcessor) { - import postProcessor.{bTypes, frontendAccess} - import frontendAccess.{compilerSettings} - import bTypes.* - import coreBTypes.jliLambdaMetaFactoryAltMetafactoryHandle +class BackendUtils(val ppa: PostProcessorFrontendAccess, val ts: CoreBTypes)(using Context) { - lazy val classfileVersion: Int = BackendUtils.classfileVersionMap(compilerSettings.target.toInt) + lazy val classfileVersion: Int = BackendUtils.classfileVersionMap(ppa.compilerSettings.target.toInt) lazy val extraProc: Int = { import GenBCodeOps.addFlagIf - val majorVersion: Int = (classfileVersion & 0xFF) - val emitStackMapFrame = (majorVersion >= 50) + val majorVersion: Int = classfileVersion & 0xFF + val emitStackMapFrame = majorVersion >= 50 asm.ClassWriter.COMPUTE_MAXS .addFlagIf(emitStackMapFrame, asm.ClassWriter.COMPUTE_FRAMES) } @@ -37,7 +45,7 @@ class BackendUtils(val postProcessor: PostProcessor) { val insn = iter.next() insn match { case indy: InvokeDynamicInsnNode - if indy.bsm == jliLambdaMetaFactoryAltMetafactoryHandle => + if indy.bsm == ts.jliLambdaMetaFactoryAltMetafactoryHandle => import java.lang.invoke.LambdaMetafactory.FLAG_SERIALIZABLE val metafactoryFlags = indy.bsmArgs(3).asInstanceOf[Integer].toInt val isSerializable = (metafactoryFlags & FLAG_SERIALIZABLE) != 0 @@ -64,6 +72,8 @@ class BackendUtils(val postProcessor: PostProcessor) { * ... * return indy[scala.runtime.LambdaDeserialize.bootstrap, targetMethodGroup${NUM_GROUPS-1}](l) * } + * } + * } * * We use invokedynamic here to enable caching within the deserializer without needing to * host a static field in the enclosing class. This allows us to add this method to interfaces @@ -75,8 +85,6 @@ class BackendUtils(val postProcessor: PostProcessor) { */ def addLambdaDeserialize(classNode: ClassNode, implMethodsArray: Array[Handle]): Unit = { import asm.Opcodes.* - import bTypes.* - import coreBTypes.* val cw = classNode @@ -89,10 +97,10 @@ class BackendUtils(val postProcessor: PostProcessor) { val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serializedLamdaObjDesc, null, null) def emitLambdaDeserializeIndy(targetMethods: Seq[Handle]): Unit = { mv.visitVarInsn(ALOAD, 0) - mv.visitInvokeDynamicInsn("lambdaDeserialize", serializedLamdaObjDesc, jliLambdaDeserializeBootstrapHandle, targetMethods*) + mv.visitInvokeDynamicInsn("lambdaDeserialize", serializedLamdaObjDesc, ts.jliLambdaDeserializeBootstrapHandle, targetMethods*) } - val targetMethodGroupLimit = 255 - 1 - 3 // JVM limit. See See MAX_MH_ARITY in CallSite.java + val targetMethodGroupLimit = 255 - 1 - 3 // JVM limit. See MAX_MH_ARITY in CallSite.java val groups: Array[Array[Handle]] = implMethodsArray.grouped(targetMethodGroupLimit).toArray val numGroups = groups.length @@ -102,7 +110,7 @@ class BackendUtils(val postProcessor: PostProcessor) { def nextLabel(i: Int) = if (i == numGroups - 2) terminalLabel else initialLabels(i + 1) for ((label, i) <- initialLabels.iterator.zipWithIndex) { - mv.visitTryCatchBlock(label, nextLabel(i), nextLabel(i), jlIllegalArgExceptionRef.internalName) + mv.visitTryCatchBlock(label, nextLabel(i), nextLabel(i), ts.jlIllegalArgExceptionRef.internalName) } for ((label, i) <- initialLabels.iterator.zipWithIndex) { mv.visitLabel(label) @@ -115,8 +123,7 @@ class BackendUtils(val postProcessor: PostProcessor) { } private lazy val serializedLamdaObjDesc = { - import coreBTypes.{ObjectRef, jliSerializedLambdaRef} - MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor + MethodBType(ts.jliSerializedLambdaRef :: Nil, ts.ObjectRef).descriptor } /** @@ -126,11 +133,11 @@ class BackendUtils(val postProcessor: PostProcessor) { // type InternalName = String val c = new NestedClassesCollector[ClassBType](nestedOnly = true) { def declaredNestedClasses(internalName: InternalName): List[ClassBType] = - bTypes.classBTypeFromInternalName(internalName).info.memberClasses + ts.classBTypeFromInternalName(internalName).info.get.nestedClasses.get def getClassIfNested(internalName: InternalName): Option[ClassBType] = { - val c = bTypes.classBTypeFromInternalName(internalName) - Option.when(c.isNestedClass)(c) + val c = ts.classBTypeFromInternalName(internalName) + Option.when(c.isNestedClass.get)(c) } def raiseError(msg: String, sig: String, e: Option[Throwable]): Unit = { @@ -157,14 +164,15 @@ class BackendUtils(val postProcessor: PostProcessor) { // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler val allNestedClasses = new mutable.TreeSet[ClassBType]()(using Ordering.by(_.internalName)) allNestedClasses ++= declaredInnerClasses - refedInnerClasses.foreach(allNestedClasses ++= _.enclosingNestedClassesChain) + refedInnerClasses.foreach(allNestedClasses ++= _.enclosingNestedClassesChain.get) for nestedClass <- allNestedClasses do { // Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes. - val Some(e) = nestedClass.innerClassAttributeEntry: @unchecked + val Some(e) = nestedClass.innerClassAttributeEntry.get: @unchecked jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags) } } + } object BackendUtils { diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index 093d4f997aa7..15c675098bad 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -4,10 +4,8 @@ package dotty.tools.backend.jvm import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.ast.Trees.{PackageDef, ValDef} import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.core.Phases.Phase import scala.collection.mutable -import scala.jdk.CollectionConverters.* import dotty.tools.dotc.interfaces import dotty.tools.dotc.report @@ -20,25 +18,19 @@ import Phases.* import Symbols.* import StdNames.nme -import java.io.DataOutputStream -import java.nio.channels.ClosedByInterruptException - import dotty.tools.tasty.{ TastyBuffer, TastyHeaderUnpickler } import dotty.tools.dotc.core.tasty.TastyUnpickler -import scala.tools.asm import scala.tools.asm.tree.* import tpd.* import dotty.tools.io.AbstractFile import dotty.tools.dotc.util import dotty.tools.dotc.util.NoSourcePosition +import DottyBackendInterface.symExtensions +class CodeGen(val backendUtils: BackendUtils, val primitives: DottyPrimitives, val frontendAccess: PostProcessorFrontendAccess, val ts: CoreBTypesFromSymbols)(using Context) { -class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( val bTypes: BTypesFromSymbols[int.type]) { self => - import DottyBackendInterface.symExtensions - import bTypes.* - - private lazy val mirrorCodeGen = Impl.JMirrorBuilder() + private lazy val mirrorCodeGen = impl.JMirrorBuilder() private def genBCode(using Context) = Phases.genBCodePhase.asInstanceOf[GenBCode] private def postProcessor(using Context) = genBCode.postProcessor @@ -90,7 +82,6 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( def genTastyAndSetAttributes(claszSymbol: Symbol, store: ClassNode): Unit = - import Impl.createJAttribute for (binary <- unit.pickled.get(claszSymbol.asClass)) { generatedTasty += GeneratedTasty(store, binary) val tasty = @@ -105,14 +96,14 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( buffer.writeUncompressedLong(hi) buffer.bytes - val dataAttr = createJAttribute(nme.TASTYATTR.mangledString, tasty, 0, tasty.length) + val dataAttr = impl.createJAttribute(nme.TASTYATTR.mangledString, tasty, 0, tasty.length) store.visitAttribute(dataAttr) } def genClassDefs(tree: Tree): Unit = tree match { case EmptyTree => () - case PackageDef(_, stats) => stats foreach genClassDefs + case PackageDef(_, stats) => stats.foreach(genClassDefs) case ValDef(_, _, _) => () // module val not emitted case td: TypeDef => frontendAccess.frontendSynch(genClassDef(td)) } @@ -156,7 +147,7 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( } private def genClass(cd: TypeDef, unit: CompilationUnit): ClassNode = { - val b = new Impl.SyncAndTryBuilder(unit) {} + val b = new impl.SyncAndTryBuilder(unit) b.genPlainClass(cd) b.cnode } @@ -165,11 +156,8 @@ class CodeGen(val int: DottyBackendInterface, val primitives: DottyPrimitives)( mirrorCodeGen.genMirrorClass(classSym, unit) } - - sealed transparent trait ImplEarlyInit{ - val int: self.int.type = self.int - val bTypes: self.bTypes.type = self.bTypes - protected val primitives: DottyPrimitives = self.primitives + private class Impl(using Context) extends BCodeHelpers(backendUtils), BCodeSkelBuilder, BCodeBodyBuilder(primitives), BCodeSyncAndTry { + val ts: CoreBTypesFromSymbols = CodeGen.this.ts } - object Impl extends ImplEarlyInit with BCodeSyncAndTry + private val impl = new Impl() } diff --git a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala index 41333c78cdad..e27c81343954 100644 --- a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala @@ -2,271 +2,82 @@ package dotty.tools package backend package jvm - -import dotty.tools.dotc.core.Symbols.* -import dotty.tools.dotc.transform.Erasure -import scala.tools.asm.{Handle, Opcodes} -import dotty.tools.dotc.core.StdNames +import java.util.concurrent.ConcurrentHashMap import BTypes.InternalName -import PostProcessorFrontendAccess.Lazy - -abstract class CoreBTypes { - val bTypes: BTypes - import bTypes.* - - def primitiveTypeMap: Map[Symbol, PrimitiveBType] - - def boxedClasses: Set[ClassBType] - - def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] - - def boxResultType: Map[Symbol, ClassBType] - - def unboxResultType: Map[Symbol, PrimitiveBType] - - def srNothingRef : ClassBType - def srNullRef : ClassBType - - def ObjectRef : ClassBType - def StringRef : ClassBType - def jlClassRef : ClassBType - def jlThrowableRef : ClassBType - def jlCloneableRef : ClassBType - def jiSerializableRef : ClassBType - def jlClassCastExceptionRef : ClassBType - def jlIllegalArgExceptionRef : ClassBType - def jliSerializedLambdaRef : ClassBType - - def srBoxesRuntimeRef: ClassBType - - def jliLambdaMetaFactoryMetafactoryHandle : Handle - def jliLambdaMetaFactoryAltMetafactoryHandle : Handle - def jliLambdaDeserializeBootstrapHandle : Handle - def jliStringConcatFactoryMakeConcatWithConstantsHandle: Handle - - def asmBoxTo : Map[BType, MethodNameAndType] - def asmUnboxTo: Map[BType, MethodNameAndType] - - def typeOfArrayOp: Map[Int, BType] -} - -abstract class CoreBTypesFromSymbols[I <: DottyBackendInterface] extends CoreBTypes { - val bTypes: BTypesFromSymbols[I] - - import bTypes.* - import DottyBackendInterface.* - import dotty.tools.dotc.core.Contexts.Context - import frontendAccess.perRunLazy - /** - * Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above - * the first use of `classBTypeFromSymbol` because that method looks at the map. - */ - override def primitiveTypeMap: Map[Symbol, bTypes.PrimitiveBType] = _primitiveTypeMap.get - private lazy val _primitiveTypeMap: Lazy[Map[Symbol, PrimitiveBType]] = perRunLazy: - Map( - defn.UnitClass -> UNIT, - defn.BooleanClass -> BOOL, - defn.CharClass -> CHAR, - defn.ByteClass -> BYTE, - defn.ShortClass -> SHORT, - defn.IntClass -> INT, - defn.LongClass -> LONG, - defn.FloatClass -> FLOAT, - defn.DoubleClass -> DOUBLE - ) - - /** - * Map from primitive types to their boxed class type. Useful when pushing class literals onto the - * operand stack (ldc instruction taking a class literal), see genConstant. - */ - override def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _boxedClassOfPrimitive.get - private lazy val _boxedClassOfPrimitive: Lazy[Map[PrimitiveBType, ClassBType]] = perRunLazy(Map( - UNIT -> classBTypeFromSymbol(requiredClass[java.lang.Void]), - BOOL -> classBTypeFromSymbol(requiredClass[java.lang.Boolean]), - BYTE -> classBTypeFromSymbol(requiredClass[java.lang.Byte]), - SHORT -> classBTypeFromSymbol(requiredClass[java.lang.Short]), - CHAR -> classBTypeFromSymbol(requiredClass[java.lang.Character]), - INT -> classBTypeFromSymbol(requiredClass[java.lang.Integer]), - LONG -> classBTypeFromSymbol(requiredClass[java.lang.Long]), - FLOAT -> classBTypeFromSymbol(requiredClass[java.lang.Float]), - DOUBLE -> classBTypeFromSymbol(requiredClass[java.lang.Double]) - )) - - lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet - - /** - * 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 ClassBType `java/lang/Byte`. - */ - override def boxResultType: Map[Symbol, ClassBType] = _boxResultType.get - private lazy val _boxResultType: Lazy[Map[Symbol, ClassBType]] = perRunLazy{ - val boxMethods = defn.ScalaValueClasses().map{x => // @darkdimius Are you sure this should be a def? - (x, Erasure.Boxing.boxMethod(x.asClass)) - }.toMap - for ((valueClassSym, boxMethodSym) <- boxMethods) - yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym)) - } - - /** - * Maps the method symbol for an unbox method to the primitive type of the result. - * For example, the method symbol for `Byte.unbox()`) is mapped to the PrimitiveBType BYTE. */ - override def unboxResultType: Map[Symbol, PrimitiveBType] = _unboxResultType.get - private lazy val _unboxResultType = perRunLazy[Map[Symbol, PrimitiveBType]]{ - val unboxMethods: Map[Symbol, Symbol] = - defn.ScalaValueClasses().map(x => (x, Erasure.Boxing.unboxMethod(x.asClass))).toMap - for ((valueClassSym, unboxMethodSym) <- unboxMethods) - yield unboxMethodSym -> primitiveTypeMap(valueClassSym) - } - - /* - * srNothingRef and srNullRef exist at run-time only. They are the bytecode-level manifestation (in - * method signatures only) of what shows up as NothingClass (scala.Nothing) resp. NullClass (scala.Null) in Scala ASTs. - * - * Therefore, when srNothingRef or srNullRef are to be emitted, a mapping is needed: the internal - * names of NothingClass and NullClass can't be emitted as-is. - * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$` - */ - override def srNothingRef: ClassBType = _srNothingRef.get - private lazy val _srNothingRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass("scala.runtime.Nothing$"))) - - override def srNullRef: ClassBType = _srNullRef.get - private lazy val _srNullRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass("scala.runtime.Null$"))) - - override def ObjectRef: ClassBType = _ObjectRef.get - private lazy val _ObjectRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(defn.ObjectClass)) - - override def StringRef: ClassBType = _StringRef.get - private lazy val _StringRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(defn.StringClass)) - - override def jlClassRef: ClassBType = _jlClassRef.get - private lazy val _jlClassRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.Class[?]])) - - override def jlThrowableRef: ClassBType = _jlThrowableRef.get - private lazy val _jlThrowableRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(defn.ThrowableClass)) +import BackendReporting.NoClassBTypeInfo +import dotty.tools.dotc.core.Symbols.Symbol +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.backend.jvm.PostProcessorFrontendAccess.Lazy - override def jlCloneableRef: ClassBType = _jlCloneableRef.get - private lazy val _jlCloneableRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(defn.JavaCloneableClass)) +import scala.tools.asm.Handle - override def jiSerializableRef: ClassBType = _jiSerializableRef.get - private lazy val _jiSerializableRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.io.Serializable])) +case class MethodNameAndType(name: String, methodType: MethodBType) - override def jlClassCastExceptionRef: ClassBType = _jlClassCastExceptionRef.get - private lazy val _jlClassCastExceptionRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.ClassCastException])) +abstract class CoreBTypes(private val frontendAccess: PostProcessorFrontendAccess)(using ctx: Context) { + def primitiveTypeMap: Map[Symbol, PrimitiveBType] - override def jlIllegalArgExceptionRef: ClassBType = _jlIllegalArgExceptionRef.get - private lazy val _jlIllegalArgExceptionRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.IllegalArgumentException])) + def boxedClasses: Set[ClassBType] - override def jliSerializedLambdaRef: ClassBType = _jliSerializedLambdaRef.get - private lazy val _jliSerializedLambdaRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda])) + def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] - override def srBoxesRuntimeRef: ClassBType = _srBoxesRuntimeRef.get - private lazy val _srBoxesRuntimeRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])) + def boxResultType: Map[Symbol, ClassBType] - private def jliCallSiteRef: ClassBType = _jliCallSiteRef.get - private lazy val _jliCallSiteRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite])) + def unboxResultType: Map[Symbol, PrimitiveBType] - private def jliLambdaMetafactoryRef: ClassBType = _jliLambdaMetafactoryRef.get - private lazy val _jliLambdaMetafactoryRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory])) + def srNothingRef : ClassBType + def srNullRef : ClassBType - private def jliMethodHandleRef: ClassBType = _jliMethodHandleRef.get - private lazy val _jliMethodHandleRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(defn.MethodHandleClass)) + def ObjectRef : ClassBType + def StringRef : ClassBType + def PredefRef : ClassBType + def jlClassRef : ClassBType + def jlThrowableRef : ClassBType + def jlCloneableRef : ClassBType + def jiSerializableRef : ClassBType + def jlClassCastExceptionRef : ClassBType + def jlIllegalArgExceptionRef : ClassBType + def jliSerializedLambdaRef : ClassBType + def jliMethodHandleRef: ClassBType - private def jliMethodHandlesLookupRef: ClassBType = _jliMethodHandlesLookupRef.get - private lazy val _jliMethodHandlesLookupRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(defn.MethodHandlesLookupClass)) + def srBoxesRuntimeRef : ClassBType + def srBoxedUnitRef : ClassBType + def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType] + def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType] - private def jliMethodTypeRef: ClassBType = _jliMethodTypeRef.get - private lazy val _jliMethodTypeRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType])) + def javaBoxMethods : Map[InternalName, MethodNameAndType] + def javaUnboxMethods : Map[InternalName, MethodNameAndType] - // since JDK 9 - private def jliStringConcatFactoryRef: ClassBType = _jliStringConcatFactoryRef.get - private lazy val _jliStringConcatFactoryRef: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass("java.lang.invoke.StringConcatFactory"))) + def predefAutoBoxMethods : Map[String, MethodBType] + def predefAutoUnboxMethods : Map[String, MethodBType] - private def srLambdaDeserialize: ClassBType = _srLambdaDeserialize.get - private lazy val _srLambdaDeserialize: Lazy[ClassBType] = perRunLazy(classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize])) + def srRefCreateMethods : Map[InternalName, MethodNameAndType] + def srRefZeroMethods : Map[InternalName, MethodNameAndType] + def primitiveBoxConstructors : Map[InternalName, MethodNameAndType] + def srRefConstructors : Map[InternalName, MethodNameAndType] + def tupleClassConstructors : Map[InternalName, MethodNameAndType] - override def jliLambdaMetaFactoryMetafactoryHandle = _jliLambdaMetaFactoryMetafactoryHandle.get - private lazy val _jliLambdaMetaFactoryMetafactoryHandle: Lazy[Handle] = perRunLazy{new Handle( - Opcodes.H_INVOKESTATIC, - jliLambdaMetafactoryRef.internalName, - "metafactory", - MethodBType( - List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, jliMethodTypeRef, jliMethodHandleRef, jliMethodTypeRef), - jliCallSiteRef - ).descriptor, - /* itf = */ false)} + def jliLambdaMetaFactoryMetafactoryHandle : Handle + def jliLambdaMetaFactoryAltMetafactoryHandle : Handle + def jliLambdaDeserializeBootstrapHandle : Handle + def jliStringConcatFactoryMakeConcatWithConstantsHandle: Handle - override def jliLambdaMetaFactoryAltMetafactoryHandle = _jliLambdaMetaFactoryAltMetafactoryHandle.get - private lazy val _jliLambdaMetaFactoryAltMetafactoryHandle: Lazy[Handle] = perRunLazy{ new Handle( - Opcodes.H_INVOKESTATIC, - jliLambdaMetafactoryRef.internalName, - "altMetafactory", - MethodBType( - List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, ArrayBType(ObjectRef)), - jliCallSiteRef - ).descriptor, - /* itf = */ false)} + def asmBoxTo : Map[BType, MethodNameAndType] + def asmUnboxTo: Map[BType, MethodNameAndType] - override def jliLambdaDeserializeBootstrapHandle: Handle = _jliLambdaDeserializeBootstrapHandle.get - private lazy val _jliLambdaDeserializeBootstrapHandle: Lazy[Handle] = perRunLazy{ new Handle( - Opcodes.H_INVOKESTATIC, - srLambdaDeserialize.internalName, - "bootstrap", - MethodBType( - List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, ArrayBType(jliMethodHandleRef)), - jliCallSiteRef - ).descriptor, - /* itf = */ false)} + def typeOfArrayOp: Map[Int, BType] - override def jliStringConcatFactoryMakeConcatWithConstantsHandle = _jliStringConcatFactoryMakeConcatWithConstantsHandle.get - private lazy val _jliStringConcatFactoryMakeConcatWithConstantsHandle: Lazy[Handle] = perRunLazy{ new Handle( - Opcodes.H_INVOKESTATIC, - jliStringConcatFactoryRef.internalName, - "makeConcatWithConstants", - MethodBType( - List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, StringRef, ArrayBType(ObjectRef)), - jliCallSiteRef - ).descriptor, - /* itf = */ false)} + // Concurrent maps because stack map frames are computed when in the class writer, which + // might run on multiple classes concurrently. + private val classBTypeCache: Lazy[ConcurrentHashMap[InternalName, ClassBType]] = + frontendAccess.perRunLazy(new ConcurrentHashMap[InternalName, ClassBType]) - /** - * Methods in scala.runtime.BoxesRuntime - * No need to wrap in Lazy to synchronize access, symbols won't change - */ - lazy val asmBoxTo : Map[BType, MethodNameAndType] = Map( - BOOL -> MethodNameAndType("boxToBoolean", MethodBType(List(BOOL), boxedClassOfPrimitive(BOOL))), - BYTE -> MethodNameAndType("boxToByte", MethodBType(List(BYTE), boxedClassOfPrimitive(BYTE))), - CHAR -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR), boxedClassOfPrimitive(CHAR))), - SHORT -> MethodNameAndType("boxToShort", MethodBType(List(SHORT), boxedClassOfPrimitive(SHORT))), - INT -> MethodNameAndType("boxToInteger", MethodBType(List(INT), boxedClassOfPrimitive(INT))), - LONG -> MethodNameAndType("boxToLong", MethodBType(List(LONG), boxedClassOfPrimitive(LONG))), - FLOAT -> MethodNameAndType("boxToFloat", MethodBType(List(FLOAT), boxedClassOfPrimitive(FLOAT))), - DOUBLE -> MethodNameAndType("boxToDouble", MethodBType(List(DOUBLE), boxedClassOfPrimitive(DOUBLE))) - ) + /** See doc of ClassBType.apply. This is where to use that method from. */ + def classBType[T](internalName: InternalName, t: T, fromSymbol: Boolean)(init: (ClassBType, T) => Either[NoClassBTypeInfo, ClassInfo]): ClassBType = + ClassBType(internalName, t, fromSymbol, this, classBTypeCache.get)(init) - lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map( - BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectRef), BOOL)), - BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectRef), BYTE)), - CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectRef), CHAR)), - SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectRef), SHORT)), - INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectRef), INT)), - LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectRef), LONG)), - FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectRef), FLOAT)), - DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectRef), DOUBLE)) - ) + /** Obtain a previously constructed ClassBType for a given internal name. */ + def classBTypeFromInternalName(internalName: InternalName): ClassBType = + classBTypeCache.get.get(internalName) - lazy val typeOfArrayOp: Map[Int, BType] = { - import dotty.tools.backend.ScalaPrimitivesOps.* - Map( - (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ - (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ - (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ - (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ - (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ - (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ - (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ - (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ - (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectRef)) * - ) - } -} +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/backend/jvm/CoreBTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/CoreBTypesFromSymbols.scala new file mode 100644 index 000000000000..837a95805b5b --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/CoreBTypesFromSymbols.scala @@ -0,0 +1,604 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.core.Symbols.{requiredClass as _, *} +import dotty.tools.dotc.transform.Erasure + +import scala.tools.asm.{Handle, Opcodes} +import dotty.tools.dotc.core.{StdNames, Symbols} +import BTypes.* +import dotty.tools.dotc.util.ReadOnlyMap +import dotty.tools.dotc.core.Contexts.{Context, atPhase} +import dotty.tools.dotc.core.Names.* +import dotty.tools.dotc.core.StdNames.* +import BCodeAsmCommon.* +import dotty.tools.dotc.core.Flags.{JavaDefined, ModuleClass, PackageClass, Trait} +import dotty.tools.dotc.core.Phases.{Phase, flattenPhase, lambdaLiftPhase} +import DottyBackendInterface.{*, given} +import PostProcessorFrontendAccess.{Lazy, LazyWithoutLock} + +import scala.annotation.threadUnsafe +import scala.tools.asm + + +final class CoreBTypesFromSymbols(val ppa: PostProcessorFrontendAccess, val superCallsMap: ReadOnlyMap[Symbol, List[ClassSymbol]])(using val ctx: Context) extends CoreBTypes(ppa) { + + @threadUnsafe private lazy val classBTypeFromInternalNameMap = + collection.concurrent.TrieMap.empty[String, ClassBType] + + /** + * Cache for the method classBTypeFromSymbol. + */ + @threadUnsafe private lazy val convertedClasses = collection.mutable.HashMap.empty[Symbol, ClassBType] + + /** + * The ClassBType for a class symbol `sym`. + */ + def classBTypeFromSymbol(classSym: Symbol): ClassBType = { + assert(classSym != NoSymbol, "Cannot create ClassBType from NoSymbol") + assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") + assert( + classSym != defn.NothingClass && classSym != defn.NullClass, + s"Cannot create ClassBType for special class symbol ${classSym.showFullName}") + + convertedClasses.synchronized: + convertedClasses.getOrElse(classSym, { + val internalName = classSym.javaBinaryName + // We first create and add the ClassBType to the hash map before computing its info. This + // allows initializing cyclic dependencies, see the comment on variable ClassBType._info. + val result = classBType(internalName, classSym, true)((ct, cs) => Right(createClassInfo(ct, cs))) + convertedClasses(classSym) = result + result + }) + } + + def mirrorClassBTypeFromSymbol(moduleClassSym: Symbol): ClassBType = { + assert(moduleClassSym.isTopLevelModuleClass, s"not a top-level module class: $moduleClassSym") + val internalName = moduleClassSym.javaBinaryName.stripSuffix(StdNames.str.MODULE_SUFFIX) + classBType(internalName, moduleClassSym, true)((_, mcs) => + Right(ClassInfo( + superClass = Some(ObjectRef), + interfaces = Nil, + flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, + nestedClasses = LazyWithoutLock(getMemberClasses(mcs).map(classBTypeFromSymbol)), + nestedInfo = LazyWithoutLock(None) + )) + ) + } + + private def createClassInfo(classBType: ClassBType, classSym: Symbol): ClassInfo = { + val superClassSym: Symbol = { + val t = classSym.asClass.superClass + if (t.exists) t + else if (classSym.is(ModuleClass)) { + // workaround #371 + + println(s"Warning: mocking up superclass for $classSym") + defn.ObjectClass + } + else t + } + assert( + if (classSym == defn.ObjectClass) + superClassSym == NoSymbol + else if (classSym.isInterface) + superClassSym == defn.ObjectClass + else + // A ClassBType for a primitive class (scala.Boolean et al.) is only created when compiling these classes. + ((superClassSym != NoSymbol) && !superClassSym.isInterface) || primitiveTypeMap.contains(classSym), + s"Bad superClass for $classSym: $superClassSym" + ) + val superClass = if (superClassSym == NoSymbol) None + else Some(classBTypeFromSymbol(superClassSym)) + + /* + * All interfaces implemented by a class, except for those inherited through the superclass. + * Redundant interfaces are removed unless there is a super call to them. + */ + val superInterfaces: List[Symbol] = { + val directlyInheritedTraits = classSym.directlyInheritedTraits + val directlyInheritedTraitsSet = directlyInheritedTraits.toSet + val allBaseClasses = directlyInheritedTraits.iterator.flatMap(_.asClass.baseClasses.drop(1)).toSet + val superCalls = superCallsMap.getOrElse(classSym, List.empty) + val superCallsSet = superCalls.toSet + val additional = superCalls.filter(t => !directlyInheritedTraitsSet(t) && t.is(Trait)) + // if (additional.nonEmpty) + // println(s"$fullName: adding supertraits $additional") + directlyInheritedTraits.filter(t => !allBaseClasses(t) || superCallsSet(t)) ++ additional + } + + val interfaces = superInterfaces.map(classBTypeFromSymbol) + + val flags = BCodeUtils.javaFlags(classSym) + + /* The InnerClass table of a class C must contain all nested classes of C, even if they are only + * declared but not otherwise referenced in C (from the bytecode or a method / field signature). + * We collect them here. + */ + val nestedClassSymbols = { + // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect + // member classes right after lambdalift, we obtain all nested classes, including local and + // anonymous ones. + val nestedClasses = getNestedClasses(classSym) + + // If this is a top-level class, and it has a companion object, the member classes of the + // companion are added as members of the class. For example: + // class C { } + // object C { + // class D + // def f = { class E } + // } + // The class D is added as a member of class C. The reason is that the InnerClass attribute + // for D will containt class "C" and NOT the module class "C$" as the outer class of D. + // This is done by buildNestedInfo, the reason is Java compatibility, see comment in BTypes. + // For consistency, the InnerClass entry for D needs to be present in C - to Java it looks + // like D is a member of C, not C$. + val linkedClass = classSym.linkedClass + val companionModuleMembers = { + if (classSym.linkedClass.isTopLevelModuleClass) getMemberClasses(classSym.linkedClass) + else Nil + } + + nestedClasses ++ companionModuleMembers + } + + /** + * For nested java classes, the scala compiler creates both a class and a module (and therefore + * a module class) symbol. For example, in `class A { class B {} }`, the nestedClassSymbols + * for A contain both the class B and the module class B. + * Here we get rid of the module class B, making sure that the class B is present. + */ + val nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => { + if (s.is(JavaDefined) && s.is(ModuleClass)) { + // We could also search in nestedClassSymbols for s.linkedClassOfClass, but sometimes that + // returns NoSymbol, so it doesn't work. + val nb = nestedClassSymbols.count(mc => mc.name == s.name && mc.owner == s.owner) + // this assertion is specific to how ScalaC works. It doesn't apply to dotty, as n dotty there will be B & B$ + // assert(nb == 2, s"Java member module without member class: $s - $nestedClassSymbols") + false + } else true + }) + + val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) + + val nestedInfo = buildNestedInfo(classSym) + + ClassInfo(superClass, interfaces, flags, LazyWithoutLock(memberClasses), LazyWithoutLock(nestedInfo)) + } + + /** For currently compiled classes: All locally defined classes including local classes. + * The empty list for classes that are not currently compiled. + */ + private def getNestedClasses(sym: Symbol): List[Symbol] = definedClasses(sym, flattenPhase) + + /** For currently compiled classes: All classes that are declared as members of this class + * (but not inherited ones). The empty list for classes that are not currently compiled. + */ + private def getMemberClasses(sym: Symbol): List[Symbol] = definedClasses(sym, lambdaLiftPhase) + + private def definedClasses(sym: Symbol, phase: Phase) = + if (sym.isDefinedInCurrentRun) + atPhase(phase) { + toDenot(sym).info.decls.filter(sym => sym.isClass && !sym.isEffectivelyErased) + } + else Nil + + private def buildNestedInfo(innerClassSym: Symbol): Option[NestedInfo] = { + assert(innerClassSym.isClass, s"Cannot build NestedInfo for non-class symbol $innerClassSym") + + val isNested = !innerClassSym.originalOwner.originalLexicallyEnclosingClass.is(PackageClass) + if (!isNested) None + else { + // See comment in BTypes, when is a class marked static in the InnerClass table. + val isStaticNestedClass = innerClassSym.originalOwner.originalLexicallyEnclosingClass.isOriginallyStaticOwner + + // After lambdalift (which is where we are), the rawowoner field contains the enclosing class. + val enclosingClassSym = { + if (innerClassSym.isClass) { + atPhase(flattenPhase.prev) { + toDenot(innerClassSym).owner.enclosingClass + } + } + else atPhase(flattenPhase.prev)(innerClassSym.enclosingClass) + } //todo is handled specially for JavaDefined symbols in scalac + + val enclosingClass: ClassBType = classBTypeFromSymbol(enclosingClassSym) + + val outerName: Option[String] = { + if (isAnonymousOrLocalClass(innerClassSym)) { + None + } else { + val outerName = innerClassSym.originalOwner.originalLexicallyEnclosingClass.javaBinaryName + def dropModule(str: String): String = + if (str.nonEmpty && str.last == '$') str.take(str.length - 1) else str + // Java compatibility. See the big comment in BTypes that summarizes the InnerClass spec. + val outerNameModule = + if (innerClassSym.originalOwner.originalLexicallyEnclosingClass.isTopLevelModuleClass) dropModule(outerName) + else outerName + Some(outerNameModule) + } + } + + val innerName: Option[String] = { + if (innerClassSym.isAnonymousClass || innerClassSym.isAnonymousFunction) None + else { + val original = innerClassSym.initial + Some(atPhase(original.validFor.phaseId)(innerClassSym.name).mangledString) // moduleSuffix for module classes + } + } + + Some(NestedInfo(enclosingClass, outerName, innerName, isStaticNestedClass)) + } + } + + /** + * This is basically a re-implementation of sym.isStaticOwner, but using the originalOwner chain. + * + * The problem is that we are interested in a source-level property. Various phases changed the + * symbol's properties in the meantime, mostly lambdalift modified (destructively) the owner. + * Therefore, `sym.isStatic` is not what we want. For example, in + * object T { def f { object U } } + * the owner of U is T, so UModuleClass.isStatic is true. Phase travel does not help here. + */ + extension (sym: Symbol) + private def isOriginallyStaticOwner: Boolean = + sym.is(PackageClass) || sym.is(ModuleClass) && sym.originalOwner.originalLexicallyEnclosingClass.isOriginallyStaticOwner + + + /** + * Maps primitive types to their corresponding PrimitiveBType. The map is defined lexically above + * the first use of `classBTypeFromSymbol` because that method looks at the map. + */ + override def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _primitiveTypeMap.get + private lazy val _primitiveTypeMap: Lazy[Map[Symbol, PrimitiveBType]] = ppa.perRunLazy: + Map( + defn.UnitClass -> UNIT, + defn.BooleanClass -> BOOL, + defn.CharClass -> CHAR, + defn.ByteClass -> BYTE, + defn.ShortClass -> SHORT, + defn.IntClass -> INT, + defn.LongClass -> LONG, + defn.FloatClass -> FLOAT, + defn.DoubleClass -> DOUBLE + ) + + /** + * Map from primitive types to their boxed class type. Useful when pushing class literals onto the + * operand stack (ldc instruction taking a class literal), see genConstant. + */ + override def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _boxedClassOfPrimitive.get + private lazy val _boxedClassOfPrimitive: Lazy[Map[PrimitiveBType, ClassBType]] = ppa.perRunLazy(Map( + UNIT -> classBTypeFromSymbol(requiredClass[java.lang.Void]), + BOOL -> classBTypeFromSymbol(requiredClass[java.lang.Boolean]), + BYTE -> classBTypeFromSymbol(requiredClass[java.lang.Byte]), + SHORT -> classBTypeFromSymbol(requiredClass[java.lang.Short]), + CHAR -> classBTypeFromSymbol(requiredClass[java.lang.Character]), + INT -> classBTypeFromSymbol(requiredClass[java.lang.Integer]), + LONG -> classBTypeFromSymbol(requiredClass[java.lang.Long]), + FLOAT -> classBTypeFromSymbol(requiredClass[java.lang.Float]), + DOUBLE -> classBTypeFromSymbol(requiredClass[java.lang.Double]) + )) + + lazy val boxedClasses: Set[ClassBType] = boxedClassOfPrimitive.values.toSet + + /** + * 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 ClassBType `java/lang/Byte`. + */ + override def boxResultType: Map[Symbol, ClassBType] = _boxResultType.get + private lazy val _boxResultType: Lazy[Map[Symbol, ClassBType]] = ppa.perRunLazy{ + val boxMethods = defn.ScalaValueClasses().map{x => + (x, Erasure.Boxing.boxMethod(x.asClass)) + }.toMap + for ((valueClassSym, boxMethodSym) <- boxMethods) + yield boxMethodSym -> boxedClassOfPrimitive(primitiveTypeMap(valueClassSym)) + } + + /** + * Maps the method symbol for an unbox method to the primitive type of the result. + * For example, the method symbol for `Byte.unbox()` is mapped to the PrimitiveBType BYTE. */ + override def unboxResultType: Map[Symbol, PrimitiveBType] = _unboxResultType.get + private lazy val _unboxResultType = ppa.perRunLazy[Map[Symbol, PrimitiveBType]]{ + val unboxMethods: Map[Symbol, Symbol] = + defn.ScalaValueClasses().map(x => (x, Erasure.Boxing.unboxMethod(x.asClass))).toMap + for ((valueClassSym, unboxMethodSym) <- unboxMethods) + yield unboxMethodSym -> primitiveTypeMap(valueClassSym) + } + + /* + * srNothingRef and srNullRef exist at run-time only. They are the bytecode-level manifestation (in + * method signatures only) of what shows up as NothingClass (scala.Nothing) resp. NullClass (scala.Null) in Scala ASTs. + * + * Therefore, when srNothingRef or srNullRef are to be emitted, a mapping is needed: the internal + * names of NothingClass and NullClass can't be emitted as-is. + * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$` + */ + override def srNothingRef: ClassBType = _srNothingRef.get + private lazy val _srNothingRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass("scala.runtime.Nothing$"))) + + override def srNullRef: ClassBType = _srNullRef.get + private lazy val _srNullRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass("scala.runtime.Null$"))) + + override def srBoxedUnitRef: ClassBType = _srBoxedUnitRef.get + private lazy val _srBoxedUnitRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass("scala.runtime.BoxedUnit"))) + + override def ObjectRef: ClassBType = _ObjectRef.get + private lazy val _ObjectRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(defn.ObjectClass)) + + override def StringRef: ClassBType = _StringRef.get + private lazy val _StringRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(defn.StringClass)) + + override def PredefRef: ClassBType = _PredefRef.get + private lazy val _PredefRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(defn.ScalaPredefModuleClass)) + + override def jlClassRef: ClassBType = _jlClassRef.get + private lazy val _jlClassRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.Class[?]])) + + override def jlThrowableRef: ClassBType = _jlThrowableRef.get + private lazy val _jlThrowableRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(defn.ThrowableClass)) + + override def jlCloneableRef: ClassBType = _jlCloneableRef.get + private lazy val _jlCloneableRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(defn.JavaCloneableClass)) + + override def jiSerializableRef: ClassBType = _jiSerializableRef.get + private lazy val _jiSerializableRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.io.Serializable])) + + override def jlClassCastExceptionRef: ClassBType = _jlClassCastExceptionRef.get + private lazy val _jlClassCastExceptionRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.ClassCastException])) + + override def jlIllegalArgExceptionRef: ClassBType = _jlIllegalArgExceptionRef.get + private lazy val _jlIllegalArgExceptionRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.IllegalArgumentException])) + + override def jliSerializedLambdaRef: ClassBType = _jliSerializedLambdaRef.get + private lazy val _jliSerializedLambdaRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda])) + + override def srBoxesRuntimeRef: ClassBType = _srBoxesRuntimeRef.get + private lazy val _srBoxesRuntimeRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])) + + private def jliCallSiteRef: ClassBType = _jliCallSiteRef.get + private lazy val _jliCallSiteRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite])) + + private def jliLambdaMetafactoryRef: ClassBType = _jliLambdaMetafactoryRef.get + private lazy val _jliLambdaMetafactoryRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory])) + + override def jliMethodHandleRef: ClassBType = _jliMethodHandleRef.get + private lazy val _jliMethodHandleRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(defn.MethodHandleClass)) + + private def jliMethodHandlesLookupRef: ClassBType = _jliMethodHandlesLookupRef.get + private lazy val _jliMethodHandlesLookupRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(defn.MethodHandlesLookupClass)) + + private def jliMethodTypeRef: ClassBType = _jliMethodTypeRef.get + private lazy val _jliMethodTypeRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType])) + + // since JDK 9 + private def jliStringConcatFactoryRef: ClassBType = _jliStringConcatFactoryRef.get + private lazy val _jliStringConcatFactoryRef: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass("java.lang.invoke.StringConcatFactory"))) + + private def srLambdaDeserialize: ClassBType = _srLambdaDeserialize.get + private lazy val _srLambdaDeserialize: Lazy[ClassBType] = ppa.perRunLazy(classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize])) + + + override def jliLambdaMetaFactoryMetafactoryHandle: Handle = _jliLambdaMetaFactoryMetafactoryHandle.get + private lazy val _jliLambdaMetaFactoryMetafactoryHandle: Lazy[Handle] = ppa.perRunLazy{new Handle( + Opcodes.H_INVOKESTATIC, + jliLambdaMetafactoryRef.internalName, + "metafactory", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, jliMethodTypeRef, jliMethodHandleRef, jliMethodTypeRef), + jliCallSiteRef + ).descriptor, + /* itf = */ false)} + + override def jliLambdaMetaFactoryAltMetafactoryHandle: Handle = _jliLambdaMetaFactoryAltMetafactoryHandle.get + private lazy val _jliLambdaMetaFactoryAltMetafactoryHandle: Lazy[Handle] = ppa.perRunLazy{ new Handle( + Opcodes.H_INVOKESTATIC, + jliLambdaMetafactoryRef.internalName, + "altMetafactory", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, ArrayBType(ObjectRef)), + jliCallSiteRef + ).descriptor, + /* itf = */ false)} + + override def jliLambdaDeserializeBootstrapHandle: Handle = _jliLambdaDeserializeBootstrapHandle.get + private lazy val _jliLambdaDeserializeBootstrapHandle: Lazy[Handle] = ppa.perRunLazy{ new Handle( + Opcodes.H_INVOKESTATIC, + srLambdaDeserialize.internalName, + "bootstrap", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, ArrayBType(jliMethodHandleRef)), + jliCallSiteRef + ).descriptor, + /* itf = */ false)} + + override def jliStringConcatFactoryMakeConcatWithConstantsHandle: Handle = _jliStringConcatFactoryMakeConcatWithConstantsHandle.get + private lazy val _jliStringConcatFactoryMakeConcatWithConstantsHandle: Lazy[Handle] = ppa.perRunLazy{ new Handle( + Opcodes.H_INVOKESTATIC, + jliStringConcatFactoryRef.internalName, + "makeConcatWithConstants", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, StringRef, ArrayBType(ObjectRef)), + jliCallSiteRef + ).descriptor, + /* itf = */ false)} + + /** + * Methods in scala.runtime.BoxesRuntime + * No need to wrap in Lazy to synchronize access, symbols won't change + */ + lazy val asmBoxTo : Map[BType, MethodNameAndType] = Map( + BOOL -> MethodNameAndType("boxToBoolean", MethodBType(List(BOOL), boxedClassOfPrimitive(BOOL))), + BYTE -> MethodNameAndType("boxToByte", MethodBType(List(BYTE), boxedClassOfPrimitive(BYTE))), + CHAR -> MethodNameAndType("boxToCharacter", MethodBType(List(CHAR), boxedClassOfPrimitive(CHAR))), + SHORT -> MethodNameAndType("boxToShort", MethodBType(List(SHORT), boxedClassOfPrimitive(SHORT))), + INT -> MethodNameAndType("boxToInteger", MethodBType(List(INT), boxedClassOfPrimitive(INT))), + LONG -> MethodNameAndType("boxToLong", MethodBType(List(LONG), boxedClassOfPrimitive(LONG))), + FLOAT -> MethodNameAndType("boxToFloat", MethodBType(List(FLOAT), boxedClassOfPrimitive(FLOAT))), + DOUBLE -> MethodNameAndType("boxToDouble", MethodBType(List(DOUBLE), boxedClassOfPrimitive(DOUBLE))) + ) + + lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map( + BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectRef), BOOL)), + BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectRef), BYTE)), + CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectRef), CHAR)), + SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectRef), SHORT)), + INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectRef), INT)), + LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectRef), LONG)), + FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectRef), FLOAT)), + DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectRef), DOUBLE)) + ) + + lazy val typeOfArrayOp: Map[Int, BType] = { + import dotty.tools.backend.ScalaPrimitivesOps.* + Map( + (List(ZARRAY_LENGTH, ZARRAY_GET, ZARRAY_SET) map (_ -> BOOL)) ++ + (List(BARRAY_LENGTH, BARRAY_GET, BARRAY_SET) map (_ -> BYTE)) ++ + (List(SARRAY_LENGTH, SARRAY_GET, SARRAY_SET) map (_ -> SHORT)) ++ + (List(CARRAY_LENGTH, CARRAY_GET, CARRAY_SET) map (_ -> CHAR)) ++ + (List(IARRAY_LENGTH, IARRAY_GET, IARRAY_SET) map (_ -> INT)) ++ + (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ + (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ + (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectRef)) * + ) + } + + // java/lang/Boolean -> MethodNameAndType(valueOf,(Z)Ljava/lang/Boolean;) + def javaBoxMethods: Map[InternalName, MethodNameAndType] = _javaBoxMethods.get + private lazy val _javaBoxMethods: Lazy[Map[InternalName, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().map(primitive => { + val boxed = defn.boxedClass(primitive) + val unboxed = primitiveTypeMap(primitive) + val method = MethodNameAndType("valueOf", MethodBType(List(unboxed), boxedClassOfPrimitive(unboxed))) + (classBTypeFromSymbol(boxed).internalName, method) + })) + } + + // java/lang/Boolean -> MethodNameAndType(booleanValue,()Z) + def javaUnboxMethods: Map[InternalName, MethodNameAndType] = _javaUnboxMethods.get + private lazy val _javaUnboxMethods: Lazy[Map[InternalName, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().map(primitive => { + val boxed = defn.boxedClass(primitive) + val name = primitive.name.toString.toLowerCase + "Value" + (classBTypeFromSymbol(boxed).internalName, MethodNameAndType(name, MethodBType(Nil, primitiveTypeMap(primitive)))) + })) + } + + private def predefBoxingMethods(isBox: Boolean, getName: (String, String) => String): Map[String, MethodBType] = + Map.from(defn.ScalaValueClassesNoUnit().map(primitive => { + val unboxed = primitiveTypeMap(primitive) + val boxed = boxedClassOfPrimitive(unboxed) + val name = getName(primitive.name.toString, defn.boxedClass(primitive).name.toString) + (name, MethodBType(List(if isBox then unboxed else boxed), if isBox then boxed else unboxed)) + })) + + // boolean2Boolean -> (Z)Ljava/lang/Boolean; + def predefAutoBoxMethods: Map[String, MethodBType] = _predefAutoBoxMethods.get + private lazy val _predefAutoBoxMethods: Lazy[Map[String, MethodBType]] = ppa.perRunLazy(predefBoxingMethods(true, (primitive, boxed) => primitive.toLowerCase + "2" + boxed)) + + // Boolean2boolean -> (Ljava/lang/Boolean;)Z + def predefAutoUnboxMethods: Map[String, MethodBType] = _predefAutoUnboxMethods.get + private lazy val _predefAutoUnboxMethods: Lazy[Map[String, MethodBType]] = ppa.perRunLazy(predefBoxingMethods(false, (primitive, boxed) => boxed + "2" + primitive.toLowerCase)) + + // scala/runtime/BooleanRef -> MethodNameAndType(create,(Z)Lscala/runtime/BooleanRef;) + def srRefCreateMethods: Map[InternalName, MethodNameAndType] = _srRefCreateMethods.get + private lazy val _srRefCreateMethods: Lazy[Map[InternalName, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().union(Set(defn.ObjectClass)).flatMap(primitive => { + val boxed = if primitive == defn.ObjectClass then primitive else defn.boxedClass(primitive) + val unboxed = if primitive == defn.ObjectClass then ObjectRef else primitiveTypeMap(primitive) + val refClass = Symbols.requiredClass("scala.runtime." + boxed.name.toString + ".Ref") + val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + boxed.name.toString + ".Ref") + List( + (classBTypeFromSymbol(refClass).internalName, MethodNameAndType(nme.create.toString, MethodBType(List(unboxed), classBTypeFromSymbol(refClass)))), + (classBTypeFromSymbol(volatileRefClass).internalName, MethodNameAndType(nme.create.toString, MethodBType(List(unboxed), classBTypeFromSymbol(volatileRefClass)))) + ) + })) + } + + // scala/runtime/BooleanRef -> MethodNameAndType(zero,()Lscala/runtime/BooleanRef;) + def srRefZeroMethods: Map[InternalName, MethodNameAndType] = _srRefZeroMethods.get + private lazy val _srRefZeroMethods: Lazy[Map[InternalName, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().union(Set(defn.ObjectClass)).flatMap(primitive => { + val boxed = if primitive == defn.ObjectClass then primitive else defn.boxedClass(primitive) + val refClass = Symbols.requiredClass("scala.runtime." + boxed.name.toString + ".Ref") + val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + boxed.name.toString + ".Ref") + List( + (classBTypeFromSymbol(refClass).internalName, MethodNameAndType(nme.zero.toString, MethodBType(List(), classBTypeFromSymbol(refClass)))), + (classBTypeFromSymbol(volatileRefClass).internalName, MethodNameAndType(nme.zero.toString, MethodBType(List(), classBTypeFromSymbol(volatileRefClass)))) + ) + })) + } + + // java/lang/Boolean -> MethodNameAndType(,(Z)V) + def primitiveBoxConstructors: Map[InternalName, MethodNameAndType] = _primitiveBoxConstructors.get + private lazy val _primitiveBoxConstructors: Lazy[Map[InternalName, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().map(primitive => { + val boxed = defn.boxedClass(primitive) + val unboxed = primitiveTypeMap(primitive) + (classBTypeFromSymbol(boxed).internalName, MethodNameAndType(nme.CONSTRUCTOR.toString, MethodBType(List(unboxed), UNIT))) + })) + } + + // Z -> MethodNameAndType(boxToBoolean,(Z)Ljava/lang/Boolean;) + def srBoxesRuntimeBoxToMethods: Map[BType, MethodNameAndType] = _srBoxesRuntimeBoxToMethods.get + private lazy val _srBoxesRuntimeBoxToMethods: Lazy[Map[BType, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().map(primitive => { + val bType = primitiveTypeMap(primitive) + val boxed = boxedClassOfPrimitive(bType) + val name = "boxTo" + defn.boxedClass(primitive).name.toString + (bType, MethodNameAndType(name, MethodBType(List(bType), boxed))) + })) + } + + // Z -> MethodNameAndType(unboxToBoolean,(Ljava/lang/Object;)Z) + def srBoxesRuntimeUnboxToMethods: Map[BType, MethodNameAndType] = _srBoxesRuntimeUnboxToMethods.get + private lazy val _srBoxesRuntimeUnboxToMethods: Lazy[Map[BType, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().map(primitive => { + val bType = primitiveTypeMap(primitive) + val name = "unboxTo" + primitive.name.toString + (bType, MethodNameAndType(name, MethodBType(List(ObjectRef), bType))) + })) + } + + // scala/runtime/BooleanRef -> MethodNameAndType(,(Z)V) + def srRefConstructors: Map[InternalName, MethodNameAndType] = _srRefConstructors.get + private lazy val _srRefConstructors: Lazy[Map[InternalName, MethodNameAndType]] = ppa.perRunLazy { + Map.from(defn.ScalaValueClassesNoUnit().union(Set(defn.ObjectClass)).flatMap(primitive => { + val boxed = if primitive == defn.ObjectClass then primitive else defn.boxedClass(primitive) + val unboxed = if primitive == defn.ObjectClass then ObjectRef else primitiveTypeMap(primitive) + val refClass = Symbols.requiredClass("scala.runtime." + boxed.name.toString + ".Ref") + val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + boxed.name.toString + ".Ref") + List( + (classBTypeFromSymbol(refClass).internalName, MethodNameAndType(nme.zero.toString, MethodBType(List(unboxed), UNIT))), + (classBTypeFromSymbol(volatileRefClass).internalName, MethodNameAndType(nme.zero.toString, MethodBType(List(unboxed), UNIT))) + ) + })) + } + + // scala/Tuple3 -> MethodNameAndType(,(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)V) + // scala/Tuple2$mcZC$sp -> MethodNameAndType(,(ZC)V) + // ... this was easy in scala2, but now we don't specialize them so we have to know each name + // tuple1 is specialized for D, I, J + // tuple2 is specialized for C, D, I, J, Z in each parameter + def tupleClassConstructors: Map[InternalName, MethodNameAndType] = _tupleClassConstructors.get + private lazy val _tupleClassConstructors: Lazy[Map[InternalName, MethodNameAndType]] = ppa.perRunLazy { + val spec1 = List(defn.DoubleClass, defn.IntClass, defn.LongClass) + val spec2 = List(defn.CharClass, defn.DoubleClass, defn.IntClass, defn.LongClass, defn.BooleanClass) + Map.from( + Iterator.concat( + (1 to 22).map { n => + ("scala/Tuple" + n, MethodNameAndType(nme.CONSTRUCTOR.toString, MethodBType(List.fill(n)(ObjectRef), UNIT))) + }, + spec1.map { sp1 => + val prim = primitiveTypeMap(sp1) + ("scala/Tuple1$mc" + prim.descriptor + "$sp", MethodNameAndType(nme.CONSTRUCTOR.toString, MethodBType(List(), UNIT))) + }, + for sp2a <- spec2; sp2b <- spec2 yield { + val primA = primitiveTypeMap(sp2a) + val primB = primitiveTypeMap(sp2b) + ("scala/Tuple2$mc" + primA.descriptor + primB.descriptor + "$sp", MethodNameAndType(nme.CONSTRUCTOR.toString, MethodBType(List(primA, primB), UNIT))) + } + ) + ) + } +} diff --git a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala index 1cd83dba707a..d4a34f436852 100644 --- a/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyBackendInterface.scala @@ -24,72 +24,6 @@ import StdNames.nme import NameKinds.{LazyBitMapName, LazyLocalName} import Names.Name -class DottyBackendInterface(val superCallsMap: ReadOnlyMap[Symbol, List[ClassSymbol]])(using val ctx: Context) { - - private val desugared = new java.util.IdentityHashMap[Type, tpd.Select] - - def cachedDesugarIdent(i: Ident): Option[tpd.Select] = { - var found = desugared.get(i.tpe) - if (found == null) { - tpd.desugarIdent(i) match { - case sel: tpd.Select => - desugared.put(i.tpe, sel) - found = sel - case _ => - } - } - if (found == null) None else Some(found) - } - - object DesugaredSelect extends DeconstructorCommon[tpd.Tree] { - - var desugared: tpd.Select | Null = null - - override def isEmpty: Boolean = - desugared eq null - - def _1: Tree = desugared.nn.qualifier - - def _2: Name = desugared.nn.name - - override def unapply(s: tpd.Tree): this.type = { - s match { - case t: tpd.Select => desugared = t - case t: Ident => - cachedDesugarIdent(t) match { - case Some(t) => desugared = t - case None => desugared = null - } - case _ => desugared = null - } - - this - } - } - - object ArrayValue extends DeconstructorCommon[tpd.JavaSeqLiteral] { - def _1: Type = field.nn.tpe match { - case JavaArrayType(elem) => elem - case _ => - report.error(em"JavaSeqArray with type ${field.nn.tpe} reached backend: $field", ctx.source.atSpan(field.nn.span)) - UnspecifiedErrorType - } - def _2: List[Tree] = field.nn.elems - } - - abstract class DeconstructorCommon[T <: AnyRef] { - var field: T | Null = null - def get: this.type = this - def isEmpty: Boolean = field eq null - def isDefined = !isEmpty - def unapply(s: T): this.type ={ - field = s - this - } - } - -} - object DottyBackendInterface { private def erasureString(clazz: Class[?]): String = { @@ -115,7 +49,7 @@ object DottyBackendInterface { given symExtensions: AnyRef with extension (sym: Symbol) - def isInterface(using Context): Boolean = (sym.is(PureInterface)) || sym.is(Trait) + def isInterface(using Context): Boolean = sym.is(PureInterface) || sym.is(Trait) def isStaticConstructor(using Context): Boolean = (sym.isStaticMember && sym.isClassConstructor) || (sym.name eq nme.STATIC_CONSTRUCTOR) @@ -127,20 +61,20 @@ object DottyBackendInterface { * TODO: remove the special handing of `LazyBitMapName` once we swtich to * the new lazy val encoding: https://github.com/scala/scala3/issues/7140 */ - def isStaticModuleField(using Context): Boolean = + private def isStaticModuleField(using Context): Boolean = sym.owner.isStaticModuleClass && sym.isField && !sym.name.is(LazyBitMapName) && !sym.name.is(LazyLocalName) - def isStaticMember(using Context): Boolean = (sym ne NoSymbol) && - (sym.is(JavaStatic) || sym.isScalaStatic || sym.isStaticModuleField) - - // guard against no sumbol cause this code is executed to select which call type(static\dynamic) to use to call array.clone + def isStaticMember(using Context): Boolean = + // guard against no symbol cause this code is executed to select which call type(static\dynamic) to use to call array.clone + (sym ne NoSymbol) && + (sym.is(JavaStatic) || sym.isScalaStatic || sym.isStaticModuleField) /** * True for module classes of modules that are top-level or owned only by objects. Module classes * for such objects will get a MODULE$ flag and a corresponding static initializer. */ def isStaticModuleClass(using Context): Boolean = - (sym.is(Module)) && { + sym.is(Module) && { // scalac uses atPickling here // this would not work if modules are created after pickling // for example by specialization @@ -150,12 +84,10 @@ object DottyBackendInterface { toDenot(sym).isStatic } } - - - + def originalLexicallyEnclosingClass(using Context): Symbol = // used to populate the EnclosingMethod attribute. - // it is very tricky in presence of classes(and annonymous classes) defined inside supper calls. + // it is very tricky in presence of classes(and anonymous classes) defined inside supper calls. if (sym.exists) { val validity = toDenot(sym).initial.validFor atPhase(validity.phaseId) { @@ -180,25 +112,4 @@ object DottyBackendInterface { end extension end symExtensions - - private val primitiveCompilationUnits = Set( - "Unit.scala", - "Boolean.scala", - "Char.scala", - "Byte.scala", - "Short.scala", - "Int.scala", - "Float.scala", - "Long.scala", - "Double.scala" - ) - - /** - * True if the current compilation unit is of a primitive class (scala.Boolean et al). - * Used only in assertions. - */ - def isCompilingPrimitive(using Context) = { - primitiveCompilationUnits(ctx.compilationUnit.source.file.name) - } - } diff --git a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala b/compiler/src/dotty/tools/backend/jvm/DottyPrimitives.scala similarity index 99% rename from compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala rename to compiler/src/dotty/tools/backend/jvm/DottyPrimitives.scala index 1fdeacb3c998..304af9ceb9c1 100644 --- a/compiler/src/dotty/tools/backend/jvm/scalaPrimitives.scala +++ b/compiler/src/dotty/tools/backend/jvm/DottyPrimitives.scala @@ -50,7 +50,8 @@ class DottyPrimitives(ictx: Context) { * @param tpe The type of the receiver object. It is used only for array * operations */ - def getPrimitive(app: Apply, tpe: Type)(using Context): Int = { + def getPrimitive(app: Apply, tpe: Type): Int = { + given Context = ictx val fun = app.fun.symbol val defn = ctx.definitions val code = app.fun match { diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 58daa01e4bdf..1e8177b42b0a 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -21,7 +21,7 @@ class GenBCode extends Phase { self => override def description: String = GenBCode.description - override def isRunnable(using Context) = super.isRunnable && !ctx.usedBestEffortTasty + override def isRunnable(using Context): Boolean = super.isRunnable && !ctx.usedBestEffortTasty private val superCallsMap = new MutableSymbolMap[List[ClassSymbol]] def registerSuperCall(sym: Symbol, calls: ClassSymbol): Unit = { @@ -33,48 +33,47 @@ class GenBCode extends Phase { self => private val entryPoints = new mutable.HashSet[String]() def registerEntryPoint(s: String): Unit = entryPoints += s - private var _backendInterface: DottyBackendInterface = uninitialized - def backendInterface(using ctx: Context): DottyBackendInterface = { - if _backendInterface eq null then + private var _frontendAccess: PostProcessorFrontendAccess | Null = null + def frontendAccess(using Context): PostProcessorFrontendAccess = { + if _frontendAccess eq null then // Enforce usage of FreshContext so we would be able to modify compilation unit between runs - val backendCtx = ctx match + val context = ctx match case fc: FreshContext => fc case ctx => ctx.fresh - _backendInterface = DottyBackendInterface(superCallsMap)(using backendCtx) - _backendInterface - } - - private var _codeGen: CodeGen = uninitialized - def codeGen(using Context): CodeGen = { - if _codeGen eq null then - val int = backendInterface - val dottyPrimitives = new DottyPrimitives(ctx) - _codeGen = new CodeGen(int, dottyPrimitives)(bTypes.asInstanceOf[BTypesFromSymbols[int.type]]) - _codeGen + _frontendAccess = PostProcessorFrontendAccess.Impl(entryPoints)(context) + _frontendAccess.nn } - private var _bTypes: BTypesFromSymbols[DottyBackendInterface] = uninitialized - def bTypes(using Context): BTypesFromSymbols[DottyBackendInterface] = { + private var _bTypes: CoreBTypesFromSymbols | Null = null + def bTypes(using Context): CoreBTypesFromSymbols = { if _bTypes eq null then - _bTypes = BTypesFromSymbols(backendInterface, frontendAccess) - _bTypes + _bTypes = CoreBTypesFromSymbols(frontendAccess, superCallsMap)(using ctx) + _bTypes.nn } - private var _frontendAccess: PostProcessorFrontendAccess | Null = uninitialized - def frontendAccess(using Context): PostProcessorFrontendAccess = { - if _frontendAccess eq null then - _frontendAccess = PostProcessorFrontendAccess.Impl(backendInterface, entryPoints) - _frontendAccess.nn - } - - private var _postProcessor: PostProcessor | Null = uninitialized + private var _postProcessor: PostProcessor | Null = null def postProcessor(using Context): PostProcessor = { if _postProcessor eq null then _postProcessor = new PostProcessor(frontendAccess, bTypes) _postProcessor.nn } - private var _generatedClassHandler: GeneratedClassHandler | Null = uninitialized + private var _backendUtils: BackendUtils | Null = null + def backendUtils(using Context): BackendUtils = { + if _backendUtils eq null then + _backendUtils = BackendUtils(frontendAccess, bTypes) + _backendUtils.nn + } + + private var _codeGen: CodeGen | Null = null + def codeGen(using Context): CodeGen = { + if _codeGen eq null then + val dottyPrimitives = new DottyPrimitives(ctx) + _codeGen = new CodeGen(backendUtils, dottyPrimitives, frontendAccess, bTypes) + _codeGen.nn + } + + private var _generatedClassHandler: GeneratedClassHandler | Null = null def generatedClassHandler(using Context): GeneratedClassHandler = { if _generatedClassHandler eq null then _generatedClassHandler = GeneratedClassHandler(postProcessor) @@ -83,8 +82,8 @@ class GenBCode extends Phase { self => override def run(using Context): Unit = frontendAccess.frontendSynchWithoutContext { - backendInterface.ctx - .asInstanceOf[FreshContext] + frontendAccess + .ctx .setCompilationUnit(ctx.compilationUnit) } codeGen.genUnit(ctx.compilationUnit) @@ -102,13 +101,13 @@ class GenBCode extends Phase { self => async <- ctx.run.nn.asyncTasty bufferedReporter <- async.sync() do - bufferedReporter.relayReports(frontendAccess.backendReporting) + frontendAccess.backendReporting.relayReports(bufferedReporter) catch case ex: Exception => report.error(s"exception from future: $ex, (${Option(ex.getCause())})") result finally - // frontendAccess and postProcessor are created lazilly, clean them up only if they were initialized + // frontendAccess and postProcessor are created lazily, clean them up only if they were initialized if _frontendAccess ne null then frontendAccess.compilerSettings.outputDirectory match { case jar: JarArchive => @@ -129,4 +128,8 @@ class GenBCode extends Phase { self => object GenBCode { val name: String = "genBCode" val description: String = "generate JVM bytecode" + + val CLASS_CONSTRUCTOR_NAME = "" + val INSTANCE_CONSTRUCTOR_NAME = "" + } diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala b/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala index 210e47566cb9..e13f4e9f89ce 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCodeOps.scala @@ -4,9 +4,7 @@ package jvm import scala.tools.asm -object GenBCodeOps extends GenBCodeOps - -class GenBCodeOps { +object GenBCodeOps { extension (flags: Int) def addFlagIf(cond: Boolean, flag: Int): Int = if cond then flags | flag else flags diff --git a/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala b/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala index 70db11fc7029..a4156e9a838e 100644 --- a/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala +++ b/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala @@ -71,7 +71,7 @@ private[jvm] object GeneratedClassHandler { } sealed abstract class WritingClassHandler(val javaExecutor: Executor) extends GeneratedClassHandler { - import postProcessor.bTypes.frontendAccess + import postProcessor.frontendAccess def tryStealing: Option[Runnable] @@ -187,5 +187,5 @@ final private class CompilationUnitInPostProcess(private var classes: List[Gener /** the main async task submitted onto the scheduler */ var task: Future[Unit] = uninitialized - val bufferedReporting = new PostProcessorFrontendAccess.BufferingBackendReporting() + val bufferedReporting = new BufferingBackendReporting() } diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala index e0910460ab0e..eaf4e12a7f9e 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala @@ -11,22 +11,20 @@ import scala.tools.asm.ClassWriter import scala.tools.asm.tree.ClassNode /** - * Implements late stages of the backend that don't depend on a Global instance, i.e., + * Implements late stages of the backend, i.e., * optimizations, post-processing and classfile serialization and writing. */ -class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: BTypes) { - self => - import bTypes.{classBTypeFromInternalName} - import frontendAccess.{backendReporting, compilerSettings} +class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, private val ts: CoreBTypes)(using Context) { - val backendUtils = new BackendUtils(this) - val classfileWriters = new ClassfileWriters(frontendAccess) - val classfileWriter = classfileWriters.ClassfileWriter() + private val backendUtils = new BackendUtils(frontendAccess, ts) + val classfileWriters = new ClassfileWriters(frontendAccess) + val classfileWriter = classfileWriters.ClassfileWriter() - type ClassnamePosition = (String, SourcePosition) + + private type ClassnamePosition = (String, SourcePosition) private val caseInsensitively = new ConcurrentHashMap[String, ClassnamePosition] - def sendToDisk(clazz: GeneratedClass, sourceFile: AbstractFile): Unit = if !compilerSettings.outputOnlyTasty then { + def sendToDisk(clazz: GeneratedClass, sourceFile: AbstractFile): Unit = if !frontendAccess.compilerSettings.outputOnlyTasty then { val classNode = clazz.classNode val internalName = classNode.name.nn val bytes = @@ -37,11 +35,11 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: serializeClass(classNode) catch case e: java.lang.RuntimeException if e.getMessage != null && e.getMessage.contains("too large!") => - backendReporting.error(em"Could not write class $internalName because it exceeds JVM code size limits. ${e.getMessage}") + frontendAccess.backendReporting.error(em"Could not write class $internalName because it exceeds JVM code size limits. ${e.getMessage}") null case ex: Throwable => - if compilerSettings.debug then ex.printStackTrace() - backendReporting.error(em"Error while emitting $internalName\n${ex.getMessage}") + if frontendAccess.compilerSettings.debug then ex.printStackTrace() + frontendAccess.backendReporting.error(em"Error while emitting $internalName\n${ex.getMessage}") null if bytes != null then @@ -57,7 +55,7 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: classfileWriter.writeTasty(classNode.name.nn, tastyGenerator(), sourceFile) } - private def warnCaseInsensitiveOverwrite(clazz: GeneratedClass) = { + private def warnCaseInsensitiveOverwrite(clazz: GeneratedClass): Unit = { val name = clazz.classNode.name val lowerCaseJavaName = name.toLowerCase val clsPos = clazz.position @@ -73,10 +71,10 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: else s" (defined in ${pos2.source.file.name})" def nicify(name: String): String = name.replace('/', '.') if name1 == name2 then - backendReporting.error( + frontendAccess.backendReporting.error( em"${nicify(name1)} and ${nicify(name2)} produce classes that overwrite one another", pos1) else - backendReporting.warning( + frontendAccess.backendReporting.warning( em"""Generated class ${nicify(name1)} differs only in case from ${nicify(name2)}$locationAddendum. | Such classes will overwrite one another on case-insensitive filesystems.""", pos1) } @@ -96,7 +94,7 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: addInnerClasses(classNode, declared, referred) } - def serializeClass(classNode: ClassNode): Array[Byte] = { + private def serializeClass(classNode: ClassNode): Array[Byte] = { val cw = new ClassWriterWithBTypeLub(backendUtils.extraProc) classNode.accept(cw) cw.toByteArray.nn @@ -114,18 +112,16 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, val bTypes: * The internal name of the least common ancestor of the types given by inameA and inameB. * It's what ASM needs to know in order to compute stack map frames, http://asm.ow2.org/doc/developer-guide.html#controlflow */ - final class ClassWriterWithBTypeLub(flags: Int) extends ClassWriter(flags) { + private final class ClassWriterWithBTypeLub(flags: Int) extends ClassWriter(flags) { /** - * This method is used by asm when computing stack map frames. It is thread-safe: it depends - * only on the BTypes component, which does not depend on global. - * TODO @lry move to a different place where no global is in scope, on bTypes. + * This method is used by asm when computing stack map frames. */ override def getCommonSuperClass(inameA: String, inameB: String): String = { // All types that appear in a class node need to have their ClassBType cached, see [[cachedClassBType]]. - val a = classBTypeFromInternalName(inameA) - val b = classBTypeFromInternalName(inameB) - val lub = a.jvmWiseLUB(b) + val a = ts.classBTypeFromInternalName(inameA) + val b = ts.classBTypeFromInternalName(inameB) + val lub = a.jvmWiseLUB(b).get val lubName = lub.internalName assert(lubName != "scala/Any") lubName // ASM caches the answer during the lifetime of a ClassWriter. We outlive that. Not sure whether caching on our side would improve things. diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala index f01a5c6f0b91..b48dbeb1bb32 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala @@ -1,46 +1,70 @@ -package dotty.tools.backend.jvm +package dotty.tools +package backend.jvm -import scala.collection.mutable.{Clearable, HashSet} -import dotty.tools.dotc.util.* -import dotty.tools.dotc.reporting.Message +import scala.collection.mutable.HashSet import dotty.tools.io.AbstractFile -import java.util.{Collection => JCollection, Map => JMap} -import dotty.tools.dotc.core.Contexts.Context + +import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.report -import dotty.tools.dotc.core.Phases +import dotty.tools.dotc.config.ScalaSettings + +import scala.collection.mutable import scala.compiletime.uninitialized /** * Functionality needed in the post-processor whose implementation depends on the compiler * frontend. All methods are synchronized. */ -sealed abstract class PostProcessorFrontendAccess(backendInterface: DottyBackendInterface) { +sealed abstract class PostProcessorFrontendAccess(val ctx: FreshContext) { import PostProcessorFrontendAccess.* def compilerSettings: CompilerSettings def withThreadLocalReporter[T](reporter: BackendReporting)(fn: => T): T + def backendReporting: BackendReporting + def directBackendReporting: BackendReporting def getEntryPoints: List[String] private val frontendLock: AnyRef = new Object() - inline final def frontendSynch[T](inline x: Context ?=> T): T = frontendLock.synchronized(x(using backendInterface.ctx)) + + inline final def frontendSynch[T](inline x: Context ?=> T)(using Context): T = frontendLock.synchronized(x) + inline final def frontendSynchWithoutContext[T](inline x: T): T = frontendLock.synchronized(x) - inline def perRunLazy[T](inline init: Context ?=> T): Lazy[T] = new Lazy(init)(using this) + + def perRunLazy[T](init: Context ?=> T)(using Context): Lazy[T] = new SynchronizedLazy(this, init) } object PostProcessorFrontendAccess { - /* A container for value with lazy initialization synchronized on compiler frontend + abstract class Lazy[T] { + def get: T + } + + /** Does not synchronize on the frontend. (But still synchronizes on itself, so terrible name) */ + class LazyWithoutLock[T](init: => T) extends Lazy[T] { + @volatile private var isInit: Boolean = false + private var v: T = uninitialized + + override def get: T = + if isInit then v + else this.synchronized { + if !isInit then v = init + isInit = true + v + } + } + + /** A container for value with lazy initialization synchronized on compiler frontend * Used for sharing variables requiring a Context for initialization, between different threads * Similar to Scala 2 BTypes.LazyVar, but without re-initialization of BTypes.LazyWithLock. These were not moved to PostProcessorFrontendAccess only due to problematic architectural decisions. */ - class Lazy[T](init: Context ?=> T)(using frontendAccess: PostProcessorFrontendAccess) { + private class SynchronizedLazy[T](frontendAccess: PostProcessorFrontendAccess, init: Context ?=> T)(using Context) extends Lazy[T] { @volatile private var isInit: Boolean = false private var v: T = uninitialized - def get: T = + override def get: T = if isInit then v else frontendAccess.frontendSynch { if !isInit then v = init @@ -64,52 +88,17 @@ object PostProcessorFrontendAccess { def outputOnlyTasty: Boolean } - sealed trait BackendReporting { - def error(message: Context ?=> Message, position: SourcePosition): Unit - def warning(message: Context ?=> Message, position: SourcePosition): Unit - def log(message: String): Unit - - def error(message: Context ?=> Message): Unit = error(message, NoSourcePosition) - def warning(message: Context ?=> Message): Unit = warning(message, NoSourcePosition) - } - - final class BufferingBackendReporting(using Context) extends BackendReporting { - // We optimise access to the buffered reports for the common case - that there are no warning/errors to report - // We could use a listBuffer etc - but that would be extra allocation in the common case - // Note - all access is externally synchronized, as this allow the reports to be generated in on thread and - // consumed in another - private var bufferedReports = List.empty[Report] - enum Report(val relay: BackendReporting => Unit): - case Error(message: Message, position: SourcePosition) extends Report(_.error(message, position)) - case Warning(message: Message, position: SourcePosition) extends Report(_.warning(message, position)) - case Log(message: String) extends Report(_.log(message)) - - def error(message: Context ?=> Message, position: SourcePosition): Unit = synchronized: - bufferedReports ::= Report.Error(message, position) - - def warning(message: Context ?=> Message, position: SourcePosition): Unit = synchronized: - bufferedReports ::= Report.Warning(message, position) - - def log(message: String): Unit = synchronized: - bufferedReports ::= Report.Log(message) - - def relayReports(toReporting: BackendReporting): Unit = synchronized: - if bufferedReports.nonEmpty then - bufferedReports.reverse.foreach(_.relay(toReporting)) - bufferedReports = Nil - } - - - class Impl[I <: DottyBackendInterface](int: I, entryPoints: HashSet[String]) extends PostProcessorFrontendAccess(int) { + class Impl(entryPoints: mutable.HashSet[String])(ctx: FreshContext) extends PostProcessorFrontendAccess(ctx) { override def compilerSettings: CompilerSettings = _compilerSettings.get - private lazy val _compilerSettings: Lazy[CompilerSettings] = perRunLazy(buildCompilerSettings) + private lazy val _compilerSettings: Lazy[CompilerSettings] = perRunLazy(buildCompilerSettings)(using ctx) private def buildCompilerSettings(using ctx: Context): CompilerSettings = new CompilerSettings { extension [T](s: dotty.tools.dotc.config.Settings.Setting[T]) def valueSetByUser: Option[T] = Option(s.value).filter(_ != s.default) - inline def s = ctx.settings - override val target = + inline def s: ScalaSettings = ctx.settings + + override val target: String = val releaseValue = Option(s.javaOutputVersion.value).filter(_.nonEmpty) val targetValue = Option(s.XuncheckedJavaOutputVersion.value).filter(_.nonEmpty) (releaseValue, targetValue) match @@ -149,12 +138,8 @@ object PostProcessorFrontendAccess { else local } - override object directBackendReporting extends BackendReporting { - def error(message: Context ?=> Message, position: SourcePosition): Unit = frontendSynch(report.error(message, position)) - def warning(message: Context ?=> Message, position: SourcePosition): Unit = frontendSynch(report.warning(message, position)) - def log(message: String): Unit = frontendSynch(report.log(message)) - } + override def directBackendReporting: BackendReporting = DirectBackendReporting(this)(using ctx) - def getEntryPoints: List[String] = frontendSynch(entryPoints.toList) + override def getEntryPoints: List[String] = frontendSynch(entryPoints.toList)(using ctx) } } diff --git a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala index d9e49cf03b30..72fb8d994dc4 100644 --- a/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala +++ b/compiler/src/dotty/tools/backend/sjs/JSPrimitives.scala @@ -76,8 +76,8 @@ class JSPrimitives(ictx: Context) extends DottyPrimitives(ictx) { override def getPrimitive(sym: Symbol): Int = jsPrimitives.getOrElse(sym, super.getPrimitive(sym)) - override def getPrimitive(app: Apply, tpe: Type)(using Context): Int = - jsPrimitives.getOrElse(app.fun.symbol, super.getPrimitive(app, tpe)) + override def getPrimitive(app: Apply, tpe: Type): Int = + jsPrimitives.getOrElse(app.fun.symbol(using ictx), super.getPrimitive(app, tpe)) override def isPrimitive(sym: Symbol): Boolean = jsPrimitives.contains(sym) || super.isPrimitive(sym) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 8e9dac1a8ed6..301a43d308d2 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -2034,9 +2034,11 @@ class Definitions { @tu private lazy val ScalaNumericValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypeList.toSet @tu private lazy val ScalaValueTypes: collection.Set[TypeRef] = ScalaNumericValueTypes `union` Set(UnitType, BooleanType) + @tu private lazy val ScalaValueTypesNoUnit: collection.Set[TypeRef] = ScalaNumericValueTypes `union` Set(BooleanType) val ScalaNumericValueClasses: PerRun[collection.Set[Symbol]] = new PerRun(ScalaNumericValueTypes.map(_.symbol)) val ScalaValueClasses: PerRun[collection.Set[Symbol]] = new PerRun(ScalaValueTypes.map(_.symbol)) + val ScalaValueClassesNoUnit: PerRun[collection.Set[Symbol]] = new PerRun(ScalaValueTypesNoUnit.map(_.symbol)) val ScalaBoxedClasses: PerRun[collection.Set[Symbol]] = new PerRun( Set(BoxedByteClass, BoxedShortClass, BoxedCharClass, BoxedIntClass, BoxedLongClass, BoxedFloatClass, BoxedDoubleClass, BoxedUnitClass, BoxedBooleanClass) diff --git a/compiler/src/dotty/tools/io/FileWriters.scala b/compiler/src/dotty/tools/io/FileWriters.scala index 8273a9fadfc4..daf25ac28a8c 100644 --- a/compiler/src/dotty/tools/io/FileWriters.scala +++ b/compiler/src/dotty/tools/io/FileWriters.scala @@ -34,17 +34,11 @@ import dotty.tools.dotc.util.{SourcePosition, NoSourcePosition} import dotty.tools.dotc.reporting.Message import dotty.tools.dotc.report -import dotty.tools.backend.jvm.PostProcessorFrontendAccess.BackendReporting import scala.annotation.constructorOnly import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicBoolean import java.util.ConcurrentModificationException -/** !!!Copied from `dotty.tools.backend.jvm.ClassfileWriters` but no `PostProcessorFrontendAccess` needed. - * this should probably be changed to wrap that class instead. - * - * Until then, any changes to this file should be copied to `dotty.tools.backend.jvm.ClassfileWriters` as well. - */ object FileWriters { type InternalName = String type NullableFile = AbstractFile | Null @@ -85,6 +79,12 @@ object FileWriters { def log(message: String): Unit = report.echo(message) + enum Report: + case Error(message: Context => Message, position: SourcePosition) + case Warning(message: Context => Message, position: SourcePosition) + case OptimizerWarning(message: Context => Message, site: String, position: SourcePosition) + case Log(message: String) + final class BufferingReporter extends DelayedReporter { // We optimise access to the buffered reports for the common case - that there are no warning/errors to report // We could use a listBuffer etc - but that would be extra allocation in the common case @@ -93,10 +93,6 @@ object FileWriters { private val _bufferedReports = AtomicReference(List.empty[Report]) private val _hasErrors = AtomicBoolean(false) - enum Report(val relay: Context ?=> BackendReporting => Unit): - case Error(message: Context => Message, position: SourcePosition) extends Report(ctx ?=> _.error(message(ctx), position)) - case Warning(message: Context => Message, position: SourcePosition) extends Report(ctx ?=> _.warning(message(ctx), position)) - case Log(message: String) extends Report(_.log(message)) /** Atomically record that an error occurred */ private def recordError(): Unit = @@ -106,8 +102,8 @@ object FileWriters { private def recordReport(report: Report): Unit = _bufferedReports.getAndUpdate(report :: _) - /** atomically extract and clear the buffered reports, must only be called at a synchonization point. */ - private def resetReports(): List[Report] = + /** atomically extract and clear the buffered reports, must only be called at a synchronization point. */ + def resetReports(): List[Report] = val curr = _bufferedReports.get() if curr.nonEmpty && !_bufferedReports.compareAndSet(curr, Nil) then throw ConcurrentModificationException("concurrent modification of buffered reports") @@ -125,12 +121,6 @@ object FileWriters { def log(message: String): Unit = recordReport(Report.Log(message)) - - /** Should only be called from main compiler thread. */ - def relayReports(toReporting: BackendReporting)(using Context): Unit = - val reports = resetReports() - if reports.nonEmpty then - reports.reverse.foreach(_.relay(toReporting)) } trait ReadOnlySettings: