Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 27 additions & 4 deletions compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import dotty.tools.dotc.core.Phases.*
import dotty.tools.dotc.core.Decorators.em
import dotty.tools.dotc.report
import dotty.tools.dotc.ast.Trees.SyntheticUnit
import dotty.tools.dotc.transform.PostTyper.lastUseAttachment

/*
*
Expand Down Expand Up @@ -249,7 +250,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
genLoadTo(elsep, expectedType, dest)
else
lineNumber(tree.cond)
genAdaptAndSendToDest(UNIT, expectedType, dest)
genAdaptAndSendToDest(UNIT, expectedType, dest, lastUses.contains(tree.symbol))
expectedType
end if
}
Expand Down Expand Up @@ -393,6 +394,11 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
}
else {
mnode.visitVarInsn(asm.Opcodes.ALOAD, 0)

if tree.hasAttachment(lastUseAttachment) then
emit(asm.Opcodes.ACONST_NULL)
mnode.visitVarInsn(asm.Opcodes.ASTORE, 0)

// When compiling Array.scala, the constructor invokes `Array.this.super.<init>`. The expectedType
// is `[Object` (computed by typeToBType, the type of This(Array) is `Array[T]`). If we would set
// the generatedType to `Array` below, the call to adapt at the end would fail. The situation is
Expand Down Expand Up @@ -436,7 +442,12 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
case None =>
if (!sym.is(Package)) {
if (sym.is(Module)) genLoadModule(sym)
else locals.load(sym)
else
locals.load(sym)
if t.hasAttachment(lastUseAttachment) then
emit(asm.Opcodes.ACONST_NULL)
val idx = locals.getOrMakeLocal(sym).idx
bc.store(idx, tk)
}
case Some(t) =>
genLoad(t, generatedType)
Expand Down Expand Up @@ -487,10 +498,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {

// emit conversion and send to the right destination
if generatedDest == LoadDestination.FallThrough then
genAdaptAndSendToDest(generatedType, expectedType, dest)
genAdaptAndSendToDest(generatedType, expectedType, dest, lastUses.contains(tree.symbol))
end genLoadTo

def genAdaptAndSendToDest(generatedType: BType, expectedType: BType, dest: LoadDestination): Unit =
def genAdaptAndSendToDest(generatedType: BType, expectedType: BType, dest: LoadDestination, cleanSyntheticCopy: Boolean): Unit =
if generatedType != expectedType then
adapt(generatedType, expectedType)

Expand All @@ -507,6 +518,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
bc.store(loc.idx, expectedType)
bc dropMany stackDiff
bc.load(loc.idx, expectedType)
if cleanSyntheticCopy then
emit(asm.Opcodes.ACONST_NULL)
bc.store(loc.idx, expectedType)
end if
bc goTo label
case LoadDestination.Return =>
Expand Down Expand Up @@ -1717,6 +1731,13 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
val lNull = new asm.Label
val lNonNull = new asm.Label

def cleanSyntheticCopy =
if lastUses.contains(r.symbol) then
emit(asm.Opcodes.ACONST_NULL)
locals.store(eqEqTempLocal)



genLoad(l, ObjectRef)
stack.push(ObjectRef)
genLoad(r, ObjectRef)
Expand All @@ -1728,10 +1749,12 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
markProgramPoint(lNull)
bc drop ObjectRef
locals.load(eqEqTempLocal)
cleanSyntheticCopy
genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump = lNonNull)

markProgramPoint(lNonNull)
locals.load(eqEqTempLocal)
cleanSyntheticCopy
genCallMethod(defn.Any_equals, InvokeStyle.Virtual)
genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump)
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import dotty.tools.dotc.core.Types.*
import dotty.tools.dotc.core.Contexts.*
import dotty.tools.dotc.util.Spans.*
import dotty.tools.dotc.report
import dotty.tools.dotc.transform.PostTyper.methodLastUses


/*
Expand Down Expand Up @@ -432,6 +433,8 @@ trait BCodeSkelBuilder extends BCodeHelpers {
val stack = new BTypesStack
// line numbers
var lastEmittedLineNr = -1
// lastUse annotated variables
var lastUses = Set.empty[Symbol]

object bc extends JCodeMethodN {
override def jmethod = PlainSkelBuilder.this.mnode
Expand Down Expand Up @@ -648,6 +651,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
val rhs = dd.rhs
locals.reset(isStaticMethod = methSymbol.isStaticMember)
jumpDest = immutable.Map.empty
lastUses = dd.getAttachment(methodLastUses).getOrElse(Set.empty[Symbol])

// check previous invocation of genDefDef exited as many varsInScope as it entered.
assert(varsInScope == null, "Unbalanced entering/exiting of GenBCode's genBlock().")
Expand Down
3 changes: 2 additions & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class Compiler {
List(new InstrumentCoverage) :: // Perform instrumentation for code coverage (if -coverage-out is set)
List(new CrossVersionChecks, // Check issues related to deprecated and experimental
new FirstTransform, // Some transformations to put trees into a canonical form
new VerifyLastUseAnnotations, // Well actually it has its own tree traverser, so it does not make that much sense to be here
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: You should usually add new phases at the end of the mega phase list

new CheckReentrant, // Internal use only: Check that compiled program has no data races involving global vars
new ElimPackagePrefixes, // Eliminate references to package prefixes in Select nodes
new CookComments, // Cook the comments: expand variables, doc, etc.
Expand Down Expand Up @@ -143,7 +144,7 @@ class Compiler {
new sjs.JUnitBootstrappers, // Generate JUnit-specific bootstrapper classes for Scala.js (not enabled by default)
new CollectEntryPoints, // Collect all entry points and save them in the context
new CollectSuperCalls, // Find classes that are called with super
new RepeatableAnnotations) :: // Aggregate repeatable annotations
new RepeatableAnnotations) :: // Aggregate repeatable annotations
Nil

/** Generate the output of the compilation */
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,7 @@ class Definitions {
@tu lazy val PublicInBinaryAnnot: ClassSymbol = requiredClass("scala.annotation.publicInBinary")
@tu lazy val WitnessNamesAnnot: ClassSymbol = requiredClass("scala.annotation.internal.WitnessNames")
@tu lazy val StableNullAnnot: ClassSymbol = requiredClass("scala.annotation.stableNull")
@tu lazy val LastUseAnnot: ClassSymbol = requiredClass("scala.annotation.lastUse")

@tu lazy val JavaRepeatableAnnot: ClassSymbol = requiredClass("java.lang.annotation.Repeatable")

Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/LambdaLift.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import util.Store
import collection.mutable.{HashMap, LinkedHashMap, ListBuffer}

import scala.compiletime.uninitialized
import dotty.tools.dotc.transform.PostTyper.lastUseAttachment
import dotty.tools.dotc.core.Flags

object LambdaLift:
import ast.tpd.*
Expand Down Expand Up @@ -284,7 +286,7 @@ class LambdaLift extends MiniPhase with IdentityDenotTransformer { thisPhase =>
val lft = lifter
if (prefix eq NoPrefix)
if (sym.enclosure != lft.currentEnclosure && !sym.isStatic)
(if (sym is Method) lft.memberRef(sym) else lft.proxyRef(sym)).withSpan(tree.span)
(if (sym is Method) lft.memberRef(sym) else lft.proxyRef(sym)).withSpan(tree.span)
else if (sym.owner.isClass) // sym was lifted out
ref(sym).withSpan(tree.span)
else
Expand Down
29 changes: 28 additions & 1 deletion compiler/src/dotty/tools/dotc/transform/PostTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,21 @@ import NameKinds.WildcardParamName
import cc.*
import dotty.tools.dotc.transform.MacroAnnotations.hasMacroAnnotation
import dotty.tools.dotc.core.NameKinds.DefaultGetterName
import dotty.tools.dotc.util.Property.{Key, StickyKey}
import dotty.tools.dotc.transform.PostTyper.enclosingMethod
import dotty.tools.dotc.transform.PostTyper.methodLastUses

object PostTyper {
val name: String = "posttyper"
val description: String = "additional checks and cleanups after type checking"

val lastUseAttachment = StickyKey[Unit]

val enclosingMethod = Key[DefDef[?]]

/** Attachment on a Method that stores local variables annotated with @lastUse */
val methodLastUses = StickyKey[Set[Symbol]]

}

/** A macro transform that runs immediately after typer and that performs the following functions:
Expand Down Expand Up @@ -56,6 +67,8 @@ object PostTyper {
* (11) Minimizes `call` fields of `Inlined` nodes to just point to the toplevel
* class from which code was inlined.
*
* (12) Replaces @lastUse annotation with an attachment, so it survives the erasure phase.
*
* The reason for making this a macro transform is that some functions (in particular
* super and protected accessors and instantiation checks) are naturally top-down and
* don't lend themselves to the bottom-up approach of a mini phase. The other two functions
Expand Down Expand Up @@ -479,13 +492,14 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
val tree1 = cpy.ValDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt))
if tree1.removeAttachment(desugar.UntupledParam).isDefined then
checkStableSelection(tree.rhs)

processValOrDefDef(super.transform(tree1))
case tree: DefDef =>
registerIfHasMacroAnnotations(tree)
Checking.checkPolyFunctionType(tree.tpt)
annotateContextResults(tree)
val tree1 = cpy.DefDef(tree)(tpt = makeOverrideTypeDeclared(tree.symbol, tree.tpt))
processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1).asInstanceOf[DefDef]))
processValOrDefDef(superAcc.wrapDefDef(tree1)(super.transform(tree1)(using ctx.withProperty(enclosingMethod, Some(tree1))).asInstanceOf[DefDef]))//I dont think I need a stickykey since i will never copy the defdef itself when managing its components right ? AND I dont need it in the next phases
case tree: TypeDef =>
registerIfHasMacroAnnotations(tree)
val sym = tree.symbol
Expand Down Expand Up @@ -569,6 +583,19 @@ class PostTyper extends MacroTransform with InfoTransformer { thisPhase =>
case tree: TypeTree =>
val tpe = if tree.isInferred then CleanupRetains()(tree.tpe) else tree.tpe
tree.withType(transformAnnotsIn(tpe))
case Typed(t, tpt: TypeTree) if tpt.tpe.hasAnnotation(defn.LastUseAnnot) =>
t match
case _: Ident =>
t.putAttachment(PostTyper.lastUseAttachment, ())
val enclosing = ctx.property(enclosingMethod).get
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question: isn't there a way to get the enclosing method without adding a property to the context? There should be an enclosingMethod helper.

val others = enclosing.getAttachment(methodLastUses).getOrElse(Set.empty)
//attach the symbol to the method
enclosing.putAttachment(methodLastUses, others + t.symbol)
Typed(t, tpt)
case _ =>
report.error("`@lastUse` annotation can only be applied on local variables", tree.srcPos)
Typed(t, tpt)

case Typed(Ident(nme.WILDCARD), _) =>
withMode(Mode.Pattern)(super.transform(tree))
// The added mode signals that bounds in a pattern need not
Expand Down
Loading