|
| 1 | +package dotty.tools |
| 2 | +package dotc |
| 3 | +package transform |
| 4 | + |
| 5 | +import ast.{Trees, tpd} |
| 6 | +import core.* |
| 7 | +import Decorators.* |
| 8 | +import NameKinds.BoundaryName |
| 9 | +import MegaPhase._ |
| 10 | +import Types._, Contexts._, Flags._, DenotTransformers._ |
| 11 | +import Symbols._, StdNames._, Trees._ |
| 12 | +import util.Property |
| 13 | +import Flags.MethodOrLazy |
| 14 | + |
| 15 | +object DropBreaks: |
| 16 | + val name: String = "dropBreaks" |
| 17 | + val description: String = "replace local Break throws by labeled returns" |
| 18 | + |
| 19 | + /** Usage data and other info associated with a Label symbol. |
| 20 | + * @param goto the return-label to use for a labeled return. |
| 21 | + * @param enclMeth the enclosing method |
| 22 | + */ |
| 23 | + class LabelUsage(val goto: TermSymbol, val enclMeth: Symbol): |
| 24 | + /** The number of references to associated label that come from labeled returns */ |
| 25 | + var returnRefs: Int = 0 |
| 26 | + /** The number of other references to assocated label */ |
| 27 | + var otherRefs: Int = 0 |
| 28 | + |
| 29 | + private val LabelUsages = new Property.Key[Map[Symbol, LabelUsage]] |
| 30 | + private val LabelsShadowedByTry = new Property.Key[Set[Symbol]] |
| 31 | + |
| 32 | +/** Rewrites local Break throws to labeled returns. |
| 33 | + * Drops `try` statements on breaks if no other uses of its label remain. |
| 34 | + * A Break throw with a `Label` created by some enclosing boundary is replaced |
| 35 | + * with a labeled return if |
| 36 | + * |
| 37 | + * - the throw and the boundary are in the same method, and |
| 38 | + * - there is no try expression inside the boundary that encloses the throw. |
| 39 | + */ |
| 40 | +class DropBreaks extends MiniPhase: |
| 41 | + import DropBreaks.* |
| 42 | + |
| 43 | + import tpd._ |
| 44 | + |
| 45 | + override def phaseName: String = DropBreaks.name |
| 46 | + |
| 47 | + override def description: String = DropBreaks.description |
| 48 | + |
| 49 | + override def runsAfterGroupsOf: Set[String] = Set(ElimByName.name) |
| 50 | + // we want by-name parameters to be converted to closures |
| 51 | + |
| 52 | + private object LabelTry: |
| 53 | + |
| 54 | + object GuardedThrow: |
| 55 | + |
| 56 | + /** `(ex, local)` provided `expr` matches |
| 57 | + * |
| 58 | + * if ex.label.eq(local) then ex.value else throw ex |
| 59 | + */ |
| 60 | + def unapply(expr: Tree)(using Context): Option[(Symbol, Symbol)] = stripTyped(expr) match |
| 61 | + case If( |
| 62 | + Apply(Select(Select(ex: Ident, label), eq), (lbl @ Ident(local)) :: Nil), |
| 63 | + Select(ex2: Ident, value), |
| 64 | + Apply(throww, (ex3: Ident) :: Nil)) |
| 65 | + if label == nme.label && eq == nme.eq && local == nme.local && value == nme.value |
| 66 | + && throww.symbol == defn.throwMethod |
| 67 | + && ex.symbol == ex2.symbol && ex.symbol == ex3.symbol => |
| 68 | + Some((ex.symbol, lbl.symbol)) |
| 69 | + case _ => |
| 70 | + None |
| 71 | + end GuardedThrow |
| 72 | + |
| 73 | + /** `(local, body)` provided `tree` matches |
| 74 | + * |
| 75 | + * try body |
| 76 | + * catch case ex: Break => |
| 77 | + * if ex.label.eq(local) then ex.value else throw ex |
| 78 | + */ |
| 79 | + def unapply(tree: Tree)(using Context): Option[(Symbol, Tree)] = stripTyped(tree) match |
| 80 | + case Try(body, CaseDef(pat @ Bind(_, Typed(_, tpt)), EmptyTree, GuardedThrow(exc, local)) :: Nil, EmptyTree) |
| 81 | + if tpt.tpe.isRef(defn.BreakClass) && exc == pat.symbol => |
| 82 | + Some((local, body)) |
| 83 | + case _ => |
| 84 | + None |
| 85 | + end LabelTry |
| 86 | + |
| 87 | + private object BreakBoundary: |
| 88 | + |
| 89 | + /** `(local, body)` provided `tree` matches |
| 90 | + * |
| 91 | + * { val local: Label[...] = ...; <LabelTry(local, body)> } |
| 92 | + */ |
| 93 | + def unapply(tree: Tree)(using Context): Option[(Symbol, Tree)] = tree match |
| 94 | + case Block((vd @ ValDef(nme.local, _, _)) :: Nil, LabelTry(caughtAndRhs)) |
| 95 | + if vd.symbol.info.isRef(defn.LabelClass) && vd.symbol == caughtAndRhs._1 => |
| 96 | + Some(caughtAndRhs) |
| 97 | + case _ => |
| 98 | + None |
| 99 | + end BreakBoundary |
| 100 | + |
| 101 | + private object BreakThrow: |
| 102 | + |
| 103 | + /** `(local, arg)` provided `tree` matches inlined |
| 104 | + * |
| 105 | + * val Label_this: ... = local |
| 106 | + * throw new Break[...](Label_this, arg) |
| 107 | + */ |
| 108 | + def unapply(tree: Tree)(using Context): Option[(Symbol, Tree)] = tree match |
| 109 | + case Inlined(_, |
| 110 | + (vd @ ValDef(label_this1, _, id: Ident)):: Nil, |
| 111 | + Apply(throww, Apply(constr, Inlined(_, _, Ident(label_this2)) :: arg :: Nil) :: Nil)) |
| 112 | + if throww.symbol == defn.throwMethod |
| 113 | + && label_this1 == nme.Label_this && label_this2 == nme.Label_this |
| 114 | + && id.symbol.name == nme.local |
| 115 | + && constr.symbol.isClassConstructor && constr.symbol.owner == defn.BreakClass => |
| 116 | + Some((id.symbol, arg)) |
| 117 | + case _ => |
| 118 | + None |
| 119 | + end BreakThrow |
| 120 | + |
| 121 | + /** The LabelUsage data associated with `lbl` in the current context */ |
| 122 | + private def labelUsage(lbl: Symbol)(using Context): Option[LabelUsage] = |
| 123 | + for |
| 124 | + usesMap <- ctx.property(LabelUsages) |
| 125 | + uses <- usesMap.get(lbl) |
| 126 | + yield |
| 127 | + uses |
| 128 | + |
| 129 | + /** If `tree` is a BreakBoundary, associate a fresh `LabelUsage` with its label. */ |
| 130 | + override def prepareForBlock(tree: Block)(using Context): Context = tree match |
| 131 | + case BreakBoundary(label, _) => |
| 132 | + val mapSoFar = ctx.property(LabelUsages).getOrElse(Map.empty) |
| 133 | + val goto = newSymbol(ctx.owner, BoundaryName.fresh(), Synthetic | Label, tree.tpe) |
| 134 | + ctx.fresh.setProperty(LabelUsages, |
| 135 | + mapSoFar.updated(label, LabelUsage(goto, ctx.owner.enclosingMethod))) |
| 136 | + case _ => |
| 137 | + ctx |
| 138 | + |
| 139 | + /** If `tree` is not a LabeledTry, include all enclosing labels in the |
| 140 | + * `LabelsShadowedByTry` context property. This means that breaks to these |
| 141 | + * labels will not be translated to labeled returns in the body of the try. |
| 142 | + */ |
| 143 | + override def prepareForTry(tree: Try)(using Context): Context = tree match |
| 144 | + case LabelTry(_, _) => ctx |
| 145 | + case _ => ctx.property(LabelUsages) match |
| 146 | + case Some(usesMap) => |
| 147 | + val setSoFar = ctx.property(LabelsShadowedByTry).getOrElse(Set.empty) |
| 148 | + ctx.fresh.setProperty(LabelsShadowedByTry, setSoFar ++ usesMap.keysIterator) |
| 149 | + case _ => ctx |
| 150 | + |
| 151 | + /** If `tree` is a BreakBoundary, transform it as follows: |
| 152 | + * - Wrap it in a labeled block if its label has local uses |
| 153 | + * - Drop the try/catch if its label has no other uses |
| 154 | + */ |
| 155 | + override def transformBlock(tree: Block)(using Context): Tree = tree match |
| 156 | + case BreakBoundary(label, expr) => |
| 157 | + val uses = ctx.property(LabelUsages).get(label) |
| 158 | + val tree1 = |
| 159 | + if uses.otherRefs > 1 then |
| 160 | + // one non-local ref is always in the catch clause; this one does not count |
| 161 | + tree |
| 162 | + else |
| 163 | + expr |
| 164 | + report.log(i"trans boundary block $label // ${uses.returnRefs}, ${uses.otherRefs}") |
| 165 | + if uses.returnRefs > 0 then Labeled(uses.goto, tree1) else tree1 |
| 166 | + case _ => |
| 167 | + tree |
| 168 | + |
| 169 | + /** Rewrite a BreakThrow |
| 170 | + * |
| 171 | + * val Label_this: ... = local |
| 172 | + * throw new Break[...](Label_this, arg) |
| 173 | + * |
| 174 | + * where `local` is defined in the current method and is not included in |
| 175 | + * LabeldShowedByTry to |
| 176 | + * |
| 177 | + * return[target] arg |
| 178 | + * |
| 179 | + * where `target` is the `goto` return label associated with `local`. |
| 180 | + * Adjust associated ref counts accordingly. The local refcount is increased |
| 181 | + * and the non-local refcount is decreased, since `local` the `Label_this` |
| 182 | + * binding containing `local` is dropped. |
| 183 | + */ |
| 184 | + override def transformInlined(tree: Inlined)(using Context): Tree = tree match |
| 185 | + case BreakThrow(lbl, arg) => |
| 186 | + report.log(i"trans inlined $arg, ${arg.source}, ${ctx.outer.source}, ${tree.source}") |
| 187 | + labelUsage(lbl) match |
| 188 | + case Some(uses: LabelUsage) |
| 189 | + if uses.enclMeth == ctx.owner.enclosingMethod |
| 190 | + && !ctx.property(LabelsShadowedByTry).getOrElse(Set.empty).contains(lbl) |
| 191 | + => |
| 192 | + uses.otherRefs -= 1 |
| 193 | + uses.returnRefs += 1 |
| 194 | + cpy.Inlined(tree)(tree.call, Nil, |
| 195 | + inContext(ctx.withSource(tree.expansion.source)) { |
| 196 | + Return(arg, ref(uses.goto)).withSpan(arg.span) |
| 197 | + }) |
| 198 | + case _ => |
| 199 | + tree |
| 200 | + case _ => |
| 201 | + tree |
| 202 | + |
| 203 | + /** If `tree` refers to an enclosing label, increase its non local recount. |
| 204 | + * This increase is corrected in `transformInlined` if the reference turns |
| 205 | + * out to be part of a BreakThrow to a local, non-shadowed label. |
| 206 | + */ |
| 207 | + override def transformIdent(tree: Ident)(using Context): Tree = |
| 208 | + if tree.symbol.name == nme.local then |
| 209 | + for uses <- labelUsage(tree.symbol) do |
| 210 | + uses.otherRefs += 1 |
| 211 | + tree |
| 212 | + |
| 213 | +end DropBreaks |
0 commit comments