diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala b/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala deleted file mode 100644 index 59ba2704f56c..000000000000 --- a/compiler/src/dotty/tools/backend/jvm/BCodeAsmCommon.scala +++ /dev/null @@ -1,117 +0,0 @@ -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 SymbolUtils.given - -/** - * Code shared between GenBCode and GenASM that depends on types defined in - * the compiler cake (Global). - */ -object BCodeAsmCommon { - - /** - * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a - * member class. This method is used to decide if we should emit an EnclosingMethod attribute. - * It is also used to decide whether the "owner" field in the InnerClass attribute should be - * null. - */ - 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 - // of isDelambdafyFunction failed (for a function declared in a package named "lambda"). - classSym.isAnonymousClass || { - val originalOwner = classSym.originalOwner - originalOwner != NoSymbol && !originalOwner.isClass - } - } - - /** - * Returns the enclosing method for non-member classes. In the following example - * - * class A { - * def f = { - * class B { - * class C - * } - * } - * } - * - * the method returns Some(f) for B, but None for C, because C is a member class. For non-member - * classes that are not enclosed by a method, it returns None: - * - * class A { - * { class B } - * } - * - * In this case, for B, we return None. - * - * 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)(using ctx: Context): Option[Symbol] = { - assert(classSym.isClass, classSym) - def enclosingMethod(sym: Symbol): Option[Symbol] = { - if (sym.isClass || sym == NoSymbol) None - else if (sym.is(Method, butNot=Synthetic)) Some(sym) - else enclosingMethod(sym.originalOwner) - } - enclosingMethod(classSym.originalOwner) - } - - /** - * 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)(using ctx: Context): Symbol = { - assert(classSym.isClass, classSym) - def enclosingClass(sym: Symbol): Symbol = { - if (sym.isClass) sym - else enclosingClass(sym.originalOwner.originalLexicallyEnclosingClass) - } - enclosingClass(classSym.originalOwner.originalLexicallyEnclosingClass) - } - - final case class EnclosingMethodEntry(owner: String, name: String | Null, methodDescriptor: String | Null) - - /** - * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not - * 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 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)(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)})") - Some(EnclosingMethodEntry( - classDesc(enclosingClassForEnclosingMethodAttribute(classSym)), - methodOpt.map(_.javaSimpleName).orNull, - methodOpt.map(methodDesc).orNull)) - } else { - None - } - } - - private def ubytesToCharArray(bytes: Array[Byte]): Array[Char] = { - val ca = new Array[Char](bytes.length) - var idx = 0 - while(idx < bytes.length) { - val b: Byte = bytes(idx) - assert((b & ~0x7f) == 0) - ca(idx) = b.asInstanceOf[Char] - idx += 1 - } - - ca - } - -} diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index c6c9b2db216c..862ec9275bdd 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -9,7 +9,6 @@ import scala.tools.asm import scala.tools.asm.{Handle, Opcodes} import BCodeHelpers.InvokeStyle import dotty.tools.dotc.ast.tpd -import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.core.Constants.* import dotty.tools.dotc.core.Flags.{Label as LabelFlag, *} import dotty.tools.dotc.core.Types.* @@ -34,17 +33,16 @@ import dotty.tools.dotc.util.SrcPos * @version 1.0 * */ -trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) extends BCodeSkelBuilder { - +trait BCodeBodyBuilder(val primitives: ScalaPrimitives) extends BCodeSkelBuilder { /* * Functionality to build the body of ASM MethodNode, except for `synchronized` and `try` expressions. */ - abstract class PlainBodyBuilder(cunit: CompilationUnit) extends PlainSkelBuilder(cunit) { + abstract class PlainBodyBuilder extends PlainSkelBuilder { private object DesugaredSelect { private val desugared = new java.util.IdentityHashMap[Type, tpd.Select] - def cached(i: Ident): Option[tpd.Select] = { + def cached(i: Ident)(using Context): Option[tpd.Select] = { var found = desugared.get(i.tpe) if (found == null) { tpd.desugarIdent(i) match { @@ -57,7 +55,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte Option(found) } - def unapply(s: tpd.Tree): Option[(Tree, Name)] = { + def unapply(s: tpd.Tree)(using Context): 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)) @@ -90,7 +88,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * Two main cases: `tree` is an assignment, * otherwise an `adapt()` to UNIT is performed if needed. */ - def genStat(tree: Tree): Unit = { + def genStat(tree: Tree)(using Context): Unit = { lineNumber(tree) tree match { @@ -135,7 +133,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } /* Generate code for primitive arithmetic operations. */ - def genArithmeticOp(tree: Tree, code: Int): BType = tree match{ + def genArithmeticOp(tree: Tree, code: Int)(using Context): BType = tree match { case Apply(fun @ DesugaredSelect(larg, _), args) => var resKind = tpeTK(larg) @@ -152,13 +150,13 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte case POS => () // nothing case NEG => bc.neg(resKind) case NOT => bc.genPrimitiveNot(resKind) - case _ => abort(s"Unknown unary operation: ${fun.symbol.showFullName} code: $code") + case _ => throw new AssertionError(s"Unknown unary operation: ${fun.symbol.showFullName} code: $code") } // binary operation case rarg :: Nil => val isShift = isShiftOp(code) - resKind = tpeTK(larg).maxType(if (isShift) INT else tpeTK(rarg), ts) + resKind = tpeTK(larg).maxType(if (isShift) INT else tpeTK(rarg), bTypes) if (isShift || isBitwiseOp(code)) { assert(resKind.isIntegralType || (resKind == BOOL), @@ -181,24 +179,24 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte case LSL | LSR | ASR => bc.genPrimitiveShift(code, resKind) - case _ => abort(s"Unknown primitive: ${fun.symbol}[$code]") + case _ => throw new AssertionError(s"Unknown primitive: ${fun.symbol}[$code]") } case _ => - abort(s"Too many arguments for primitive function: $tree") + throw new AssertionError(s"Too many arguments for primitive function: $tree") } lineNumber(tree) resKind } /* Generate primitive array operations. */ - def genArrayOp(tree: Tree, code: Int, expectedType: BType): BType = tree match{ + def genArrayOp(tree: Tree, code: Int, expectedType: BType)(using Context): BType = tree match { case Apply(DesugaredSelect(arrayObj, _), args) => import ScalaPrimitivesOps.* val k = tpeTK(arrayObj) genLoad(arrayObj, k) - val elementType = ts.typeOfArrayOp.getOrElse[BType](code, abort(s"Unknown operation on arrays: $tree code: $code")) + val elementType = bTypes.typeOfArrayOp.getOrElse[BType](code, throw new AssertionError(s"Unknown operation on arrays: $tree code: $code")) var generatedType = expectedType @@ -229,7 +227,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte generatedType } - def genLoadIfTo(tree: If, expectedType: BType, dest: LoadDestination): BType = tree match{ + def genLoadIfTo(tree: If, expectedType: BType, dest: LoadDestination)(using Context): BType = tree match { case If(condp, thenp, elsep) => val success = new asm.Label @@ -270,7 +268,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte end if } - def genPrimitiveOp(tree: Apply, expectedType: BType): BType = (tree: @unchecked) match { + def genPrimitiveOp(tree: Apply, expectedType: BType)(using Context): BType = (tree: @unchecked) match { case Apply(fun @ DesugaredSelect(receiver, _), _) => val sym = tree.symbol @@ -303,23 +301,23 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte genCoercion(code) coercionTo(code) } - else abort( + else throw new AssertionError( s"Primitive operation not handled yet: ${sym.showFullName}(${fun.symbol.name}) at: ${tree.span}" ) } - def genLoad(tree: Tree): BType = { + def genLoad(tree: Tree)(using Context): BType = { val generatedType = tpeTK(tree) genLoad(tree, generatedType) generatedType } /* Generate code for trees that produce values on the stack */ - def genLoad(tree: Tree, expectedType: BType): Unit = + def genLoad(tree: Tree, expectedType: BType)(using Context): Unit = genLoadTo(tree, expectedType, LoadDestination.FallThrough) /* Generate code for trees that produce values, sent to a given `LoadDestination`. */ - def genLoadTo(tree: Tree, expectedType: BType, dest: LoadDestination): Unit = + def genLoadTo(tree: Tree, expectedType: BType, dest: LoadDestination)(using Context): Unit = var generatedType = expectedType var generatedDest = LoadDestination.FallThrough @@ -367,7 +365,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte generatedDest = LoadDestination.Throw case New(tpt) => - abort(s"Unexpected New(${tpt.tpe.showSummary()}/$tpt) reached GenBCode.\n" + + throw new AssertionError(s"Unexpected New(${tpt.tpe.showSummary()}/$tpt) reached GenBCode.\n" + " Call was genLoad" + ((tree, expectedType))) case t @ Closure(env, call, tpt) => @@ -393,7 +391,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte stack.push(prefixTK) } - genLoadArguments(env, fun.symbol.info.firstParamTypes map ts.toTypeKind) + genLoadArguments(env, fun.symbol.info.firstParamTypes.map(bTypeLoader.bTypeFromType)) stack.restoreSize(savedStackSize) generatedType = genInvokeDynamicLambda(NoSymbol, fun.symbol, env.size, functionalInterface) @@ -403,7 +401,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte case This(qual) => val symIsModuleClass = tree.symbol.is(ModuleClass) assert(tree.symbol == claszSymbol || symIsModuleClass, - s"Trying to access the this of another class: tree.symbol = ${tree.symbol}, class symbol = $claszSymbol compilation unit: $cunit") + s"Trying to access the this of another class: tree.symbol = ${tree.symbol}, class symbol = $claszSymbol compilation unit: ${ctx.compilationUnit}") if (symIsModuleClass && tree.symbol != claszSymbol) { generatedType = genLoadModule(tree) } @@ -414,7 +412,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte // 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 = ts.classBTypeFromSymbol(claszSymbol) + generatedType = bTypeLoader.classBTypeFromSymbol(claszSymbol) } } @@ -456,7 +454,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte 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 = ts.srNullRef + case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = bTypes.srNullRef case _ => genConstant(value, l.srcPos); generatedType = tpeTK(tree) } @@ -492,7 +490,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte case t: TypeApply => // dotty specific generatedType = genTypeApply(t) - case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}") + case _ => throw new AssertionError(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}") } // emit conversion and send to the right destination @@ -500,7 +498,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte genAdaptAndSendToDest(generatedType, expectedType, dest) end genLoadTo - def genAdaptAndSendToDest(generatedType: BType, expectedType: BType, dest: LoadDestination): Unit = + def genAdaptAndSendToDest(generatedType: BType, expectedType: BType, dest: LoadDestination)(using Context): Unit = if generatedType != expectedType then adapt(generatedType, expectedType) @@ -525,7 +523,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte val thrownType = expectedType // `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)) + assert(thrownType == bTypes.srNullRef || thrownType == bTypes.srNothingRef || thrownType.asClassBType.isSubtypeOf(bTypes.jlThrowableRef)) emit(asm.Opcodes.ATHROW) end genAdaptAndSendToDest @@ -534,20 +532,22 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte /* * must-single-thread */ - def fieldLoad( field: Symbol, hostClass: Symbol = null): Unit = fieldOp(field, isLoad = true, hostClass) + def fieldLoad( field: Symbol, hostClass: Symbol | Null = null)(using Context): Unit = + fieldOp(field, isLoad = true, hostClass) /* * must-single-thread */ - def fieldStore(field: Symbol, hostClass: Symbol = null): Unit = fieldOp(field, isLoad = false, hostClass) + def fieldStore(field: Symbol, hostClass: Symbol | Null = null)(using Context): Unit = + fieldOp(field, isLoad = false, hostClass) /* * must-single-thread */ - private def fieldOp(field: Symbol, isLoad: Boolean, specificReceiver: Symbol): Unit = { + private def fieldOp(field: Symbol, isLoad: Boolean, specificReceiver: Symbol | Null)(using Context): Unit = { val useSpecificReceiver = specificReceiver != null && !field.isScalaStatic - val owner = ts.internalName(if (useSpecificReceiver) specificReceiver else field.owner) + val owner = bTypeLoader.classBTypeFromSymbol(if (useSpecificReceiver) specificReceiver else field.owner).internalName val fieldJName = field.javaSimpleName val fieldDescr = symInfoTK(field).descriptor val isStatic = field.isStaticMember @@ -565,7 +565,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * must-single-thread * Otherwise it's safe to call from multiple threads. */ - private def genConstant(const: Constant, pos: SrcPos): Unit = { + private def genConstant(const: Constant, pos: SrcPos)(using Context): Unit = { (const.tag: @switch) match { case BooleanTag => bc.boolconst(const.booleanValue) @@ -593,14 +593,14 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte case NullTag => emit(asm.Opcodes.ACONST_NULL) case ClazzTag => - val tp = ts.toTypeKind(const.typeValue) + val tp = bTypeLoader.bTypeFromType(const.typeValue) if tp.isPrimitive then - val boxedClass = ts.boxedClassOfPrimitive(tp.asPrimitiveBType) + val boxedClass = bTypes.boxedClassOfPrimitive(tp.asPrimitiveBType) mnode.visitFieldInsn( asm.Opcodes.GETSTATIC, boxedClass.internalName, "TYPE", // field name - ts.jlClassRef.descriptor + bTypes.jlClassRef.descriptor ) else val toASM = tp.toASMType @@ -611,11 +611,11 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte mnode.visitLdcInsn("") report.error("Type name is too long for the JVM", pos) - case _ => abort(s"Unknown constant value: $const") + case _ => throw new AssertionError(s"Unknown constant value: $const") } } - private def genLabeledTo(tree: Labeled, expectedType: BType, dest: LoadDestination): BType = tree match { + private def genLabeledTo(tree: Labeled, expectedType: BType, dest: LoadDestination)(using Context): BType = tree match { case Labeled(bind, expr) => val labelSym = bind.symbol @@ -634,7 +634,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte end if } - private def genReturn(r: Return): Unit = { + private def genReturn(r: Return)(using Context): Unit = { val expr: Tree = r.expr val fromSym: Symbol = if (r.from.symbol.is(LabelFlag)) r.from.symbol else NoSymbol @@ -672,7 +672,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } } // end of genReturn() - def genWhileDo(tree: WhileDo): LoadDestination = tree match{ + def genWhileDo(tree: WhileDo)(using Context): LoadDestination = tree match { case WhileDo(cond, body) => val isInfinite = cond == tpd.EmptyTree @@ -702,14 +702,14 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte LoadDestination.FallThrough } - def genTypeApply(t: TypeApply): BType = (t: @unchecked) match { + def genTypeApply(t: TypeApply)(using Context): BType = (t: @unchecked) match { case TypeApply(fun@DesugaredSelect(obj, _), targs) => val sym = fun.symbol val cast = if (sym == defn.Any_isInstanceOf) false else if (sym == defn.Any_asInstanceOf) true - else abort(s"Unexpected type application $fun[sym: ${sym.showFullName}] in: $t") + else throw new AssertionError(s"Unexpected type application $fun[sym: ${sym.showFullName}] in: $t") val l = tpeTK(obj) val r = tpeTK(targs.head) genLoadQualifier(fun) @@ -720,18 +720,18 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte else if (l.isPrimitive) { bc.drop(l) if (cast) { - mnode.visitTypeInsn(asm.Opcodes.NEW, ts.jlClassCastExceptionRef.internalName) - bc.dup(ts.ObjectRef) + mnode.visitTypeInsn(asm.Opcodes.NEW, bTypes.jlClassCastExceptionRef.internalName) + bc.dup(bTypes.ObjectRef) emit(asm.Opcodes.ATHROW) } else { bc.boolconst(false) } } else if (r.isPrimitive && cast) { - abort(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $t") + throw new AssertionError(s"Erasure should have added an unboxing operation to prevent this cast. Tree: $t") } else if (r.isPrimitive) { - bc.isInstance(ts.boxedClassOfPrimitive(r.asPrimitiveBType)) + bc.isInstance(bTypes.boxedClassOfPrimitive(r.asPrimitiveBType)) } else { assert(r.isRef, r) // ensure that it's not a method @@ -742,7 +742,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } // end of genTypeApply() - private def mkArrayConstructorCall(arr: ArrayBType, app: Apply, args: List[Tree]) = { + private def mkArrayConstructorCall(arr: ArrayBType, app: Apply, args: List[Tree])(using Context) = { val dims = arr.dimension var elemKind = arr.elementType val argsSize = args.length @@ -766,14 +766,14 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } - private def genApply(app: Apply, expectedType: BType): BType = { + private def genApply(app: Apply, expectedType: BType)(using Context): BType = { var generatedType = expectedType lineNumber(app) app match { case Apply(_, args) if app.symbol eq defn.newArrayMethod => val List(elemClaz, Literal(c: Constant), av: tpd.JavaSeqLiteral) = args: @unchecked - generatedType = ts.toTypeKind(c.typeValue) + generatedType = bTypeLoader.bTypeFromType(c.typeValue) mkArrayConstructorCall(generatedType.asArrayBType, app, av.elems) case Apply(t :TypeApply, _) => generatedType = @@ -816,7 +816,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte val ctor = fun.symbol assert(ctor.isClassConstructor, s"'new' call to non-constructor: ${ctor.name}") - generatedType = ts.toTypeKind(tpt.tpe) + generatedType = bTypeLoader.bTypeFromType(tpt.tpe) assert(generatedType.isRef, s"Non reference type cannot be instantiated: $generatedType") generatedType match { @@ -824,7 +824,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte mkArrayConstructorCall(arr, app, args) case rt: ClassBType => - assert(ts.classBTypeFromSymbol(ctor.owner) == rt, s"Symbol ${ctor.owner.showFullName} is different from $rt") + assert(bTypeLoader.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) @@ -834,22 +834,22 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte genCallMethod(ctor, InvokeStyle.Special, app) case _ => - abort(s"Cannot instantiate $tpt of kind: $generatedType") + throw new AssertionError(s"Cannot instantiate $tpt of kind: $generatedType") } 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) = ts.asmBoxTo(nativeKind) - bc.invokestatic(ts.srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false, app) - generatedType = ts.boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) + val MethodNameAndType(mname, methodType) = bTypes.asmBoxTo(nativeKind) + bc.invokestatic(bTypes.srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false, app) + generatedType = bTypes.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 = ts.unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) + val boxType = bTypes.unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType - val MethodNameAndType(mname, methodType) = ts.asmUnboxTo(boxType) - bc.invokestatic(ts.srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false, app) + val MethodNameAndType(mname, methodType) = bTypes.asmUnboxTo(boxType) + bc.invokestatic(bTypes.srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false, app) case app @ Apply(fun, args) => val sym = fun.symbol @@ -886,7 +886,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte // Example: `class C { override def clone(): Object = "hi" }` // Emitting `def f(c: C) = c.clone()` as `Object.clone()` gives a VerifyError. val target: String = tpeTK(qual).asRefBType.classOrArrayType - val methodBType = ts.asmMethodType(sym) + val methodBType = bTypeLoader.methodBTypeFromSymbol(sym) bc.invokevirtual(target, sym.javaSimpleName, methodBType.descriptor, app) generatedType = methodBType.returnType } else { @@ -911,7 +911,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte generatedType } // end of genApply() - private def genArrayValue(av: tpd.JavaSeqLiteral): BType = { + private def genArrayValue(av: tpd.JavaSeqLiteral)(using Context): BType = { val tpt = av.tpe match { case JavaArrayType(elem) => elem case _ => @@ -923,8 +923,8 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte genArray(av.elems, tpt) } - private def genArray(elems: List[Tree], elemType: Type): BType = { - val elmKind = ts.toTypeKind(elemType) + private def genArray(elems: List[Tree], elemType: Type)(using Context): BType = { + val elmKind = bTypeLoader.bTypeFromType(elemType) val generatedType = ArrayBType(elmKind) bc.iconst(elems.length) @@ -963,7 +963,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte "scala/MatchError", "", "(Ljava/lang/Object;)V", false) bc.jmethod.visitInsn(asm.Opcodes.ATHROW) - private def genMatchTo(tree: Match, expectedType: BType, dest: LoadDestination): BType = tree match { + private def genMatchTo(tree: Match, expectedType: BType, dest: LoadDestination)(using Context): BType = tree match { case Match(selector, cases) => lineNumber(tree) @@ -1009,10 +1009,10 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte flatKeys ::= value.intValue targets ::= switchBlockPoint case _ => - abort(s"Invalid alternative in alternative pattern in Match node: $tree at: ${tree.span}") + throw new AssertionError(s"Invalid alternative in alternative pattern in Match node: $tree at: ${tree.span}") } case _ => - abort(s"Invalid pattern in Match node: $tree at: ${tree.span}") + throw new AssertionError(s"Invalid pattern in Match node: $tree at: ${tree.span}") } } @@ -1075,11 +1075,11 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte Some(newCase :: existingCasesOpt.getOrElse(Nil)) } case _ => - abort(s"Invalid alternative in alternative pattern in Match node: $tree at: ${tree.span}") + throw new AssertionError(s"Invalid alternative in alternative pattern in Match node: $tree at: ${tree.span}") } case _ => - abort(s"Invalid pattern in Match node: $tree at: ${tree.span}") + throw new AssertionError(s"Invalid pattern in Match node: $tree at: ${tree.span}") } } @@ -1148,7 +1148,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte generatedType } - def genBlockTo(tree: Block, expectedType: BType, dest: LoadDestination): Unit = tree match { + def genBlockTo(tree: Block, expectedType: BType, dest: LoadDestination)(using Context): Unit = tree match { case Block(stats, expr) => val savedScope = varsInScope @@ -1171,9 +1171,9 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } end emitLocalVarScopes - def adapt(from: BType, to: BType): Unit = { - if (from.isNothingType) { - /* There are two possibilities for from.isNothingType: emitting a "throw e" expressions and + def adapt(from: BType, to: BType)(using Context): Unit = { + if (from == bTypes.srNothingRef) { + /* There are two possibilities for from being Nothing: emitting a "throw e" expressions and * loading a (phantom) value of type Nothing. * * The Nothing type in Scala's type system does not exist in the JVM. In bytecode, Nothing @@ -1219,7 +1219,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte */ if (lastInsn.getOpcode != asm.Opcodes.ATHROW) emit(asm.Opcodes.ATHROW) - } else if (from.isNullType) { + } else if (from == bTypes.srNullRef) { /* After loading an expression of type `scala.runtime.Null$`, introduce POP; ACONST_NULL. * This is required to pass the verifier: in Scala's type system, Null conforms to any * reference type. In bytecode, the type Null is represented by scala.runtime.Null$, which @@ -1247,7 +1247,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } /* Emit code to Load the qualifier of `tree` on top of the stack. */ - def genLoadQualifier(tree: Tree): BType = { + def genLoadQualifier(tree: Tree)(using Context): BType = { lineNumber(tree) tree match { case DesugaredSelect(qualifier, _) => genLoad(qualifier) @@ -1258,11 +1258,11 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte assert(t.symbol.owner == this.claszSymbol) UNIT } - case _ => abort(s"Unknown qualifier $tree") + case _ => throw new AssertionError(s"Unknown qualifier $tree") } } - def genLoadArguments(args: List[Tree], btpes: List[BType]): Unit = + def genLoadArguments(args: List[Tree], btpes: List[BType])(using Context): Unit = @tailrec def loop(args: List[Tree], btpes: List[BType]): Unit = args match case arg :: args1 => @@ -1279,12 +1279,12 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte stack.restoreSize(savedStackSize) end genLoadArguments - def genLoadModule(tree: Tree): BType = { + def genLoadModule(tree: Tree)(using Context): BType = { val module = ( if (!tree.symbol.is(PackageClass)) tree.symbol else tree.symbol.info.member(nme.PACKAGE).symbol match { - case NoSymbol => abort(s"SI-5604: Cannot use package as value: $tree") - case s => abort(s"SI-5604: found package class where package object expected: $tree") + case NoSymbol => throw new AssertionError(s"SI-5604: Cannot use package as value: $tree") + case s => throw new AssertionError(s"SI-5604: found package class where package object expected: $tree") } ) lineNumber(tree) @@ -1292,7 +1292,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte symInfoTK(module) } - def genLoadModule(module: Symbol): Unit = { + def genLoadModule(module: Symbol)(using Context): Unit = { def inStaticMethod = methSymbol != null && methSymbol.isStaticMember if (claszSymbol == module.moduleClass && jMethodName != "readResolve" && !inStaticMethod) { mnode.visitVarInsn(asm.Opcodes.ALOAD, 0) @@ -1341,12 +1341,12 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * * use `invokedynamic` with `StringConcatFactory` */ - def genStringConcat(tree: Tree): BType = { + def genStringConcat(tree: Tree)(using Context): BType = { lineNumber(tree) liftStringConcat(tree) match { // Optimization for expressions of the form "" + x case List(Literal(Constant("")), arg) => - genLoad(arg, ts.ObjectRef) + genLoad(arg, bTypes.ObjectRef) genCallMethod(defn.String_valueOf_Object, InvokeStyle.Static) case concatenations => @@ -1387,8 +1387,8 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte if (totalArgSlots + elemSlots >= MaxIndySlots) { stack.restoreSize(savedStackSize) for _ <- 0 until countConcats do - stack.push(ts.StringRef) - bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result(), ts) + stack.push(bTypes.StringRef) + bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result(), bTypes) countConcats += 1 totalArgSlots = 0 recipe.setLength(0) @@ -1416,19 +1416,19 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } } stack.restoreSize(savedStackSize) - bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result(), ts) + bc.genIndyStringConcat(recipe.toString, argTypes.result(), constVals.result(), bTypes) // If we spilled, generate one final concat if (countConcats > 1) { bc.genIndyStringConcat( TagArg.toString * countConcats, - Seq.fill(countConcats)(ts.StringRef.toASMType), + Seq.fill(countConcats)(bTypes.StringRef.toASMType), Seq.empty, - ts + bTypes ) } } - ts.StringRef + bTypes.StringRef } /** @@ -1436,7 +1436,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * invocation instruction, otherwise `method.owner`. A specific receiver class is needed to * prevent IllegalAccessError in some virtual and super calls (aladdin bug 455, i22628). */ - private def genCallMethod(method: Symbol, style: InvokeStyle, pos: Positioned | Null = null, specificReceiver: Symbol = null): BType = { + private def genCallMethod(method: Symbol, style: InvokeStyle, pos: Positioned | Null = null, specificReceiver: Symbol | Null = null)(using Context): BType = { val methodOwner = method.owner // the class used in the invocation's method descriptor in the classfile @@ -1474,16 +1474,16 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } receiverClass.info // ensure types the type is up to date; erasure may add lateINTERFACE to traits - val receiverName = ts.internalName(receiverClass) + val receiverName = bTypeLoader.classBTypeFromSymbol(receiverClass).internalName val jname = method.javaSimpleName - val bmType = ts.asmMethodType(method) + val bmType = bTypeLoader.methodBTypeFromSymbol(method) val mdescr = bmType.descriptor val isInterface = isEmittedInterface(receiverClass) import InvokeStyle.* if (style == Super) { - val ownerBType = ts.toTypeKind(method.owner.info) + val ownerBType = bTypeLoader.bTypeFromType(method.owner.info) if (isInterface && !method.is(JavaDefined)) { val staticDesc = MethodBType(ownerBType :: bmType.argumentTypes, bmType.returnType).descriptor val staticName = BackendUtils.traitSuperAccessorName(method) @@ -1509,8 +1509,8 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } // end of genCallMethod() /* Generate the scala ## method. */ - def genScalaHash(tree: Tree): BType = { - genLoad(tree, ts.ObjectRef) + def genScalaHash(tree: Tree)(using Context): BType = { + genLoad(tree, bTypes.ObjectRef) genCallMethod(NoSymbol, InvokeStyle.Static) // used to dispatch ## on primitives to ScalaRuntime.hash. Should be implemented by a miniphase } @@ -1518,7 +1518,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * Returns a list of trees that each should be concatenated, from left to right. * It turns a chained call like "a".+("b").+("c") into a list of arguments. */ - def liftStringConcat(tree: Tree): List[Tree] = tree match { + def liftStringConcat(tree: Tree)(using Context): List[Tree] = tree match { case tree @ Apply(fun @ DesugaredSelect(larg, method), rarg) => if (isPrimitive(fun) && primitives.getPrimitive(tree, larg.tpe) == ScalaPrimitivesOps.CONCAT) @@ -1595,7 +1595,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * Generate code for conditional expressions. * The jump targets success/failure of the test are `then-target` and `else-target` resp. */ - private def genCond(tree: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label): Unit = { + private def genCond(tree: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label)(using Context): Unit = { def genComparisonOp(l: Tree, r: Tree, code: Int): Unit = { val op = testOpForPrimitive(code) @@ -1607,10 +1607,10 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte 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, ts.ObjectRef) - genCZJUMP(success, failure, op, ts.ObjectRef, targetIfNoJump) + genLoad(nonNullSide, bTypes.ObjectRef) + genCZJUMP(success, failure, op, bTypes.ObjectRef, targetIfNoJump) } else { - val tk = tpeTK(l).maxType(tpeTK(r), ts) + val tk = tpeTK(l).maxType(tpeTK(r), bTypes) genLoad(l, tk) stack.push(tk) genLoad(r, tk) @@ -1692,7 +1692,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * @param l left-hand-side of the '==' * @param r right-hand-side of the '==' */ - def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label): Unit = { + def genEqEqPrimitive(l: Tree, r: Tree, success: asm.Label, failure: asm.Label, targetIfNoJump: asm.Label)(using Context): Unit = { /* True if the equality comparison is between values that require the use of the rich equality * comparator (scala.runtime.Comparator.equals). This is the case when either side of the @@ -1728,9 +1728,9 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } else defn.BoxesRunTimeModule_externalEquals } - genLoad(l, ts.ObjectRef) - stack.push(ts.ObjectRef) - genLoad(r, ts.ObjectRef) + genLoad(l, bTypes.ObjectRef) + stack.push(bTypes.ObjectRef) + genLoad(r, bTypes.ObjectRef) stack.pop() genCallMethod(equalsMethod, InvokeStyle.Static) genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump) @@ -1738,25 +1738,25 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte else { if (isNull(l)) { // null == expr -> expr eq null - genLoad(r, ts.ObjectRef) - genCZJUMP(success, failure, TestOp.EQ, ts.ObjectRef, targetIfNoJump) + genLoad(r, bTypes.ObjectRef) + genCZJUMP(success, failure, TestOp.EQ, bTypes.ObjectRef, targetIfNoJump) } else if (isNull(r)) { // expr == null -> expr eq null - genLoad(l, ts.ObjectRef) - genCZJUMP(success, failure, TestOp.EQ, ts.ObjectRef, targetIfNoJump) + genLoad(l, bTypes.ObjectRef) + genCZJUMP(success, failure, TestOp.EQ, bTypes.ObjectRef, targetIfNoJump) } else if (isNonNullExpr(l)) { // SI-7852 Avoid null check if L is statically non-null. - genLoad(l, ts.ObjectRef) - stack.push(ts.ObjectRef) - genLoad(r, ts.ObjectRef) + genLoad(l, bTypes.ObjectRef) + stack.push(bTypes.ObjectRef) + genLoad(r, bTypes.ObjectRef) stack.pop() genCallMethod(defn.Any_equals, InvokeStyle.Virtual) genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump) } else { // l == r -> Objects.equals(l, r) - genLoad(l, ts.ObjectRef) - stack.push(ts.ObjectRef) - genLoad(r, ts.ObjectRef) + genLoad(l, bTypes.ObjectRef) + stack.push(bTypes.ObjectRef) + genLoad(r, bTypes.ObjectRef) stack.pop() genCallMethod(defn.Objects_equals, InvokeStyle.Static) genCZJUMP(success, failure, TestOp.NE, BOOL, targetIfNoJump) @@ -1765,14 +1765,14 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte } - def genSynchronized(tree: Apply, expectedType: BType): BType - def genLoadTry(tree: Try): BType + def genSynchronized(tree: Apply, expectedType: BType)(using Context): BType + def genLoadTry(tree: Try)(using Context): BType - def genInvokeDynamicLambda(ctor: Symbol, lambdaTarget: Symbol, environmentSize: Int, functionalInterface: Symbol): BType = { + def genInvokeDynamicLambda(ctor: Symbol, lambdaTarget: Symbol, environmentSize: Int, functionalInterface: Symbol)(using Context): BType = { import java.lang.invoke.LambdaMetafactory.{FLAG_BRIDGES, FLAG_SERIALIZABLE} report.debuglog(s"Using invokedynamic rather than `new ${ctor.owner}`") - val generatedType = ts.classBTypeFromSymbol(functionalInterface) + val generatedType = bTypeLoader.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) @@ -1785,9 +1785,9 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte val targetHandle = new asm.Handle(invokeStyle, - ts.classBTypeFromSymbol(lambdaTarget.owner).internalName, + bTypeLoader.classBTypeFromSymbol(lambdaTarget.owner).internalName, lambdaTarget.javaSimpleName, - ts.asmMethodType(lambdaTarget).descriptor, + bTypeLoader.methodBTypeFromSymbol(lambdaTarget).descriptor, /* itf = */ isInterface) val (a,b) = lambdaTarget.info.firstParamTypes.splitAt(environmentSize) @@ -1799,30 +1799,30 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte // Requires https://github.com/scala/scala-java8-compat on the runtime classpath val returnUnit = lambdaTarget.info.resultType.typeSymbol == defn.UnitClass val functionalInterfaceDesc: String = generatedType.descriptor - val desc = capturedParamsTypes.map(tpe => ts.toTypeKind(tpe)).mkString(("("), "", ")") + functionalInterfaceDesc + val desc = capturedParamsTypes.map(bTypeLoader.bTypeFromType).mkString(("("), "", ")") + functionalInterfaceDesc val samMethod = atPhase(erasurePhase) { val samMethods = toDenot(functionalInterface).info.possibleSamMethods.toList samMethods match { case x :: Nil => x.symbol - case Nil => abort(s"${functionalInterface.show} is not a functional interface. It doesn't have abstract methods") - case xs => abort(s"${functionalInterface.show} is not a functional interface. " + + case Nil => throw new AssertionError(s"${functionalInterface.show} is not a functional interface. It doesn't have abstract methods") + case xs => throw new AssertionError(s"${functionalInterface.show} is not a functional interface. " + s"It has the following abstract methods: ${xs.map(_.name).mkString(", ")}") } } val methodName = samMethod.javaSimpleName - val samMethodBType = ts.asmMethodType(samMethod) + val samMethodBType = bTypeLoader.methodBTypeFromSymbol(samMethod) val samMethodType = samMethodBType.toASMType def boxInstantiated(instantiatedType: BType, samType: BType): BType = if(!samType.isPrimitive && instantiatedType.isPrimitive) - ts.boxedClassOfPrimitive(instantiatedType.asPrimitiveBType) + bTypes.boxedClassOfPrimitive(instantiatedType.asPrimitiveBType) else instantiatedType // TODO specialization - val instantiatedMethodBType = new MethodBType( - lambdaParamTypes.map(p => ts.toTypeKind(p)), - boxInstantiated(ts.toTypeKind(lambdaTarget.info.resultType), samMethodBType.returnType) + val instantiatedMethodBType = MethodBType( + lambdaParamTypes.map(bTypeLoader.bTypeFromType), + boxInstantiated(bTypeLoader.bTypeFromType(lambdaTarget.info.resultType), samMethodBType.returnType) ) val instantiatedMethodType = instantiatedMethodBType.toASMType @@ -1833,7 +1833,7 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte val bridgeMethods = atPhase(erasurePhase){ samMethod.allOverriddenSymbols.toList } - val overriddenMethodTypes = bridgeMethods.map(b => ts.asmMethodType(b).toASMType) + val overriddenMethodTypes = bridgeMethods.map(b => bTypeLoader.methodBTypeFromSymbol(b).toASMType) // any methods which `samMethod` overrides need bridges made for them // this is done automatically during erasure for classes we generate, but LMF needs to have them explicitly mentioned @@ -1858,9 +1858,9 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte val metafactory = if (flags != 0) - ts.jliLambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed + bTypes.jliLambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed else - ts.jliLambdaMetaFactoryMetafactoryHandle + bTypes.jliLambdaMetaFactoryMetafactoryHandle bc.jmethod.visitInvokeDynamicInsn(methodName, desc, metafactory, bsmArgs*) @@ -1874,8 +1874,8 @@ trait BCodeBodyBuilder(val primitives: ScalaPrimitives)(using ctx: Context) exte * create for Java-defined classes as well as for Java annotations * which we represent as classes. */ - private def isEmittedInterface(sym: Symbol): Boolean = sym.is(Trait) || - sym.is(JavaDefined) && (toDenot(sym).isAnnotation || sym.is(ModuleClass) && (sym.companionClass.is(PureInterface)) || sym.companionClass.is(Trait)) - + private def isEmittedInterface(sym: Symbol)(using Context): Boolean = + sym.is(Trait) || + sym.is(JavaDefined) && (toDenot(sym).isAnnotation || sym.is(ModuleClass) && (sym.companionClass.is(PureInterface)) || sym.companionClass.is(Trait)) } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index f119cb867012..ddd805d4b6d6 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -3,15 +3,9 @@ package backend package jvm import scala.language.unsafeNulls - -import scala.annotation.threadUnsafe import scala.tools.asm -import scala.tools.asm.AnnotationVisitor -import scala.tools.asm.ClassWriter +import scala.tools.asm.{AnnotationVisitor, ClassWriter, Opcodes} import scala.collection.mutable -import scala.compiletime.uninitialized - -import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.Trees import dotty.tools.dotc.core.Annotations.* @@ -32,11 +26,9 @@ import dotty.tools.dotc.core.TypeErasure import dotty.tools.dotc.transform.GenericSignatures import dotty.tools.dotc.transform.ElimErasedValueType import dotty.tools.dotc.transform.Mixin -import dotty.tools.io.AbstractFile import dotty.tools.dotc.report - import tpd.* -import SymbolUtils.given +import dotty.tools.dotc.config.ScalaSettingsProperties /* * Encapsulates functionality to convert Scala AST Trees into ASM ClassNodes. @@ -45,18 +37,24 @@ import SymbolUtils.given * @version 1.0 * */ -trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends BCodeIdiomatic { - - val ts: CoreBTypes - - private def ScalaATTRName: String = "Scala" - private def ScalaSignatureATTRName: String = "ScalaSig" - - @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") - +trait BCodeHelpers(val bTypeLoader: BTypeLoader, val bTypes: WellKnownBTypes) extends BCodeIdiomatic { + + // OK to cache because it won't change across Contexts + private var cachedClassfileVersion: Int | Null = null + protected def classfileVersion(using Context): Int = + if cachedClassfileVersion == null then + val releaseValue = Option(ctx.settings.javaOutputVersion.value).filter(_.nonEmpty) + val targetValue = Option(ctx.settings.XuncheckedJavaOutputVersion.value).filter(_.nonEmpty) + val target = (releaseValue, targetValue) match + case (Some(release), None) => release + case (None, Some(target)) => target + case (Some(release), Some(_)) => + report.warning(s"The value of ${ctx.settings.XuncheckedJavaOutputVersion.name} was overridden by ${ctx.settings.javaOutputVersion.name}") + release + case (None, None) => ScalaSettingsProperties.supportedTargetVersions.min // least supported version by default + // take advantage of the fact classfile versions are consecutive + cachedClassfileVersion = target.toInt + (Opcodes.V17 - 17) + cachedClassfileVersion.nn /* * can-multi-thread @@ -100,57 +98,83 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B /* * can-multi-thread */ - def pickleMarkerLocal = { - createJAttribute(ScalaSignatureATTRName, versionPickle.bytes, 0, versionPickle.writeIndex) + def pickleMarkerLocal(using Context) = { + createJAttribute(nme.ScalaSignatureATTR.toString, versionPickle.bytes, 0, versionPickle.writeIndex) } /* * can-multi-thread */ - def pickleMarkerForeign = { - createJAttribute(ScalaATTRName, new Array[Byte](0), 0, 0) + def pickleMarkerForeign(using Context) = { + createJAttribute(nme.ScalaATTR.toString, new Array[Byte](0), 0, 0) } } // end of trait BCPickles trait BCAnnotGen { + // OK to cache these across Contexts, what they refer to won't change + private var cachedAnnotationRetentionAttr: ClassSymbol | Null = null + private var cachedAnnotationRetentionSource: TermSymbol | Null = null + private var cachedAnnotationRetentionClass: TermSymbol | Null = null + private var cachedAnnotationRetentionRuntime: TermSymbol | Null = null + + private def annotationRetentionAttr(using Context): ClassSymbol = + if cachedAnnotationRetentionAttr eq null then + cachedAnnotationRetentionAttr = requiredClass("java.lang.annotation.Retention") + cachedAnnotationRetentionAttr.nn + + private def annotationRetentionSourceAttr(using Context): TermSymbol = + if cachedAnnotationRetentionSource eq null then + cachedAnnotationRetentionSource = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("SOURCE") + cachedAnnotationRetentionSource.nn + + private def annotationRetentionClassAttr(using Context): TermSymbol = + if cachedAnnotationRetentionClass eq null then + cachedAnnotationRetentionClass = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("CLASS") + cachedAnnotationRetentionClass.nn + + private def annotationRetentionRuntimeAttr(using Context): TermSymbol = + if cachedAnnotationRetentionRuntime eq null then + cachedAnnotationRetentionRuntime = requiredClass("java.lang.annotation.RetentionPolicy").linkedClass.requiredValue("RUNTIME") + cachedAnnotationRetentionRuntime.nn + /* * must-single-thread */ - def emitAnnotations(cw: asm.ClassVisitor, annotations: List[Annotation]): Unit = + def emitAnnotations(cw: asm.ClassVisitor, annotations: List[Annotation])(using Context): Unit = for(annot <- annotations; if shouldEmitAnnotation(annot)) { val typ = annot.tree.tpe val assocs = assocsFromApply(annot.tree) - val av = cw.visitAnnotation(ts.typeDescriptor(typ), isRuntimeVisible(annot)) + val av = cw.visitAnnotation(bTypeLoader.bTypeFromType(typ).descriptor, isRuntimeVisible(annot)) emitAssocs(av, assocs) } /* * must-single-thread */ - def emitAnnotations(mw: asm.MethodVisitor, annotations: List[Annotation]): Unit = + def emitAnnotations(mw: asm.MethodVisitor, annotations: List[Annotation])(using Context): Unit = for(annot <- annotations; if shouldEmitAnnotation(annot)) { val typ = annot.tree.tpe val assocs = assocsFromApply(annot.tree) - val av = mw.visitAnnotation(ts.typeDescriptor(typ), isRuntimeVisible(annot)) + val av = mw.visitAnnotation(bTypeLoader.bTypeFromType(typ).descriptor, isRuntimeVisible(annot)) emitAssocs(av, assocs) } /* * must-single-thread */ - def emitAnnotations(fw: asm.FieldVisitor, annotations: List[Annotation]): Unit = + def emitAnnotations(fw: asm.FieldVisitor, annotations: List[Annotation])(using Context): Unit = for(annot <- annotations; if shouldEmitAnnotation(annot)) { val typ = annot.tree.tpe val assocs = assocsFromApply(annot.tree) - val av = fw.visitAnnotation(ts.typeDescriptor(typ), isRuntimeVisible(annot)) + val av = fw.visitAnnotation(bTypeLoader.bTypeFromType(typ).descriptor, isRuntimeVisible(annot)) emitAssocs(av, assocs) } /* * must-single-thread */ - def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol]): Unit = + def emitParamNames(jmethod: asm.MethodVisitor, params: List[Symbol])(using Context): Unit = for param <- params do var access = asm.Opcodes.ACC_FINAL if param.is(Artifact) then access |= asm.Opcodes.ACC_SYNTHETIC @@ -159,31 +183,31 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B /* * must-single-thread */ - def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[Annotation]]): Unit = + def emitParamAnnotations(jmethod: asm.MethodVisitor, pannotss: List[List[Annotation]])(using Context): Unit = 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, ts.typeDescriptor(typ), isRuntimeVisible(annot)) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, bTypeLoader.bTypeFromType(typ).descriptor, isRuntimeVisible(annot)) emitAssocs(pannVisitor, assocs) } - private def shouldEmitAnnotation(annot: Annotation): Boolean = { + private def shouldEmitAnnotation(annot: Annotation)(using Context): Boolean = { annot.symbol.is(JavaDefined) && - retentionPolicyOf(annot) != AnnotationRetentionSourceAttr + retentionPolicyOf(annot) != annotationRetentionSourceAttr } - private def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, Object)]): Unit = { + private def emitAssocs(av: asm.AnnotationVisitor, assocs: List[(Name, Object)])(using Context): Unit = { for ((name, value) <- assocs) emitArgument(av, name.mangledString, value.asInstanceOf[Tree]) av.visitEnd() } private def emitArgument(av: AnnotationVisitor, - name: String, - arg: Tree): Unit = { + name: String, + arg: Tree)(using Context): Unit = { val narg = normalizeArgument(arg) // Transformation phases are not run on annotation trees, so we need to run // `constToLiteral` at this point. @@ -195,12 +219,12 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B 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, ts.typeToTypeKind(TypeErasure.erasure(const.typeValue)).toASMType) + case ClazzTag => av.visit(name, bTypeLoader.bTypeFromType(TypeErasure.erasure(const.typeValue)).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 case t: tpd.RefTree if t.symbol.owner.linkedClass.isAllOf(JavaEnum) => - val edesc = ts.typeDescriptor(t.tpe) // the class descriptor of the enumeration class. + val edesc = bTypeLoader.bTypeFromType(t.tpe).descriptor // the class descriptor of the enumeration class. val evalue = t.symbol.javaSimpleName // value the actual enumeration value. av.visitEnum(name, edesc, evalue) // Handle final val aliases to Java enum values. @@ -211,7 +235,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B case _ => false } => val enumRef = atPhase(erasurePhase)(t.symbol.info.finalResultType.asInstanceOf[TermRef]) - val edesc = ts.typeDescriptor(enumRef) + val edesc = bTypeLoader.bTypeFromType(enumRef).descriptor val evalue = enumRef.termSymbol.javaSimpleName av.visitEnum(name, edesc, evalue) case t: SeqLiteral => @@ -242,17 +266,17 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B // see http://www.scala-lang.org/sid/10 (Storage of pickled Scala signatures in class files) // also JVMS Sec. 4.7.16.1 The element_value structure and JVMS Sec. 4.4.7 The CONSTANT_Utf8_info Structure. if (sb.fitsInOneString) { - av.visit(name, BCodeAsmCommon.strEncode(sb)) + av.visit(name, BCodeUtils.strEncode(sb)) } else { val arrAnnotV: asm.AnnotationVisitor = av.visitArray(name) - for(arg <- BCodeAsmCommon.arrEncode(sb)) { arrAnnotV.visit(name, arg) } + for(arg <- BCodeUtils.arrEncode(sb)) { arrAnnotV.visit(name, arg) } arrAnnotV.visitEnd() } // for the lazy val in ScalaSigBytes to be GC'ed, the invoker of emitAnnotations() should hold the ScalaSigBytes in a method-local var that doesn't escape. */ case t @ Apply(constr, args) if t.tpe.classSymbol.is(JavaAnnotation) => val typ = t.tpe.classSymbol.denot.info val assocs = assocsFromApply(t) - val desc = ts.typeDescriptor(typ) // the class descriptor of the nested annotation class + val desc = bTypeLoader.bTypeFromType(typ).descriptor // the class descriptor of the nested annotation class val nestedVisitor = av.visitAnnotation(name, desc) emitAssocs(nestedVisitor, assocs) @@ -270,9 +294,9 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B case _ => arg } - private def isRuntimeVisible(annot: Annotation): Boolean = - if (toDenot(annot.tree.tpe.typeSymbol).hasAnnotation(AnnotationRetentionAttr)) - retentionPolicyOf(annot) == AnnotationRetentionRuntimeAttr + private def isRuntimeVisible(annot: Annotation)(using Context): Boolean = + if (toDenot(annot.tree.tpe.typeSymbol).hasAnnotation(annotationRetentionAttr)) + retentionPolicyOf(annot) == annotationRetentionRuntimeAttr else { // SI-8926: if the annotation class symbol doesn't have a @RetentionPolicy annotation, the // annotation is emitted with visibility `RUNTIME` @@ -280,11 +304,11 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B true } - private def retentionPolicyOf(annot: Annotation): Symbol = - annot.tree.tpe.typeSymbol.getAnnotation(AnnotationRetentionAttr). - flatMap(_.argument(0).map(_.tpe.termSymbol)).getOrElse(AnnotationRetentionClassAttr) + private def retentionPolicyOf(annot: Annotation)(using Context): Symbol = + annot.tree.tpe.typeSymbol.getAnnotation(annotationRetentionAttr). + flatMap(_.argument(0).map(_.tpe.termSymbol)).getOrElse(annotationRetentionClassAttr) - private def assocsFromApply(tree: Tree): List[(Name, Tree)] = { + private def assocsFromApply(tree: Tree)(using Context): List[(Name, Tree)] = { tree match { case Block(_, expr) => assocsFromApply(expr) case Apply(fun, args) => @@ -301,8 +325,6 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B trait BCJGenSigGen { - def getCurrentCUnit(): CompilationUnit - /** * Generates the generic signature for `sym` before erasure. * @@ -312,7 +334,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B * Machine Specification, §4.3.4, or `null` if `sym` doesn't need a generic signature. * @see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4 */ - def getGenericSignature(sym: Symbol, owner: Symbol): String | Null = { + def getGenericSignature(sym: Symbol, owner: Symbol)(using Context): String | Null = { atPhase(erasurePhase) { // Finding the member's type is nontrivial because of erasure and how it interacts with other phases. def computeMemberType(): Type = { @@ -357,10 +379,10 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B * * must-single-thread */ - private def addForwarder(jclass: asm.ClassVisitor, module: Symbol, m: Symbol, isSynthetic: Boolean): Unit = { - val moduleName = ts.internalName(module) + private def addForwarder(jclass: asm.ClassVisitor, module: Symbol, m: Symbol, isSynthetic: Boolean)(using Context): Unit = { + val moduleName = bTypeLoader.classBTypeFromSymbol(module).internalName val methodInfo = module.thisType.memberInfo(m) - val paramJavaTypes: List[BType] = methodInfo.firstParamTypes.map(ts.toTypeKind) + val paramJavaTypes: List[BType] = methodInfo.firstParamTypes.map(bTypeLoader.bTypeFromType) // val paramNames = 0 until paramJavaTypes.length.map("x_" + _) /* Forwarders must not be marked final, @@ -379,7 +401,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B val (throws, others) = m.annotations.partition(_.symbol eq defn.ThrowsAnnot) val thrownExceptions: List[String] = getExceptions(throws) - val jReturnType = ts.toTypeKind(methodInfo.resultType) + val jReturnType = bTypeLoader.bTypeFromType(methodInfo.resultType) val mdesc = MethodBType(paramJavaTypes, jReturnType).descriptor val mirrorMethodName = m.javaSimpleName val lengthOk = if jgensig ne null then BCodeUtils.checkConstantStringLength(jgensig) @@ -405,7 +427,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B mirrorMethod.visitCode() - mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, str.MODULE_INSTANCE_FIELD, ts.symDescriptor(module)) + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, str.MODULE_INSTANCE_FIELD, bTypeLoader.classBTypeFromSymbol(module).descriptor) var index = 0 for(jparamType <- paramJavaTypes) { @@ -414,7 +436,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B index += jparamType.size } - mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, ts.asmMethodType(m).descriptor, false) + mirrorMethod.visitMethodInsn(asm.Opcodes.INVOKEVIRTUAL, moduleName, mirrorMethodName, bTypeLoader.methodBTypeFromSymbol(m).descriptor, false) mirrorMethod.visitInsn(jReturnType.typedOpcode(asm.Opcodes.IRETURN)) mirrorMethod.visitMaxs(0, 0) // just to follow protocol, dummy arguments @@ -429,7 +451,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B * * must-single-thread */ - def addForwarders(jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol): Unit = { + def addForwarders(jclass: asm.ClassVisitor, jclassName: String, moduleClass: Symbol)(using Context): Unit = { assert(moduleClass.is(ModuleClass), moduleClass) report.debuglog(s"Dumping mirror class for object: $moduleClass") @@ -464,7 +486,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B /** The members of this type that have all of `required` flags but none of `excluded` flags set. * The members are sorted by name and signature to guarantee a stable ordering. */ - private def sortedMembersBasedOnFlags(tp: Type, required: Flag, excluded: FlagSet): List[Symbol] = { + private def sortedMembersBasedOnFlags(tp: Type, required: Flag, excluded: FlagSet)(using Context): List[Symbol] = { // The output of `memberNames` is a Set, sort it to guarantee a stable ordering. val names = tp.memberNames(takeAllFilter).toSeq.sorted val buffer = mutable.ListBuffer[Symbol]() @@ -485,9 +507,9 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B * * must-single-thread */ - def getExceptions(excs: List[Annotation]): List[String] = { + def getExceptions(excs: List[Annotation])(using Context): List[String] = { for (case ThrownException(exc) <- excs.distinct) - yield ts.internalName(TypeErasure.erasure(exc).classSymbol) + yield bTypeLoader.classBTypeFromSymbol(TypeErasure.erasure(exc).classSymbol).internalName } } // end of trait BCForwardersGen @@ -524,9 +546,6 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B /* builder of mirror classes */ class JMirrorBuilder extends JCommonBuilder { - private var cunit: CompilationUnit = uninitialized - 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 * on the MODULE instance of the given Scala object. It will only be @@ -535,29 +554,27 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B * * must-single-thread */ - def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { + def genMirrorClass(moduleClass: Symbol)(using Context): asm.tree.ClassNode = { assert(moduleClass.is(ModuleClass)) assert(moduleClass.companionClass == NoSymbol, moduleClass) - this.cunit = cunit - val bType = ts.mirrorClassBTypeFromSymbol(moduleClass) - val moduleName = ts.internalName(moduleClass) // + "$" + val bType = bTypeLoader.mirrorClassBTypeFromSymbol(moduleClass) + val moduleName = bTypeLoader.classBTypeFromSymbol(moduleClass).internalName val mirrorName = bType.internalName val mirrorClass = new asm.tree.ClassNode if !BCodeUtils.checkConstantStringLength(mirrorName) then report.error("Mirror class name is too long for the JVM", moduleClass.srcPos) return mirrorClass // not filled, but we cannot create it, and we just reported an error mirrorClass.visit( - backendUtils.classfileVersion, + classfileVersion, bType.info.flags, mirrorName, null /* no java-generic-signature */, - ts.ObjectRef.internalName, + bTypes.ObjectRef.internalName, EMPTY_STRING_ARRAY ) if (BackendUtils.emitSource) { - mirrorClass.visitSource("" + cunit.source.file.name, - null /* SourceDebugExtension */) + mirrorClass.visitSource("" + ctx.compilationUnit.source.file.name, null /* SourceDebugExtension */) } val ssa = None // getAnnotPickle(mirrorName, if (moduleClass.is(Module)) moduleClass.companionClass else moduleClass.companionModule) @@ -588,15 +605,15 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B /* * must-single-thread */ - def isAndroidParcelableClass(sym: Symbol) = + def isAndroidParcelableClass(sym: Symbol)(using Context) = (AndroidParcelableInterface != NoSymbol) && (sym.info.parents.map(_.typeSymbol) contains AndroidParcelableInterface) /* * must-single-thread */ - def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String): Unit = { - val androidCreatorType = ts.getClassBType(AndroidCreatorClass) + def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String)(using Context): Unit = { + val androidCreatorType = null.asInstanceOf[ClassBType] // would hit an assert error if it ever ran because AndroidCreatorClass is NoSymbol: bTypeLoader.getClassBType(AndroidCreatorClass) val tdesc_creator = androidCreatorType.descriptor cnode.visitField( @@ -686,7 +703,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B // @M don't generate java generics sigs for (members of) implementation // classes, as they are monomorphic (TODO: ok?) - private final def needsGenericSignature(sym: Symbol): Boolean = !( + private final def needsGenericSignature(sym: Symbol)(using Context): Boolean = !( // pp: this condition used to include sym.hasexpandedname, but this leads // to the total loss of generic information if a private member is // accessed from a closure: both the field and the accessor were generated @@ -699,7 +716,7 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B || sym.is(Bridge) ) - private def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol): String = { + private def getStaticForwarderGenericSignature(sym: Symbol, moduleClass: Symbol)(using Context): String = { // scala/bug#3452 Static forwarder generation uses the same erased signature as the method if forwards to. // By rights, it should use the signature as-seen-from the module class, and add suitable // primitive and value-class boxing/unboxing. @@ -712,11 +729,6 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B getGenericSignatureHelper(sym, moduleClass, memberTpe).orNull else null } - - def abort(msg: String): Nothing = { - report.error(msg) - throw new RuntimeException(msg) - } } object BCodeHelpers { diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index 2023b210e37f..dfe7da286239 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -3,13 +3,11 @@ package backend package jvm import scala.language.unsafeNulls - import scala.tools.asm import scala.annotation.switch import scala.tools.asm.tree.MethodInsnNode -import dotty.tools.dotc.core.Contexts.Context -import dotty.tools.dotc.report import dotty.tools.dotc.ast.Positioned +import dotty.tools.dotc.core.Contexts.Context /* * A high-level facade to the ASM API for bytecode generation. @@ -18,9 +16,9 @@ import dotty.tools.dotc.ast.Positioned * @version 1.0 * */ -trait BCodeIdiomatic(using Context) { +trait BCodeIdiomatic { - def recordCallsitePosition(m: MethodInsnNode, pos: Positioned | Null): Unit + def recordCallsitePosition(m: MethodInsnNode, pos: Positioned | Null)(using Context): Unit val CLASS_CONSTRUCTOR_NAME = "" val INSTANCE_CONSTRUCTOR_NAME = "" @@ -89,7 +87,7 @@ trait BCodeIdiomatic(using Context) { def jmethod: asm.tree.MethodNode - import asm.Opcodes; + import asm.Opcodes final def emit(opc: Int): Unit = { jmethod.visitInsn(opc) } @@ -104,7 +102,7 @@ trait BCodeIdiomatic(using Context) { jmethod.visitLdcInsn(java.lang.Long.valueOf(-1)) jmethod.visitInsn(Opcodes.LXOR) } else { - abort(s"Impossible to negate an $kind") + throw new AssertionError(s"Impossible to negate an $kind") } end genPrimitiveNot @@ -175,7 +173,7 @@ trait BCodeIdiomatic(using Context) { recipe: String, argTypes: Seq[asm.Type], constants: Seq[String], - ts: CoreBTypes + ts: WellKnownBTypes ): Unit = { jmethod.visitInvokeDynamicInsn( "makeConcatWithConstants", @@ -339,23 +337,23 @@ trait BCodeIdiomatic(using Context) { final def rem(tk: BType): Unit = { emitPrimitive(JCodeMethodN.remOpcodes, tk) } // can-multi-thread // can-multi-thread - final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Positioned | Null): Unit = { + final def invokespecial(owner: String, name: String, desc: String, itf: Boolean, pos: Positioned | Null)(using Context): Unit = { emitInvoke(Opcodes.INVOKESPECIAL, owner, name, desc, itf, pos) } // can-multi-thread - final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Positioned | Null): Unit = { + final def invokestatic(owner: String, name: String, desc: String, itf: Boolean, pos: Positioned | Null)(using Context): Unit = { emitInvoke(Opcodes.INVOKESTATIC, owner, name, desc, itf, pos) } // can-multi-thread - final def invokeinterface(owner: String, name: String, desc: String, pos: Positioned | Null): Unit = { + final def invokeinterface(owner: String, name: String, desc: String, pos: Positioned | Null)(using Context): Unit = { emitInvoke(Opcodes.INVOKEINTERFACE, owner, name, desc, itf = true, pos) } // can-multi-thread - final def invokevirtual(owner: String, name: String, desc: String, pos: Positioned | Null): Unit = { + final def invokevirtual(owner: String, name: String, desc: String, pos: Positioned | Null)(using Context): Unit = { emitInvoke(Opcodes.INVOKEVIRTUAL, owner, name, desc, itf = false, pos) } - def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Positioned | Null): Unit = { + def emitInvoke(opcode: Int, owner: String, name: String, desc: String, itf: Boolean, pos: Positioned | Null)(using Context): Unit = { val node = new MethodInsnNode(opcode, owner, name, desc, itf) jmethod.instructions.add(node) recordCallsitePosition(node, pos) @@ -421,7 +419,7 @@ trait BCodeIdiomatic(using Context) { i = 1 while (i < keys.length) { if (keys(i-1) == keys(i)) { - abort("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") + throw new AssertionError("duplicate keys in SWITCH, can't pick arbitrarily one of them to evict, see SI-6011.") } i += 1 } @@ -540,11 +538,6 @@ trait BCodeIdiomatic(using Context) { jmethod.visitTypeInsn(Opcodes.CHECKCAST, tk.classOrArrayType) } - def abort(msg: String): Nothing = { - report.error(msg) - throw new RuntimeException(msg) - } - } // end of class JCodeMethodN /* Constant-valued val-members of JCodeMethodN at the companion object, so as to avoid re-initializing them multiple times. */ diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index fb16b9d07253..95f86dffbda8 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -8,7 +8,6 @@ import scala.collection.{immutable, mutable} import scala.tools.asm import dotty.tools.dotc.ast.tpd import dotty.tools.dotc.ast.TreeTypeMap -import dotty.tools.dotc.CompilationUnit import dotty.tools.dotc.ast.Trees.SyntheticUnit import dotty.tools.dotc.core.Decorators.* import dotty.tools.dotc.core.Flags.* @@ -30,9 +29,7 @@ import tpd.* * @version 1.0 * */ -trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { - - lazy val NativeAttr: Symbol = requiredClass[scala.native] +trait BCodeSkelBuilder extends BCodeHelpers { final class BTypesStack: // Anecdotally, growing past 16 to 32 is common; growing past 32 is rare @@ -127,7 +124,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { * - `genSynchronized() * - `jumpDest` , `cleanups` , `labelDefsAtOrUnder` */ - abstract class PlainSkelBuilder(cunit: CompilationUnit) + abstract class PlainSkelBuilder extends BCClassGen with BCAnnotGen with JAndroidBuilder @@ -151,30 +148,28 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { /* ---------------- idiomatic way to ask questions to typer ---------------- */ - def paramTKs(app: Apply, take: Int = -1): List[BType] = app match { + def paramTKs(app: Apply, take: Int = -1)(using Context): List[BType] = app match { case Apply(fun, _) => val funSym = fun.symbol - funSym.info.firstParamTypes.map(ts.toTypeKind) // this tracks mentioned inner classes (in innerClassBufferASM) + funSym.info.firstParamTypes.map(bTypeLoader.bTypeFromType) // this tracks mentioned inner classes (in innerClassBufferASM) } - def symInfoTK(sym: Symbol): BType = { - ts.toTypeKind(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM) + def symInfoTK(sym: Symbol)(using Context): BType = { + bTypeLoader.bTypeFromType(sym.info) // this tracks mentioned inner classes (in innerClassBufferASM) } - def tpeTK(tree: Tree): BType = { ts.toTypeKind(tree.tpe) } - - override def getCurrentCUnit(): CompilationUnit = { cunit } + def tpeTK(tree: Tree)(using Context): BType = { bTypeLoader.bTypeFromType(tree.tpe) } /* ---------------- helper utils for generating classes and fields ---------------- */ - def genPlainClass(cd0: TypeDef) = (cd0: @unchecked) match { + def genPlainClass(cd0: TypeDef)(using Context) = (cd0: @unchecked) match { case TypeDef(_, impl: Template) => assert(cnode == null, "GenBCode detected nested methods.") claszSymbol = cd0.symbol isCZParcelable = isAndroidParcelableClass(claszSymbol) isCZStaticModule = claszSymbol.isStaticModuleClass - thisName = ts.internalName(claszSymbol) + thisName = bTypeLoader.classBTypeFromSymbol(claszSymbol).internalName cnode = new ClassNode1() @@ -232,7 +227,11 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { claszSymbol.typeRef, privateWithin = NoSymbol, coord = claszSymbol.coord - ).entered + ) + // While we could use `.entered` on `moduleField` to have it handled like any other field later, + // this would require some compensating in the tree checker as we're adding a "magical" field + // that isn't defined in the AST. So instead, we emit it separately: + addClassField(moduleField) val thisMap = new TreeMap { override def transform(tree: Tree)(using Context) = { @@ -299,17 +298,18 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { /* * must-single-thread */ - private def initJClass(jclass: asm.ClassVisitor): Unit = { + private def initJClass(jclass: asm.ClassVisitor)(using Context): Unit = { val ps = claszSymbol.info.parents - val superClass: String = if (ps.isEmpty) ts.ObjectRef.internalName else ts.internalName(ps.head.typeSymbol) + val superClass: String = if ps.isEmpty then bTypes.ObjectRef.internalName + else bTypeLoader.classBTypeFromSymbol(ps.head.typeSymbol).internalName // We need to emit not only directly implemented interfaces, but also any indirectly implemented ones that are the target of super calls. // (This somewhat convoluted sequence of operations exists to maintain the exact order of inheritance from a previous version. // It could be cleaned up given some work to make sure changing the order isn't a problem.) val directInterfaces = claszSymbol.directlyInheritedTraits - val directInterfacesBTypes = directInterfaces.map(ts.classBTypeFromSymbol) - val baseClassesBTypes = directInterfaces.iterator.flatMap(_.asClass.baseClasses.drop(1)).map(ts.classBTypeFromSymbol).toSet + val directInterfacesBTypes = directInterfaces.map(bTypeLoader.classBTypeFromSymbol) + val baseClassesBTypes = directInterfaces.iterator.flatMap(_.asClass.baseClasses.drop(1)).map(bTypeLoader.classBTypeFromSymbol).toSet val additionalBTypes = superCallTargets.filter(!directInterfacesBTypes.contains(_)) val interfaces = directInterfacesBTypes.filter(t => !baseClassesBTypes(t) || superCallTargets(t)) ++ additionalBTypes @@ -342,16 +342,16 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { if !lengthOk then report.error("Class name is too long for the JVM", claszSymbol.srcPos) return - cnode.visit(backendUtils.classfileVersion, flags, + cnode.visit(classfileVersion, flags, thisName, thisSignature, superClass, interfaceNames.toArray) if (BackendUtils.emitSource) { - cnode.visitSource(cunit.source.file.name, null /* SourceDebugExtension */) + cnode.visitSource(ctx.compilationUnit.source.file.name, null /* SourceDebugExtension */) } - BCodeAsmCommon.enclosingMethodAttribute(claszSymbol, ts.internalName, ts.asmMethodType(_).descriptor) match { - case Some(BCodeAsmCommon.EnclosingMethodEntry(className, methodName, methodDescriptor)) => + BCodeUtils.enclosingMethodAttribute(claszSymbol, bTypeLoader.classBTypeFromSymbol(_).internalName, bTypeLoader.methodBTypeFromSymbol(_).descriptor) match { + case Some(BCodeUtils.EnclosingMethodEntry(className, methodName, methodDescriptor)) => cnode.visitOuterClass(className, methodName, methodDescriptor) case _ => () } @@ -384,7 +384,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { /* * must-single-thread */ - private def fabricateStaticInitAndroid(): Unit = { + private def fabricateStaticInitAndroid()(using Context): Unit = { val clinit: asm.MethodVisitor = cnode.visitMethod( asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_STATIC, // TODO confirm whether we really don't want ACC_SYNTHETIC nor ACC_DEPRECATED @@ -401,20 +401,35 @@ trait BCodeSkelBuilder(using ctx: Context) 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) = { + private def javaFieldFlags(sym: Symbol)(using Context) = { import asm.Opcodes.* import GenBCodeOps.addFlagIf BCodeUtils.javaFlags(sym) - .addFlagIf(sym.hasAnnotation(TransientAttr), ACC_TRANSIENT) - .addFlagIf(sym.hasAnnotation(VolatileAttr), ACC_VOLATILE) + .addFlagIf(sym.hasAnnotation(defn.TransientAnnot), ACC_TRANSIENT) + .addFlagIf(sym.hasAnnotation(defn.VolatileAnnot), ACC_VOLATILE) .addFlagIf(!sym.is(Mutable), ACC_FINAL) } - def addClassFields(): Unit = { + def addClassField(f: Symbol)(using Context): Unit = { + val javagensig = getGenericSignature(f, claszSymbol) + val flags = javaFieldFlags(f) + + assert(!f.isStaticMember || !claszSymbol.is(Trait) || !f.is(Mutable), + s"interface $claszSymbol cannot have non-final static field $f") + + val jfield = new asm.tree.FieldNode( + flags, + f.javaSimpleName, + symInfoTK(f).descriptor, + javagensig, + null // no initial value + ) + cnode.fields.add(jfield) + emitAnnotations(jfield, f.annotations) + } + + def addClassFields()(using Context): Unit = /* Non-method term members are fields, except for module members. Module * members can only happen on .NET (no flatten) for inner traits. There, * a module symbol is generated (transformInfo in mixin) which is used @@ -422,25 +437,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { * backend emits them as static). * No code is needed for this module symbol. */ - for (f <- claszSymbol.info.decls.filter(p => p.isTerm && !p.is(Method))) { - val javagensig = getGenericSignature(f, claszSymbol) - val flags = javaFieldFlags(f) - - assert(!f.isStaticMember || !claszSymbol.is(Trait) || !f.is(Mutable), - s"interface $claszSymbol cannot have non-final static field $f") - - val jfield = new asm.tree.FieldNode( - flags, - f.javaSimpleName, - symInfoTK(f).descriptor, - javagensig, - null // no initial value - ) - cnode.fields.add(jfield) - emitAnnotations(jfield, f.annotations) - } - - } // end of method addClassFields() + claszSymbol.info.decls.filter(p => p.isTerm && !p.is(Method)).foreach(addClassField) // current method var mnode: MethodNode1 = null @@ -468,16 +465,16 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { * corresponding expected type. The `LoadDestination` can never be `FallThrough` here. */ var jumpDest: immutable.Map[ /* Labeled */ Symbol, (BType, LoadDestination) ] = null - def registerJumpDest(labelSym: Symbol, expectedType: BType, dest: LoadDestination): Unit = { + def registerJumpDest(labelSym: Symbol, expectedType: BType, dest: LoadDestination)(using Context): Unit = { assert(labelSym.is(Label), s"trying to register a jump-dest for a non-label symbol, at: ${labelSym.span}") assert(dest != LoadDestination.FallThrough, s"trying to register a FallThrough dest for label, at: ${labelSym.span}") assert(!jumpDest.contains(labelSym), s"trying to register a second jump-dest for label, at: ${labelSym.span}") jumpDest += (labelSym -> (expectedType, dest)) } - def findJumpDest(labelSym: Symbol): (BType, LoadDestination) = { + def findJumpDest(labelSym: Symbol)(using Context): (BType, LoadDestination) = { assert(labelSym.is(Label), s"trying to map a non-label symbol to an asm.Label, at: ${labelSym.span}") jumpDest.getOrElse(labelSym, { - abort(s"unknown label symbol, for label at: ${labelSym.span}") + throw new AssertionError(s"unknown label symbol, for label at: ${labelSym.span}") }) } @@ -554,31 +551,31 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { /* Make a fresh local variable, ensuring a unique name. * The invoker must make sure inner classes are tracked for the sym's tpe. */ - def makeLocal(tk: BType, name: String, tpe: Type, pos: Span): Symbol = { + def makeLocal(tk: BType, name: String, tpe: Type, pos: Span)(using Context): Symbol = { val locSym = newSymbol(methSymbol, name.toTermName, Synthetic, tpe, NoSymbol, pos) makeLocal(locSym, tk) locSym } - def makeLocal(locSym: Symbol): Local = { + def makeLocal(locSym: Symbol)(using Context): Local = { makeLocal(locSym, symInfoTK(locSym)) } - def getOrMakeLocal(locSym: Symbol): Local = { + def getOrMakeLocal(locSym: Symbol)(using Context): Local = { // `getOrElse` below has the same effect as `getOrElseUpdate` because `makeLocal()` adds an entry to the `locals` map. slots.getOrElse(locSym, makeLocal(locSym)) } - def reuseLocal(sym: Symbol, loc: Local): Unit = + def reuseLocal(sym: Symbol, loc: Local)(using Context): Unit = val existing = slots.put(sym, loc) if (existing.isDefined) report.error("attempt to create duplicate local var.", ctx.source.atSpan(sym.span)) - def reuseThisSlot(sym: Symbol): Unit = + def reuseThisSlot(sym: Symbol)(using Context): Unit = reuseLocal(sym, Local(symInfoTK(sym), sym.javaSimpleName, 0, sym.is(Synthetic))) - private def makeLocal(sym: Symbol, tk: BType): Local = { + private def makeLocal(sym: Symbol, tk: BType)(using Context): Local = { assert(nxtIdx != -1, "not a valid start index") val loc = Local(tk, sym.javaSimpleName, nxtIdx, sym.is(Synthetic)) val existing = slots.put(sym, loc) @@ -638,7 +635,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl); case _ => false } ) } - def lineNumber(tree: Tree): Unit = { + def lineNumber(tree: Tree)(using Context): Unit = { @tailrec def getNonLabelNode(a: asm.tree.AbstractInsnNode): asm.tree.AbstractInsnNode = a match { case a: asm.tree.LabelNode => getNonLabelNode(a.getPrevious) @@ -667,7 +664,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { } // on entering a method - def resetMethodBookkeeping(dd: DefDef) = { + def resetMethodBookkeeping(dd: DefDef)(using Context) = { val rhs = dd.rhs locals.reset(isStaticMethod = methSymbol.isStaticMember) jumpDest = immutable.Map.empty @@ -686,7 +683,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { /* ---------------- top-down traversal invoking ASM Tree API along the way ---------------- */ - def gen(tree: Tree): Unit = { + def gen(tree: Tree)(using Context): Unit = { tree match { case tpd.EmptyTree => () @@ -722,19 +719,18 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { genDefDef(dd) case tree: Template => - val body = - if (tree.constr.rhs.isEmpty) tree.body - else tree.constr :: tree.body - body.foreach(gen) + if !tree.constr.rhs.isEmpty then + gen(tree.constr) + tree.body.foreach(gen) - case _ => abort(s"Illegal tree in gen: $tree") + case _ => throw new AssertionError(s"Illegal tree in gen: $tree") } } /* * must-single-thread */ - private def initJMethod(flags: Int, params: List[Symbol]): Unit = { + private def initJMethod(flags: Int, params: List[Symbol])(using Context): Unit = { val jgensig = getGenericSignature(methSymbol, claszSymbol) val (excs, others) = methSymbol.annotations.partition(_.symbol eq defn.ThrowsAnnot) @@ -744,7 +740,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { if (isMethSymStaticCtor) CLASS_CONSTRUCTOR_NAME else jMethodName - val mdesc = ts.asmMethodType(methSymbol).descriptor + val mdesc = bTypeLoader.methodBTypeFromSymbol(methSymbol).descriptor val lengthOk = if jgensig ne null then BCodeUtils.checkConstantStringLength(jgensig) else BCodeUtils.checkConstantStringLength(bytecodeName, mdesc) if !lengthOk then @@ -766,7 +762,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { } // end of method initJMethod - private def genTraitConstructorDefDef(dd: DefDef): Unit = + private def genTraitConstructorDefDef(dd: DefDef)(using Context): Unit = val statifiedDef = makeStatifiedDefDef(dd) genDefDef(statifiedDef) @@ -784,7 +780,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { * static def foo($self: Enclosing, x: Int): String = $self.toString() + x * }}} */ - private def makeStatifiedDefDef(dd: DefDef): DefDef = + private def makeStatifiedDefDef(dd: DefDef)(using Context): DefDef = val origSym = dd.symbol.asTerm val newSym = BackendUtils.makeStatifiedDefSymbol(origSym, origSym.name) tpd.DefDef(newSym, { paramRefss => @@ -805,7 +801,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { ).transform(dd.rhs) }) - private def genStaticForwarderForDefDef(dd: DefDef): Unit = + private def genStaticForwarderForDefDef(dd: DefDef)(using Context): Unit = val forwarderDef = makeStaticForwarder(dd) genDefDef(forwarderDef) @@ -820,7 +816,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { * in subtraits and subclasses, since the whole point of this forward is to * encode super calls. */ - private def makeStaticForwarder(dd: DefDef): DefDef = + private def makeStaticForwarder(dd: DefDef)(using Context): DefDef = // !!! // This logic is somewhat duplicated in the inline info definition, which is not very clean, // but remember to change it there if you make changes here @@ -834,7 +830,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { .withAttachment(BCodeHelpers.UseInvokeSpecial, ()) }) - def genDefDef(dd: DefDef): Unit = { + def genDefDef(dd: DefDef)(using Context): Unit = { val rhs = dd.rhs val vparamss = dd.termParamss // the only method whose implementation is not emitted: getClass() @@ -843,7 +839,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { methSymbol = dd.symbol jMethodName = methSymbol.javaSimpleName - returnType = ts.asmMethodType(methSymbol).returnType + returnType = bTypeLoader.methodBTypeFromSymbol(methSymbol).returnType isMethSymStaticCtor = methSymbol.name.isStaticConstructorName resetMethodBookkeeping(dd) @@ -866,7 +862,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { return } - val isNative = methSymbol.hasAnnotation(NativeAttr) + val isNative = methSymbol.hasAnnotation(defn.NativeAnnot) val isAbstractMethod = (methSymbol.is(Deferred) || (methSymbol.owner.is(Trait) && ((methSymbol.is(Deferred)) || methSymbol.isClassConstructor))) val flags = import GenBCodeOps.addFlagIf @@ -962,7 +958,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { * * TODO document, explain interplay with `fabricateStaticInitAndroid()` */ - private def appendToStaticCtor(): Unit = { + private def appendToStaticCtor()(using Context): Unit = { def insertBefore( location: asm.tree.AbstractInsnNode, @@ -984,7 +980,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = ts.classBTypeFromSymbol(AndroidCreatorClass).descriptor + val andrFieldDescr = bTypeLoader.classBTypeFromSymbol(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", @@ -994,9 +990,9 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { ) // INVOKESTATIC CREATOR(): android.os.Parcelable$Creator; -- TODO where does this Android method come from? val callee = claszSymbol.companionModule.info.member(androidFieldName).symbol - val jowner = ts.internalName(callee.owner) + val jowner = bTypeLoader.classBTypeFromSymbol(callee.owner).internalName val jname = callee.javaSimpleName - val jtype = ts.asmMethodType(callee).descriptor + val jtype = bTypeLoader.methodBTypeFromSymbol(callee).descriptor insnParcA = new asm.tree.MethodInsnNode(asm.Opcodes.INVOKESTATIC, jowner, jname, jtype, false) // PUTSTATIC `thisName`.CREATOR; insnParcB = new asm.tree.FieldInsnNode(asm.Opcodes.PUTSTATIC, thisName, "CREATOR", andrFieldDescr) @@ -1016,7 +1012,7 @@ trait BCodeSkelBuilder(using ctx: Context) extends BCodeHelpers { } } - def genLoadTo(tree: Tree, expectedType: BType, dest: LoadDestination): Unit + def genLoadTo(tree: Tree, expectedType: BType, dest: LoadDestination)(using Context): Unit } // end of class PlainSkelBuilder diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala index dd783a4cb7fd..1df91692ee12 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala @@ -5,7 +5,6 @@ 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 @@ -18,15 +17,15 @@ import tpd.* * @version 1.0 * */ -trait BCodeSyncAndTry(using ctx: Context) extends BCodeBodyBuilder { +trait BCodeSyncAndTry extends BCodeBodyBuilder { /* * Functionality to lower `synchronized` and `try` expressions. */ - class SyncAndTryBuilder(cunit: CompilationUnit) extends PlainBodyBuilder(cunit) { + class SyncAndTryBuilder extends PlainBodyBuilder { - def genSynchronized(tree: Apply, expectedType: BType): BType = (tree: @unchecked) match { + def genSynchronized(tree: Apply, expectedType: BType)(using Context): BType = (tree: @unchecked) match { case Apply(TypeApply(fun, _), args) => - val monitor = locals.makeLocal(ts.ObjectRef, "monitor", defn.ObjectType, tree.span) + val monitor = locals.makeLocal(bTypes.ObjectRef, "monitor", defn.ObjectType, tree.span) val monCleanup = new asm.Label // if the synchronized block returns a result, store it in a local variable. @@ -36,7 +35,7 @@ trait BCodeSyncAndTry(using ctx: Context) extends BCodeBodyBuilder { /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ genLoadQualifier(fun) - bc.dup(ts.ObjectRef) + bc.dup(bTypes.ObjectRef) locals.store(monitor) emit(asm.Opcodes.MONITORENTER) @@ -178,7 +177,7 @@ trait BCodeSyncAndTry(using ctx: Context) extends BCodeBodyBuilder { * - "exception-handler-version-of-finally-block" respectively. * */ - def genLoadTry(tree: Try): BType = tree match { + def genLoadTry(tree: Try)(using Context): BType = tree match { case Try(block, catches, finalizer) => val kind = tpeTK(tree) @@ -186,7 +185,7 @@ trait BCodeSyncAndTry(using ctx: Context) 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(ts.jlThrowableRef, caseBody) + case Ident(nme.WILDCARD) => NamelessEH(bTypes.jlThrowableRef, caseBody) case Bind(_, _) => BoundEH (pat.symbol, caseBody) } } @@ -343,7 +342,7 @@ trait BCodeSyncAndTry(using ctx: Context) 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(ts.jlThrowableRef, "exc", defn.ThrowableType, finalizer.span)) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(bTypes.jlThrowableRef, "exc", defn.ThrowableType, finalizer.span)) bc.store(eIdx, eTK) emitFinalizer(finalizer, null, isDuplicate = true) bc.load(eIdx, eTK) @@ -448,7 +447,7 @@ trait BCodeSyncAndTry(using ctx: Context) extends BCodeBodyBuilder { } /* `tmp` (if non-null) is the symbol of the local-var used to preserve the result of the try-body, see `guardResult` */ - private def emitFinalizer(finalizer: Tree, tmp: Symbol, isDuplicate: Boolean): Unit = { + private def emitFinalizer(finalizer: Tree, tmp: Symbol, isDuplicate: Boolean)(using Context): Unit = { var saved: immutable.Map[ /* Labeled */ Symbol, (BType, LoadDestination) ] = null if (isDuplicate) { saved = jumpDest @@ -463,7 +462,7 @@ trait BCodeSyncAndTry(using ctx: Context) extends BCodeBodyBuilder { } /* Does this tree have a try-catch block? */ - private def mayCleanStack(tree: Tree): Boolean = tree.find { // TODO: use existsSubTree + private def mayCleanStack(tree: Tree)(using Context): Boolean = tree.find { // TODO: use existsSubTree case Try(_, _, _) => true case _ => false }.isDefined diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala b/compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala index a483e0b8d42e..43d5c4b693ee 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeUtils.scala @@ -13,9 +13,11 @@ package dotty.tools.backend.jvm import dotty.tools.backend.jvm.GenBCode.* +import dotty.tools.backend.jvm.SymbolUtils.symExtensions 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.Flags.{AbstractOrTrait, Artifact, Bridge, Deferred, Enum, Final, JavaEnum, JavaVarargs, Method, Mutable, Private, Synchronized, Synthetic, Trait} import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.report import scala.annotation.{switch, tailrec} import scala.collection.mutable @@ -462,7 +464,6 @@ object BCodeUtils { } } - /** * Return the Java modifiers for the given symbol. * Java modifiers for classes: @@ -482,8 +483,6 @@ object BCodeUtils { * and they would fail verification after lifted. */ final def javaFlags(sym: Symbol)(using Context): Int = { - import SymbolUtils.given - // 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 @@ -516,4 +515,93 @@ object BCodeUtils { .addFlagIf(sym.isDeprecated, ACC_DEPRECATED) .addFlagIf(sym.is(Enum), ACC_ENUM) } + + + /** + * True if `classSym` is an anonymous class or a local class. I.e., false if `classSym` is a + * member class. This method is used to decide if we should emit an EnclosingMethod attribute. + * It is also used to decide whether the "owner" field in the InnerClass attribute should be + * null. + */ + 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 + // of isDelambdafyFunction failed (for a function declared in a package named "lambda"). + classSym.isAnonymousClass || { + val originalOwner = classSym.originalOwner + originalOwner != NoSymbol && !originalOwner.isClass + } + } + + /** + * Returns the enclosing method for non-member classes. In the following example + * + * class A { + * def f = { + * class B { + * class C + * } + * } + * } + * + * the method returns Some(f) for B, but None for C, because C is a member class. For non-member + * classes that are not enclosed by a method, it returns None: + * + * class A { + * { class B } + * } + * + * In this case, for B, we return None. + * + * 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)(using ctx: Context): Option[Symbol] = { + assert(classSym.isClass, classSym) + @tailrec + def enclosingMethod(sym: Symbol): Option[Symbol] = { + if (sym.isClass || sym == NoSymbol) None + else if (sym.is(Method, butNot=Synthetic)) Some(sym) + else enclosingMethod(sym.originalOwner) + } + enclosingMethod(classSym.originalOwner) + } + + /** + * 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)(using ctx: Context): Symbol = { + assert(classSym.isClass, classSym) + @tailrec + def enclosingClass(sym: Symbol): Symbol = { + if (sym.isClass) sym + else enclosingClass(sym.originalOwner.originalLexicallyEnclosingClass) + } + enclosingClass(classSym.originalOwner.originalLexicallyEnclosingClass) + } + + final case class EnclosingMethodEntry(owner: String, name: String | Null, methodDescriptor: String | Null) + + /** + * Data for emitting an EnclosingMethod attribute. None if `classSym` is a member class (not + * 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 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)(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)})") + Some(EnclosingMethodEntry( + classDesc(enclosingClassForEnclosingMethodAttribute(classSym)), + methodOpt.map(_.javaSimpleName).orNull, + methodOpt.map(methodDesc).orNull)) + } else { + None + } + } } diff --git a/compiler/src/dotty/tools/backend/jvm/BTypeLoader.scala b/compiler/src/dotty/tools/backend/jvm/BTypeLoader.scala new file mode 100644 index 000000000000..3bbaf202d21f --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/BTypeLoader.scala @@ -0,0 +1,397 @@ +package dotty.tools +package backend +package jvm + +import java.util.concurrent.ConcurrentHashMap +import BTypes.InternalName +import dotty.tools.backend.jvm.BCodeUtils.isAnonymousOrLocalClass +import dotty.tools.backend.jvm.SymbolUtils.symExtensions +import dotty.tools.dotc.core.Symbols.{ClassSymbol, NoSymbol, Symbol, defn} +import dotty.tools.dotc.core.Contexts.* +import dotty.tools.dotc.core.Decorators.toTermName +import dotty.tools.dotc.core.Flags.{Final, JavaDefined, Method, ModuleClass, ModuleVal, PackageClass, Trait} +import dotty.tools.dotc.core.Phases.{Phase, flattenPhase, lambdaLiftPhase, picklerPhase} +import dotty.tools.dotc.core.StdNames.nme +import dotty.tools.dotc.core.{StdNames, Types} +import dotty.tools.dotc.core.Types.{JavaArrayType, Type, TypeRef, abstractTermNameFilter} + +import scala.annotation.tailrec +import scala.tools.asm +import scala.tools.asm.tree.ClassNode + +final class BTypeLoader(primitives: ScalaPrimitives, inlineInfoLoader: () => Option[InlineInfoLoader]) { + // Concurrent map because stack map frames are computed when in the class writer, which + // might run on multiple classes concurrently. + private val classBTypeCache = new ConcurrentHashMap[InternalName, ClassBType] + + /** Maps special symbols, including primitive types, to their corresponding BType. */ + // It's OK to cache this because all Contexts that go through here share their defns. + // No locking, it's OK if this map gets initialized twice (though a little inefficient). + private var specialBTypes: Map[Symbol, BType] | Null = null + + + /** See doc of ClassBType.apply. This is where to use that method from. */ + def classBType[T](internalName: InternalName)(init: ClassBType => Either[T, ClassInfo]): Either[T, ClassBType] = + ClassBType(internalName, classBTypeCache)(init) + + /** See doc of ClassBType.apply. This is where to use that method from. Version that cannot fail. */ + def classBType(internalName: InternalName)(init: ClassBType => ClassInfo): ClassBType = + ClassBType(internalName, classBTypeCache)(ct => Right(init(ct))).fold(_ => assert(false), identity) + + /** Obtain a previously constructed ClassBType for a given internal name, or None if no such ClassBType was constructed. */ + def previouslyConstructedClassBType(internalName: InternalName): Option[ClassBType] = + Option(classBTypeCache.get(internalName)) + + def bTypeFromSymbol(sym: Symbol)(using Context): BType = { + if specialBTypes eq null then + specialBTypes = 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, + defn.NothingClass -> classBTypeFromSymbol(defn.RuntimeNothingClass), + defn.NullClass -> classBTypeFromSymbol(defn.RuntimeNullClass) + ) + specialBTypes.nn.getOrElse(sym, classBTypeFromSymbol(sym)) + } + + /** + * The ClassBType for a class symbol `sym`. + */ + def classBTypeFromSymbol(classSym0: Symbol)(using Context): ClassBType = { + // For each java class, the scala compiler creates a class and a module (thus a module class). + // If the symbol is a java module class, we use the java class instead. This ensures that the + // ClassBType is created from the main class (instead of the module class). + // The two symbols have the same name, so the resulting internalName is the same. + val classSym = if classSym0.isAllOf(JavaDefined | ModuleClass, butNot = PackageClass) then classSym0.linkedClass + else classSym0 + assert(classSym != NoSymbol, s"classSym1 is none: ${classSym} (${classSym.flagsString}); while compiling ${ctx.compilationUnit.source.file.name}") + + assert(classSym.isClass, s"Cannot create ClassBType from non-class symbol $classSym") // also covers the NoSymbol case + assert( + classSym != defn.NothingClass && classSym != defn.NullClass, + s"Cannot create ClassBType for special class symbol ${classSym.showFullName}") + assert(classSym != defn.ArrayClass || BackendUtils.compilingArray, classSym) + assert(!classSym.isPrimitiveValueClass || BackendUtils.compilingPrimitive, s"Found $classSym while compiling ${ctx.compilationUnit.source.file.name}") + classBType(classSym.javaBinaryName)(ct => createClassInfo(ct, classSym.asClass)) + } + + def mirrorClassBTypeFromSymbol(moduleClassSym: Symbol)(using Context): ClassBType = { + assert(moduleClassSym.isTopLevelModuleClass, s"not a top-level module class: $moduleClassSym") + val internalName = moduleClassSym.javaBinaryName.stripSuffix(StdNames.str.MODULE_SUFFIX) + classBType(internalName)(_ => + ClassInfo( + superClass = Some(classBTypeFromSymbol(defn.ObjectClass)), + interfaces = Nil, + flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, + nestedClasses = getMemberClasses(moduleClassSym).map(classBTypeFromSymbol), + nestedInfo = None, + inlineInfo = InlineInfo.empty + ) + ) + } + + /* + * must-single-thread + */ + def methodBTypeFromSymbol(msym: Symbol)(using Context): MethodBType = { + assert(msym.is(Method), s"not a method-symbol: $msym") + val resT: BType = + if (msym.isClassConstructor || msym.isConstructor) UNIT + else bTypeFromType(msym.info.resultType) + MethodBType(msym.info.firstParamTypes.map(bTypeFromType), resT) + } + + /** + * This method returns the BType for a type reference, for example a parameter type. + * + * If the result is a ClassBType for a nested class, it is added to the innerClassBufferASM. + * + * If `t` references a class, toTypeKind ensures that the class is not an implementation class. + * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation + * classes. + */ + def bTypeFromType(tp: Type)(using Context): BType = { + tp.widenDealias match + case JavaArrayType(el) => ArrayBType(bTypeFromType(el)) // Array type such as Array[Int] (kept by erasure) + case t: TypeRef => bTypeFromSymbol(t.symbol) // Common reference to a type such as scala.Int or java.lang.String + case Types.ClassInfo(_, sym, _, _, _) => bTypeFromSymbol(sym) // We get here, for example, for genCallMethod, which invokes bTypeFromType(method.owner.info) + case tp => + throw new AssertionError(s"an unexpected type representation reached the compiler backend while compiling ${ctx.compilationUnit}: $tp.") + } + + /** + * Visit the class node and collect all referenced nested classes. + */ + def collectNestedClasses(classNode: ClassNode): (Iterable[ClassBType], Iterable[ClassBType]) = { + val c = new NestedClassesCollector[ClassBType](nestedOnly = true) { + def declaredNestedClasses(internalName: InternalName): List[ClassBType] = + previouslyConstructedClassBType(internalName).get.info.nestedClasses + + def getClassIfNested(internalName: InternalName): Option[ClassBType] = { + val c = previouslyConstructedClassBType(internalName).get + Option.when(c.isNestedClass)(c) + } + + def raiseError(msg: String, sig: String, e: Option[Throwable]): Unit = { + // don't crash on invalid generic signatures + } + } + c.visit(classNode) + (c.declaredInnerClasses, c.referredInnerClasses) + } + + private def createClassInfo(classBType: ClassBType, classSym: Symbol)(using Context): 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.is(Trait)) + superClassSym == defn.ObjectClass + else + // A ClassBType for a primitive class (scala.Boolean et al.) is only created when compiling these classes. + ((superClassSym != NoSymbol) && !superClassSym.is(Trait)) || classSym.isPrimitiveValueClass, + s"Bad superClass for $classSym: $superClassSym" + ) + val superClass = if (superClassSym == NoSymbol) None + else Some(classBTypeFromSymbol(superClassSym)) + + // List only directly inherited interfaces. + // This is not only a performance optimization (as the JVM needs to handle fewer inheritance declarations), + // but also required for correctness in the presence of sealed interfaces (see i23479): + // if `C` inherits from `non-sealed A` which itself inherits from `sealed B permits A`, then having `C` inherit from `B` directly is illegal. + val allBaseClasses = classSym.directlyInheritedTraits.iterator.flatMap(_.asClass.baseClasses.drop(1)).toSet + val interfaces = classSym.directlyInheritedTraits.filter(!allBaseClasses(_)).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. + * + * (In Scala 2, we had an assertion that there must be exactly 2 nested class symbols with the same name and owner, + * but in Dotty there will be B & B$) + */ + val nestedClassSymbolsNoJavaModuleClasses = nestedClassSymbols.filter(s => !(s.is(JavaDefined) && s.is(ModuleClass))) + + val memberClasses = nestedClassSymbolsNoJavaModuleClasses.map(classBTypeFromSymbol) + + val nestedInfo = buildNestedInfo(classSym) + + val inlineInfo = inlineInfoLoader() match { + case Some(loader) => buildInlineInfo(loader, classSym.asClass, classBType.internalName) + case None => InlineInfo.empty + } + + ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo, inlineInfo) + } + + /** 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)(using Context): 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)(using Context): List[Symbol] = definedClasses(sym, lambdaLiftPhase) + + private def definedClasses(sym: Symbol, phase: Phase)(using Context) = + if (sym.isDefinedInCurrentRun) + atPhase(phase) { + sym.info.decls.filter(sym => sym.isClass && !sym.isEffectivelyErased) + } + else Nil + + private def buildNestedInfo(innerClassSym: Symbol)(using Context): 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 = isOriginallyStaticOwner(innerClassSym.originalOwner.originalLexicallyEnclosingClass) + + // After lambdalift (which is where we are), the raw owner field contains the enclosing class. + val enclosingClassSym = { + if (innerClassSym.isClass) { + atPhase(flattenPhase.prev) { + 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.lastPhaseId)(innerClassSym.name).mangledString) // moduleSuffix for module classes + } + } + + Some(NestedInfo(enclosingClass, outerName, innerName, isStaticNestedClass)) + } + } + + /* + * Note that the InlineInfo is only built from the symbolic information for classes that are being + * compiled. For all other classes we delegate to inlineInfoFromClassfile. The reason is that + * mixed-in methods are only added to class symbols being compiled, but not to other classes + * extending traits. Creating the InlineInfo from the symbol would prevent these mixins from being + * inlined. + * + * So for classes being compiled, the InlineInfo is created here and stored in the ScalaInlineInfo + * classfile attribute. + */ + private def buildInlineInfo(inlineInfoLoader: InlineInfoLoader, classSym: ClassSymbol, internalName: InternalName)(using Context): InlineInfo = { + // phase travel required (or at least it was in Scala 2). for nested classes, it checks if the + // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level, + // so it would return `false`. + if atPhase(picklerPhase.next) { + classSym.isDefinedInCurrentRun + } then buildInlineInfoFromClassSymbol(classSym) // // InlineInfo required for classes being compiled, we have to create the classfile attribute + // For classes not being compiled, the InlineInfo is read from the classfile attribute. This + // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class + // symbols being compiled. For non-compiled classes, we could not build MethodInlineInfos + // for those mixin members, which prevents inlining. + else inlineInfoLoader.loadInlineInfoFor(internalName) + } + + /** + * Build the [[InlineInfo]] for a class symbol. + */ + private def buildInlineInfoFromClassSymbol(classSym: ClassSymbol)(using Context): InlineInfo = { + // We only want an approximation of SAMs for inlining heuristics, no need to check FunctionalInterface annotations or such + val abstractMembers = classSym.memberNames(abstractTermNameFilter).iterator.map(classSym.classInfo.member).map(_.symbol).filter(_.is(Method)).toList + val sam = abstractMembers match + case List(single) => + val btype = methodBTypeFromSymbol(single) + Some(single.javaSimpleName + btype.descriptor) + case _ => None + + def keepMember(sym: Symbol) = sym.is(Method) && !primitives.isPrimitive(sym) + + val classMethods = classSym.info.decls.iterator.filter(keepMember) + val methods = if classSym.is(JavaDefined) then + // Phase travel important for nested classes (scala-dev#402). When a java class symbol A$B + // is compiled from source, this ensures that `companionModule` doesn't return the `A$B` + // symbol created for the `A$B.class` file on the classpath, which might be different. + val companion = atPhase(picklerPhase.next) { + classSym.companionModule + } + val staticMethods = companion.info.decls.iterator.filter(m => !m.isConstructor && keepMember(m)) + staticMethods ++ classMethods + else + val staticForwarders = if classSym.is(Trait) then + // !!! This logic duplicates PlainSkelBuilder::makeStaticForwarder, copy changes there !!! + classSym.info.decls.filter(s => s.isTerm && !s.isPrivate && !s.isStaticMember && s.name != nme.TRAIT_CONSTRUCTOR).map(s => { + BackendUtils.makeStatifiedDefSymbol(s.asTerm, BackendUtils.traitSuperAccessorName(s).toTermName) + }) + else Nil + classMethods ++ staticForwarders + + // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some + // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. + val methodInlineInfos = new collection.mutable.TreeMap[(String, String), MethodInlineInfo]() + methods.foreach { + methodSym => + val name = methodSym.javaSimpleName // same as in genDefDef + val signature = (name, methodBTypeFromSymbol(methodSym).descriptor) + + // In a trait, accesses to "modules" like enums are translated by the frontend as final methods, + // even though they are logically not final since classes implementing the trait will also have that method, + // so we must explicitly consider them to be non-final. + // TODO: This feels like something fundamentally weird in trees that should not exist. + val info = MethodInlineInfo( + effectivelyFinal = methodSym.isEffectivelyFinal && !methodSym.is(ModuleVal), + annotatedInline = methodSym.hasAnnotation(defn.InlineAnnot), + annotatedNoInline = methodSym.hasAnnotation(defn.NoInlineAnnot)) + + methodInlineInfos(signature) = info + } + + // if we have a symbol, we're compiling the class, so we assume it's accessible + InlineInfo(classSym.is(Final), sam, methodInlineInfos, None, isAccessible = true) + } + + /** + * 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. + */ + @tailrec + private def isOriginallyStaticOwner(sym: Symbol)(using Context): Boolean = + sym.is(PackageClass) || sym.is(ModuleClass) && isOriginallyStaticOwner(sym.originalOwner.originalLexicallyEnclosingClass) +} \ No newline at end of file diff --git a/compiler/src/dotty/tools/backend/jvm/BTypes.scala b/compiler/src/dotty/tools/backend/jvm/BTypes.scala index 3df3e791a164..584e97907759 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypes.scala @@ -69,13 +69,6 @@ sealed trait BType { 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 || @@ -94,8 +87,9 @@ sealed trait BType { this match { case ArrayBType(component) => - if (other.isObjectType || other.isJlCloneableType || other.isJiSerializableType) true - else other match { + other match { + case ClassBType(name) => + name == "java/lang/Object" || name == "java/lang/Cloneable" || name == "java/io/Serializable" case ArrayBType(otherComponent) => // Array[Short]().isInstanceOf[Array[Int]] is false // but Array[String]().isInstanceOf[Array[Object]] is true @@ -106,10 +100,11 @@ sealed trait BType { case classType: ClassBType => // Quick test for Object to make a common case fast - other.isObjectType || (other match { + other match { + case ClassBType("java/lang/Object") => true case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) case _ => false - }) + } case _ => // there are no bool/byte/short/char primitives at runtime, they are represented as ints. @@ -130,13 +125,13 @@ sealed trait BType { * Compute the upper bound of two types. * Takes promotions of numeric primitives into account. */ - final def maxType(other: BType, ts: CoreBTypes): BType = this match { + final def maxType(other: BType, ts: WellKnownBTypes): BType = this match { case pt: PrimitiveBType => pt.maxValueType(other) case _: ArrayBType | _: ClassBType => - if isNothingType then return other - if other.isNothingType then return this - if this == other then return this + if this == ts.srNothingRef then return other + if other == ts.srNothingRef then return this + if this == other then return this assert(other.isRef, s"Cannot compute maxType: $this, $other") // Approximate `lub`. The common type of two references is always ObjectReference. @@ -224,14 +219,6 @@ sealed trait BType { 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. @@ -243,9 +230,12 @@ sealed trait PrimitiveBType extends BType { def uncomparable: Nothing = throw new AssertionError(s"Cannot compute maxValueType: $this, $other") - if !other.isPrimitive && !other.isNothingType then uncomparable + other match { + case ClassBType("scala/runtime/Nothing$") => return this + case _ => () + } - if other.isNothingType then return this + if !other.isPrimitive then uncomparable if this == other then return this this match { @@ -310,14 +300,6 @@ 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 @@ -664,7 +646,7 @@ final case class MethodInlineInfo(effectivelyFinal: Boolean = false, /** * A ClassBType represents a class or interface type. */ -case class ClassBType private(val internalName: String, private val ts: CoreBTypes) extends RefBType { +case class ClassBType private(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 @@ -685,13 +667,6 @@ case class ClassBType private(val internalName: String, private val ts: CoreBTyp checkInfoConsistency() } - 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) - 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 @@ -768,7 +743,7 @@ case class ClassBType private(val internalName: String, private val ts: CoreBTyp def isSubtypeOf(other: ClassBType): Boolean = { if (this == other) return true if (isInterface) { - if (other == ts.ObjectRef) return true // interfaces conform to Object + if (other.internalName == "java/lang/Object") 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 { @@ -789,8 +764,8 @@ case class ClassBType private(val internalName: String, private val ts: CoreBTyp * 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 + def jvmWiseLUB(other: ClassBType, ts: WellKnownBTypes): ClassBType = { + def isNotNullOrNothing(c: ClassBType) = c != ts.srNullRef && c != ts.srNothingRef assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLUB for null or nothing: $this - $other") val res: ClassBType = (this.isInterface, other.isInterface) match { @@ -807,14 +782,14 @@ case class ClassBType private(val internalName: String, private val ts: CoreBTyp if (this.isSubtypeOf(other)) other else ts.ObjectRef case _ => - firstCommonSuffix(superClassesChain, other.superClassesChain) + firstCommonSuffix(superClassesChain, other.superClassesChain, ts) } assert(isNotNullOrNothing(res), s"jvmWiseLUB computed: $res") res } - private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { + private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType], ts: WellKnownBTypes): ClassBType = { var chainA = as.tail var chainB = bs.tail var fcs = ts.ObjectRef @@ -834,7 +809,6 @@ object ClassBType { * already exist in the cache * * @param internalName The name of the class - * @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 @@ -842,12 +816,12 @@ object ClassBType { * @tparam T The type of the error result. * @return The `ClassBType` */ - final def apply[T](internalName: InternalName, ts: CoreBTypes, cache: ConcurrentHashMap[InternalName, ClassBType]) + final def apply[T](internalName: InternalName, cache: ConcurrentHashMap[InternalName, ClassBType]) (init: ClassBType => Either[T, ClassInfo]): Either[T, ClassBType] = { val cached = cache.get(internalName) if cached ne null then Right(cached) else { - val newRes = new ClassBType(internalName, ts) + val newRes = new ClassBType(internalName) // synchronized is required to ensure proper initialization of info. // see comment on def info newRes.synchronized { @@ -887,14 +861,6 @@ object ClassBType { } case class ArrayBType(componentType: BType) extends RefBType { - - 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 @@ -906,16 +872,7 @@ case class ArrayBType(componentType: BType) extends RefBType { } } -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 - -} +case class MethodBType(argumentTypes: List[BType], returnType: BType) extends BType object BTypes { /** diff --git a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala index 6ee9bfdc87e5..47d7df6eb006 100644 --- a/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala +++ b/compiler/src/dotty/tools/backend/jvm/BackendUtils.scala @@ -2,7 +2,6 @@ package dotty.tools package backend.jvm import dotty.tools.backend.jvm.BTypes.InternalName -import dotty.tools.backend.jvm.PostProcessorFrontendAccess.Lazy import dotty.tools.dotc.core.Contexts.{Context, ctx} import dotty.tools.dotc.core.Definitions import dotty.tools.dotc.core.Flags.{JavaStatic, Method} @@ -24,7 +23,7 @@ import scala.tools.asm.{Handle, Opcodes, Type} * This component hosts tools and utilities used in the backend that require access to a `CoreBTypes` * instance. */ -class BackendUtils(val ppa: PostProcessorFrontendAccess, val ts: CoreBTypes)(using Context) { +class BackendUtils(val ts: WellKnownBTypes) { /** * Classes with indyLambda closure instantiations where the SAM type is serializable (e.g. Scala's @@ -33,11 +32,8 @@ class BackendUtils(val ppa: PostProcessorFrontendAccess, val ts: CoreBTypes)(usi * inlining: when inlining an indyLambda instruction into a class, we need to make sure the class * has the method. */ - private val indyLambdaImplMethods: Lazy[ConcurrentHashMap[InternalName, mutable.Map[MethodNode, mutable.Map[InvokeDynamicInsnNode, asm.Handle]]]] = - ppa.perRunLazy(new ConcurrentHashMap) - - // take advantage of the fact classfile versions are consecutive - lazy val classfileVersion: Int = ppa.compilerSettings.target.toInt + (Opcodes.V17 - 17) + private val indyLambdaImplMethods: ConcurrentHashMap[InternalName, mutable.Map[MethodNode, mutable.Map[InvokeDynamicInsnNode, asm.Handle]]] = + new ConcurrentHashMap def collectSerializableLambdas(classNode: ClassNode): Array[Handle] = { val indyLambdaBodyMethods = new mutable.ArrayBuffer[Handle] @@ -128,27 +124,6 @@ class BackendUtils(val ppa: PostProcessorFrontendAccess, val ts: CoreBTypes)(usi MethodBType(ts.jliSerializedLambdaRef :: Nil, ts.ObjectRef).descriptor } - /** - * Visit the class node and collect all referenced nested classes. - */ - def collectNestedClasses(classNode: ClassNode): (Iterable[ClassBType], Iterable[ClassBType]) = { - val c = new NestedClassesCollector[ClassBType](nestedOnly = true) { - def declaredNestedClasses(internalName: InternalName): List[ClassBType] = - ts.classBTypeFromInternalName(internalName).get.info.nestedClasses - - def getClassIfNested(internalName: InternalName): Option[ClassBType] = { - val c = ts.classBTypeFromInternalName(internalName).get - Option.when(c.isNestedClass)(c) - } - - def raiseError(msg: String, sig: String, e: Option[Throwable]): Unit = { - // don't crash on invalid generic signatures - } - } - c.visit(classNode) - (c.declaredInnerClasses, c.referredInnerClasses) - } - /* * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner * classes in BTypes.scala. @@ -175,13 +150,13 @@ class BackendUtils(val ppa: PostProcessorFrontendAccess, val ts: CoreBTypes)(usi } def onIndyLambdaImplMethodIfPresent[T](hostClass: InternalName)(action: mutable.Map[MethodNode, mutable.Map[InvokeDynamicInsnNode, asm.Handle]] => T): Option[T] = - indyLambdaImplMethods.get.get(hostClass) match { + indyLambdaImplMethods.get(hostClass) match { case null => None case methods => Some(methods.synchronized(action(methods))) } def onIndyLambdaImplMethod[T](hostClass: InternalName)(action: mutable.Map[MethodNode, mutable.Map[InvokeDynamicInsnNode, asm.Handle]] => T): T = { - val methods = indyLambdaImplMethods.get.computeIfAbsent(hostClass, _ => mutable.Map.empty) + val methods = indyLambdaImplMethods.computeIfAbsent(hostClass, _ => mutable.Map.empty) methods.synchronized(action(methods)) } @@ -193,38 +168,6 @@ class BackendUtils(val ppa: PostProcessorFrontendAccess, val ts: CoreBTypes)(usi onIndyLambdaImplMethodIfPresent(hostClass)(_.get(method).foreach(_.remove(indy))) } - /** - * The methods used as lambda bodies for IndyLambda instructions within `hostClass`. Note that - * the methods are not necessarily defined within the `hostClass` (when an IndyLambda is inlined - * into a different class). - */ - def indyLambdaBodyMethods(hostClass: InternalName): mutable.SortedSet[Handle] = { - object handleOrdering extends Ordering[Handle] { - override def compare(x: Handle, y: Handle): Int = { - if (x eq y) return 0 - - val t = Ordering.Int.compare(x.getTag, y.getTag) - if (t != 0) return t - - val i = Ordering.Boolean.compare(x.isInterface, y.isInterface) - if (x.isInterface != y.isInterface) return i - - val o = x.getOwner.compareTo(y.getOwner) - if (o != 0) return o - - val n = x.getName.compareTo(y.getName) - if (n != 0) return n - - x.getDesc.compareTo(y.getDesc) - } - } - - given Ordering[Handle] = handleOrdering - val res = mutable.TreeSet.empty[Handle] - onIndyLambdaImplMethodIfPresent(hostClass)(methods => res.addAll(methods.valuesIterator.flatMap(_.valuesIterator))) - res - } - /** * The methods used as lambda bodies for IndyLambda instructions within `method` of `hostClass`. */ @@ -355,8 +298,7 @@ class BackendUtils(val ppa: PostProcessorFrontendAccess, val ts: CoreBTypes)(usi (ts.StringRef.internalName, MethodBType(List(ArrayBType(CHAR)), UNIT).descriptor)) lazy val modulesAllowSkipInitialization: Set[InternalName] = - if (!ppa.compilerSettings.optAllowSkipCoreModuleInit) Set.empty - else Set( + Set( "scala/Predef$", "scala/runtime/ScalaRunTime$", "scala/runtime/Scala3RunTime$", diff --git a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala b/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala deleted file mode 100644 index 27b5a5c603c9..000000000000 --- a/compiler/src/dotty/tools/backend/jvm/ClassfileWriters.scala +++ /dev/null @@ -1,286 +0,0 @@ -package dotty.tools.backend.jvm - -import java.io.{BufferedOutputStream, DataOutputStream, File, FileOutputStream, IOException} -import java.nio.ByteBuffer -import java.nio.channels.{ClosedByInterruptException, FileChannel} -import java.nio.charset.StandardCharsets.UTF_8 -import java.nio.file.* -import java.nio.file.attribute.FileAttribute -import java.util -import java.util.concurrent.ConcurrentHashMap -import java.util.zip.{CRC32, Deflater, ZipEntry, ZipOutputStream} -import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.core.Decorators.em -import dotty.tools.dotc.util.chaining.* -import dotty.tools.io.{AbstractFile, PlainFile, VirtualFile} -import dotty.tools.io.PlainFile.toPlainFile -import BTypes.InternalName -import dotty.tools.dotc.report -import dotty.tools.io.JarArchive - -/** !!! This file is now copied in `dotty.tools.io.FileWriters` in a more general way that does not rely upon - * `PostProcessorFrontendAccess`, this should probably be changed to wrap that class instead. - * - * Until then, any changes to this file should be copied to `dotty.tools.io.FileWriters` as well. - */ -class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess)(using ctx: Context) { - import frontendAccess.compilerSettings - - sealed trait TastyWriter { - def writeTasty(name: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): Unit - } - - /** - * The interface to writing classfiles. GeneratedClassHandler calls these methods to generate the - * directory and files that are created, and eventually calls `close` when the writing is complete. - * - * The companion object is responsible for constructing a appropriate and optimal implementation for - * the supplied settings. - * - * Operations are threadsafe. - */ - sealed trait ClassfileWriter extends TastyWriter { - /** - * Write a classfile - */ - def writeClass(name: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile - - - /** - * Close the writer. Behavior is undefined after a call to `close`. - */ - def close(): Unit - - protected def classRelativePath(className: InternalName, suffix: String = ".class"): String = - className.replace('.', '/') + suffix - } - - object ClassfileWriter { - private def getDirectory(dir: String): Path = Paths.get(dir) - - def apply(jarManifestMainClass: Option[String]): ClassfileWriter = { - // In Scala 2 depenening on cardinality of distinct output dirs MultiClassWriter could have been used - // In Dotty we always use single output directory - val basicClassWriter = new SingleClassWriter( - FileWriter(compilerSettings.outputDirectory, jarManifestMainClass) - ) - - val withAdditionalFormats = - compilerSettings.dumpClassesDirectory - .map(getDirectory) - .filter{path => Files.exists(path).tap{ok => if !ok then report.error(em"Output dir does not exist: ${path.toString}")}} - .map(out => FileWriter(out.toPlainFile, None)) - .fold[ClassfileWriter](basicClassWriter)(new DebugClassWriter(basicClassWriter, _)) - - // val enableStats = settings.areStatisticsEnabled && settings.YaddBackendThreads.value == 1 - // if (enableStats) new WithStatsWriter(withAdditionalFormats) else - withAdditionalFormats - } - - private final class SingleClassWriter(underlying: FileWriter) extends ClassfileWriter { - override def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile = { - underlying.writeFile(classRelativePath(className), bytes) - } - override def writeTasty(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): Unit = { - underlying.writeFile(classRelativePath(className, ".tasty"), bytes) - } - - - override def close(): Unit = underlying.close() - } - - private final class DebugClassWriter(basic: ClassfileWriter, dump: FileWriter) extends ClassfileWriter { - override def writeClass(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): AbstractFile = { - val outFile = basic.writeClass(className, bytes, sourceFile) - dump.writeFile(classRelativePath(className), bytes) - outFile - } - - override def writeTasty(className: InternalName, bytes: Array[Byte], sourceFile: AbstractFile): Unit = { - basic.writeTasty(className, bytes, sourceFile) - } - - override def close(): Unit = { - basic.close() - dump.close() - } - } - } - - sealed trait FileWriter { - def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile - def close(): Unit - } - - object FileWriter { - def apply(file: AbstractFile, jarManifestMainClass: Option[String]): FileWriter = - if (file.isInstanceOf[JarArchive]) { - val jarCompressionLevel = compilerSettings.jarCompressionLevel - // Writing to non-empty JAR might be an undefined behaviour, e.g. in case if other files where - // created using `AbstractFile.bufferedOutputStream`instead of JarWriter - val jarFile = file.underlyingSource.getOrElse{ - throw new IllegalStateException("No underlying source for jar") - } - assert(file.isEmpty, s"Unsafe writing to non-empty JAR: $jarFile") - new JarEntryWriter(jarFile, jarManifestMainClass, jarCompressionLevel) - } - else if (file.isVirtual) new VirtualFileWriter(file) - else if (file.isDirectory) new DirEntryWriter(file.file.nn.toPath) - else throw new IllegalStateException(s"don't know how to handle an output of $file [${file.getClass}]") - } - - private final class JarEntryWriter(file: AbstractFile, mainClass: Option[String], compressionLevel: Int) extends FileWriter { - //keep these imports local - avoid confusion with scala naming - import java.util.jar.Attributes.Name.{MANIFEST_VERSION, MAIN_CLASS} - import java.util.jar.{JarOutputStream, Manifest} - - val storeOnly = compressionLevel == Deflater.NO_COMPRESSION - - val jarWriter: JarOutputStream = { - import scala.util.Properties.* - val manifest = new Manifest - val attrs = manifest.getMainAttributes - attrs.put(MANIFEST_VERSION, "1.0") - attrs.put(ScalaCompilerVersion, versionNumberString) - mainClass.foreach(c => attrs.put(MAIN_CLASS, c)) - - val jar = new JarOutputStream(new BufferedOutputStream(new FileOutputStream(file.file), 64000), manifest) - jar.setLevel(compressionLevel) - if (storeOnly) jar.setMethod(ZipOutputStream.STORED) - jar - } - - lazy val crc = new CRC32 - - override def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile = this.synchronized { - val entry = new ZipEntry(relativePath) - if (storeOnly) { - // When using compression method `STORED`, the ZIP spec requires the CRC and compressed/ - // uncompressed sizes to be written before the data. The JarOutputStream could compute the - // values while writing the data, but not patch them into the stream after the fact. So we - // need to pre-compute them here. The compressed size is taken from size. - // https://stackoverflow.com/questions/1206970/how-to-create-uncompressed-zip-archive-in-java/5868403 - // With compression method `DEFLATED` JarOutputStream computes and sets the values. - entry.setSize(bytes.length) - crc.reset() - crc.update(bytes) - entry.setCrc(crc.getValue) - } - jarWriter.putNextEntry(entry) - try jarWriter.write(bytes, 0, bytes.length) - finally jarWriter.flush() - // important detail here, even on Windows, Zinc expects the separator within the jar - // to be the system default, (even if in the actual jar file the entry always uses '/'). - // see https://github.com/sbt/zinc/blob/dcddc1f9cfe542d738582c43f4840e17c053ce81/internal/compiler-bridge/src/main/scala/xsbt/JarUtils.scala#L47 - val pathInJar = - if File.separatorChar == '/' then relativePath - else relativePath.replace('/', File.separatorChar) - PlainFile.toPlainFile(Paths.get(s"${file.absolutePath}!$pathInJar")) - } - - override def close(): Unit = this.synchronized(jarWriter.close()) - } - - private final class DirEntryWriter(base: Path) extends FileWriter { - val builtPaths = new ConcurrentHashMap[Path, java.lang.Boolean]() - val noAttributes = Array.empty[FileAttribute[?]] - private val isWindows = scala.util.Properties.isWin - - private def checkName(component: Path): Unit = if (isWindows) { - val specials = raw"(?i)CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9]".r - val name = component.toString - def warnSpecial(): Unit = report.warning(em"path component is special Windows device: ${name}") - specials.findPrefixOf(name).foreach(prefix => if (prefix.length == name.length || name(prefix.length) == '.') warnSpecial()) - } - - def ensureDirForPath(baseDir: Path, filePath: Path): Unit = { - import java.lang.Boolean.TRUE - val parent = filePath.getParent - if (!builtPaths.containsKey(parent)) { - parent.iterator.forEachRemaining(checkName) - try Files.createDirectories(parent, noAttributes*) - catch { - case e: FileAlreadyExistsException => - // `createDirectories` reports this exception if `parent` is an existing symlink to a directory - // but that's fine for us (and common enough, `scalac -d /tmp` on mac targets symlink). - if (!Files.isDirectory(parent)) - throw new FileConflictException(s"Can't create directory $parent; there is an existing (non-directory) file in its path", e) - } - builtPaths.put(baseDir, TRUE) - var current = parent - while ((current ne null) && (null ne builtPaths.put(current, TRUE))) { - current = current.getParent - } - } - checkName(filePath.getFileName()) - } - - // the common case is that we are are creating a new file, and on MS Windows the create and truncate is expensive - // because there is not an options in the windows API that corresponds to this so the truncate is applied as a separate call - // even if the file is new. - // as this is rare, its best to always try to create a new file, and it that fails, then open with truncate if that fails - - private val fastOpenOptions = util.EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) - private val fallbackOpenOptions = util.EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) - - override def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile = { - val path = base.resolve(relativePath) - try { - ensureDirForPath(base, path) - val os = if (isWindows) { - try FileChannel.open(path, fastOpenOptions) - catch { - case _: FileAlreadyExistsException => FileChannel.open(path, fallbackOpenOptions) - } - } else FileChannel.open(path, fallbackOpenOptions) - - try os.write(ByteBuffer.wrap(bytes), 0L) - catch { - case ex: ClosedByInterruptException => - try Files.deleteIfExists(path) // don't leave a empty of half-written classfile around after an interrupt - catch { case _: java.io.IOException => () } - throw ex - } - os.close() - } catch { - case e: FileConflictException => - report.error(em"error writing ${path.toString}: ${e.getMessage}") - case e: java.nio.file.FileSystemException => - if (compilerSettings.debug) e.printStackTrace() - report.error(em"error writing ${path.toString}: ${e.getClass.getName} ${e.getMessage}") - } - AbstractFile.getFile(path).nn // we just wrote the file, so it had better exist - } - - override def close(): Unit = () - } - - private final class VirtualFileWriter(base: AbstractFile) extends FileWriter { - private def getFile(base: AbstractFile, path: String): AbstractFile = { - def ensureDirectory(dir: AbstractFile): AbstractFile = - if (dir.isDirectory) dir - else throw new FileConflictException(s"${base.path}/${path}: ${dir.path} is not a directory") - val components = path.split('/') - var dir = base - for i <- 0 until components.length - 1 do - dir = ensureDirectory(dir).subdirectoryNamed(components(i).toString) - ensureDirectory(dir).fileNamed(components.last.toString) - } - - private def writeBytes(outFile: AbstractFile, bytes: Array[Byte]): Unit = { - val out = new DataOutputStream(outFile.bufferedOutput) - try out.write(bytes, 0, bytes.length) - finally out.close() - } - - override def writeFile(relativePath: String, bytes: Array[Byte]): AbstractFile = { - val outFile = getFile(base, relativePath) - writeBytes(outFile, bytes) - outFile - } - override def close(): Unit = () - } - - /** Can't output a file due to the state of the file system. */ - class FileConflictException(msg: String, cause: Throwable | Null = null) extends IOException(msg, cause) -} diff --git a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala index dcac24f35543..1947e16a9313 100644 --- a/compiler/src/dotty/tools/backend/jvm/CodeGen.scala +++ b/compiler/src/dotty/tools/backend/jvm/CodeGen.scala @@ -1,15 +1,11 @@ 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 scala.collection.mutable -import dotty.tools.dotc.interfaces -import dotty.tools.dotc.report - -import java.util.Optional +import dotty.tools.dotc.{CompilationUnit, interfaces, report, util} import dotty.tools.dotc.sbt.ExtractDependencies import dotty.tools.dotc.core.* import Contexts.* @@ -22,18 +18,18 @@ import dotty.tools.dotc.core.tasty.TastyUnpickler import scala.tools.asm.tree.* import tpd.* import dotty.tools.io.AbstractFile -import dotty.tools.dotc.util import dotty.tools.dotc.ast.Positioned import dotty.tools.dotc.util.NoSourcePosition import SymbolUtils.given import dotty.tools.backend.ScalaPrimitives +import dotty.tools.dotc.interfaces.CompilerCallback import opt.CallGraph -class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, val frontendAccess: PostProcessorFrontendAccess, val callGraph: CallGraph, val ts: CoreBTypes)(using Context) { - private class Impl(using Context) extends BCodeHelpers(backendUtils), BCodeSkelBuilder, BCodeBodyBuilder(primitives), BCodeSyncAndTry { - val ts: CoreBTypes = CodeGen.this.ts - - def recordCallsitePosition(m: MethodInsnNode, pos: Positioned | Null): Unit = +class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, val frontendAccess: PostProcessorFrontendAccess, + val callGraph: CallGraph, val bTypeLoader: BTypeLoader, val bTypes: WellKnownBTypes, + val generatedClassHandler: GeneratedClassHandler) { + private class Impl extends BCodeHelpers(bTypeLoader, bTypes), BCodeBodyBuilder(primitives), BCodeSyncAndTry { + def recordCallsitePosition(m: MethodInsnNode, pos: Positioned | Null)(using Context): Unit = callGraph.callsitePositions.get(m) = pos match { case p: Positioned => p.sourcePos case null => NoSourcePosition @@ -43,28 +39,22 @@ class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, v private lazy val mirrorCodeGen = impl.JMirrorBuilder() - private def genBCode(using Context) = Phases.genBCodePhase.asInstanceOf[GenBCode] - private def postProcessor(using Context) = genBCode.postProcessor - private def generatedClassHandler(using Context) = genBCode.generatedClassHandler - /** - * Generate ASM ClassNodes for classes found in a compilation unit. The resulting classes are - * passed to the `GenBCode.generatedClassHandler`. + * Generate ASM ClassNodes for classes found in the context's compilation unit. The resulting classes are + * passed to the `generatedClassHandler`. */ - def genUnit(unit: CompilationUnit)(using ctx: Context): Unit = { + def genUnit()(using ctx: Context): Unit = { val generatedClasses = mutable.ListBuffer.empty[GeneratedClass] val generatedTasty = mutable.ListBuffer.empty[GeneratedTasty] def genClassDef(cd: TypeDef): Unit = try val sym = cd.symbol - val sourceFile = unit.source.file - - - val mainClassNode = genClass(cd, unit) + val sourceFile = ctx.compilationUnit.source.file + val mainClassNode = genClass(cd) val mirrorClassNode = if !sym.isTopLevelModuleClass then null - else if sym.companionClass == NoSymbol then genMirrorClass(sym, unit) + else if sym.companionClass == NoSymbol then mirrorCodeGen.genMirrorClass(sym) else report.log(s"No mirror class for module with linked class: ${sym.fullName}", NoSourcePosition) null @@ -79,18 +69,17 @@ class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, v sourceClassName = sym.javaClassName, position = sym.srcPos.sourcePos, isArtifact = isArtifact, - onFileCreated = onFileCreated(classNode, sym, unit.source) + onFileCreated = onFileCreated(classNode, sym, ctx.compilationUnit.source) ) registerGeneratedClass(mainClassNode, isArtifact = false) registerGeneratedClass(mirrorClassNode, isArtifact = true) catch case ex: TypeError => - report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", cd.sourcePos) - + report.error(s"Error while emitting ${ctx.compilationUnit.source}\n${ex.getMessage}", cd.sourcePos) def genTastyAndSetAttributes(claszSymbol: Symbol, store: ClassNode): Unit = - for (binary <- unit.pickled.get(claszSymbol.asClass)) { + for (binary <- ctx.compilationUnit.pickled.get(claszSymbol.asClass)) { generatedTasty += GeneratedTasty(store, binary) val tasty = val uuid = new TastyHeaderUnpickler(TastyUnpickler.scala3CompilerConfig, binary()).readHeader() @@ -113,12 +102,12 @@ class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, v case EmptyTree => () case PackageDef(_, stats) => stats.foreach(genClassDefs) case ValDef(_, _, _) => () // module val not emitted - case td: TypeDef => frontendAccess.frontendSynch(genClassDef(td)) + case td: TypeDef => genClassDef(td) } - genClassDefs(unit.tpdTree) + genClassDefs(ctx.compilationUnit.tpdTree) generatedClassHandler.process( - GeneratedCompilationUnit(unit.source.file, generatedClasses.toList, generatedTasty.toList) + GeneratedCompilationUnit(ctx.compilationUnit.source.file, generatedClasses.toList, generatedTasty.toList) ) } @@ -129,8 +118,9 @@ class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, v } clsFile => { val className = cls.name.replace('/', '.') - if (ctx.compilerCallback ne null) - ctx.compilerCallback.onClassGenerated(sourceFile, convertAbstractFile(clsFile), className) + ctx.compilerCallback match + case cb: CompilerCallback => cb.onClassGenerated(sourceFile, clsFile, className) + case null => () ctx.withIncCallback: cb => if isLocal then @@ -144,24 +134,10 @@ class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, v } } - /** Convert a `dotty.tools.io.AbstractFile` into a - * `dotty.tools.dotc.interfaces.AbstractFile`. - */ - private def convertAbstractFile(absfile: dotty.tools.io.AbstractFile): interfaces.AbstractFile = - new interfaces.AbstractFile { - override def name = absfile.name - override def path = absfile.path - override def jfile: Optional[java.io.File] = Optional.ofNullable(absfile.file) - } - - private def genClass(cd: TypeDef, unit: CompilationUnit): ClassNode = { - val b = new impl.SyncAndTryBuilder(unit) + private def genClass(cd: TypeDef)(using Context): ClassNode = { + val b = new impl.SyncAndTryBuilder b.genPlainClass(cd) b.cnode } - private def genMirrorClass(classSym: Symbol, unit: CompilationUnit): ClassNode = { - mirrorCodeGen.genMirrorClass(classSym, unit) - } - } diff --git a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala deleted file mode 100644 index b34c6e30d002..000000000000 --- a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala +++ /dev/null @@ -1,230 +0,0 @@ -package dotty.tools -package backend -package jvm - -import java.util.concurrent.ConcurrentHashMap -import BTypes.InternalName -import dotty.tools.dotc.core.Symbols.{Symbol, defn} -import dotty.tools.dotc.core.Contexts.{Context, ctx} -import dotty.tools.backend.jvm.PostProcessorFrontendAccess.Lazy -import dotty.tools.dotc.core.Flags.{JavaDefined, Method, ModuleClass} -import dotty.tools.dotc.core.Types -import dotty.tools.dotc.core.Types.{AnnotatedType, JavaArrayType, RefinedType, SingletonType, ThisType, Type, TypeRef} -import dotty.tools.dotc.report - -import scala.tools.asm.Handle - -case class MethodNameAndType(name: String, methodType: MethodBType) - -abstract class CoreBTypes(private val frontendAccess: PostProcessorFrontendAccess)(using ctx: Context) { - 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 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 - - def srBoxesRuntimeRef : ClassBType - def srBoxedUnitRef : ClassBType - def srBoxesRuntimeBoxToMethods : Map[BType, MethodNameAndType] - def srBoxesRuntimeUnboxToMethods : Map[BType, MethodNameAndType] - - def javaBoxMethods : Map[InternalName, MethodNameAndType] - def javaUnboxMethods : Map[InternalName, MethodNameAndType] - - def predefAutoBoxMethods : Map[String, MethodBType] - def predefAutoUnboxMethods : Map[String, MethodBType] - - 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] - - 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] - - // 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]) - - /** See doc of ClassBType.apply. This is where to use that method from. */ - def classBType[T](internalName: InternalName)(init: ClassBType => Either[T, ClassInfo]): Either[T, ClassBType] = - ClassBType(internalName, this, classBTypeCache.get)(init) - - /** See doc of ClassBType.apply. This is where to use that method from. Version that cannot fail. */ - def classBType(internalName: InternalName)(init: ClassBType => ClassInfo): ClassBType = - ClassBType(internalName, this, classBTypeCache.get)(ct => Right(init(ct))).fold(_ => assert(false), identity) - - /** Obtain a previously constructed ClassBType for a given internal name, or None if no such ClassBType was constructed. */ - def classBTypeFromInternalName(internalName: InternalName): Option[ClassBType] = - Option(classBTypeCache.get.get(internalName)) - - def classBTypeFromSymbol(classSym: Symbol): ClassBType - def mirrorClassBTypeFromSymbol(moduleClassSym: Symbol): ClassBType - - /** - * The class internal name for a given class symbol. - */ - final def internalName(sym: Symbol)(using Context): String = { - // For each java class, the scala compiler creates a class and a module (thus a module class). - // If the `sym` is a java module class, we use the java class instead. This ensures that the - // ClassBType is created from the main class (instead of the module class). - // The two symbols have the same name, so the resulting internalName is the same. - val classSym = if (sym.is(JavaDefined) && sym.is(ModuleClass)) sym.linkedClass else sym - getClassBType(classSym).internalName - } - - private def assertClassNotArray(sym: Symbol)(using Context): Unit = { - assert(sym.isClass, sym) - assert(sym != defn.ArrayClass || BackendUtils.compilingArray, sym) - } - - private def assertClassNotArrayNotPrimitive(sym: Symbol)(using Context): Unit = { - assertClassNotArray(sym) - assert(!primitiveTypeMap.contains(sym) || BackendUtils.compilingPrimitive, sym) - } - - /** - * The ClassBType for a class symbol. - * - * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly, - * scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files - * for the Nothing / Null. If used for example as a parameter type, we use the runtime classes - * in the classfile method signature. - * - * Note that the referenced class symbol may be an implementation class. For example when - * compiling a mixed-in method that forwards to the static method in the implementation class, - * the class descriptor of the receiver (the implementation class) is obtained by creating the - * ClassBType. - */ - final def getClassBType(sym: Symbol)(using Context): ClassBType = { - assertClassNotArrayNotPrimitive(sym) - - if (sym == defn.NothingClass) srNothingRef - else if (sym == defn.NullClass) srNullRef - else classBTypeFromSymbol(sym) - } - - /* - * must-single-thread - */ - final def asmMethodType(msym: Symbol)(using Context): MethodBType = { - assert(msym.is(Method), s"not a method-symbol: $msym") - val resT: BType = - if (msym.isClassConstructor || msym.isConstructor) UNIT - else toTypeKind(msym.info.resultType) - MethodBType(msym.info.firstParamTypes.map(toTypeKind), resT) - } - - /** - * The jvm descriptor of a type. - */ - final def typeDescriptor(t: Type): String = { - toTypeKind(t).descriptor - } - - /** - * The jvm descriptor for a symbol. - */ - final def symDescriptor(sym: Symbol)(using Context): String = getClassBType(sym).descriptor - - final def toTypeKind(tp: Type)(using Context): BType = typeToTypeKind(tp) - - /** - * This method returns the BType for a type reference, for example a parameter type. - * - * If the result is a ClassBType for a nested class, it is added to the innerClassBufferASM. - * - * If `t` references a class, toTypeKind ensures that the class is not an implementation class. - * See also comment on getClassBTypeAndRegisterInnerClass, which is invoked for implementation - * classes. - */ - final def typeToTypeKind(tp: Type)(using Context): BType = { - val defn = ctx.definitions - - /** - * Primitive types are represented as TypeRefs to the class symbol of, for example, scala.Int. - * The `primitiveTypeMap` maps those class symbols to the corresponding PrimitiveBType. - */ - def primitiveOrClassToBType(sym: Symbol): BType = { - assert(sym.isClass, sym) - assert(sym != defn.ArrayClass || BackendUtils.compilingArray, sym) - primitiveTypeMap.getOrElse(sym, getClassBType(sym)) - } - - /** - * When compiling Array.scala, the type parameter T is not erased and shows up in method - * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. - */ - def nonClassTypeRefToBType(sym: Symbol): ClassBType = { - assert(sym.isType && BackendUtils.compilingArray, sym) - ObjectRef - } - - tp.widenDealias match { - case JavaArrayType(el) => ArrayBType(typeToTypeKind(el)) // Array type such as Array[Int] (kept by erasure) - case t: TypeRef => - t.info match { - - case _ => - if (!t.symbol.isClass) nonClassTypeRefToBType(t.symbol) // See comment on nonClassTypeRefToBType - else primitiveOrClassToBType(t.symbol) // Common reference to a type such as scala.Int or java.lang.String - } - 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 - * 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) - - /* 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 - * test suite don't produce any warning. - */ - - case tp => - report.warning( - s"an unexpected type representation reached the compiler backend while compiling ${ctx.compilationUnit}: $tp. " + - "If possible, please file a bug on https://github.com/scala/scala3/issues") - - tp match { - case tp: ThisType if tp.cls == defn.ArrayClass => 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 => getClassBType(tp.cls) - // case t: SingletonType => primitiveOrClassToBType(t.classSymbol) - case t: SingletonType => typeToTypeKind(t.underlying) - case t: RefinedType => typeToTypeKind(t.parent) - } - } - } -} \ 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 deleted file mode 100644 index 2be3ac755353..000000000000 --- a/compiler/src/dotty/tools/backend/jvm/CoreBTypesFromSymbols.scala +++ /dev/null @@ -1,665 +0,0 @@ -package dotty.tools.backend.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, Symbols} -import BTypes.* -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.{Final, JavaDefined, Method, ModuleClass, ModuleVal, PackageClass, Trait} -import dotty.tools.dotc.core.Phases.{Phase, flattenPhase, lambdaLiftPhase, picklerPhase} -import SymbolUtils.given -import PostProcessorFrontendAccess.Lazy -import dotty.tools.backend.ScalaPrimitives -import dotty.tools.dotc.core.Decorators.toTermName -import dotty.tools.dotc.core.Types.abstractTermNameFilter - -import scala.tools.asm - -final class CoreBTypesFromSymbols(ppa: PostProcessorFrontendAccess, primitives: ScalaPrimitives, inlineInfoLoader: () => Option[InlineInfoLoader])(using val ctx: Context) extends CoreBTypes(ppa) { - /** - * 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}") - - classBType(classSym.javaBinaryName)(ct => createClassInfo(ct, classSym.asClass)) - } - - 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)(_ => - ClassInfo( - superClass = Some(ObjectRef), - interfaces = Nil, - flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, - nestedClasses = getMemberClasses(moduleClassSym).map(classBTypeFromSymbol), - nestedInfo = None, - inlineInfo = InlineInfo.empty - ) - ) - } - - 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.is(Trait)) - superClassSym == defn.ObjectClass - else - // A ClassBType for a primitive class (scala.Boolean et al.) is only created when compiling these classes. - ((superClassSym != NoSymbol) && !superClassSym.is(Trait)) || primitiveTypeMap.contains(classSym), - s"Bad superClass for $classSym: $superClassSym" - ) - val superClass = if (superClassSym == NoSymbol) None - else Some(classBTypeFromSymbol(superClassSym)) - - // List only directly inherited interfaces. - // This is not only a performance optimization (as the JVM needs to handle fewer inheritance declarations), - // but also required for correctness in the presence of sealed interfaces (see i23479): - // if `C` inherits from `non-sealed A` which itself inherits from `sealed B permits A`, then having `C` inherit from `B` directly is illegal. - val allBaseClasses = classSym.directlyInheritedTraits.iterator.flatMap(_.asClass.baseClasses.drop(1)).toSet - val interfaces = classSym.directlyInheritedTraits.filter(!allBaseClasses(_)).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) - - val inlineInfo = inlineInfoLoader() match { - case Some(loader) => buildInlineInfo(loader, classSym.asClass, classBType.internalName) - case None => InlineInfo.empty - } - - ClassInfo(superClass, interfaces, flags, memberClasses, nestedInfo, inlineInfo) - } - - /** 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.lastPhaseId)(innerClassSym.name).mangledString) // moduleSuffix for module classes - } - } - - Some(NestedInfo(enclosingClass, outerName, innerName, isStaticNestedClass)) - } - } - - /* - * Note that the InlineInfo is only built from the symbolic information for classes that are being - * compiled. For all other classes we delegate to inlineInfoFromClassfile. The reason is that - * mixed-in methods are only added to class symbols being compiled, but not to other classes - * extending traits. Creating the InlineInfo from the symbol would prevent these mixins from being - * inlined. - * - * So for classes being compiled, the InlineInfo is created here and stored in the ScalaInlineInfo - * classfile attribute. - */ - private def buildInlineInfo(inlineInfoLoader: InlineInfoLoader, classSym: ClassSymbol, internalName: InternalName): InlineInfo = { - // phase travel required (or at least it was in Scala 2). for nested classes, it checks if the - // enclosingTopLevelClass is being compiled. after flatten, all classes are considered top-level, - // so it would return `false`. - if atPhase(picklerPhase.next) { - classSym.isDefinedInCurrentRun - } then buildInlineInfoFromClassSymbol(classSym) // // InlineInfo required for classes being compiled, we have to create the classfile attribute - // For classes not being compiled, the InlineInfo is read from the classfile attribute. This - // fixes an issue with mixed-in methods: the mixin phase enters mixin methods only to class - // symbols being compiled. For non-compiled classes, we could not build MethodInlineInfos - // for those mixin members, which prevents inlining. - else inlineInfoLoader.loadInlineInfoFor(internalName) - } - - /** - * Build the [[InlineInfo]] for a class symbol. - */ - private def buildInlineInfoFromClassSymbol(classSym: ClassSymbol): InlineInfo = { - // We only want an approximation of SAMs for inlining heuristics, no need to check FunctionalInterface annotations or such - val abstractMembers = classSym.memberNames(abstractTermNameFilter).iterator.map(classSym.classInfo.member).map(_.symbol).filter(_.is(Method)).toList - val sam = abstractMembers match - case List(single) => - val btype = asmMethodType(single) - Some(single.javaSimpleName + btype.descriptor) - case _ => None - - def keepMember(sym: Symbol) = sym.is(Method) && !primitives.isPrimitive(sym) - - val classMethods = classSym.info.decls.iterator.filter(keepMember) - val methods = if classSym.is(JavaDefined) then - // Phase travel important for nested classes (scala-dev#402). When a java class symbol A$B - // is compiled from source, this ensures that `companionModule` doesn't return the `A$B` - // symbol created for the `A$B.class` file on the classpath, which might be different. - val companion = atPhase(picklerPhase.next) { - classSym.companionModule - } - val staticMethods = companion.info.decls.iterator.filter(m => !m.isConstructor && keepMember(m)) - staticMethods ++ classMethods - else - val staticForwarders = if classSym.is(Trait) then - // !!! This logic duplicates PlainSkelBuilder::makeStaticForwarder, copy changes there !!! - classSym.info.decls.filter(s => s.isTerm && !s.isPrivate && !s.isStaticMember && s.name != nme.TRAIT_CONSTRUCTOR).map(s => { - BackendUtils.makeStatifiedDefSymbol(s.asTerm, BackendUtils.traitSuperAccessorName(s).toTermName) - }) - else Nil - classMethods ++ staticForwarders - - // Primitive methods cannot be inlined, so there's no point in building a MethodInlineInfo. Also, some - // primitive methods (e.g., `isInstanceOf`) have non-erased types, which confuses [[typeToBType]]. - val methodInlineInfos = new collection.mutable.TreeMap[(String, String), MethodInlineInfo]() - methods.foreach { - methodSym => - val name = methodSym.javaSimpleName // same as in genDefDef - val signature = (name, asmMethodType(methodSym).descriptor) - - // In a trait, accesses to "modules" like enums are translated by the frontend as final methods, - // even though they are logically not final since classes implementing the trait will also have that method, - // so we must explicitly consider them to be non-final. - // TODO: This feels like something fundamentally weird in trees that should not exist. - val info = MethodInlineInfo( - effectivelyFinal = methodSym.isEffectivelyFinal && !methodSym.is(ModuleVal), - annotatedInline = methodSym.hasAnnotation(defn.InlineAnnot), - annotatedNoInline = methodSym.hasAnnotation(defn.NoInlineAnnot)) - - methodInlineInfos(signature) = info - } - - // if we have a symbol, we're compiling the class, so we assume it's accessible - InlineInfo(classSym.is(Final), sam, methodInlineInfos, None, isAccessible = true) - } - - /** - * 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." + primitive.name.toString + "Ref") - val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + primitive.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." + primitive.name.toString + "Ref") - val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + primitive.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." + primitive.name.toString + "Ref") - val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + primitive.name.toString + "Ref") - List( - (classBTypeFromSymbol(refClass).internalName, MethodNameAndType(nme.CONSTRUCTOR.toString, MethodBType(List(unboxed), UNIT))), - (classBTypeFromSymbol(volatileRefClass).internalName, MethodNameAndType(nme.CONSTRUCTOR.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/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index 48594398a9b5..a071e53d7893 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -6,16 +6,12 @@ import dotty.tools.dotc.report import dotty.tools.dotc.core.* import dotty.tools.dotc.interfaces.CompilerCallback import Contexts.* -import Symbols.* import dotty.tools.backend.ScalaPrimitives -import dotty.tools.backend.jvm.opt.{BCodeRepository, BTypesFromClassfile} +import dotty.tools.backend.jvm.opt.{BCodeRepository, BTypesFromClassfile, CallGraph} +import dotty.tools.dotc.core.Decorators.em import dotty.tools.io.* import scala.collection.mutable -import scala.compiletime.uninitialized -import java.util.concurrent.TimeoutException -import scala.concurrent.duration.Duration -import scala.concurrent.Await /** * GenBCode has 3 parts: @@ -27,7 +23,7 @@ import scala.concurrent.Await * Parts 2 and 3 do not require a Context and can be parallelized. * * It is crucial that parts 2 and 3 do not accidentally depend on a Context, - * which is why we have a "post-processor frontend interface" that hides this Context. + * which is why we have abstractions to hide it such as OptimizerSettings. */ class GenBCode extends Phase { self => @@ -38,14 +34,17 @@ class GenBCode extends Phase { self => override def isRunnable(using Context): Boolean = super.isRunnable && !ctx.usedBestEffortTasty + private var _backendUtils: BackendUtils | Null = null + def backendUtils(using Context): BackendUtils = { + if _backendUtils eq null then + _backendUtils = BackendUtils(wellKnownBTypes) + _backendUtils.nn + } + 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 context = ctx match - case fc: FreshContext => fc - case ctx => ctx.fresh - _frontendAccess = PostProcessorFrontendAccess.Impl(context) + _frontendAccess = PostProcessorFrontendAccess(ctx) _frontendAccess.nn } @@ -59,87 +58,104 @@ class GenBCode extends Phase { self => private var _byteCodeRepository: BCodeRepository | Null = null def byteCodeRepository(using Context): BCodeRepository = { if _byteCodeRepository eq null then - _byteCodeRepository = BCodeRepository(frontendAccess, backendUtils, bTypes) + _byteCodeRepository = BCodeRepository(ctx.platform.classPath, backendUtils) _byteCodeRepository.nn } private var _bTypesFromClassfile: BTypesFromClassfile | Null = null def bTypesFromClassfile(using Context): BTypesFromClassfile = { if _bTypesFromClassfile eq null then - _bTypesFromClassfile = BTypesFromClassfile(byteCodeRepository, bTypes) + _bTypesFromClassfile = BTypesFromClassfile(byteCodeRepository, bTypeLoader) _bTypesFromClassfile.nn } - private var _bTypes: CoreBTypes | Null = null - def bTypes(using Context): CoreBTypes = { - if _bTypes eq null then + private var _bTypeLoader: BTypeLoader | Null = null + def bTypeLoader(using Context): BTypeLoader = { + if _bTypeLoader eq null then // lazy load to break the circular dependency - def inlineInfoLoader() = Option.when[InlineInfoLoader](frontendAccess.compilerSettings.optInlinerEnabled)(bTypesFromClassfile) - _bTypes = CoreBTypesFromSymbols(frontendAccess, primitives, inlineInfoLoader)(using ctx) - _bTypes.nn + def inlineInfoLoader() = Option.when[InlineInfoLoader](ctx.settings.optInlineEnabled)(bTypesFromClassfile) + _bTypeLoader = BTypeLoader(primitives, inlineInfoLoader) + _bTypeLoader.nn } - private var _backendUtils: BackendUtils | Null = null - def backendUtils(using Context): BackendUtils = { - if _backendUtils eq null then - _backendUtils = BackendUtils(frontendAccess, bTypes) - _backendUtils.nn + private var _wellKnownBTypes: WellKnownBTypes | Null = null + def wellKnownBTypes(using Context): WellKnownBTypes = { + if _wellKnownBTypes eq null then + // lazy load to break the circular dependency + def inlineInfoLoader() = Option.when[InlineInfoLoader](ctx.settings.optInlineEnabled)(bTypesFromClassfile) + _wellKnownBTypes = WellKnownBTypes(frontendAccess, bTypeLoader)(using ctx) + _wellKnownBTypes.nn + } + + private var _callGraph: CallGraph | Null = null + def callGraph(using Context): CallGraph = { + if _callGraph eq null then + _callGraph = new CallGraph(frontendAccess, byteCodeRepository, bTypesFromClassfile) + _callGraph.nn } private var _postProcessor: PostProcessor | Null = null def postProcessor(using Context): PostProcessor = { if _postProcessor eq null then - _postProcessor = new PostProcessor(frontendAccess, byteCodeRepository, bTypesFromClassfile, backendUtils, bTypes) + _postProcessor = new PostProcessor(frontendAccess, byteCodeRepository, bTypesFromClassfile, callGraph, backendUtils, bTypeLoader, wellKnownBTypes) _postProcessor.nn } + private var _generatedClassHandler: GeneratedClassHandler | Null = null + def generatedClassHandler(using Context): GeneratedClassHandler = { + if _generatedClassHandler eq null then { + val handler = ctx.settings.YbackendParallelism.value match { + case 1 => GeneratedClassHandler.serial(postProcessor) + case maxThreads => + // The thread pool queue is limited in size. When it's full, the `CallerRunsPolicy` causes + // a new task to be executed on the main thread, which provides back-pressure. + // The queue size is large enough to ensure that running a task on the main thread does + // not take longer than to exhaust the queue for the backend workers. + val queueSize = ctx.settings.YbackendWorkerQueue.valueSetByUser.getOrElse(maxThreads * 2) + GeneratedClassHandler.parallel(postProcessor, maxThreads, queueSize, this, ctx.profiler) + } + _generatedClassHandler = + if ctx.settings.optInlineEnabled || ctx.settings.optClosureInvocations + then GeneratedClassHandler.withGlobalOptimizations(handler) + else handler + } + _generatedClassHandler.nn + } + private var _codeGen: CodeGen | Null = null def codeGen(using Context): CodeGen = { if _codeGen eq null then - _codeGen = new CodeGen(backendUtils, primitives, frontendAccess, postProcessor.callGraph, bTypes) + _codeGen = new CodeGen(backendUtils, primitives, frontendAccess, callGraph, bTypeLoader, wellKnownBTypes, generatedClassHandler) _codeGen.nn } - private var _generatedClassHandler: GeneratedClassHandler | Null = null - def generatedClassHandler(using Context): GeneratedClassHandler = { - if _generatedClassHandler eq null then - _generatedClassHandler = GeneratedClassHandler(postProcessor) - _generatedClassHandler.nn - } - protected def run(using Context): Unit = - frontendAccess.frontendSynch { - frontendAccess - .ctx - .setCompilationUnit(ctx.compilationUnit) - } - codeGen.genUnit(ctx.compilationUnit) - (ctx.compilerCallback: CompilerCallback | Null) match { + codeGen.genUnit() + ctx.compilerCallback match case cb: CompilerCallback => cb.onSourceCompiled(ctx.source) case null => () - } override def runOn(units: List[CompilationUnit])(using ctx:Context): List[CompilationUnit] = { try val result = super.runOn(units) - generatedClassHandler.complete() + for (exn, f) <- generatedClassHandler.complete() do + report.error(em"unable to write $f $exn") + exn.printStackTrace() result finally - // 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 => - if (ctx.run.nn.suspendedUnits.nonEmpty) - // If we close the jar the next run will not be able to write on the jar. - // But if we do not close it we cannot use it as part of the macro classpath of the suspended files. - report.error("Can not suspend and output to a jar at the same time. See suspension with -Xprint-suspension.") - - jar.close() - case _ => () - } + ctx.settings.outputDir.value match + case jar: JarArchive => + if (ctx.run.nn.suspendedUnits.nonEmpty) + // If we close the jar the next run will not be able to write on the jar. + // But if we do not close it we cannot use it as part of the macro classpath of the suspended files. + report.error("Cannot suspend and output to a jar at the same time. See suspension with -Xprint-suspension.") + jar.close() + case _ => () + // created lazily, clean them up only if they were initialized if _postProcessor ne null then - postProcessor.classfileWriter.close() - generatedClassHandler.close() + postProcessor.close() + if _generatedClassHandler ne null then + generatedClassHandler.close() } } diff --git a/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala b/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala index dbc1b744341e..7201e0f71d28 100644 --- a/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala +++ b/compiler/src/dotty/tools/backend/jvm/GeneratedClassHandler.scala @@ -8,7 +8,7 @@ import scala.concurrent.duration.Duration import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor, Future} import dotty.tools.dotc.core.Contexts.* import dotty.tools.io.AbstractFile -import dotty.tools.dotc.profile.ThreadPoolFactory +import dotty.tools.dotc.profile.{Profiler, ThreadPoolFactory} import dotty.tools.dotc.core.Phases import dotty.tools.dotc.core.Decorators.em @@ -22,7 +22,6 @@ import scala.compiletime.uninitialized */ private[jvm] sealed trait GeneratedClassHandler { val postProcessor: PostProcessor - val ctx: Context /** * Pass the result of code generation for a compilation unit to this handler for post-processing @@ -30,9 +29,10 @@ private[jvm] sealed trait GeneratedClassHandler { def process(unit: GeneratedCompilationUnit): Unit /** - * If running in parallel, block until all generated classes are handled + * If running in parallel, block until all generated classes are handled. + * Returns any exceptions encountered during processing, with the corresponding file. */ - def complete(): Unit + def complete(): List[(Throwable, AbstractFile)] /** * Invoked at the end of the jvm phase @@ -41,45 +41,35 @@ private[jvm] sealed trait GeneratedClassHandler { } private[jvm] object GeneratedClassHandler { - def apply(postProcessor: PostProcessor)(using ictx: Context): GeneratedClassHandler = { - val compilerSettings = postProcessor.frontendAccess.compilerSettings - val handler = compilerSettings.backendParallelism match { - case 1 => new SyncWritingClassHandler(postProcessor, ictx) - - case maxThreads => - // if (settings.areStatisticsEnabled) - // runReporting.warning( - // NoPosition, - // "JVM statistics are not reliable with multi-threaded JVM class writing.\n" + - // "To collect compiler statistics remove the " + settings.YaddBackendThreads.name + " setting.", - // WarningCategory.Other, - // site = "" - // ) - val additionalThreads = maxThreads - 1 - // The thread pool queue is limited in size. When it's full, the `CallerRunsPolicy` causes - // a new task to be executed on the main thread, which provides back-pressure. - // The queue size is large enough to ensure that running a task on the main thread does - // not take longer than to exhaust the queue for the backend workers. - val queueSize = compilerSettings.backendMaxWorkerQueue.getOrElse(maxThreads * 2) - val threadPoolFactory = ThreadPoolFactory(Phases.genBCodePhase) - val javaExecutor = threadPoolFactory.newBoundedQueueFixedThreadPool(additionalThreads, queueSize, new CallerRunsPolicy, "non-ast") - new AsyncWritingClassHandler(postProcessor, ictx, javaExecutor) - } - - if compilerSettings.optInlinerEnabled || compilerSettings.optClosureInvocations then - new GlobalOptimisingGeneratedClassHandler(postProcessor, ictx, handler) - else - handler + def serial(postProcessor: PostProcessor): GeneratedClassHandler = + new SyncWritingClassHandler(postProcessor) + + def parallel(postProcessor: PostProcessor, maxThreads: Int, queueSize: Int, genBCode: GenBCode, profiler: Profiler): GeneratedClassHandler = { + // if (settings.areStatisticsEnabled) + // runReporting.warning( + // NoPosition, + // "JVM statistics are not reliable with multi-threaded JVM class writing.\n" + + // "To collect compiler statistics remove the " + settings.YaddBackendThreads.name + " setting.", + // WarningCategory.Other, + // site = "" + // ) + val additionalThreads = maxThreads - 1 + val threadPoolFactory = ThreadPoolFactory(genBCode, profiler) + val javaExecutor = threadPoolFactory.newBoundedQueueFixedThreadPool(additionalThreads, queueSize, new CallerRunsPolicy, "non-ast") + new AsyncWritingClassHandler(postProcessor, javaExecutor) } - private class GlobalOptimisingGeneratedClassHandler(val postProcessor: PostProcessor, val ctx: Context, underlying: WritingClassHandler) - extends GeneratedClassHandler { + def withGlobalOptimizations(handler: GeneratedClassHandler): GeneratedClassHandler = + new GlobalOptimisingGeneratedClassHandler(handler) + + private class GlobalOptimisingGeneratedClassHandler(underlying: GeneratedClassHandler) extends GeneratedClassHandler { + override val postProcessor: PostProcessor = underlying.postProcessor private val generatedUnits = ListBuffer.empty[GeneratedCompilationUnit] def process(unit: GeneratedCompilationUnit): Unit = generatedUnits += unit - def complete(): Unit = { + def complete(): List[(Throwable, AbstractFile)] = { val allGeneratedUnits = generatedUnits.result() generatedUnits.clear() postProcessor.runGlobalOptimizations(allGeneratedUnits) @@ -92,38 +82,35 @@ private[jvm] object GeneratedClassHandler { override def toString: String = s"GloballyOptimising[$underlying]" } - sealed abstract class WritingClassHandler(val javaExecutor: Executor) extends GeneratedClassHandler { - import postProcessor.frontendAccess - + private sealed abstract class WritingClassHandler(val javaExecutor: Executor) extends GeneratedClassHandler { def tryStealing: Option[Runnable] private val processingUnits = ListBuffer.empty[CompilationUnitInPostProcess] def process(unit: GeneratedCompilationUnit): Unit = { - val unitInPostProcess = new CompilationUnitInPostProcess(unit.classes, unit.tasty, unit.sourceFile)(using unit.ctx) + val unitInPostProcess = new CompilationUnitInPostProcess(unit.classes, unit.tasty, unit.sourceFile) postProcessUnit(unitInPostProcess) processingUnits += unitInPostProcess } - protected implicit val executionContext: ExecutionContextExecutor = ExecutionContext.fromExecutor(javaExecutor) + private val executionContext: ExecutionContextExecutor = ExecutionContext.fromExecutor(javaExecutor) - final def postProcessUnit(unitInPostProcess: CompilationUnitInPostProcess): Unit = { + private def postProcessUnit(unitInPostProcess: CompilationUnitInPostProcess): Unit = { + given ExecutionContext = executionContext unitInPostProcess.task = Future: // we 'take' classes to reduce the memory pressure // as soon as the class is consumed and written, we release its data - unitInPostProcess.takeClasses().foreach: - postProcessor.sendToDisk(_, unitInPostProcess.sourceFile) - unitInPostProcess.takeTasty().foreach: - postProcessor.sendToDisk(_, unitInPostProcess.sourceFile) + unitInPostProcess.takeClasses().foreach(postProcessor.sendToDisk) + unitInPostProcess.takeTasty().foreach(postProcessor.sendToDisk) } - protected def takeProcessingUnits(): List[CompilationUnitInPostProcess] = { + private def takeProcessingUnits(): List[CompilationUnitInPostProcess] = { val result = processingUnits.result() processingUnits.clear() result } - final def complete(): Unit = { + final def complete(): List[(Exception, AbstractFile)] = { def stealWhileWaiting(unitInPostProcess: CompilationUnitInPostProcess): Unit = { val task = unitInPostProcess.task while (!task.isCompleted) @@ -146,22 +133,20 @@ private[jvm] object GeneratedClassHandler { * loss, due to the memory being retained longer for tasks than it might otherwise. * Most of the memory in the CompilationUnitInPostProcess is reclaimable anyway as the classes are dereferenced after use. */ - takeProcessingUnits().foreach { unitInPostProcess => + takeProcessingUnits().flatMap { unitInPostProcess => try stealWhileWaiting(unitInPostProcess) // We know the future is complete, throw the exception if it completed with a failure unitInPostProcess.task.value.get.get + Nil catch case _: ClosedByInterruptException => throw new InterruptedException() - case e: Exception => - e.printStackTrace() - given Context = ctx - report.error(em"unable to write ${unitInPostProcess.sourceFile} $e") + case e: Exception => List((e, unitInPostProcess.sourceFile)) } } } - private final class SyncWritingClassHandler(val postProcessor: PostProcessor, val ctx: Context) + private final class SyncWritingClassHandler(val postProcessor: PostProcessor) extends WritingClassHandler(_.nn.run()) { override def toString: String = "SyncWriting" @@ -169,7 +154,7 @@ private[jvm] object GeneratedClassHandler { def tryStealing: Option[Runnable] = None } - private final class AsyncWritingClassHandler(val postProcessor: PostProcessor, val ctx: Context, override val javaExecutor: ThreadPoolExecutor) + private final class AsyncWritingClassHandler(val postProcessor: PostProcessor, override val javaExecutor: ThreadPoolExecutor) extends WritingClassHandler(javaExecutor) { override def toString: String = s"AsyncWriting[additional threads:${javaExecutor.getMaximumPoolSize}]" @@ -182,27 +167,26 @@ private[jvm] object GeneratedClassHandler { def tryStealing: Option[Runnable] = Option(javaExecutor.getQueue.poll()) } -} + /** + * State for a compilation unit being post-processed. + * - Holds the classes to post-process (released for GC when no longer used) + * - Keeps a reference to the future that runs the post-processor + * - Buffers messages reported during post-processing + */ + final private class CompilationUnitInPostProcess(private var classes: List[GeneratedClass], private var tasty: List[GeneratedTasty], val sourceFile: AbstractFile) { + def takeClasses(): List[GeneratedClass] = { + val c = classes + classes = Nil + c + } -/** - * State for a compilation unit being post-processed. - * - Holds the classes to post-process (released for GC when no longer used) - * - Keeps a reference to the future that runs the post-processor - * - Buffers messages reported during post-processing - */ -final private class CompilationUnitInPostProcess(private var classes: List[GeneratedClass], private var tasty: List[GeneratedTasty], val sourceFile: AbstractFile)(using Context) { - def takeClasses(): List[GeneratedClass] = { - val c = classes - classes = Nil - c - } + def takeTasty(): List[GeneratedTasty] = { + val v = tasty + tasty = Nil + v + } - def takeTasty(): List[GeneratedTasty] = { - val v = tasty - tasty = Nil - v + /** the main async task submitted onto the scheduler */ + var task: Future[Unit] = uninitialized } - - /** the main async task submitted onto the scheduler */ - var task: Future[Unit] = uninitialized } diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala index fcaaf66bf382..c2093c2ee8b5 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessor.scala @@ -1,9 +1,9 @@ package dotty.tools.backend.jvm import java.util.concurrent.ConcurrentHashMap -import scala.collection.mutable.ListBuffer -import dotty.tools.dotc.util.{NoSourcePosition, SourcePosition} +import dotty.tools.dotc.util.SourcePosition import dotty.tools.io.AbstractFile +import dotty.tools.io.FileWriters import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Decorators.em @@ -11,30 +11,43 @@ import scala.tools.asm.ClassWriter import scala.tools.asm.tree.ClassNode import dotty.tools.backend.jvm.opt.* import dotty.tools.dotc.report +import dotty.tools.io.PlainFile.toPlainFile +import java.nio.file.{Files, Paths} import scala.tools.asm +import scala.util.chaining.scalaUtilChainingOps /** * Implements late stages of the backend, i.e., * optimizations, post-processing and classfile serialization and writing. */ -class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, - private val byteCodeRepository: BCodeRepository, private val bTypesFromClassfile: BTypesFromClassfile, - private val backendUtils: BackendUtils, private val ts: CoreBTypes)(using Context) { - - val callGraph = new CallGraph(frontendAccess, byteCodeRepository, bTypesFromClassfile, ts) - private val closureOptimizer = new ClosureOptimizer(frontendAccess, backendUtils, byteCodeRepository, callGraph, ts, bTypesFromClassfile) - private val heuristics = new InlinerHeuristics(frontendAccess, backendUtils, byteCodeRepository, callGraph, ts) - private val inliner = new Inliner(frontendAccess, backendUtils, callGraph, ts, bTypesFromClassfile, byteCodeRepository, heuristics, closureOptimizer) - private val localOpt = new LocalOpt(backendUtils, frontendAccess, callGraph, inliner, ts, bTypesFromClassfile) - val classfileWriters = new ClassfileWriters(frontendAccess) - val classfileWriter = classfileWriters.ClassfileWriter(frontendAccess.compilerSettings.mainClass) - +class PostProcessor(frontendAccess: PostProcessorFrontendAccess, + byteCodeRepository: BCodeRepository, bTypesFromClassfile: BTypesFromClassfile, + callGraph: CallGraph, backendUtils: BackendUtils, + bTypeLoader: BTypeLoader, bTypes: WellKnownBTypes)(using Context) { + + private val optSettings = new OptimizerSettings() + private val closureOptimizer = new ClosureOptimizer(frontendAccess, backendUtils, byteCodeRepository, callGraph, bTypes, bTypesFromClassfile, optSettings) + private val heuristics = new InlinerHeuristics(frontendAccess, backendUtils, byteCodeRepository, callGraph, bTypes, optSettings) + private val inliner = new Inliner(frontendAccess, backendUtils, callGraph, bTypeLoader, bTypesFromClassfile, byteCodeRepository, heuristics, closureOptimizer, optSettings) + private val localOpt = new LocalOpt(backendUtils, callGraph, inliner, bTypes, bTypesFromClassfile, optSettings) + + given FileWriters.ReadOnlyContext = FileWriters.ReadOnlyContext.eager + private val classfileWriter: FileWriters.ClassfileWriter = { + val dumpClassesPath = + ctx.settings.Xdumpclasses.valueSetByUser + .map(p => Paths.get(p)) + .filter(path => Files.exists(path).tap(ok => if !ok then report.error(em"Output dir does not exist: ${path.toString}"))) + .map(_.toPlainFile) + + FileWriters.ClassfileWriter(ctx.settings.outputDir.value, ctx.settings.XmainClass.valueSetByUser, dumpClassesPath) + } private type ClassnamePosition = (String, SourcePosition) private val caseInsensitively = new ConcurrentHashMap[String, ClassnamePosition] - def sendToDisk(clazz: GeneratedClass, sourceFile: AbstractFile): Unit = if !frontendAccess.compilerSettings.outputOnlyTasty then { + @annotation.nowarn("cat=deprecation") + def sendToDisk(clazz: GeneratedClass): Unit = if !ctx.settings.YoutputOnlyTasty.value then { val classNode = clazz.classNode val internalName = classNode.name.nn val bytes = @@ -50,20 +63,20 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, report.error(em"Could not write class $internalName because it exceeds JVM code size limits. ${e.getMessage}") null case ex: Exception => - if frontendAccess.compilerSettings.debug then ex.printStackTrace() + if ctx.debug then ex.printStackTrace() report.error(em"Error while emitting $internalName\n${ex.getMessage}") null if bytes != null then TraceUtils.traceSerializedClassIfRequested(internalName, bytes) - val clsFile = classfileWriter.writeClass(internalName, bytes, sourceFile) + val clsFile = classfileWriter.writeClass(internalName, bytes) clazz.onFileCreated(clsFile) } - def sendToDisk(tasty: GeneratedTasty, sourceFile: AbstractFile): Unit = { + def sendToDisk(tasty: GeneratedTasty): Unit = { val GeneratedTasty(classNode, tastyGenerator) = tasty val internalName = classNode.name.nn - classfileWriter.writeTasty(classNode.name.nn, tastyGenerator(), sourceFile) + classfileWriter.writeTasty(classNode.name.nn, tastyGenerator()) } def runGlobalOptimizations(generatedUnits: Iterable[GeneratedCompilationUnit]): Unit = { @@ -78,12 +91,15 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, if !c.isArtifact // skip call graph for mirror / bean: we don't inline into them, and they are not referenced from other classes do callGraph.addClass(c.classNode) - if frontendAccess.compilerSettings.optInlinerEnabled then + if ctx.settings.optInlineEnabled then inliner.runInlinerAndClosureOptimizer() - else if frontendAccess.compilerSettings.optClosureInvocations then + else if ctx.settings.optClosureInvocations then closureOptimizer.rewriteClosureApplyInvocations(None, scala.collection.mutable.Map.empty) } + def close(): Unit = + classfileWriter.close() + private def warnCaseInsensitiveOverwrite(clazz: GeneratedClass): Unit = { val name = clazz.classNode.name val lowerCaseJavaName = name.toLowerCase @@ -117,10 +133,9 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, } private def setInnerClasses(classNode: ClassNode): Unit = { - import backendUtils.{collectNestedClasses, addInnerClasses} classNode.innerClasses.nn.clear() - val (declared, referred) = collectNestedClasses(classNode) - addInnerClasses(classNode, declared, referred) + val (declared, referred) = bTypeLoader.collectNestedClasses(classNode) + backendUtils.addInnerClasses(classNode, declared, referred) } private def serializeClass(classNode: ClassNode): Array[Byte] = { @@ -147,10 +162,11 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess, * 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 = ts.classBTypeFromInternalName(inameA).get - val b = ts.classBTypeFromInternalName(inameB).get - val lub = a.jvmWiseLUB(b) + // All types that appear in a class node need to have their ClassBType cached, + // i.e., have been loaded either from symbols or from class files. + val a = bTypeLoader.previouslyConstructedClassBType(inameA).get + val b = bTypeLoader.previouslyConstructedClassBType(inameB).get + val lub = a.jvmWiseLUB(b, bTypes) 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. @@ -168,5 +184,5 @@ case class GeneratedClass( isArtifact: Boolean, onFileCreated: AbstractFile => Unit) case class GeneratedTasty(classNode: ClassNode, tastyGen: () => Array[Byte]) -case class GeneratedCompilationUnit(sourceFile: AbstractFile, classes: List[GeneratedClass], tasty: List[GeneratedTasty])(using val ctx: Context) +case class GeneratedCompilationUnit(sourceFile: AbstractFile, classes: List[GeneratedClass], tasty: List[GeneratedTasty]) diff --git a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala index 28caf886f66b..c9dce10f98f4 100644 --- a/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala +++ b/compiler/src/dotty/tools/backend/jvm/PostProcessorFrontendAccess.scala @@ -1,34 +1,25 @@ package dotty.tools package backend.jvm -import scala.collection.mutable.HashSet -import dotty.tools.io.AbstractFile -import dotty.tools.dotc.core.Contexts.* -import dotty.tools.dotc.classpath.* +import dotty.tools.dotc.core.Contexts.Context import dotty.tools.dotc.report -import dotty.tools.dotc.config.ScalaSettings import dotty.tools.dotc.reporting.Message import dotty.tools.dotc.util.SrcPos -import scala.collection.mutable import scala.compiletime.uninitialized /** * Abstracts the frontend data structures, specially the Context, that need to be accessed in a single-threaded manner. */ -sealed abstract class PostProcessorFrontendAccess(val ctx: FreshContext) { +final class PostProcessorFrontendAccess(val ctx: Context) { import PostProcessorFrontendAccess.* - def compilerSettings: CompilerSettings - - def findClassFileAndModuleFile(name: String): Option[(io.AbstractFile, Option[io.AbstractFile])] - def optimizerWarning(msg: Context ?=> Message, site: String, pos: SrcPos): Unit = report.optimizerWarning(msg(using ctx), site, pos)(using ctx) private val frontendLock: AnyRef = new Object() - inline final def frontendSynch[T](inline x: T): T = frontendLock.synchronized(x) + private[PostProcessorFrontendAccess] def frontendSynch[T](x: => T): T = frontendLock.synchronized(x) def perRunLazy[T](init: => T): Lazy[T] = new SynchronizedLazy(this, init) } @@ -54,117 +45,4 @@ object PostProcessorFrontendAccess { v } } - - sealed trait CompilerSettings { - def debug: Boolean - def target: String // javaOutputVersion - - def dumpClassesDirectory: Option[String] - def outputDirectory: AbstractFile - - def mainClass: Option[String] - - def jarCompressionLevel: Int - def backendParallelism: Int - def backendMaxWorkerQueue: Option[Int] - def outputOnlyTasty: Boolean - - def optUnreachableCode: Boolean - def optNullnessTracking: Boolean - def optBoxUnbox: Boolean - def optCopyPropagation: Boolean - def optRedundantCasts: Boolean - def optSimplifyJumps: Boolean - def optCompactLocals: Boolean - def optClosureInvocations: Boolean - def optAllowSkipCoreModuleInit: Boolean - def optAssumeModulesNonNull: Boolean - def optAllowSkipClassLoading: Boolean - - def optInlinerEnabled: Boolean - def optInlineFrom: List[String] - def optInlineHeuristics: String - - def optWarningNoInlineMixed: Boolean - def optWarningNoInlineMissingBytecode: Boolean - def optWarningNoInlineMissingScalaInlineInfoAttr: Boolean - def optWarningEmitAtInlineFailed: Boolean - def optWarningEmitAnyInlineFailed: Boolean - - def optLogInline: Option[String] - def optTrace: Option[String] - - } - - class Impl(ctx: FreshContext) extends PostProcessorFrontendAccess(ctx) { - override def compilerSettings: CompilerSettings = _compilerSettings.get - 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: 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 - case (Some(release), None) => release - case (None, Some(target)) => target - case (Some(release), Some(_)) => - report.warning(s"The value of ${s.XuncheckedJavaOutputVersion.name} was overridden by ${ctx.settings.javaOutputVersion.name}") - release - case (None, None) => "17" // least supported version by default - - override val debug: Boolean = ctx.debug - override val dumpClassesDirectory: Option[String] = s.Xdumpclasses.valueSetByUser - override val outputDirectory: AbstractFile = s.outputDir.value - override val mainClass: Option[String] = s.XmainClass.valueSetByUser - override val jarCompressionLevel: Int = s.XjarCompressionLevel.value - override val backendParallelism: Int = s.YbackendParallelism.value - override val backendMaxWorkerQueue: Option[Int] = s.YbackendWorkerQueue.valueSetByUser - - @annotation.nowarn("cat=deprecation") - override val outputOnlyTasty: Boolean = s.YoutputOnlyTasty.value - - override def optUnreachableCode: Boolean = s.optUnreachableCode - override def optNullnessTracking: Boolean = s.optNullnessTracking - override def optBoxUnbox: Boolean = s.optBoxUnbox - override def optCopyPropagation: Boolean = s.optCopyPropagation - override def optRedundantCasts: Boolean = s.optRedundantCasts - override def optSimplifyJumps: Boolean = s.optSimplifyJumps - override def optCompactLocals: Boolean = s.optCompactLocals - override def optClosureInvocations: Boolean = s.optClosureInvocations - override def optAllowSkipCoreModuleInit: Boolean = s.optAllowSkipCoreModuleInit - override def optAssumeModulesNonNull: Boolean = s.optAssumeModulesNonNull - override def optAllowSkipClassLoading: Boolean = s.optAllowSkipClassLoading - override def optInlinerEnabled: Boolean = s.optInline.value.nonEmpty - override def optInlineFrom: List[String] = s.optInline.value - override def optInlineHeuristics: String = s.YoptInlineHeuristics.value - override def optWarningNoInlineMixed: Boolean = s.optWarningNoInlineMixed - override def optWarningNoInlineMissingBytecode: Boolean = s.optWarningNoInlineMissingBytecode - override def optWarningNoInlineMissingScalaInlineInfoAttr: Boolean = s.optWarningNoInlineMissingScalaInlineInfoAttr - override def optWarningEmitAtInlineFailed: Boolean = s.optWarningEmitAtInlineFailed - override def optWarningEmitAnyInlineFailed: Boolean = s.optWarningEmitAnyInlineFailed - override def optLogInline: Option[String] = s.YoptLogInline.valueSetByUser - override def optTrace: Option[String] = s.YoptTrace.valueSetByUser - } - - /* Create a class path for the backend, based on the given class path. - * Used to make classes available to the inliner's bytecode repository. - * In particular, if ct.sym is used for compilation, replace it with jrt. - */ - private lazy val optimizerClassPath = ctx.platform.classPath(using ctx) match { - case cp @ AggregateClassPath(entries) if entries.head.isInstanceOf[CtSymClassPath] => - JrtClassPath(release = None) match { - case Some(jrt) => AggregateClassPath(entries.drop(1).prepended(jrt)) - case _ => cp - } - case cp => cp - } - - override def findClassFileAndModuleFile(name: String): Option[(io.AbstractFile, Option[io.AbstractFile])] = - optimizerClassPath.findClassFileAndModuleFile(name) - } } diff --git a/compiler/src/dotty/tools/backend/jvm/WellKnownBTypes.scala b/compiler/src/dotty/tools/backend/jvm/WellKnownBTypes.scala new file mode 100644 index 000000000000..1059f29ce150 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/WellKnownBTypes.scala @@ -0,0 +1,348 @@ +package dotty.tools.backend.jvm + +import dotty.tools.dotc.core.Symbols.* +import dotty.tools.dotc.transform.Erasure + +import scala.tools.asm.{Handle, Opcodes} +import dotty.tools.dotc.core.Symbols +import BTypes.* +import dotty.tools.dotc.core.Contexts.Context +import dotty.tools.dotc.core.StdNames.* +import PostProcessorFrontendAccess.Lazy + + +case class MethodNameAndType(name: String, methodType: MethodBType) + +final class WellKnownBTypes(ppa: PostProcessorFrontendAccess, ts: BTypeLoader)(using Context) { + + def ObjectRef: ClassBType = _ObjectRef.get + private lazy val _ObjectRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.ObjectClass)) + + def srNothingRef: ClassBType = _srNothingRef.get + private lazy val _srNothingRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.RuntimeNothingClass)) + + def srNullRef: ClassBType = _srNullRef.get + private lazy val _srNullRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.RuntimeNullClass)) + + /** + * 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. + */ + def boxedClassOfPrimitive: Map[BType, ClassBType] = _boxedClassOfPrimitive.get + private lazy val _boxedClassOfPrimitive: Lazy[Map[BType, ClassBType]] = ppa.perRunLazy(Map( + UNIT -> ts.classBTypeFromSymbol(requiredClass[java.lang.Void]), + BOOL -> ts.classBTypeFromSymbol(requiredClass[java.lang.Boolean]), + BYTE -> ts.classBTypeFromSymbol(requiredClass[java.lang.Byte]), + SHORT -> ts.classBTypeFromSymbol(requiredClass[java.lang.Short]), + CHAR -> ts.classBTypeFromSymbol(requiredClass[java.lang.Character]), + INT -> ts.classBTypeFromSymbol(requiredClass[java.lang.Integer]), + LONG -> ts.classBTypeFromSymbol(requiredClass[java.lang.Long]), + FLOAT -> ts.classBTypeFromSymbol(requiredClass[java.lang.Float]), + DOUBLE -> ts.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`. + */ + 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(ts.bTypeFromSymbol(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. */ + def unboxResultType: Map[Symbol, BType] = _unboxResultType.get + private lazy val _unboxResultType = ppa.perRunLazy[Map[Symbol, BType]]{ + val unboxMethods: Map[Symbol, Symbol] = + defn.ScalaValueClasses().map(x => (x, Erasure.Boxing.unboxMethod(x.asClass))).toMap + for ((valueClassSym, unboxMethodSym) <- unboxMethods) + yield unboxMethodSym -> ts.bTypeFromSymbol(valueClassSym) + } + + def srBoxedUnitRef: ClassBType = _srBoxedUnitRef.get + private lazy val _srBoxedUnitRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[scala.runtime.BoxedUnit])) + + def StringRef: ClassBType = _StringRef.get + private lazy val _StringRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.StringClass)) + + def PredefRef: ClassBType = _PredefRef.get + private lazy val _PredefRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.ScalaPredefModuleClass)) + + def jlClassRef: ClassBType = _jlClassRef.get + private lazy val _jlClassRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.Class[?]])) + + def jlThrowableRef: ClassBType = _jlThrowableRef.get + private lazy val _jlThrowableRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.ThrowableClass)) + + def jlCloneableRef: ClassBType = _jlCloneableRef.get + private lazy val _jlCloneableRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.JavaCloneableClass)) + + def jiSerializableRef: ClassBType = _jiSerializableRef.get + private lazy val _jiSerializableRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.io.Serializable])) + + def jlClassCastExceptionRef: ClassBType = _jlClassCastExceptionRef.get + private lazy val _jlClassCastExceptionRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.ClassCastException])) + + def jlIllegalArgExceptionRef: ClassBType = _jlIllegalArgExceptionRef.get + private lazy val _jlIllegalArgExceptionRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.IllegalArgumentException])) + + def jliSerializedLambdaRef: ClassBType = _jliSerializedLambdaRef.get + private lazy val _jliSerializedLambdaRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda])) + + def srBoxesRuntimeRef: ClassBType = _srBoxesRuntimeRef.get + private lazy val _srBoxesRuntimeRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime])) + + private def jliCallSiteRef: ClassBType = _jliCallSiteRef.get + private lazy val _jliCallSiteRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite])) + + private def jliLambdaMetafactoryRef: ClassBType = _jliLambdaMetafactoryRef.get + private lazy val _jliLambdaMetafactoryRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory])) + + private def jliMethodHandleRef: ClassBType = _jliMethodHandleRef.get + private lazy val _jliMethodHandleRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.MethodHandleClass)) + + private def jliMethodHandlesLookupRef: ClassBType = _jliMethodHandlesLookupRef.get + private lazy val _jliMethodHandlesLookupRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(defn.MethodHandlesLookupClass)) + + private def jliMethodTypeRef: ClassBType = _jliMethodTypeRef.get + private lazy val _jliMethodTypeRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType])) + + private def jliStringConcatFactoryRef: ClassBType = _jliStringConcatFactoryRef.get + private lazy val _jliStringConcatFactoryRef: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[java.lang.invoke.StringConcatFactory])) + + private def srLambdaDeserialize: ClassBType = _srLambdaDeserialize.get + private lazy val _srLambdaDeserialize: Lazy[ClassBType] = ppa.perRunLazy(ts.classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize])) + + + 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)} + + 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)} + + 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)} + + 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 = ts.bTypeFromSymbol(primitive) + val method = MethodNameAndType("valueOf", MethodBType(List(unboxed), boxedClassOfPrimitive(unboxed))) + (ts.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" + (ts.classBTypeFromSymbol(boxed).internalName, MethodNameAndType(name, MethodBType(Nil, ts.bTypeFromSymbol(primitive)))) + })) + } + + private def predefBoxingMethods(isBox: Boolean, getName: (String, String) => String): Map[String, MethodBType] = + Map.from(defn.ScalaValueClassesNoUnit().map(primitive => { + val unboxed = ts.bTypeFromSymbol(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 ts.bTypeFromSymbol(primitive) + val refClass = Symbols.requiredClass("scala.runtime." + primitive.name.toString + "Ref") + val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + primitive.name.toString + "Ref") + List( + (ts.classBTypeFromSymbol(refClass).internalName, MethodNameAndType(nme.create.toString, MethodBType(List(unboxed), ts.bTypeFromSymbol(refClass)))), + (ts.classBTypeFromSymbol(volatileRefClass).internalName, MethodNameAndType(nme.create.toString, MethodBType(List(unboxed), ts.bTypeFromSymbol(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." + primitive.name.toString + "Ref") + val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + primitive.name.toString + "Ref") + List( + (ts.classBTypeFromSymbol(refClass).internalName, MethodNameAndType(nme.zero.toString, MethodBType(List(), ts.bTypeFromSymbol(refClass)))), + (ts.classBTypeFromSymbol(volatileRefClass).internalName, MethodNameAndType(nme.zero.toString, MethodBType(List(), ts.bTypeFromSymbol(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 = ts.bTypeFromSymbol(primitive) + (ts.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 = ts.bTypeFromSymbol(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 = ts.bTypeFromSymbol(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 ts.bTypeFromSymbol(primitive) + val refClass = Symbols.requiredClass("scala.runtime." + primitive.name.toString + "Ref") + val volatileRefClass = Symbols.requiredClass("scala.runtime.Volatile" + primitive.name.toString + "Ref") + List( + (ts.classBTypeFromSymbol(refClass).internalName, MethodNameAndType(nme.CONSTRUCTOR.toString, MethodBType(List(unboxed), UNIT))), + (ts.classBTypeFromSymbol(volatileRefClass).internalName, MethodNameAndType(nme.CONSTRUCTOR.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 = ts.bTypeFromSymbol(sp1) + ("scala/Tuple1$mc" + prim.descriptor + "$sp", MethodNameAndType(nme.CONSTRUCTOR.toString, MethodBType(List(), UNIT))) + }, + for sp2a <- spec2; sp2b <- spec2 yield { + val primA = ts.bTypeFromSymbol(sp2a) + val primB = ts.bTypeFromSymbol(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/opt/BCodeRepository.scala b/compiler/src/dotty/tools/backend/jvm/opt/BCodeRepository.scala index 6f625619e589..59a4b453e10b 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/BCodeRepository.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/BCodeRepository.scala @@ -15,11 +15,11 @@ package dotty.tools.backend.jvm.opt import dotty.tools.backend.jvm.BCodeUtils.* import dotty.tools.backend.jvm.BTypes.InternalName import dotty.tools.backend.jvm.BackendUtils.LambdaMetaFactoryCall -import dotty.tools.backend.jvm.PostProcessorFrontendAccess.Lazy import dotty.tools.backend.jvm.opt.* -import dotty.tools.backend.jvm.{BackendUtils, ClassNode1, CoreBTypes, PostProcessorFrontendAccess} -import dotty.tools.dotc.core.Decorators.em -import dotty.tools.dotc.util.NoSourcePosition +import dotty.tools.backend.jvm.{BackendUtils, ClassNode1} +import dotty.tools.dotc.classpath.{AggregateClassPath, CtSymClassPath, JrtClassPath} +import dotty.tools.io +import dotty.tools.io.ClassPath import scala.collection.{concurrent, mutable} import scala.jdk.CollectionConverters.* @@ -31,7 +31,7 @@ import scala.tools.asm.{Attribute, ClassReader, Type} * The BCodeRepository provides utilities to read the bytecode of classfiles from the compilation * classpath. Parsed classes are cached in the `classes` map. */ -class BCodeRepository(frontendAccess: PostProcessorFrontendAccess, backendUtils: BackendUtils, ts: CoreBTypes) { +class BCodeRepository(classPath: ClassPath, backendUtils: BackendUtils) { type ClassAndModuleNodes = (ClassNode, Option[ModuleNode]) @@ -39,7 +39,7 @@ class BCodeRepository(frontendAccess: PostProcessorFrontendAccess, backendUtils: * Contains ClassNodes and the canonical path of the source file path of classes being compiled in * the current compilation run. */ - val compilingClasses: Lazy[concurrent.Map[InternalName, (ClassAndModuleNodes, String)]] = frontendAccess.perRunLazy(concurrent.TrieMap.empty) + val compilingClasses: concurrent.Map[InternalName, (ClassAndModuleNodes, String)] = concurrent.TrieMap.empty /** * Prevent the code repository from growing too large. Profiling reveals that the average size @@ -54,16 +54,16 @@ class BCodeRepository(frontendAccess: PostProcessorFrontendAccess, backendUtils: * Note - although this is typed a mutable.Map, individual simple get and put operations are threadsafe as the * underlying data structure is synchronized. */ - private val parsedClasses: Lazy[mutable.Map[InternalName, Either[ClassNotFound, ClassAndModuleNodes]]] = - frontendAccess.perRunLazy(FifoCache[InternalName, Either[ClassNotFound, ClassAndModuleNodes]](maxCacheSize, threadsafe = true)) + private val parsedClasses: mutable.Map[InternalName, Either[ClassNotFound, ClassAndModuleNodes]] = + FifoCache[InternalName, Either[ClassNotFound, ClassAndModuleNodes]](maxCacheSize, threadsafe = true) def add(classNode: ClassNode, sourceFilePath: Option[String]): Unit = sourceFilePath match { - case Some(path) if path != "" => compilingClasses.get(classNode.name) = ((classNode, None), path) - case _ => parsedClasses.get(classNode.name) = Right(classNode, None) + case Some(path) if path != "" => compilingClasses(classNode.name) = ((classNode, None), path) + case _ => parsedClasses(classNode.name) = Right(classNode, None) } private def parsedClassNode(internalName: InternalName): Either[ClassNotFound, ClassAndModuleNodes] = { - parsedClasses.get.getOrElseUpdate(internalName, parseClass(internalName)) + parsedClasses.getOrElseUpdate(internalName, parseClass(internalName)) } /** @@ -71,7 +71,7 @@ class BCodeRepository(frontendAccess: PostProcessorFrontendAccess, backendUtils: * the class node is not yet available, it is parsed from the classfile on the compile classpath. */ def classNodeAndSourceFilePath(internalName: InternalName): Either[ClassNotFound, (ClassAndModuleNodes, Option[String])] = { - compilingClasses.get.get(internalName) match { + compilingClasses.get(internalName) match { case Some((c, p)) => Right((c, Some(p))) case _ => parsedClassNode(internalName).map((_, None)) } @@ -82,7 +82,7 @@ class BCodeRepository(frontendAccess: PostProcessorFrontendAccess, backendUtils: * the classfile on the compile classpath. */ def classNode(internalName: InternalName): Either[ClassNotFound, ClassAndModuleNodes] = { - compilingClasses.get.get(internalName) match { + compilingClasses.get(internalName) match { case Some((c, _)) => Right(c) case None => parsedClassNode(internalName) } @@ -158,11 +158,12 @@ class BCodeRepository(frontendAccess: PostProcessorFrontendAccess, backendUtils: // https://docs.oracle.com/javase/specs/jvms/se11/html/jvms-2.html#jvms-2.9.3 def findSignaturePolymorphic(owner: ClassNode): Option[MethodNode] = { def hasObjectArrayParam(m: MethodNode) = Type.getArgumentTypes(m.desc) match { - case Array(pt) => pt.getDimensions == 1 && pt.getElementType.getInternalName == ts.ObjectRef.internalName + case Array(pt) => pt.getDimensions == 1 && pt.getElementType.getInternalName == "java/lang/Object" case _ => false } - // Don't try to build a BType for `VarHandle`, it doesn't exist on JDK 8 - if (owner.name == ts.jliMethodHandleRef.internalName || owner.name == "java/lang/invoke/VarHandle") + // We don't need to explicitly load the BTypes of MethodHandle or VarHandle for later use here, + // because if we've reached this point, `owner` has been loaded already. + if (owner.name == "java/lang/invoke/MethodHandle" || owner.name == "java/lang/invoke/VarHandle") owner.methods.asScala.find(m => m.name == name && isNativeMethod(m) && @@ -282,44 +283,50 @@ class BCodeRepository(frontendAccess: PostProcessorFrontendAccess, backendUtils: } } - private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassAndModuleNodes] = { - val fullName = internalName.replace('/', '.') - frontendAccess.findClassFileAndModuleFile(fullName).flatMap { (classFile, moduleFile) => - val classNode = new ClassNode1 - val classReader = new ClassReader(classFile.toByteArray) - - try { - // Passing the InlineInfoAttributePrototype makes the ClassReader invoke the specific `read` - // method of the InlineInfoAttribute class, instead of putting the byte array into a generic - // Attribute. - // We don't need frames when inlining, but we want to keep the local variable table, so we - // don't use SKIP_DEBUG. - classReader.accept(classNode, Array[Attribute](InlineInfoAttributePrototype), ClassReader.SKIP_FRAMES) - // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after - // inlining. - // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining? - // OR: instead of skipping line numbers for inlined code, use write a SourceDebugExtension - // attribute that contains JSR-45 data that encodes debugging info. - // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 - // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html - removeLineNumbersAndAddLMFImplMethods(classNode) - - val moduleNode = moduleFile.map(f => - val node = new ClassNode1 - val moduleReader = new ClassReader(f.toByteArray) - moduleReader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) - node.module - ) - - Some(classNode, moduleNode) - } catch { - case ex: Exception => - frontendAccess.optimizerWarning(em"Error while reading InlineInfoAttribute: ${ex.getMessage}", fullName, NoSourcePosition) - None + /* Create a class path for the backend, based on the given class path. + * Used to make classes available to the inliner's bytecode repository. + * In particular, if ct.sym is used for compilation, replace it with jrt. + */ + private lazy val optimizerClassPath = classPath match { + case cp@AggregateClassPath(entries) if entries.head.isInstanceOf[CtSymClassPath] => + JrtClassPath(release = None) match { + case Some(jrt) => AggregateClassPath(entries.drop(1).prepended(jrt)) + case _ => cp } - } match { - case Some(nodes) => Right(nodes) - case None => Left(ClassNotFound(internalName)) - } + case cp => cp + } + + private def parseClass(internalName: InternalName): Either[ClassNotFound, ClassAndModuleNodes] = { + try + val fullName = internalName.replace('/', '.') + optimizerClassPath.findClassFileAndModuleFile(fullName) match + case Some(classFile, moduleFile) => + val classNode = new ClassNode1 + val classReader = new ClassReader(classFile.toByteArray) + // Passing the InlineInfoAttributePrototype makes the ClassReader invoke the specific `read` + // method of the InlineInfoAttribute class, instead of putting the byte array into a generic + // Attribute. + // We don't need frames when inlining, but we want to keep the local variable table, so we + // don't use SKIP_DEBUG. + classReader.accept(classNode, Array[Attribute](InlineInfoAttributePrototype), ClassReader.SKIP_FRAMES) + // SKIP_FRAMES leaves line number nodes. Remove them because they are not correct after + // inlining. + // TODO: we need to remove them also for classes that are not parsed from classfiles, why not simplify and do it once when inlining? + // OR: instead of skipping line numbers for inlined code, use write a SourceDebugExtension + // attribute that contains JSR-45 data that encodes debugging info. + // https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 + // https://jcp.org/aboutJava/communityprocess/final/jsr045/index.html + removeLineNumbersAndAddLMFImplMethods(classNode) + val moduleNode = moduleFile.map(f => + val node = new ClassNode1 + val moduleReader = new ClassReader(f.toByteArray) + moduleReader.accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) + node.module + ) + Right(classNode, moduleNode) + case None => + Left(ClassNotFound(internalName)) + catch + case _: Exception => Left(ClassNotFound(internalName)) } } diff --git a/compiler/src/dotty/tools/backend/jvm/opt/BTypesFromClassfile.scala b/compiler/src/dotty/tools/backend/jvm/opt/BTypesFromClassfile.scala index 788d140eeaaf..12f2046ec9ee 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/BTypesFromClassfile.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/BTypesFromClassfile.scala @@ -13,7 +13,6 @@ package dotty.tools.backend.jvm.opt import dotty.tools.backend.jvm.BTypes.InternalName -import dotty.tools.backend.jvm.PostProcessorFrontendAccess.Lazy import dotty.tools.backend.jvm.opt.{BCodeRepository, ClassNotFound, NoClassBTypeInfo, OptimizerWarning} import dotty.tools.backend.jvm.* import dotty.tools.dotc.core.StdNames.nme @@ -24,7 +23,7 @@ import scala.jdk.CollectionConverters.* import scala.tools.asm.Opcodes import scala.tools.asm.tree.{ClassNode, InnerClassNode, ModuleNode} -class BTypesFromClassfile(val byteCodeRepository: BCodeRepository, ts: CoreBTypes) extends InlineInfoLoader { +class BTypesFromClassfile(byteCodeRepository: BCodeRepository, bTypeLoader: BTypeLoader) extends InlineInfoLoader { /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType @@ -63,10 +62,7 @@ class BTypesFromClassfile(val byteCodeRepository: BCodeRepository, ts: CoreBType * be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined. */ def classBTypeFromParsedClassfile(internalName: InternalName): Either[OptimizerWarning, ClassBType] = { - // JLS §4.1 "There is also a special null type, the type of the expression null [...] - // In practice, the programmer can ignore the null type and just pretend that null is merely a special literal that can be of any reference type." - if internalName == "null" then Right(ts.ObjectRef) - else ts.classBType(internalName) { _ => + bTypeLoader.classBType(internalName) { _ => byteCodeRepository.classNode(internalName) match { case Left(msg) => Left(NoClassBTypeInfo(msg)) case Right(c, m) => computeClassInfoFromClassNode(c, m) @@ -78,7 +74,7 @@ class BTypesFromClassfile(val byteCodeRepository: BCodeRepository, ts: CoreBType * Construct the [[BTypes.ClassBType]] for a parsed classfile. */ def classBTypeFromClassNode(classNode: ClassNode, moduleNode: Option[ModuleNode]): Either[OptimizerWarning, ClassBType] = { - ts.classBType(classNode.name) { _ => + bTypeLoader.classBType(classNode.name) { _ => computeClassInfoFromClassNode(classNode, moduleNode) } } @@ -86,7 +82,7 @@ class BTypesFromClassfile(val byteCodeRepository: BCodeRepository, ts: CoreBType private def computeClassInfoFromClassNode(classNode: ClassNode, moduleNode: Option[ModuleNode]): Either[OptimizerWarning, ClassInfo] = { val superClass = classNode.superName match { case null => - assert(classNode.name == ts.ObjectRef.internalName, s"class with missing super type: ${classNode.name}") + assert(classNode.name == "java/lang/Object", s"class with missing super type: ${classNode.name}") Right(None) case superName => classBTypeFromParsedClassfile(superName).map(Some.apply) diff --git a/compiler/src/dotty/tools/backend/jvm/opt/BoxUnbox.scala b/compiler/src/dotty/tools/backend/jvm/opt/BoxUnbox.scala index 7382d047b4b2..35dd1dbb79eb 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/BoxUnbox.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/BoxUnbox.scala @@ -26,7 +26,7 @@ import dotty.tools.backend.jvm.analysis.{AsmAnalyzer, ProdConsAnalyzer} import dotty.tools.backend.jvm.BCodeUtils.* import dotty.tools.dotc.core.StdNames.nme -final class BoxUnbox(backendUtils: BackendUtils, callGraph: CallGraph, ts: CoreBTypes) { +final class BoxUnbox(backendUtils: BackendUtils, callGraph: CallGraph, ts: WellKnownBTypes) { /** * Eliminate box-unbox pairs within `method`. Such appear commonly after closure elimination: diff --git a/compiler/src/dotty/tools/backend/jvm/opt/CallGraph.scala b/compiler/src/dotty/tools/backend/jvm/opt/CallGraph.scala index ae95ee4493e9..ed04e73dc57d 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/CallGraph.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/CallGraph.scala @@ -29,8 +29,7 @@ import dotty.tools.dotc.util.{SourcePosition, NoSourcePosition} import dotty.tools.backend.jvm.PostProcessorFrontendAccess.Lazy class CallGraph(frontendAccess: PostProcessorFrontendAccess, - byteCodeRepository: BCodeRepository, bTypesFromClassfile: BTypesFromClassfile, - ts: CoreBTypes) { + byteCodeRepository: BCodeRepository, bTypesFromClassfile: BTypesFromClassfile) { /** * The call graph contains the callsites in the program being compiled. diff --git a/compiler/src/dotty/tools/backend/jvm/opt/ClosureOptimizer.scala b/compiler/src/dotty/tools/backend/jvm/opt/ClosureOptimizer.scala index e93f4915d893..5a03f42bf0cc 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/ClosureOptimizer.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/ClosureOptimizer.scala @@ -30,7 +30,8 @@ import BCodeUtils.* class ClosureOptimizer(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, byteCodeRepository: BCodeRepository, callGraph: CallGraph, - ts: CoreBTypes, bTypesFromClassfile: BTypesFromClassfile) { + ts: WellKnownBTypes, bTypesFromClassfile: BTypesFromClassfile, + settings: OptimizerSettings) { import ClosureOptimizer.* @@ -142,7 +143,7 @@ class ClosureOptimizer(ppa: PostProcessorFrontendAccess, backendUtils: BackendUt if (closureInit.ownerMethod != previousMethod) { previousMethod = closureInit.ownerMethod changedMethods += previousMethod.nn - val state = inlinerState.getOrElseUpdate(previousMethod.nn, new MethodInlinerState(ppa.compilerSettings.optLogInline)) + val state = inlinerState.getOrElseUpdate(previousMethod.nn, new MethodInlinerState(settings.optLogInline)) state.inlineLog.logClosureRewrite(closureInit, invocations, invocations.headOption.flatMap(p => state.outerCallsite(p._1))) } } @@ -409,7 +410,7 @@ class ClosureOptimizer(ppa: PostProcessorFrontendAccess, backendUtils: BackendUt // the method node is needed for building the call graph entry val bodyMethod = byteCodeRepository.methodNode(lambdaBodyHandle.getOwner, lambdaBodyHandle.getName, lambdaBodyHandle.getDesc) - val sourceFilePath = byteCodeRepository.compilingClasses.get.get(lambdaBodyHandle.getOwner).map(_._2) + val sourceFilePath = byteCodeRepository.compilingClasses.get(lambdaBodyHandle.getOwner).map(_._2) val callee = bodyMethod.flatMap({ case (bodyMethodNode, bodyMethodDeclClass) => bTypesFromClassfile.classBTypeFromParsedClassfile(bodyMethodDeclClass).flatMap(bodyDeclClassType => diff --git a/compiler/src/dotty/tools/backend/jvm/opt/CopyProp.scala b/compiler/src/dotty/tools/backend/jvm/opt/CopyProp.scala index 8993e6232024..05d43b7e1ffc 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/CopyProp.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/CopyProp.scala @@ -27,7 +27,10 @@ import dotty.tools.backend.jvm.BackendUtils.* import scala.tools.asm -class CopyProp(backendUtils: BackendUtils, callGraph: CallGraph, inliner: Inliner, ts: CoreBTypes, optAllowSkipClassLoading: Boolean) { +class CopyProp(backendUtils: BackendUtils, callGraph: CallGraph, inliner: Inliner, ts: WellKnownBTypes, settings: OptimizerSettings) { + + private val modulesAllowSkipInitialization = + if settings.optAllowSkipCoreModuleInit then backendUtils.modulesAllowSkipInitialization else Set.empty /** * For every `xLOAD n`, find all local variable slots that are aliases of `n` using an @@ -473,7 +476,7 @@ class CopyProp(backendUtils: BackendUtils, callGraph: CallGraph, inliner: Inline handleInputs(prod, 1) case GETFIELD | GETSTATIC => - if (backendUtils.isBoxedUnit(prod) || BackendUtils.isJavaLangStaticLoad(prod) || BackendUtils.isModuleLoad(prod, backendUtils.modulesAllowSkipInitialization)) toRemove += prod + if (backendUtils.isBoxedUnit(prod) || BackendUtils.isJavaLangStaticLoad(prod) || BackendUtils.isModuleLoad(prod, modulesAllowSkipInitialization)) toRemove += prod else popAfterProd() // keep potential class initialization (static field) or NPE (instance field) case INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC | INVOKEINTERFACE => @@ -521,7 +524,7 @@ class CopyProp(backendUtils: BackendUtils, callGraph: CallGraph, inliner: Inline toRemove += prod case _ => - if (optAllowSkipClassLoading) toRemove += prod + if (settings.optAllowSkipClassLoading) toRemove += prod else popAfterProd() } diff --git a/compiler/src/dotty/tools/backend/jvm/opt/Inliner.scala b/compiler/src/dotty/tools/backend/jvm/opt/Inliner.scala index eafc532a51be..f6f8de4c6e4d 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/Inliner.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/Inliner.scala @@ -30,8 +30,9 @@ import dotty.tools.backend.jvm.BackendUtils.LambdaMetaFactoryCall import BCodeUtils.* class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, - callGraph: CallGraph, coreBTypes: CoreBTypes, bTypesFromClassfile: BTypesFromClassfile, byteCodeRepository: BCodeRepository, - heuristics: InlinerHeuristics, closureOptimizer: ClosureOptimizer) { + callGraph: CallGraph, bTypeLoader: BTypeLoader, bTypesFromClassfile: BTypesFromClassfile, byteCodeRepository: BCodeRepository, + heuristics: InlinerHeuristics, closureOptimizer: ClosureOptimizer, + settings: OptimizerSettings) { // True if all instructions (they would cause an IllegalAccessError otherwise) can potentially be // inlined in a later inlining round. @@ -63,7 +64,7 @@ class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, } def runInlinerAndClosureOptimizer(): Unit = { - val runClosureOptimizer = ppa.compilerSettings.optClosureInvocations + val runClosureOptimizer = settings.optClosureInvocations var round = 0 var changedByClosureOptimizer = mutable.LinkedHashSet.empty[MethodNode] @@ -139,7 +140,7 @@ class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, // rolled back. This avoids cloning the illegal instructions in case `m` itself gets inlined. if (requests.nonEmpty && !changedMethodHasIllegalAccess) { val (method, rs) = requests.dequeue() - val state = inlinerState.getOrElseUpdate(method, new MethodInlinerState(ppa.compilerSettings.optLogInline)) + val state = inlinerState.getOrElseUpdate(method, new MethodInlinerState(settings.optLogInline)) var changed = false def doInline(r: InlineRequest, aliasFrame: AliasingFrame[Value], w: Option[IllegalAccessInstructions]): Map[AbstractInsnNode, AbstractInsnNode] = { @@ -206,14 +207,14 @@ class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, state.rootInlinedCallsiteWithWarning(r.callsite.callsiteInstruction, returnForwarderIfNoOther = false) match { case Some(inlinedCallsite) => val rw = inlinedCallsite.warning.get - if (rw.emitWarning(ppa.compilerSettings)) { + if (rw.emitWarning(settings)) { ppa.optimizerWarning( em"${rw.toString + inlineChainSuffix(r.callsite, state.inlineChain(inlinedCallsite.eliminatedCallsite.callsiteInstruction, skipForwarders = true))}", BackendUtils.siteString(inlinedCallsite.eliminatedCallsite.callsiteClass.internalName, inlinedCallsite.eliminatedCallsite.callsiteMethod.name), inlinedCallsite.eliminatedCallsite.callsitePosition) } case _ => - if (w.emitWarning(ppa.compilerSettings)) + if (w.emitWarning(settings)) ppa.optimizerWarning( em"${w.toString + inlineChainSuffix(r.callsite, state.inlineChain(r.callsite.callsiteInstruction, skipForwarders = true))}", BackendUtils.siteString(r.callsite.callsiteClass.internalName, r.callsite.callsiteMethod.name), @@ -236,7 +237,7 @@ class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, // look at all callsites in a methods again, also those that were previously not selected for // inlining. after inlining, types might get more precise and make a callsite inlineable. val method = changedMethods.dequeue() - val state = inlinerState.getOrElseUpdate(method, new MethodInlinerState(ppa.compilerSettings.optLogInline)) + val state = inlinerState.getOrElseUpdate(method, new MethodInlinerState(settings.optLogInline)) def isLoop(call: MethodInsnNode, callee: Callee): Boolean = callee.callee == method || { @@ -267,7 +268,7 @@ class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, val callsite = inlinedCallsite.eliminatedCallsite val w = inlinedCallsite.warning.get state.inlineLog.logRollback(callsite, s"Instruction ${LogUtils.textify(notInlinedIllegalInsn)} would cause an IllegalAccessError, and is not selected for (or failed) inlining", state.outerCallsite(notInlinedIllegalInsn)) - if (w.emitWarning(ppa.compilerSettings)) + if (w.emitWarning(settings)) ppa.optimizerWarning( em"${w.toString + inlineChainSuffix(callsite, state.inlineChain(callsite.callsiteInstruction, skipForwarders = true))}", BackendUtils.siteString(callsite.callsiteClass.internalName, callsite.callsiteMethod.name), @@ -422,7 +423,7 @@ class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, // New labels for the cloned instructions val labelsMap = cloneLabels(callee) val sameSourceFile = sourceFilePath match { - case Some(calleeSource) => byteCodeRepository.compilingClasses.get.get(callsite.callsiteClass.internalName) match { + case Some(calleeSource) => byteCodeRepository.compilingClasses.get(callsite.callsiteClass.internalName) match { case Some((_, `calleeSource`)) => true case _ => false } @@ -794,7 +795,7 @@ class Inliner(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, private val isInternalCache = mutable.Map.empty[String, Either[OptimizerWarning, Boolean]] private def isInternal(name: String): Either[OptimizerWarning, Boolean] = { isInternalCache.getOrElseUpdate(name, - coreBTypes.classBTypeFromInternalName(name) match + bTypeLoader.previouslyConstructedClassBType(name) match case Some(ct) => Right(!ct.info.inlineInfo.isAccessible) case None => bTypesFromClassfile.classBTypeFromParsedClassfile(name) match case Left(l) => Left(l) diff --git a/compiler/src/dotty/tools/backend/jvm/opt/InlinerHeuristics.scala b/compiler/src/dotty/tools/backend/jvm/opt/InlinerHeuristics.scala index 3c9ebbdcf99b..fc17c8054d96 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/InlinerHeuristics.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/InlinerHeuristics.scala @@ -28,13 +28,15 @@ import PostProcessorFrontendAccess.Lazy import dotty.tools.backend.jvm.BCodeUtils.{isStrictfpMethod, isSynchronizedMethod} import dotty.tools.dotc.report -class InlinerHeuristics(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, byteCodeRepository: BCodeRepository, callGraph: CallGraph, ts: CoreBTypes) { +class InlinerHeuristics(ppa: PostProcessorFrontendAccess, backendUtils: BackendUtils, byteCodeRepository: BCodeRepository, + callGraph: CallGraph, ts: WellKnownBTypes, + settings: OptimizerSettings) { - private lazy val inlineSourceMatcher: Lazy[InlineSourceMatcher] = ppa.perRunLazy(new InlineSourceMatcher(ppa.compilerSettings.optInlineFrom)) + private lazy val inlineSourceMatcher: InlineSourceMatcher = new InlineSourceMatcher(settings.optInlineFrom) - def canInlineFromSource(sourceFilePath: Option[String], calleeDeclarationClass: InternalName): Boolean = { - inlineSourceMatcher.get.allowFromSources && sourceFilePath.isDefined || - inlineSourceMatcher.get.allow(calleeDeclarationClass) + private def canInlineFromSource(sourceFilePath: Option[String], calleeDeclarationClass: InternalName): Boolean = { + inlineSourceMatcher.allowFromSources && sourceFilePath.isDefined || + inlineSourceMatcher.allow(calleeDeclarationClass) } /** @@ -47,7 +49,7 @@ class InlinerHeuristics(ppa: PostProcessorFrontendAccess, backendUtils: BackendU // classpath. In order to get only the callsites being compiled, we start at the map of // compilingClasses in the byteCodeRepository. val compilingMethods = for { - ((classNode, _), _) <- byteCodeRepository.compilingClasses.get.valuesIterator + ((classNode, _), _) <- byteCodeRepository.compilingClasses.valuesIterator methodNode <- classNode.methods.iterator.asScala } yield methodNode @@ -59,18 +61,18 @@ class InlinerHeuristics(ppa: PostProcessorFrontendAccess, backendUtils: BackendU case Some(Right(req)) => requests += req case Some(Left(w)) => - if (w.emitWarning(ppa.compilerSettings)) { + if (w.emitWarning(settings)) { ppa.optimizerWarning(em"${w.toString}", BackendUtils.siteString(callsite.callsiteClass.internalName, callsite.callsiteMethod.name), callsite.callsitePosition) } case None => - if (callsiteWarning.exists(_.emitWarning(ppa.compilerSettings))) { + if (callsiteWarning.exists(_.emitWarning(settings))) { ppa.optimizerWarning(em"there was a problem determining if method ${callee.name} can be inlined: \n${callsiteWarning.get.toString}", BackendUtils.siteString(callsite.callsiteClass.internalName, callsite.callsiteMethod.name), pos) } } case callsite @ UnknownCallsite(ins, meth, clas, pos, _, warning) => - if (warning.emitWarning(ppa.compilerSettings)) { + if (warning.emitWarning(settings)) { ppa.optimizerWarning(em"failed to determine if ${ins.name} should be inlined:\n${warning.toString}", BackendUtils.siteString(clas.internalName, meth.name), pos) } } @@ -174,7 +176,7 @@ class InlinerHeuristics(ppa: PostProcessorFrontendAccess, backendUtils: BackendU case Some(w) => Some(Left(w)) case None => - Some(Right(InlineRequest(callsite, reason, ppa.compilerSettings.optLogInline.isEmpty, ppa.compilerSettings.optInlineHeuristics == "everything"))) + Some(Right(InlineRequest(callsite, reason, settings.optLogInline.isEmpty, settings.optInlineHeuristics == "everything"))) } } @@ -188,7 +190,7 @@ class InlinerHeuristics(ppa: PostProcessorFrontendAccess, backendUtils: BackendU if (isGeneratedForwarder) None else { val callee = callsite.callee - ppa.compilerSettings.optInlineHeuristics match { + settings.optInlineHeuristics match { case "everything" => requestIfCanInline(callsite, AnnotatedInline) diff --git a/compiler/src/dotty/tools/backend/jvm/opt/LocalOpt.scala b/compiler/src/dotty/tools/backend/jvm/opt/LocalOpt.scala index a0b93ffb1d91..63df69271d1e 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/LocalOpt.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/LocalOpt.scala @@ -150,12 +150,14 @@ import dotty.tools.backend.jvm.BackendUtils.isArrayGetLength * Note on updating the call graph: whenever an optimization eliminates a callsite or a closure * instantiation, we eliminate the corresponding entry from the call graph. */ -class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, callGraph: CallGraph, inliner: Inliner, ts: CoreBTypes, bTypesFromClassfile: BTypesFromClassfile) { +class LocalOpt(backendUtils: BackendUtils, callGraph: CallGraph, inliner: Inliner, + ts: WellKnownBTypes, bTypesFromClassfile: BTypesFromClassfile, + settings: OptimizerSettings) { import LocalOptImpls.* private val boxUnbox = new BoxUnbox(backendUtils, callGraph, ts) - private val copyProp = new CopyProp(backendUtils, callGraph, inliner, ts, ppa.compilerSettings.optAllowSkipClassLoading) + private val copyProp = new CopyProp(backendUtils, callGraph, inliner, ts, settings) /** * Remove unreachable instructions from all (non-abstract) methods and apply various other @@ -205,7 +207,7 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal // for local variables in dead blocks. Maybe that's a bug in the ASM framework. var currentTrace: String | Null = null - val doTrace = ppa.compilerSettings.optTrace match { + val doTrace = settings.optTrace match { case Some(v: String) => val prefix = if (v == "_") "" else v s"$ownerClassName.${method.name}".startsWith(prefix) @@ -242,46 +244,46 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal traceIfChanged("beforeMethodOpt") // NULLNESS OPTIMIZATIONS - val runNullness = ppa.compilerSettings.optNullnessTracking && requestNullness + val runNullness = settings.optNullnessTracking && requestNullness val nullnessOptChanged = runNullness && nullnessOptimizations(method, ownerClassName) traceIfChanged("nullness") // UNREACHABLE CODE // Both AliasingAnalyzer (used in copyProp) and ProdConsAnalyzer (used in eliminateStaleStores, // boxUnboxElimination) require not having unreachable instructions (null frames). - val runDCE = (ppa.compilerSettings.optUnreachableCode && (requestDCE || nullnessOptChanged)) || - ppa.compilerSettings.optBoxUnbox || - ppa.compilerSettings.optCopyPropagation + val runDCE = (settings.optUnreachableCode && (requestDCE || nullnessOptChanged)) || + settings.optBoxUnbox || + settings.optCopyPropagation val codeRemoved = if (runDCE) LocalOptImpls.removeUnreachableCodeImpl(method, ownerClassName, callGraph, backendUtils) else false traceIfChanged("dce") // BOX-UNBOX - val runBoxUnbox = ppa.compilerSettings.optBoxUnbox && (requestBoxUnbox || nullnessOptChanged) + val runBoxUnbox = settings.optBoxUnbox && (requestBoxUnbox || nullnessOptChanged) val boxUnboxChanged = runBoxUnbox && boxUnbox.boxUnboxElimination(method, ownerClassName) traceIfChanged("boxUnbox") // COPY PROPAGATION - val runCopyProp = ppa.compilerSettings.optCopyPropagation && (requestCopyProp || boxUnboxChanged) + val runCopyProp = settings.optCopyPropagation && (requestCopyProp || boxUnboxChanged) val copyPropChanged = runCopyProp && copyProp.copyPropagation(method, ownerClassName) traceIfChanged("copyProp") // STALE STORES - val runStaleStores = ppa.compilerSettings.optCopyPropagation && (requestStaleStores || nullnessOptChanged || codeRemoved || boxUnboxChanged || copyPropChanged) + val runStaleStores = settings.optCopyPropagation && (requestStaleStores || nullnessOptChanged || codeRemoved || boxUnboxChanged || copyPropChanged) val (storesRemoved, intrinsicRewrittenByStaleStores, callInlinedByStaleStores) = if (!runStaleStores) (false, false, false) else copyProp.eliminateStaleStoresAndRewriteSomeIntrinsics(method, ownerClassName) traceIfChanged("staleStores") // REDUNDANT CASTS - val runRedundantCasts = ppa.compilerSettings.optRedundantCasts && (requestRedundantCasts || boxUnboxChanged || intrinsicRewrittenByStaleStores || callInlinedByStaleStores) + val runRedundantCasts = settings.optRedundantCasts && (requestRedundantCasts || boxUnboxChanged || intrinsicRewrittenByStaleStores || callInlinedByStaleStores) val (typeInsnChanged, intrinsicRewrittenByCasts) = if (!runRedundantCasts) (false, false) else eliminateRedundantCastsAndRewriteSomeIntrinsics(method, ownerClassName) traceIfChanged("redundantCasts") // PUSH-POP - val runPushPop = ppa.compilerSettings.optCopyPropagation && (requestPushPop || storesRemoved || typeInsnChanged) + val runPushPop = settings.optCopyPropagation && (requestPushPop || storesRemoved || typeInsnChanged) val (pushPopRemoved, pushPopCastAdded, pushPopNullCheckAdded) = if (!runPushPop) (false, false, false) else copyProp.eliminatePushPop(method, ownerClassName) traceIfChanged("pushPop") // STORE-LOAD PAIRS - val runStoreLoad = ppa.compilerSettings.optCopyPropagation && (requestStoreLoad || boxUnboxChanged || copyPropChanged || pushPopRemoved) + val runStoreLoad = settings.optCopyPropagation && (requestStoreLoad || boxUnboxChanged || copyPropChanged || pushPopRemoved) val storeLoadRemoved = runStoreLoad && copyProp.eliminateStoreLoad(method) traceIfChanged("storeLoadPairs") @@ -291,7 +293,7 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal // SIMPLIFY JUMPS // almost all of the above optimizations enable simplifying more jumps, so we just run it in every iteration - val runSimplifyJumps = ppa.compilerSettings.optSimplifyJumps + val runSimplifyJumps = settings.optSimplifyJumps val jumpsChanged = runSimplifyJumps && simplifyJumps(method) traceIfChanged("simplifyJumps") @@ -341,11 +343,11 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal requestPushPop = true, requestStoreLoad = true) - if (ppa.compilerSettings.optUnreachableCode) BackendUtils.setDceDone(method) + if (settings.optUnreachableCode) BackendUtils.setDceDone(method) // (*) Removing stale local variable descriptors is required for correctness, see comment in `methodOptimizations` val localsRemoved = - if (ppa.compilerSettings.optCompactLocals) compactLocalVariables(method) // also removes unused + if (settings.optCompactLocals) compactLocalVariables(method) // also removes unused else if (requireEliminateUnusedLocals) removeUnusedLocalVariableNodes(method)() // (*) else false traceIfChanged("localVariables") @@ -381,7 +383,7 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal */ private def nullnessOptimizations(method: MethodNode, ownerClassName: InternalName): Boolean = { Limits.sizeOKForNullness(method) && { - lazy val nullnessAnalyzer = new NullnessAnalyzer(method, ownerClassName, backendUtils.isNonNullMethodInvocation, ppa.compilerSettings.optAssumeModulesNonNull) + lazy val nullnessAnalyzer = new NullnessAnalyzer(method, ownerClassName, backendUtils.isNonNullMethodInvocation, settings.optAssumeModulesNonNull) // When running nullness optimizations the method may still have unreachable code. Analyzer // frames of unreachable instructions are `null`. @@ -538,7 +540,7 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal a.length - 2 == b.length && a(0) == 'L' && a.last == ';' && a.regionMatches(1, b, 0, b.length) || b.length - 2 == a.length && b(0) == 'L' && b.last == ';' && b.regionMatches(1, a, 0, a.length) } - sameClass(aDescOrIntN, bDescOrIntN) || sameClass(bDescOrIntN, ts.ObjectRef.internalName) || { + sameClass(aDescOrIntN, bDescOrIntN) || sameClass(bDescOrIntN, "java/lang/Object") || { val aType = bTypesFromClassfile.bTypeForDescriptorOrInternalNameFromClassfile(aDescOrIntN) val bType = bTypesFromClassfile.bTypeForDescriptorOrInternalNameFromClassfile(bDescOrIntN) // TODO instead of getOrElse, we should bubble the warning up... @@ -572,7 +574,7 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal } lazy val typeAnalyzer = new NonLubbingTypeFlowAnalyzer(method, owner) - lazy val nullnessAnalyzer = new NullnessAnalyzer(method, owner, backendUtils.isNonNullMethodInvocation, ppa.compilerSettings.optAssumeModulesNonNull) + lazy val nullnessAnalyzer = new NullnessAnalyzer(method, owner, backendUtils.isNonNullMethodInvocation, settings.optAssumeModulesNonNull) // cannot remove instructions while iterating, it gets the analysis out of synch (indexed by instructions) val toReplace = mutable.Map.empty[AbstractInsnNode, List[AbstractInsnNode]] @@ -593,16 +595,17 @@ class LocalOpt(backendUtils: BackendUtils, ppa: PostProcessorFrontendAccess, cal val frame = typeAnalyzer.frameAt(ti) frame.getValue(frame.stackTop) }) - if (isSubType(valueDesc, ti.desc)) { - if (opc == CHECKCAST) { - toReplace(ti) = Nil - } else if (valueNullness == NotNullValue) { - toReplace(ti) = List(getPop(1), new InsnNode(ICONST_1)) + if valueDesc != "Lnull;" then + if (isSubType(valueDesc, ti.desc)) { + if (opc == CHECKCAST) { + toReplace(ti) = Nil + } else if (valueNullness == NotNullValue) { + toReplace(ti) = List(getPop(1), new InsnNode(ICONST_1)) + } + } else if (opc == INSTANCEOF && isUnrelated(valueDesc, ti.desc)) { + // the two types are unrelated, so the instance check is known to fail + toReplace(ti) = List(getPop(1), new InsnNode(ICONST_0)) } - } else if (opc == INSTANCEOF && isUnrelated(valueDesc, ti.desc)) { - // the two types are unrelated, so the instance check is known to fail - toReplace(ti) = List(getPop(1), new InsnNode(ICONST_0)) - } } } diff --git a/compiler/src/dotty/tools/backend/jvm/opt/OptimizerSettings.scala b/compiler/src/dotty/tools/backend/jvm/opt/OptimizerSettings.scala new file mode 100644 index 000000000000..593fd1f014c5 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/opt/OptimizerSettings.scala @@ -0,0 +1,34 @@ +package dotty.tools.backend.jvm.opt + +import dotty.tools.dotc.core.Contexts.Context + +import scala.annotation.constructorOnly + +/** + * Encapsulates settings so that the optimizer can use them without directly depending on a Context, + * since the context outside of settings is single-threaded, + * and it would become tempting to use it for something else. + */ +class OptimizerSettings(using @constructorOnly ctx: Context): + val optUnreachableCode: Boolean = ctx.settings.optUnreachableCode + val optNullnessTracking: Boolean = ctx.settings.optNullnessTracking + val optBoxUnbox: Boolean = ctx.settings.optBoxUnbox + val optCopyPropagation: Boolean = ctx.settings.optCopyPropagation + val optRedundantCasts: Boolean = ctx.settings.optRedundantCasts + val optSimplifyJumps: Boolean = ctx.settings.optSimplifyJumps + val optCompactLocals: Boolean = ctx.settings.optCompactLocals + val optClosureInvocations: Boolean = ctx.settings.optClosureInvocations + val optAllowSkipCoreModuleInit: Boolean = ctx.settings.optAllowSkipCoreModuleInit + val optAssumeModulesNonNull: Boolean = ctx.settings.optAssumeModulesNonNull + val optAllowSkipClassLoading: Boolean = ctx.settings.optAllowSkipClassLoading + val optInlinerEnabled: Boolean = ctx.settings.optInline.value.nonEmpty + val optInlineFrom: List[String] = ctx.settings.optInline.value + val optInlineHeuristics: String = ctx.settings.YoptInlineHeuristics.value + val optWarningNoInlineMixed: Boolean = ctx.settings.optWarningNoInlineMixed + val optWarningNoInlineMissingBytecode: Boolean = ctx.settings.optWarningNoInlineMissingBytecode + val optWarningNoInlineMissingScalaInlineInfoAttr: Boolean = ctx.settings.optWarningNoInlineMissingScalaInlineInfoAttr + val optWarningEmitAtInlineFailed: Boolean = ctx.settings.optWarningEmitAtInlineFailed + val optWarningEmitAnyInlineFailed: Boolean = ctx.settings.optWarningEmitAnyInlineFailed + val optLogInline: Option[String] = ctx.settings.YoptLogInline.valueSetByUser + val optTrace: Option[String] = ctx.settings.YoptTrace.valueSetByUser + diff --git a/compiler/src/dotty/tools/backend/jvm/opt/OptimizerWarning.scala b/compiler/src/dotty/tools/backend/jvm/opt/OptimizerWarning.scala index a643098fed1e..259f1d874e50 100644 --- a/compiler/src/dotty/tools/backend/jvm/opt/OptimizerWarning.scala +++ b/compiler/src/dotty/tools/backend/jvm/opt/OptimizerWarning.scala @@ -2,14 +2,13 @@ package dotty.tools.backend.jvm.opt import dotty.tools.backend.jvm.BackendUtils import dotty.tools.backend.jvm.BTypes.InternalName -import dotty.tools.backend.jvm.PostProcessorFrontendAccess.CompilerSettings import dotty.tools.dotc.util.SourcePosition import scala.tools.asm.tree.AbstractInsnNode sealed trait OptimizerWarning { - def emitWarning(settings: CompilerSettings): Boolean + def emitWarning(settings: OptimizerSettings): Boolean } sealed trait MissingBytecodeWarning extends OptimizerWarning { @@ -29,7 +28,7 @@ sealed trait MissingBytecodeWarning extends OptimizerWarning { missingClass.map(c => s" Reason:\n$c").getOrElse("") } - def emitWarning(settings: CompilerSettings): Boolean = this match { + def emitWarning(settings: OptimizerSettings): Boolean = this match { case ClassNotFound(_) => settings.optWarningNoInlineMissingBytecode @@ -54,7 +53,7 @@ final case class NoClassBTypeInfo(cause: MissingBytecodeWarning) extends Optimiz override def toString: String = cause.toString - def emitWarning(settings: CompilerSettings): Boolean = + def emitWarning(settings: OptimizerSettings): Boolean = cause.emitWarning(settings) } @@ -82,7 +81,7 @@ sealed trait CalleeInfoWarning extends OptimizerWarning { s"Error while computing the inline information for method $warningMessageSignature:\n" + cause } - def emitWarning(settings: CompilerSettings): Boolean = this match { + def emitWarning(settings: OptimizerSettings): Boolean = this match { case MethodInlineInfoIncomplete(_, _, _, cause) => cause.emitWarning(settings) case MethodInlineInfoMissing(_, _, _, Some(cause)) => cause.emitWarning(settings) @@ -150,7 +149,7 @@ sealed trait CannotInlineWarning extends OptimizerWarning { warning + reason } - def emitWarning(settings: CompilerSettings): Boolean = { + def emitWarning(settings: OptimizerSettings): Boolean = { settings.optWarningEmitAnyInlineFailed || annotatedInline && settings.optWarningEmitAtInlineFailed } @@ -182,7 +181,7 @@ case class ResultingMethodTooLarge(calleeDeclarationClass: InternalName, name: S case object UnknownInvokeDynamicInstruction extends OptimizerWarning { override def toString = "The callee contains an InvokeDynamic instruction with an unknown bootstrap method (not a LambdaMetaFactory)." - def emitWarning(settings: CompilerSettings): Boolean = settings.optWarningEmitAnyInlineFailed + def emitWarning(settings: OptimizerSettings): Boolean = settings.optWarningEmitAnyInlineFailed } /** @@ -192,7 +191,7 @@ case object UnknownInvokeDynamicInstruction extends OptimizerWarning { sealed trait RewriteClosureApplyToClosureBodyFailed extends OptimizerWarning { def pos: SourcePosition - override def emitWarning(settings: CompilerSettings): Boolean = this match { + override def emitWarning(settings: OptimizerSettings): Boolean = this match { case RewriteClosureAccessCheckFailed(_, cause) => cause.emitWarning(settings) case RewriteClosureIllegalAccess(_, _) => settings.optWarningEmitAnyInlineFailed } @@ -224,7 +223,7 @@ sealed trait ClassInlineInfoWarning extends OptimizerWarning { s"Cannot read ScalaInlineInfo version $version in classfile $internalName. Use a more recent compiler." } - def emitWarning(settings: CompilerSettings): Boolean = this match { + def emitWarning(settings: OptimizerSettings): Boolean = this match { case NoInlineInfoAttribute(_) => settings.optWarningNoInlineMissingScalaInlineInfoAttr case ClassNotFoundWhenBuildingInlineInfoFromSymbol(cause) => cause.emitWarning(settings) case UnknownScalaInlineInfoVersion(_, _) => settings.optWarningNoInlineMissingScalaInlineInfoAttr diff --git a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala index d4f3ccaacb97..5ad5c0824b63 100644 --- a/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala +++ b/compiler/src/dotty/tools/dotc/config/ScalaSettings.scala @@ -383,6 +383,7 @@ private sealed trait OptimizerSettings: |such as `*`, `<`, `>`, and `$`: `'-opt-inline:p.*,!p.C$D' '-opt-inline:'`. |Quoting may not be needed in a build file.""".stripMargin val optInline: Setting[List[String]] = MultiStringSetting(RootSetting, "opt-inline", "filter", inlineHelp) + def optInlineEnabled(using Context): Boolean = optInline.value.nonEmpty val YoptInlineHeuristics = ChoiceSetting( ForkSetting, diff --git a/compiler/src/dotty/tools/dotc/config/Settings.scala b/compiler/src/dotty/tools/dotc/config/Settings.scala index 8965bc857c8c..108d29083a6c 100644 --- a/compiler/src/dotty/tools/dotc/config/Settings.scala +++ b/compiler/src/dotty/tools/dotc/config/Settings.scala @@ -343,6 +343,7 @@ object Settings: object Setting: extension [T](setting: Setting[T]) def value(using Context): T = setting.valueIn(ctx.settingsState) + def valueSetByUser(using Context): Option[T] = Option(setting.value).filter(_ != setting.default) def update(x: T)(using Context): SettingsState = setting.updateIn(ctx.settingsState, x) def isDefault(using Context): Boolean = setting.isDefaultIn(ctx.settingsState) diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 4d5ed74d2040..89df8cd90f0e 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -46,7 +46,7 @@ import scala.annotation.tailrec object Contexts { - private val (compilerCallbackLoc, store1) = Store.empty.newLocation[CompilerCallback]() + private val (compilerCallbackLoc, store1) = Store.empty.newLocation[CompilerCallback | Null]() private val (incCallbackLoc, store2) = store1.newLocation[IncrementalCallback | Null]() private val (printerFnLoc, store3) = store2.newLocation[Context => Printer](new RefinedPrinter(_)) private val (settingsStateLoc, store4) = store3.newLocation[SettingsState]() @@ -166,7 +166,7 @@ object Contexts { def store: Store /** The compiler callback implementation, or null if no callback will be called. */ - def compilerCallback: CompilerCallback = store(compilerCallbackLoc) + def compilerCallback: CompilerCallback | Null = store(compilerCallbackLoc) /** The Zinc callback implementation if we are run from Zinc, null otherwise */ def incCallback: IncrementalCallback | Null = store(incCallbackLoc) diff --git a/compiler/src/dotty/tools/dotc/core/Definitions.scala b/compiler/src/dotty/tools/dotc/core/Definitions.scala index 5692c41d45f8..9c783b567507 100644 --- a/compiler/src/dotty/tools/dotc/core/Definitions.scala +++ b/compiler/src/dotty/tools/dotc/core/Definitions.scala @@ -484,6 +484,15 @@ class Definitions { } def NullType: TypeRef = NullClass.typeRef + /* + * RuntimeNothingClass and RuntimeNullClass exist at run-time only. + * They are the run-time manifestation (in method signatures only) + * of what shows up as NothingClass (scala.Nothing) resp. NullClass (scala.Null) in Scala ASTs. + * Therefore, when NothingClass or NullClass are to be emitted, a mapping is needed. + */ + @tu lazy val RuntimeNothingClass: Symbol = requiredClass("scala.runtime.Nothing$") + @tu lazy val RuntimeNullClass: Symbol = requiredClass("scala.runtime.Null$") + @tu lazy val InvokerModule = requiredModule("scala.runtime.coverage.Invoker") @tu lazy val InvokedMethodRef = InvokerModule.requiredMethodRef("invoked") diff --git a/compiler/src/dotty/tools/dotc/core/Phases.scala b/compiler/src/dotty/tools/dotc/core/Phases.scala index 07a373605679..4a08351680ec 100644 --- a/compiler/src/dotty/tools/dotc/core/Phases.scala +++ b/compiler/src/dotty/tools/dotc/core/Phases.scala @@ -4,7 +4,6 @@ package core import Periods.* import Contexts.* -import dotty.tools.backend.jvm.GenBCode import DenotTransformers.* import Denotations.* import Decorators.* @@ -254,7 +253,6 @@ object Phases { private var myMixinPhase: Phase = uninitialized private var myCountOuterAccessesPhase: Phase = uninitialized private var myFlattenPhase: Phase = uninitialized - private var myGenBCodePhase: Phase = uninitialized private var myCheckCapturesPhase: Phase = uninitialized private var myCheckCapturesPhaseId: PhaseId = -2 @@ -287,7 +285,6 @@ object Phases { final def lambdaLiftPhase: Phase = myLambdaLiftPhase final def countOuterAccessesPhase = myCountOuterAccessesPhase final def flattenPhase: Phase = myFlattenPhase - final def genBCodePhase: Phase = myGenBCodePhase final def checkCapturesPhase: Phase = myCheckCapturesPhase final def checkCapturesPhaseId: PhaseId = myCheckCapturesPhaseId @@ -320,7 +317,6 @@ object Phases { myFlattenPhase = phaseOfClass(classOf[Flatten]) myExplicitOuterPhase = phaseOfClass(classOf[ExplicitOuter]) myGettersPhase = phaseOfClass(classOf[Getters]) - myGenBCodePhase = phaseOfClass(classOf[GenBCode]) myCheckCapturesPhase = phaseOfClass(classOf[CheckCaptures]) } @@ -580,7 +576,6 @@ object Phases { def mixinPhase(using Context): Phase = ctx.base.mixinPhase def lambdaLiftPhase(using Context): Phase = ctx.base.lambdaLiftPhase def flattenPhase(using Context): Phase = ctx.base.flattenPhase - def genBCodePhase(using Context): Phase = ctx.base.genBCodePhase def checkCapturesPhase(using Context): Phase = ctx.base.checkCapturesPhase def checkCapturesPhaseId(using Context): PhaseId = ctx.base.checkCapturesPhaseId diff --git a/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala b/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala index 8ee81c94f735..182e97180348 100644 --- a/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala +++ b/compiler/src/dotty/tools/dotc/profile/ChromeTrace.scala @@ -36,7 +36,7 @@ object ChromeTrace { } } -/** Allows writing a subset of captrue traces based on https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview# +/** Allows writing a subset of capture traces based on https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview# * Can be visualized using https://ui.perfetto.dev/, Chrome's about://tracing (outdated) or the tooling in https://www.google.com.au/search?q=catapult+tracing&oq=catapult+tracing+&aqs=chrome..69i57.3974j0j4&sourceid=chrome&ie=UTF-8 */ final class ChromeTrace(f: Path) extends Closeable { import ChromeTrace.EventType diff --git a/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala b/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala index d07a38c37485..8c48d15e32db 100644 --- a/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala +++ b/compiler/src/dotty/tools/dotc/profile/ThreadPoolFactory.scala @@ -24,7 +24,7 @@ sealed trait ThreadPoolFactory { object ThreadPoolFactory { - def apply(phase: Phase)(using Context): ThreadPoolFactory = ctx.profiler match { + def apply(phase: Phase, profiler: Profiler): ThreadPoolFactory = profiler match { case NoOpProfiler => new BasicThreadPoolFactory(phase) case r: RealProfiler => new ProfilingThreadPoolFactory(phase, r) } diff --git a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala index 90cad7a9d2ea..02af8476d5e5 100644 --- a/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala +++ b/compiler/src/dotty/tools/dotc/transform/GenericSignatures.scala @@ -195,7 +195,8 @@ object GenericSignatures { // likely to end up with Foo.Empty where it needs Foo.Empty$. def fullNameInSig(sym: Symbol): Unit = { assert(sym.isClass) - val name = atPhase(genBCodePhase) { sanitizeName(sym.fullName).replace('.', '/') } + // Time travel necessary so we get the full name after inner classes have been lifted to package scope + val name = atPhase(flattenPhase.next) { sanitizeName(sym.fullName).replace('.', '/') } builder.append('L').append(name) } diff --git a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala index 28051f4d410f..d98589d22386 100644 --- a/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala +++ b/compiler/src/dotty/tools/dotc/transform/TreeChecker.scala @@ -598,8 +598,7 @@ object TreeChecker { def isNonMagicalMember(x: Symbol) = !x.isValueClassConvertMethod && - !x.name.is(DocArtifactName) && - !(ctx.phase.id >= genBCodePhase.id && x.name == str.MODULE_INSTANCE_FIELD.toTermName) + !x.name.is(DocArtifactName) val decls = cls.classInfo.decls.toList.toSet.filter(isNonMagicalMember) val defined = impl.body.map(_.symbol) diff --git a/compiler/src/dotty/tools/io/AbstractFile.scala b/compiler/src/dotty/tools/io/AbstractFile.scala index cfd7ba7aded3..321efb9b34a9 100644 --- a/compiler/src/dotty/tools/io/AbstractFile.scala +++ b/compiler/src/dotty/tools/io/AbstractFile.scala @@ -81,7 +81,7 @@ object AbstractFile { * * ''Note: This library is considered experimental and should not be used unless you know what you are doing.'' */ -abstract class AbstractFile extends Iterable[AbstractFile] { +abstract class AbstractFile extends Iterable[AbstractFile] with dotty.tools.dotc.interfaces.AbstractFile { /** Returns the name of this abstract file. */ def name: String @@ -123,6 +123,10 @@ abstract class AbstractFile extends Iterable[AbstractFile] { case _: UnsupportedOperationException => null } + /** Adapts `file` to the `dotty.tools.dotc.interfaces.AbstractFile` interface */ + def jfile: java.util.Optional[JFile] = + java.util.Optional.ofNullable(file) + /** Returns the underlying Path if any and null otherwise. */ def jpath: JPath | Null diff --git a/compiler/src/dotty/tools/io/FileWriters.scala b/compiler/src/dotty/tools/io/FileWriters.scala index fcf63a366404..84a86aa233d3 100644 --- a/compiler/src/dotty/tools/io/FileWriters.scala +++ b/compiler/src/dotty/tools/io/FileWriters.scala @@ -11,10 +11,7 @@ import java.io.IOException import java.nio.ByteBuffer import java.nio.channels.ClosedByInterruptException import java.nio.channels.FileChannel -import java.nio.file.FileAlreadyExistsException -import java.nio.file.Files -import java.nio.file.Path -import java.nio.file.StandardOpenOption +import java.nio.file.{FileAlreadyExistsException, Files, Path, Paths, StandardOpenOption} import java.nio.file.attribute.FileAttribute import java.util import java.util.concurrent.ConcurrentHashMap @@ -23,12 +20,9 @@ import java.util.zip.Deflater import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream import scala.collection.mutable - -import dotty.tools.dotc.core.Contexts, Contexts.Context +import dotty.tools.dotc.core.Contexts.* import dotty.tools.dotc.core.Decorators.em - -import dotty.tools.dotc.util.{SourcePosition, NoSourcePosition} - +import dotty.tools.dotc.util.{NoSourcePosition, SourcePosition} import dotty.tools.dotc.reporting.Message import dotty.tools.dotc.report @@ -38,8 +32,8 @@ import java.util.concurrent.atomic.AtomicBoolean import java.util.ConcurrentModificationException object FileWriters { - type InternalName = String - type NullableFile = AbstractFile | Null + private def classRelativePath(className: String, suffix: String): String = + className.replace('.', '/') + suffix inline def ctx(using ReadOnlyContext): ReadOnlyContext = summon[ReadOnlyContext] @@ -157,7 +151,7 @@ object FileWriters { * The interface to writing classfiles. GeneratedClassHandler calls these methods to generate the * directory and files that are created, and eventually calls `close` when the writing is complete. * - * The companion object is responsible for constructing a appropriate and optimal implementation for + * The companion object is responsible for constructing an appropriate and optimal implementation for * the supplied settings. * * Operations are threadsafe. @@ -168,43 +162,97 @@ object FileWriters { * * @param name the internal name of the class, e.g. "scala.Option" */ - def writeTasty(name: InternalName, bytes: Array[Byte])(using ReadOnlyContext): NullableFile + def writeTasty(name: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile /** * Close the writer. Behavior is undefined after a call to `close`. */ def close(): Unit - - protected def classToRelativePath(className: InternalName): String = - className.replace('.', '/') + ".tasty" } object TastyWriter { - def apply(output: AbstractFile)(using ReadOnlyContext): TastyWriter = { - - // In Scala 2 depenening on cardinality of distinct output dirs MultiClassWriter could have been used + def apply(output: AbstractFile)(using ReadOnlyContext): TastyWriter = + // In Scala 2 depending on cardinality of distinct output dirs MultiClassWriter could have been used // In Dotty we always use single output directory - val basicTastyWriter = new SingleTastyWriter( - FileWriter(output, None) - ) + new SingleTastyWriter(FileWriter(output, None)) - basicTastyWriter + private final class SingleTastyWriter(underlying: FileWriter) extends TastyWriter { + + override def writeTasty(className: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = { + underlying.writeFile(classRelativePath(className, ".tasty"), bytes) + } + + override def close(): Unit = underlying.close() } - private final class SingleTastyWriter(underlying: FileWriter) extends TastyWriter { + } + + + /** + * The interface to writing classfiles. GeneratedClassHandler calls these methods to generate the + * directory and files that are created, and eventually calls `close` when the writing is complete. + * + * The companion object is responsible for constructing an appropriate and optimal implementation for + * the supplied settings. + * + * Operations are threadsafe. + */ + sealed trait ClassfileWriter extends TastyWriter { + /** + * Write a classfile + */ + def writeClass(name: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile - override def writeTasty(className: InternalName, bytes: Array[Byte])(using ReadOnlyContext): NullableFile = { - underlying.writeFile(classToRelativePath(className), bytes) + /** + * Close the writer. Behavior is undefined after a call to `close`. + */ + def close(): Unit + } + + object ClassfileWriter { + def apply(output: AbstractFile, jarManifestMainClass: Option[String], dumpClassesPath: Option[AbstractFile])(using ReadOnlyContext): ClassfileWriter = { + // In Scala 2 depending on cardinality of distinct output dirs MultiClassWriter could have been used + // In Dotty we always use single output directory + val basicClassWriter = new SingleClassWriter(FileWriter(output, jarManifestMainClass)) + dumpClassesPath match + case None => basicClassWriter + case Some(out) => new DebugClassWriter(basicClassWriter, FileWriter(out, None)) + } + + private final class SingleClassWriter(underlying: FileWriter) extends ClassfileWriter { + override def writeClass(className: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = { + underlying.writeFile(classRelativePath(className, ".class"), bytes) + } + + override def writeTasty(className: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = { + underlying.writeFile(classRelativePath(className, ".tasty"), bytes) } override def close(): Unit = underlying.close() } + private final class DebugClassWriter(basic: ClassfileWriter, dump: FileWriter) extends ClassfileWriter { + override def writeClass(className: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = { + val outFile = basic.writeClass(className, bytes) + dump.writeFile(classRelativePath(className, ".class"), bytes) + outFile + } + + override def writeTasty(className: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = { + basic.writeTasty(className, bytes) + } + + override def close(): Unit = { + basic.close() + dump.close() + } + } } + sealed trait FileWriter { - def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext): NullableFile + def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile def close(): Unit } @@ -248,7 +296,7 @@ object FileWriters { lazy val crc = new CRC32 - override def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext): NullableFile = this.synchronized { + override def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = this.synchronized { val entry = new ZipEntry(relativePath) if (storeOnly) { // When using compression method `STORED`, the ZIP spec requires the CRC and compressed/ @@ -265,7 +313,13 @@ object FileWriters { jarWriter.putNextEntry(entry) try jarWriter.write(bytes, 0, bytes.length) finally jarWriter.flush() - null + // important detail here, even on Windows, Zinc expects the separator within the jar + // to be the system default, (even if in the actual jar file the entry always uses '/'). + // see https://github.com/sbt/zinc/blob/dcddc1f9cfe542d738582c43f4840e17c053ce81/internal/compiler-bridge/src/main/scala/xsbt/JarUtils.scala#L47 + val pathInJar = + if java.io.File.separatorChar == '/' then relativePath + else relativePath.replace('/', java.io.File.separatorChar) + PlainFile.toPlainFile(Paths.get(s"${file.absolutePath}!$pathInJar")) } override def close(): Unit = this.synchronized(jarWriter.close()) @@ -305,15 +359,15 @@ object FileWriters { checkName(filePath.getFileName()) } - // the common case is that we are are creating a new file, and on MS Windows the create and truncate is expensive + // the common case is that we are creating a new file, and on MS Windows the create and truncate is expensive // because there is not an options in the windows API that corresponds to this so the truncate is applied as a separate call // even if the file is new. - // as this is rare, its best to always try to create a new file, and it that fails, then open with truncate if that fails + // as this is rare, it's best to always try to create a new file, and it that fails, then open with truncate if that fails private val fastOpenOptions = util.EnumSet.of(StandardOpenOption.CREATE_NEW, StandardOpenOption.WRITE) private val fallbackOpenOptions = util.EnumSet.of(StandardOpenOption.CREATE, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING) - override def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext): NullableFile = { + override def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = { val path = base.resolve(relativePath) try { ensureDirForPath(base, path) @@ -327,7 +381,7 @@ object FileWriters { try os.write(ByteBuffer.wrap(bytes), 0L) catch { case ex: ClosedByInterruptException => - try Files.deleteIfExists(path) // don't leave a empty of half-written classfile around after an interrupt + try Files.deleteIfExists(path) // don't leave an empty of half-written classfile around after an interrupt catch { case _: java.io.IOException => () } throw ex } @@ -339,7 +393,7 @@ object FileWriters { if (ctx.settings.debug) e.printStackTrace() ctx.reporter.error(em"error writing ${path.toString}: ${e.getClass.getName} ${e.getMessage}") } - AbstractFile.getFile(path) + AbstractFile.getFile(path).nn // we just wrote to it so it better still exist } override def close(): Unit = () @@ -363,7 +417,7 @@ object FileWriters { finally out.close() } - override def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext):NullableFile = { + override def writeFile(relativePath: String, bytes: Array[Byte])(using ReadOnlyContext): AbstractFile = { val outFile = getFile(base, relativePath) writeBytes(outFile, bytes) outFile