From fea5ccf6897fabe8e5e9b7ec1560c020a93564fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Wed, 31 Mar 2021 13:21:18 +0200 Subject: [PATCH 01/15] Fix derivedNames test on mac --- tests/generic-java-signatures/derivedNames.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/generic-java-signatures/derivedNames.scala b/tests/generic-java-signatures/derivedNames.scala index 0608bc54142b..f42c6ebab6a2 100644 --- a/tests/generic-java-signatures/derivedNames.scala +++ b/tests/generic-java-signatures/derivedNames.scala @@ -8,6 +8,8 @@ object Test { val out2 = "Test$Foo$A$B$>" // Linux and sometimes Windows if (scala.util.Properties.isWin) assert(returnType.toString == out1 || returnType.toString == out2) + else if (scala.util.Properties.isMac) + assert(returnType.toString == out1) else assert(returnType.toString == out2) } From d852757251d1c7367690242f4a0e5a254fe8575c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Wed, 3 Feb 2021 13:18:04 +0100 Subject: [PATCH 02/15] Generate SMAPs for files with inlined calls --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 3 +- .../tools/backend/jvm/InlinedPositioner.scala | 111 ++++++++++++++++++ .../src/dotty/tools/dotc/typer/Inliner.scala | 6 +- 3 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 43a926001456..1ef8297e4029 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -100,6 +100,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { def genPlainClass(cd0: TypeDef) = cd0 match { case TypeDef(_, impl: Template) => + assert(cnode == null, "GenBCode detected nested methods.") innerClassBufferASM.clear() @@ -277,7 +278,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - cnode.visitSource(cunit.source.file.name, null /* SourceDebugExtension */) + cnode.visitSource(cunit.source.file.name, InlinedsPositioner(cunit).debugExtension.orNull) } enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala new file mode 100644 index 000000000000..7d82ccdcf1be --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -0,0 +1,111 @@ +package dotty.tools +package backend +package jvm + +import dotc.CompilationUnit +import dotc.ast.tpd._ +import dotc.util.{ SourcePosition, SourceFile } +import dotc.core.Contexts._ +import dotc.typer.Inliner.InliningPosition +import collection.mutable + +import scala.collection.mutable.StringBuilder + +class InlinedsPositioner(cunit: CompilationUnit)(using Context): + private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) + + private class File(id: Int, name: String, path: Option[String]): + def write(b: mutable.StringBuilder): Unit = + if path.isDefined then b ++= "+ " + b append id + b += ' ' + b ++= name + b += '\n' + path.foreach { p => + b ++= p + b += '\n' + } + end File + + private class Mapping( + inputStartLine: Int, + fileId: Int, + repeatCount: Int, + outputStartLine: Int, + increment: Int + ): + extension (b: mutable.StringBuilder) def appendNotDefault(prefix: Char, value: Int): Unit = + if value != 1 then + b += prefix + b append value + + def write(b: mutable.StringBuilder): Unit = + b append (inputStartLine + 1) + b.appendNotDefault('#', fileId) + b.appendNotDefault(',', repeatCount) + b += ':' + b append (outputStartLine + 1) + b.appendNotDefault(',', increment) + b += '\n' + end Mapping + + private class Stratum(name: String, files: List[File], mappings: List[Mapping]): + def write(b: mutable.StringBuilder): Unit = + b ++= "*S " + b ++= name + b ++= "\n*F\n" + files.foreach(_.write(b)) + b ++= "*L\n" + mappings.foreach(_.write(b)) + b ++= "*E\n" + + private val requests = mutable.ListBuffer.empty[Request] + private val allocatedLines = mutable.Map.empty[SourcePosition, Int] + private var lastLine = cunit.tpdTree.sourcePos.endLine + + private def allocate(origPos: SourcePosition): Int = + val line = lastLine + 1 + lastLine += origPos.lines.length + allocatedLines += (origPos -> line) + line + + private class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: + override def traverse(tree: Tree)(using Context): Unit = + if tree.source != enclosingFile then + tree.getAttachment(InliningPosition) match + case Some(targetPos) => + val firstFakeLine = allocatedLines.applyOrElse(tree.sourcePos, allocate) + requests += Request(targetPos, tree.sourcePos, firstFakeLine) + RequestCollector(tree.source).traverseChildren(tree) + case None => + // Not exactly sure in which cases it is happening. Should we report warning? + RequestCollector(tree.source).traverseChildren(tree) + else traverseChildren(tree) + end RequestCollector + + RequestCollector(cunit.source).traverse(cunit.tpdTree) + + def debugExtension: Option[String] = Option.when(requests.nonEmpty) { + val scalaStratum = + val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source).toList + val mappings = requests.distinctBy(_.firstFakeLine).map { case Request(_, origPos, firstFakeLine) => + Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) + }.toList + Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) + + val debugStratum = + val mappings = requests.map { case Request(targetPos, origPos, firstFakeLine) => + Mapping(targetPos.startLine, 1, 1, firstFakeLine, origPos.lines.length) + }.toList + Stratum("ScalaDebug", File(1, cunit.source.name, None) :: Nil, mappings) + + + val b = new StringBuilder + b ++= "SMAP\n" + scalaStratum.write(b) + debugStratum.write(b) + b.toString + } + + + diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 408c8ad9de65..c314d5992418 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -30,12 +30,15 @@ import transform.{PostTyper, Inlining} import collection.mutable import reporting.trace import util.Spans.Span +import util.Property import dotty.tools.dotc.transform.{Splicer, TreeMapWithStages} import quoted.QuoteUtils object Inliner { import tpd._ + val InliningPosition = new Property.StickyKey[SourcePosition] + /** `sym` is an inline method with a known body to inline. */ def hasBodyToInline(sym: SymDenotation)(using Context): Boolean = @@ -226,8 +229,7 @@ object Inliner { val tree1 = if inlined.bindings.isEmpty then inlined.expansion else cpy.Block(inlined)(inlined.bindings, inlined.expansion) - // Reposition in the outer most inlined call - if (enclosingInlineds.nonEmpty) tree1 else reposition(tree1, inlined.span) + tree1.withAttachment(InliningPosition, inlined.sourcePos) def reposition(tree: Tree, callSpan: Span)(using Context): Tree = { // Reference test tests/run/i4947b From ae5d1b033771792b1bf29ccf52c1abf5ac32ae94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 11 Feb 2021 16:28:50 +0100 Subject: [PATCH 03/15] Include duplicates of inlined calls in SMAP --- .../src/dotty/tools/backend/jvm/InlinedPositioner.scala | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index 7d82ccdcf1be..3ad75a7f0a00 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -60,13 +60,11 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): b ++= "*E\n" private val requests = mutable.ListBuffer.empty[Request] - private val allocatedLines = mutable.Map.empty[SourcePosition, Int] private var lastLine = cunit.tpdTree.sourcePos.endLine private def allocate(origPos: SourcePosition): Int = val line = lastLine + 1 lastLine += origPos.lines.length - allocatedLines += (origPos -> line) line private class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: @@ -74,7 +72,7 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): if tree.source != enclosingFile then tree.getAttachment(InliningPosition) match case Some(targetPos) => - val firstFakeLine = allocatedLines.applyOrElse(tree.sourcePos, allocate) + val firstFakeLine = allocate(tree.sourcePos) requests += Request(targetPos, tree.sourcePos, firstFakeLine) RequestCollector(tree.source).traverseChildren(tree) case None => @@ -88,7 +86,7 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): def debugExtension: Option[String] = Option.when(requests.nonEmpty) { val scalaStratum = val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source).toList - val mappings = requests.distinctBy(_.firstFakeLine).map { case Request(_, origPos, firstFakeLine) => + val mappings = requests.map { case Request(_, origPos, firstFakeLine) => Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) }.toList Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) From 72d13d514c518a2f73fafd5a04e811de84d76d03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Thu, 11 Feb 2021 16:32:37 +0100 Subject: [PATCH 04/15] Add missing info to SMAP header --- compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index 3ad75a7f0a00..66ae3a2c5260 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -100,6 +100,9 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): val b = new StringBuilder b ++= "SMAP\n" + b ++= cunit.source.name + b += '\n' + b ++= "Scala\n" scalaStratum.write(b) debugStratum.write(b) b.toString From 8f117eee59743fa195549c04054ce8b515fb54ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 15 Feb 2021 13:03:33 +0100 Subject: [PATCH 05/15] Update information about lines for inlined content --- .../src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala | 8 ++++++-- .../src/dotty/tools/backend/jvm/InlinedPositioner.scala | 5 +++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 1ef8297e4029..34b515817524 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -80,6 +80,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { var isCZParcelable = false var isCZStaticModule = false + var inlinedsPositioner: InlinedsPositioner = null + /* ---------------- idiomatic way to ask questions to typer ---------------- */ def paramTKs(app: Apply, take: Int = -1): List[BType] = app match { @@ -278,7 +280,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - cnode.visitSource(cunit.source.file.name, InlinedsPositioner(cunit).debugExtension.orNull) + inlinedsPositioner = InlinedsPositioner(cunit) + cnode.visitSource(cunit.source.file.name, inlinedsPositioner.debugExtension.orNull) } enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { @@ -542,7 +545,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { } def lineNumber(tree: Tree): Unit = { if (!emitLines || !tree.span.exists) return; - val nr = ctx.source.offsetToLine(tree.span.point) + 1 + val nr = if (tree.source != cunit.source) inlinedsPositioner.lineFor(tree.sourcePos) else ctx.source.offsetToLine(tree.span.point) + 1 + if (nr != lastEmittedLineNr) { lastEmittedLineNr = nr lastInsn match { diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index 66ae3a2c5260..c8d0edd4cf07 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -108,5 +108,10 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): b.toString } + def lineFor(sourcePos: SourcePosition): Int = + val request = requests.find(_.origPos.contains(sourcePos)).get + val offset = sourcePos.startLine - request.origPos.startLine + request.firstFakeLine + offset + 1 + From a3115c4911cb78750287f445da168efb09e908c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 15 Feb 2021 14:22:53 +0100 Subject: [PATCH 06/15] Make InlinePositioner not crash on not found inline request --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 23 +++++++++---------- .../tools/backend/jvm/InlinedPositioner.scala | 13 +++++++---- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 34b515817524..e4f9ef5d7128 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -545,18 +545,17 @@ trait BCodeSkelBuilder extends BCodeHelpers { } def lineNumber(tree: Tree): Unit = { if (!emitLines || !tree.span.exists) return; - val nr = if (tree.source != cunit.source) inlinedsPositioner.lineFor(tree.sourcePos) else ctx.source.offsetToLine(tree.span.point) + 1 - - if (nr != lastEmittedLineNr) { - lastEmittedLineNr = nr - lastInsn match { - case lnn: asm.tree.LineNumberNode => - // overwrite previous landmark as no instructions have been emitted for it - lnn.line = nr - case _ => - mnode.visitLineNumber(nr, currProgramPoint()) - } - } + val nr = if (tree.source != cunit.source) inlinedsPositioner.lineFor(tree.sourcePos) else Some(ctx.source.offsetToLine(tree.span.point) + 1) + nr match + case Some(nr) if nr != lastEmittedLineNr => + lastEmittedLineNr = nr + lastInsn match + case lnn: asm.tree.LineNumberNode => + // overwrite previous landmark as no instructions have been emitted for it + lnn.line = nr + case _ => + mnode.visitLineNumber(nr, currProgramPoint()) + case _ => () } // on entering a method diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index c8d0edd4cf07..7733161b386c 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -6,6 +6,7 @@ import dotc.CompilationUnit import dotc.ast.tpd._ import dotc.util.{ SourcePosition, SourceFile } import dotc.core.Contexts._ +import dotc.report import dotc.typer.Inliner.InliningPosition import collection.mutable @@ -108,10 +109,14 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): b.toString } - def lineFor(sourcePos: SourcePosition): Int = - val request = requests.find(_.origPos.contains(sourcePos)).get - val offset = sourcePos.startLine - request.origPos.startLine - request.firstFakeLine + offset + 1 + def lineFor(sourcePos: SourcePosition): Option[Int] = + requests.find(_.origPos.contains(sourcePos)) match + case Some(request) => + val offset = sourcePos.startLine - request.origPos.startLine + Some(request.firstFakeLine + offset + 1) + case None => + report.inform(s"${sourcePos.show} was supposed to be inlined in ${cunit.source} but it cannot be found.") + None From 8834789aadbe506c8602e5f126b70e50a6a1ffde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 11:18:08 +0100 Subject: [PATCH 07/15] Solve not registering inlining position for tuples also removes code that was relocationg inlined calls but now is unreachable --- .../tools/backend/jvm/InlinedPositioner.scala | 7 ++- .../src/dotty/tools/dotc/typer/Inliner.scala | 51 ++----------------- 2 files changed, 6 insertions(+), 52 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala index 7733161b386c..332bf18b0ef6 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala @@ -68,6 +68,8 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): lastLine += origPos.lines.length line +// if (source.name contains "Tuple.sca") && startLine == 35 && endLine == 35 then + private class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = if tree.source != enclosingFile then @@ -115,8 +117,5 @@ class InlinedsPositioner(cunit: CompilationUnit)(using Context): val offset = sourcePos.startLine - request.origPos.startLine Some(request.firstFakeLine + offset + 1) case None => - report.inform(s"${sourcePos.show} was supposed to be inlined in ${cunit.source} but it cannot be found.") + report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") None - - - diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index c314d5992418..d3ad10dbde9e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -226,58 +226,13 @@ object Inliner { /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = + // In case that bindings are present we are adding SourcePosition attachement both to the resulting block and to the expansion + // as in some cases the block is removed in one of later phases and attachment is lost. val tree1 = if inlined.bindings.isEmpty then inlined.expansion - else cpy.Block(inlined)(inlined.bindings, inlined.expansion) + else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inlined.sourcePos)) tree1.withAttachment(InliningPosition, inlined.sourcePos) - def reposition(tree: Tree, callSpan: Span)(using Context): Tree = { - // Reference test tests/run/i4947b - - val curSource = ctx.compilationUnit.source - - // Tree copier that changes the source of all trees to `curSource` - val cpyWithNewSource = new TypedTreeCopier { - override protected def sourceFile(tree: tpd.Tree): SourceFile = curSource - override protected val untpdCpy: untpd.UntypedTreeCopier = new untpd.UntypedTreeCopier { - override protected def sourceFile(tree: untpd.Tree): SourceFile = curSource - } - } - - /** Removes all Inlined trees, replacing them with blocks. - * Repositions all trees directly inside an inlined expansion of a non empty call to the position of the call. - * Any tree directly inside an empty call (inlined in the inlined code) retains their position. - * - * Until we implement JSR-45, we cannot represent in output positions in other source files. - * So, reposition inlined code from other files with the call position. - */ - class Reposition extends TreeMap(cpyWithNewSource) { - - override def transform(tree: Tree)(using Context): Tree = { - def fixSpan[T <: untpd.Tree](copied: T): T = - copied.withSpan(if tree.source == curSource then tree.span else callSpan) - def finalize(copied: untpd.Tree) = - fixSpan(copied).withAttachmentsFrom(tree).withTypeUnchecked(tree.tpe) - - inContext(ctx.withSource(curSource)) { - tree match - case tree: Ident => finalize(untpd.Ident(tree.name)(curSource)) - case tree: Literal => finalize(untpd.Literal(tree.const)(curSource)) - case tree: This => finalize(untpd.This(tree.qual)(curSource)) - case tree: JavaSeqLiteral => finalize(untpd.JavaSeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: SeqLiteral => finalize(untpd.SeqLiteral(transform(tree.elems), transform(tree.elemtpt))(curSource)) - case tree: Bind => finalize(untpd.Bind(tree.name, transform(tree.body))(curSource)) - case tree: TypeTree => finalize(tpd.TypeTree(tree.tpe)) - case tree: DefTree => super.transform(tree).setDefTree - case EmptyTree => tree - case _ => fixSpan(super.transform(tree)) - } - } - } - - (new Reposition).transform(tree) - } - /** Leave only a call trace consisting of * - a reference to the top-level class from which the call was inlined, * - the call's position From 73202e53cb96c6752354b698f0947d0023bf4fd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 13:54:46 +0100 Subject: [PATCH 08/15] Refactor InlinedsPositioner into InlinedSourceMap --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 10 +- .../tools/backend/jvm/InlinedPositioner.scala | 121 ----------------- .../tools/backend/jvm/InlinedSourceMaps.scala | 126 ++++++++++++++++++ 3 files changed, 132 insertions(+), 125 deletions(-) delete mode 100644 compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala create mode 100644 compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index e4f9ef5d7128..af42c2992ec2 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -25,6 +25,8 @@ import dotty.tools.dotc.util.Spans._ import dotty.tools.dotc.report import dotty.tools.dotc.transform.SymUtils._ +import InlinedSourceMaps._ + /* * * @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/ @@ -80,7 +82,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { var isCZParcelable = false var isCZStaticModule = false - var inlinedsPositioner: InlinedsPositioner = null + var sourceMap: InlinedSourceMap = null /* ---------------- idiomatic way to ask questions to typer ---------------- */ @@ -280,8 +282,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - inlinedsPositioner = InlinedsPositioner(cunit) - cnode.visitSource(cunit.source.file.name, inlinedsPositioner.debugExtension.orNull) + sourceMap = sourceMapFor(cunit) + cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull) } enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match { @@ -545,7 +547,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { } def lineNumber(tree: Tree): Unit = { if (!emitLines || !tree.span.exists) return; - val nr = if (tree.source != cunit.source) inlinedsPositioner.lineFor(tree.sourcePos) else Some(ctx.source.offsetToLine(tree.span.point) + 1) + val nr = if (tree.source != cunit.source) sourceMap.lineFor(tree.sourcePos) else Some(ctx.source.offsetToLine(tree.span.point) + 1) nr match case Some(nr) if nr != lastEmittedLineNr => lastEmittedLineNr = nr diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala b/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala deleted file mode 100644 index 332bf18b0ef6..000000000000 --- a/compiler/src/dotty/tools/backend/jvm/InlinedPositioner.scala +++ /dev/null @@ -1,121 +0,0 @@ -package dotty.tools -package backend -package jvm - -import dotc.CompilationUnit -import dotc.ast.tpd._ -import dotc.util.{ SourcePosition, SourceFile } -import dotc.core.Contexts._ -import dotc.report -import dotc.typer.Inliner.InliningPosition -import collection.mutable - -import scala.collection.mutable.StringBuilder - -class InlinedsPositioner(cunit: CompilationUnit)(using Context): - private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) - - private class File(id: Int, name: String, path: Option[String]): - def write(b: mutable.StringBuilder): Unit = - if path.isDefined then b ++= "+ " - b append id - b += ' ' - b ++= name - b += '\n' - path.foreach { p => - b ++= p - b += '\n' - } - end File - - private class Mapping( - inputStartLine: Int, - fileId: Int, - repeatCount: Int, - outputStartLine: Int, - increment: Int - ): - extension (b: mutable.StringBuilder) def appendNotDefault(prefix: Char, value: Int): Unit = - if value != 1 then - b += prefix - b append value - - def write(b: mutable.StringBuilder): Unit = - b append (inputStartLine + 1) - b.appendNotDefault('#', fileId) - b.appendNotDefault(',', repeatCount) - b += ':' - b append (outputStartLine + 1) - b.appendNotDefault(',', increment) - b += '\n' - end Mapping - - private class Stratum(name: String, files: List[File], mappings: List[Mapping]): - def write(b: mutable.StringBuilder): Unit = - b ++= "*S " - b ++= name - b ++= "\n*F\n" - files.foreach(_.write(b)) - b ++= "*L\n" - mappings.foreach(_.write(b)) - b ++= "*E\n" - - private val requests = mutable.ListBuffer.empty[Request] - private var lastLine = cunit.tpdTree.sourcePos.endLine - - private def allocate(origPos: SourcePosition): Int = - val line = lastLine + 1 - lastLine += origPos.lines.length - line - -// if (source.name contains "Tuple.sca") && startLine == 35 && endLine == 35 then - - private class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: - override def traverse(tree: Tree)(using Context): Unit = - if tree.source != enclosingFile then - tree.getAttachment(InliningPosition) match - case Some(targetPos) => - val firstFakeLine = allocate(tree.sourcePos) - requests += Request(targetPos, tree.sourcePos, firstFakeLine) - RequestCollector(tree.source).traverseChildren(tree) - case None => - // Not exactly sure in which cases it is happening. Should we report warning? - RequestCollector(tree.source).traverseChildren(tree) - else traverseChildren(tree) - end RequestCollector - - RequestCollector(cunit.source).traverse(cunit.tpdTree) - - def debugExtension: Option[String] = Option.when(requests.nonEmpty) { - val scalaStratum = - val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source).toList - val mappings = requests.map { case Request(_, origPos, firstFakeLine) => - Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) - }.toList - Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) - - val debugStratum = - val mappings = requests.map { case Request(targetPos, origPos, firstFakeLine) => - Mapping(targetPos.startLine, 1, 1, firstFakeLine, origPos.lines.length) - }.toList - Stratum("ScalaDebug", File(1, cunit.source.name, None) :: Nil, mappings) - - - val b = new StringBuilder - b ++= "SMAP\n" - b ++= cunit.source.name - b += '\n' - b ++= "Scala\n" - scalaStratum.write(b) - debugStratum.write(b) - b.toString - } - - def lineFor(sourcePos: SourcePosition): Option[Int] = - requests.find(_.origPos.contains(sourcePos)) match - case Some(request) => - val offset = sourcePos.startLine - request.origPos.startLine - Some(request.firstFakeLine + offset + 1) - case None => - report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") - None diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala new file mode 100644 index 000000000000..851248837800 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -0,0 +1,126 @@ +package dotty.tools +package backend +package jvm + +import dotc.CompilationUnit +import dotc.ast.tpd._ +import dotc.util.{ SourcePosition, SourceFile } +import dotc.core.Contexts._ +import dotc.report +import dotc.typer.Inliner.InliningPosition +import collection.mutable + +import scala.collection.mutable.StringBuilder + +object InlinedSourceMaps: + private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) + + private class File(id: Int, name: String, path: Option[String]): + def write(b: mutable.StringBuilder): Unit = + if path.isDefined then b ++= "+ " + b append id + b += ' ' + b ++= name + b += '\n' + path.foreach { p => + b ++= p + b += '\n' + } + end File + + private class Mapping( + inputStartLine: Int, + fileId: Int, + repeatCount: Int, + outputStartLine: Int, + increment: Int + ): + extension (b: mutable.StringBuilder) def appendNotDefault(prefix: Char, value: Int): Unit = + if value != 1 then + b += prefix + b append value + + def write(b: mutable.StringBuilder): Unit = + b append (inputStartLine + 1) + b.appendNotDefault('#', fileId) + b.appendNotDefault(',', repeatCount) + b += ':' + b append (outputStartLine + 1) + b.appendNotDefault(',', increment) + b += '\n' + end Mapping + + private class Stratum(name: String, files: List[File], mappings: List[Mapping]): + def write(b: mutable.StringBuilder): Unit = + b ++= "*S " + b ++= name + b ++= "\n*F\n" + files.foreach(_.write(b)) + b ++= "*L\n" + mappings.foreach(_.write(b)) + b ++= "*E\n" + end Stratum + + def sourceMapFor(cunit: CompilationUnit)(using Context): InlinedSourceMap = + val requests = mutable.ListBuffer.empty[Request] + var lastLine = cunit.tpdTree.sourcePos.endLine + + class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: + override def traverse(tree: Tree)(using Context): Unit = + if tree.source != enclosingFile then + tree.getAttachment(InliningPosition) match + case Some(targetPos) => + val firstFakeLine = allocate(tree.sourcePos) + requests += Request(targetPos, tree.sourcePos, firstFakeLine) + RequestCollector(tree.source).traverseChildren(tree) + case None => + // Not exactly sure in which cases it is happening. Should we report warning? + RequestCollector(tree.source).traverseChildren(tree) + else traverseChildren(tree) + end RequestCollector + + def allocate(origPos: SourcePosition): Int = + val line = lastLine + 1 + lastLine += origPos.lines.length + line + + RequestCollector(cunit.source).traverse(cunit.tpdTree) + InlinedSourceMap(cunit, requests.toList) + end sourceMapFor + + class InlinedSourceMap private[InlinedSourceMaps] (cunit: CompilationUnit, requests: List[Request])(using Context): + def debugExtension: Option[String] = Option.when(requests.nonEmpty) { + val scalaStratum = + val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source) + val mappings = requests.map { case Request(_, origPos, firstFakeLine) => + Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) + } + Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) + + val debugStratum = + val mappings = requests.map { case Request(targetPos, origPos, firstFakeLine) => + Mapping(targetPos.startLine, 1, 1, firstFakeLine, origPos.lines.length) + } + Stratum("ScalaDebug", File(1, cunit.source.name, None) :: Nil, mappings) + + + val b = new StringBuilder + b ++= "SMAP\n" + b ++= cunit.source.name + b += '\n' + b ++= "Scala\n" + scalaStratum.write(b) + debugStratum.write(b) + b.toString + } + + def lineFor(sourcePos: SourcePosition): Option[Int] = + requests.find(_.origPos.contains(sourcePos)) match + case Some(request) => + val offset = sourcePos.startLine - request.origPos.startLine + Some(request.firstFakeLine + offset + 1) + case None => + report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.") + None + + From e61e94f7b58a3cd797ce2a4c31051dff3cea4095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 14:11:30 +0100 Subject: [PATCH 09/15] Filter out information about inlining in the same file --- compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 851248837800..c17225c3f767 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -67,7 +67,7 @@ object InlinedSourceMaps: class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = - if tree.source != enclosingFile then + if tree.source != enclosingFile && tree.source != cunit.source then tree.getAttachment(InliningPosition) match case Some(targetPos) => val firstFakeLine = allocate(tree.sourcePos) From df06d9d731f844d5b5c19054625025aab59013fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 17:51:57 +0100 Subject: [PATCH 10/15] Add information aobut internal class name to SMAP --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 2 +- .../tools/backend/jvm/InlinedSourceMaps.scala | 28 +++++++++++++++---- .../src/dotty/tools/dotc/typer/Inliner.scala | 12 +++++--- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index af42c2992ec2..a1d15642c906 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -282,7 +282,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { superClass, interfaceNames.toArray) if (emitSource) { - sourceMap = sourceMapFor(cunit) + sourceMap = sourceMapFor(cunit)(s => classBTypeFromSymbol(s).internalName) cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull) } diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index c17225c3f767..92c54051dabc 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -6,11 +6,11 @@ import dotc.CompilationUnit import dotc.ast.tpd._ import dotc.util.{ SourcePosition, SourceFile } import dotc.core.Contexts._ +import dotc.core.Symbols.Symbol import dotc.report import dotc.typer.Inliner.InliningPosition import collection.mutable -import scala.collection.mutable.StringBuilder object InlinedSourceMaps: private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) @@ -61,17 +61,26 @@ object InlinedSourceMaps: b ++= "*E\n" end Stratum - def sourceMapFor(cunit: CompilationUnit)(using Context): InlinedSourceMap = + def sourceMapFor(cunit: CompilationUnit)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap = val requests = mutable.ListBuffer.empty[Request] var lastLine = cunit.tpdTree.sourcePos.endLine + var internalNames = Map.empty[SourceFile, String] class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: override def traverse(tree: Tree)(using Context): Unit = if tree.source != enclosingFile && tree.source != cunit.source then tree.getAttachment(InliningPosition) match - case Some(targetPos) => + case Some(InliningPosition(targetPos, cls)) => val firstFakeLine = allocate(tree.sourcePos) requests += Request(targetPos, tree.sourcePos, firstFakeLine) + cls match + case Some(symbol) if !internalNames.isDefinedAt(tree.source) => + internalNames += (tree.source -> internalNameProvider(symbol)) + // We are skipping any internal name info if we already have one stored in our map + // because a debugger will use internal name only to localize matching source. + // Both old and new internal names are associated with the same source file + // so it doesn't matter if internal name is not matching used symbol. + case _ => () RequestCollector(tree.source).traverseChildren(tree) case None => // Not exactly sure in which cases it is happening. Should we report warning? @@ -85,17 +94,24 @@ object InlinedSourceMaps: line RequestCollector(cunit.source).traverse(cunit.tpdTree) - InlinedSourceMap(cunit, requests.toList) + InlinedSourceMap(cunit, requests.toList, internalNames) end sourceMapFor - class InlinedSourceMap private[InlinedSourceMaps] (cunit: CompilationUnit, requests: List[Request])(using Context): + class InlinedSourceMap private[InlinedSourceMaps] ( + cunit: CompilationUnit, + requests: List[Request], + internalNames: Map[SourceFile, String])(using Context): + def debugExtension: Option[String] = Option.when(requests.nonEmpty) { val scalaStratum = val files = cunit.source :: requests.map(_.origPos.source).distinct.filter(_ != cunit.source) val mappings = requests.map { case Request(_, origPos, firstFakeLine) => Mapping(origPos.startLine, files.indexOf(origPos.source) + 1, origPos.lines.length, firstFakeLine, 1) } - Stratum("Scala", files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, None) }, Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings) + Stratum("Scala", + files.zipWithIndex.map { case (f, n) => File(n + 1, f.name, internalNames.get(f)) }, + Mapping(0, 1, cunit.tpdTree.sourcePos.lines.length, 0, 1) +: mappings + ) val debugStratum = val mappings = requests.map { case Request(targetPos, origPos, firstFakeLine) => diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index d3ad10dbde9e..769acae2ade4 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -37,7 +37,8 @@ import quoted.QuoteUtils object Inliner { import tpd._ - val InliningPosition = new Property.StickyKey[SourcePosition] + object InliningPosition extends Property.StickyKey[InliningPosition] + case class InliningPosition(sourcePos: SourcePosition, topLevelSymbol: Option[Symbol]) /** `sym` is an inline method with a known body to inline. */ @@ -226,12 +227,15 @@ object Inliner { /** Replace `Inlined` node by a block that contains its bindings and expansion */ def dropInlined(inlined: Inlined)(using Context): Tree = - // In case that bindings are present we are adding SourcePosition attachement both to the resulting block and to the expansion + val topLevelClass = Some(inlined.call.symbol.topLevelClass).filter(_.exists) + val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) + + // In case that bindings are present we are adding InliningPosition attachement both to the resulting block and to the expansion // as in some cases the block is removed in one of later phases and attachment is lost. val tree1 = if inlined.bindings.isEmpty then inlined.expansion - else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inlined.sourcePos)) - tree1.withAttachment(InliningPosition, inlined.sourcePos) + else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inliningPosition)) + tree1.withAttachment(InliningPosition, inliningPosition) /** Leave only a call trace consisting of * - a reference to the top-level class from which the call was inlined, From dc85c0c3f803332c855465650ad5b0f42d7102e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 18:42:20 +0100 Subject: [PATCH 11/15] Fix problems with only first instance of inlined call being found --- .../tools/backend/jvm/BCodeSkelBuilder.scala | 34 ++++++++++++------- .../tools/backend/jvm/InlinedSourceMaps.scala | 4 +-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index a1d15642c906..a08e6a3397b4 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -377,6 +377,8 @@ trait BCodeSkelBuilder extends BCodeHelpers { var shouldEmitCleanup = false // line numbers var lastEmittedLineNr = -1 + // by real line number we mean line number that is not pointing to virtual lines added by inlined calls + var lastRealLineNr = -1 object bc extends JCodeMethodN { override def jmethod = PlainSkelBuilder.this.mnode @@ -545,19 +547,27 @@ trait BCodeSkelBuilder extends BCodeHelpers { case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl); case _ => false } ) } + + def emitNr(nr: Int): Unit = + if nr != lastEmittedLineNr then + lastEmittedLineNr = nr + lastInsn match + case lnn: asm.tree.LineNumberNode => + // overwrite previous landmark as no instructions have been emitted for it + lnn.line = nr + case _ => + mnode.visitLineNumber(nr, currProgramPoint()) + def lineNumber(tree: Tree): Unit = { - if (!emitLines || !tree.span.exists) return; - val nr = if (tree.source != cunit.source) sourceMap.lineFor(tree.sourcePos) else Some(ctx.source.offsetToLine(tree.span.point) + 1) - nr match - case Some(nr) if nr != lastEmittedLineNr => - lastEmittedLineNr = nr - lastInsn match - case lnn: asm.tree.LineNumberNode => - // overwrite previous landmark as no instructions have been emitted for it - lnn.line = nr - case _ => - mnode.visitLineNumber(nr, currProgramPoint()) - case _ => () + if !emitLines || !tree.span.exists then return; + if tree.source != cunit.source then + sourceMap.lineFor(tree.sourcePos, lastRealLineNr) match + case Some(nr) => emitNr(nr) + case None => () + else + val nr = ctx.source.offsetToLine(tree.span.point) + 1 + lastRealLineNr = nr + emitNr(nr) } // on entering a method diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 92c54051dabc..8062c80f4f5d 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -130,8 +130,8 @@ object InlinedSourceMaps: b.toString } - def lineFor(sourcePos: SourcePosition): Option[Int] = - requests.find(_.origPos.contains(sourcePos)) match + def lineFor(sourcePos: SourcePosition, lastRealNr: Int): Option[Int] = + requests.find(r => r.origPos.contains(sourcePos) && r.targetPos.endLine + 1 >= lastRealNr) match case Some(request) => val offset = sourcePos.startLine - request.origPos.startLine Some(request.firstFakeLine + offset + 1) From 26febb894f6bb3ae95bf66f3d81841e5cce45c1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 18:50:00 +0100 Subject: [PATCH 12/15] Disable YCheckPosition --- compiler/src/dotty/tools/dotc/Compiler.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/Compiler.scala b/compiler/src/dotty/tools/dotc/Compiler.scala index 4818fca07d0b..ef1e4d6a2d68 100644 --- a/compiler/src/dotty/tools/dotc/Compiler.scala +++ b/compiler/src/dotty/tools/dotc/Compiler.scala @@ -37,7 +37,6 @@ class Compiler { /** Phases dealing with the frontend up to trees ready for TASTY pickling */ protected def frontendPhases: List[List[Phase]] = List(new FrontEnd) :: // Compiler frontend: scanner, parser, namer, typer - List(new YCheckPositions) :: // YCheck positions List(new sbt.ExtractDependencies) :: // Sends information on classes' dependencies to sbt via callbacks List(new semanticdb.ExtractSemanticDB) :: // Extract info into .semanticdb files List(new PostTyper) :: // Additional checks and cleanups after type checking From d5eda767a180015a1900c114e46a460ea6766ac4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Sun, 21 Feb 2021 19:16:02 +0100 Subject: [PATCH 13/15] Add explanation to InlinedSourceMaps --- .../tools/backend/jvm/InlinedSourceMaps.scala | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index 8062c80f4f5d..de476aaf50e2 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -11,7 +11,27 @@ import dotc.report import dotc.typer.Inliner.InliningPosition import collection.mutable - +/** + * Tool for generating virtual lines for inlined calls and keeping track of them. + + * How it works: + * - For every inlined call it assumes that empty lines are appended to the source file. These + * lines are not added anywhere in physical form. We only assume that they exist only to be used + * by `LineNumberTable` and `SourceDebugExtension`. The number of these virtual lines is every + * time equal to the size of line range of the expansion of inlined call. + * - It generates SMAP (as defined by JSR-45) containing two strata. The first stratum (`Scala`) + * is describing the mapping from the real source files to the real and virtual lines in our + * assumed source. The second stratum (`ScalaDebug`) is mapping from virtual lines to + * corresponding inlined calls. + * - Generated SMAP is written to the bytecode in `SourceDebugExtension` + * - During the generation of the bytecode backed is asking `InlinedSourceMap` about position of + * all trees that have source different from the main source of given compilation unit. + * The response to that request is number of the virtual line that is corresponding to particular + * line from the other source. + * - Debuggers can use information stored in `LineNumberTable` and `SourceDebugExtension` to + * correctly guess which line of inlined method is currently executed. They can also construct + * stack frames for inlined calls. + **/ object InlinedSourceMaps: private case class Request(targetPos: SourcePosition, origPos: SourcePosition, firstFakeLine: Int) From 228b90b4b8add7aac383d49b179a5703a36394d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Mon, 22 Mar 2021 14:42:10 +0100 Subject: [PATCH 14/15] Skip inline proxies during accessibility assertions --- compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala index a9f2aaec2052..6db51f8fc4e7 100644 --- a/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala +++ b/compiler/src/dotty/tools/dotc/transform/ExpandPrivate.scala @@ -65,7 +65,7 @@ class ExpandPrivate extends MiniPhase with IdentityDenotTransformer { thisPhase private def ensurePrivateAccessible(d: SymDenotation)(using Context) = if (isVCPrivateParamAccessor(d)) d.ensureNotPrivate.installAfter(thisPhase) - else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass) { + else if (d.is(PrivateTerm) && !d.owner.is(Package) && d.owner != ctx.owner.lexicallyEnclosingClass && !d.is(InlineProxy)) { // Paths `p1` and `p2` are similar if they have a common suffix that follows // possibly different directory paths. That is, their common suffix extends // in both cases either to the start of the path or to a file separator character. From 7104e0fb08bb57f4fe7d210f2a5ca4d38edf00d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Marks?= Date: Wed, 24 Mar 2021 09:53:22 +0100 Subject: [PATCH 15/15] Sort inlining requests before creating SMAP Fixes SMAP generation breaking bytecode idempotency assertions --- .../dotty/tools/backend/jvm/InlinedSourceMaps.scala | 11 ++++++----- compiler/src/dotty/tools/dotc/typer/Inliner.scala | 8 ++------ 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala index de476aaf50e2..9db3bc0bc443 100644 --- a/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala +++ b/compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala @@ -82,8 +82,7 @@ object InlinedSourceMaps: end Stratum def sourceMapFor(cunit: CompilationUnit)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap = - val requests = mutable.ListBuffer.empty[Request] - var lastLine = cunit.tpdTree.sourcePos.endLine + val requests = mutable.ListBuffer.empty[(SourcePosition, SourcePosition)] var internalNames = Map.empty[SourceFile, String] class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser: @@ -91,8 +90,8 @@ object InlinedSourceMaps: if tree.source != enclosingFile && tree.source != cunit.source then tree.getAttachment(InliningPosition) match case Some(InliningPosition(targetPos, cls)) => - val firstFakeLine = allocate(tree.sourcePos) - requests += Request(targetPos, tree.sourcePos, firstFakeLine) + requests += (targetPos -> tree.sourcePos) + cls match case Some(symbol) if !internalNames.isDefinedAt(tree.source) => internalNames += (tree.source -> internalNameProvider(symbol)) @@ -108,13 +107,15 @@ object InlinedSourceMaps: else traverseChildren(tree) end RequestCollector + var lastLine = cunit.tpdTree.sourcePos.endLine def allocate(origPos: SourcePosition): Int = val line = lastLine + 1 lastLine += origPos.lines.length line RequestCollector(cunit.source).traverse(cunit.tpdTree) - InlinedSourceMap(cunit, requests.toList, internalNames) + val allocated = requests.sortBy(_._1.start).map(r => Request(r._1, r._2, allocate(r._2))) + InlinedSourceMap(cunit, allocated.toList, internalNames) end sourceMapFor class InlinedSourceMap private[InlinedSourceMaps] ( diff --git a/compiler/src/dotty/tools/dotc/typer/Inliner.scala b/compiler/src/dotty/tools/dotc/typer/Inliner.scala index 769acae2ade4..c93abcff87af 100644 --- a/compiler/src/dotty/tools/dotc/typer/Inliner.scala +++ b/compiler/src/dotty/tools/dotc/typer/Inliner.scala @@ -230,12 +230,8 @@ object Inliner { val topLevelClass = Some(inlined.call.symbol.topLevelClass).filter(_.exists) val inliningPosition = InliningPosition(inlined.sourcePos, topLevelClass) - // In case that bindings are present we are adding InliningPosition attachement both to the resulting block and to the expansion - // as in some cases the block is removed in one of later phases and attachment is lost. - val tree1 = - if inlined.bindings.isEmpty then inlined.expansion - else cpy.Block(inlined)(inlined.bindings, inlined.expansion.withAttachment(InliningPosition, inliningPosition)) - tree1.withAttachment(InliningPosition, inliningPosition) + val withPos = inlined.expansion.withAttachment(InliningPosition, inliningPosition) + if inlined.bindings.isEmpty then withPos else cpy.Block(inlined)(inlined.bindings, withPos) /** Leave only a call trace consisting of * - a reference to the top-level class from which the call was inlined,