Skip to content

Implement JSR-45 for Scala 3 #17055

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 44 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
209c0c3
Include duplicates of inlined calls in SMAP
Kordyjan Feb 11, 2021
3ffee6d
Add missing info to SMAP header
Kordyjan Feb 11, 2021
9931299
Update information about lines for inlined content
Kordyjan Feb 15, 2021
43e063f
Make InlinePositioner not crash on not found inline request
Kordyjan Feb 15, 2021
903a760
Solve not registering inlining position for tuples
Kordyjan Feb 21, 2021
9533416
Refactor InlinedsPositioner into InlinedSourceMap
Kordyjan Feb 21, 2021
adf534c
Filter out information about inlining in the same file
Kordyjan Feb 21, 2021
67902ff
Add information aobut internal class name to SMAP
Kordyjan Feb 21, 2021
5de29f6
Fix problems with only first instance of inlined call being found
Kordyjan Feb 21, 2021
c37e569
Disable YCheckPosition
Kordyjan Feb 21, 2021
d68a3a8
Add explanation to InlinedSourceMaps
Kordyjan Feb 21, 2021
1ff59ed
Skip inline proxies during accessibility assertions
Kordyjan Mar 22, 2021
fd200c8
Sort inlining requests before creating SMAP
Kordyjan Mar 24, 2021
b0ebeed
Adapt Inliner to the most recent refactoring on the `main`
anatoliykmetyuk Jul 15, 2022
9c1e626
Comment out warning about inline positions recording
anatoliykmetyuk Jul 15, 2022
1194495
Don't generate mappings for the quotes compiled at runtime by the sta…
anatoliykmetyuk Aug 12, 2022
5f57a5e
Inlined code gets virtual line numbers in stack traces
anatoliykmetyuk Aug 12, 2022
3bb9b42
Fix virtual line numbers in assert-stack
anatoliykmetyuk Aug 12, 2022
e713b07
Don't do ExpandPrivate checks on synthetic methods.
anatoliykmetyuk Aug 26, 2022
fd458f0
Comments and some useful prints added
Tetchki Feb 27, 2023
2d46e51
Revert "Comments and some useful prints added"
Tetchki Feb 27, 2023
9b95e3e
Removed synthetic ExpandPrivate check
Tetchki Feb 27, 2023
ba32028
CI Test
Tetchki Feb 27, 2023
91e9f92
Reverted InlineProxy and removed source location assert
Tetchki Mar 6, 2023
45b7099
Fixed inlines in scoverage not being repositioned
Tetchki May 8, 2023
32b1208
Fixed 2 identical consecutive inlines generates wrong line number
Tetchki May 8, 2023
b0e7c72
Attempted fix for nested and parameter inlines generate wrong line nu…
Tetchki May 8, 2023
86c4379
Keep Inlined nodes until BCode builder
Tetchki May 16, 2023
ee4dc3c
Use Inlined nodes' information instead of using attachements
Tetchki May 16, 2023
a4c6fef
Added JS Inlined code gen and simplified JVM Inlined BCode gen
Tetchki May 17, 2023
2c42d1f
Removed unecessary check
Tetchki May 17, 2023
95f897e
Removed unused code
Tetchki May 17, 2023
dfd779b
Added missing generated destination and used dropInlined to correctly…
Tetchki May 24, 2023
86e03ff
Cleanup
Tetchki May 24, 2023
156bf7b
Use dropInlined to correctly drop an inlined node
Tetchki May 24, 2023
27ac306
Added case to manage inlined nodes and correctly drop them
Tetchki May 24, 2023
37b71f3
Edited wrong file
Tetchki May 24, 2023
07e4f05
Cleanup and adapted description
Tetchki May 24, 2023
353bc21
Fix ArrayApply tests
julienrf May 24, 2023
0b34347
Updated check files
Tetchki Jun 5, 2023
f94cfe9
Set missing span of classOf tree
julienrf Jun 5, 2023
0cca3fa
Fix position issues at the “copy” level
julienrf Jun 5, 2023
f742d28
Add missing span to inline expansion
Tetchki Jun 5, 2023
f08fea4
Added SMAP string builder
Tetchki Jun 29, 2023
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
5 changes: 5 additions & 0 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.Contexts._
import dotty.tools.dotc.core.Phases._
import dotty.tools.dotc.core.Decorators.em
import dotty.tools.dotc.report
import dotty.tools.dotc.inlines.Inlines

/*
*
Expand Down Expand Up @@ -479,6 +480,10 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
case t: TypeApply => // dotty specific
generatedType = genTypeApply(t)

case inlined @ Inlined(_, _, _) =>
genLoadTo(Inlines.dropInlined(inlined) , expectedType, dest)
generatedDest = dest

case _ => abort(s"Unexpected tree in genLoad: $tree/${tree.getClass} at: ${tree.span}")
}

Expand Down
38 changes: 27 additions & 11 deletions compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package backend
package jvm

import scala.language.unsafeNulls

import scala.annotation.tailrec

import scala.collection.{ mutable, immutable }
Expand All @@ -24,6 +23,9 @@ import dotty.tools.dotc.util.Spans._
import dotty.tools.dotc.report
import dotty.tools.dotc.transform.SymUtils._

import InlinedSourceMaps._
import dotty.tools.dotc.inlines.Inlines.InliningPosition

/*
*
* @author Miguel Garcia, http://lamp.epfl.ch/~magarcia/ScalaCompilerCornerReloaded/
Expand Down Expand Up @@ -91,6 +93,8 @@ trait BCodeSkelBuilder extends BCodeHelpers {
var isCZParcelable = false
var isCZStaticModule = false

var sourceMap: InlinedSourceMap = null

/* ---------------- idiomatic way to ask questions to typer ---------------- */

def paramTKs(app: Apply, take: Int = -1): List[BType] = app match {
Expand Down Expand Up @@ -276,7 +280,8 @@ trait BCodeSkelBuilder extends BCodeHelpers {
superClass, interfaceNames.toArray)

if (emitSource) {
cnode.visitSource(cunit.source.file.name, null /* SourceDebugExtension */)
sourceMap = sourceMapFor(cunit)(s => classBTypeFromSymbol(s).internalName)
cnode.visitSource(cunit.source.file.name, sourceMap.debugExtension.orNull)
}

enclosingMethodAttribute(claszSymbol, internalName, asmMethodType(_).descriptor) match {
Expand Down Expand Up @@ -555,26 +560,37 @@ trait BCodeSkelBuilder extends BCodeHelpers {
case labnode: asm.tree.LabelNode => (labnode.getLabel == lbl);
case _ => false } )
}
def lineNumber(tree: Tree): Unit = {

def emitNr(nr: Int): Unit =

@tailrec
def getNonLabelNode(a: asm.tree.AbstractInsnNode): asm.tree.AbstractInsnNode = a match {
case a: asm.tree.LabelNode => getNonLabelNode(a.getPrevious)
case _ => a
case _ => a
}

if (!emitLines || !tree.span.exists) return;
val nr = ctx.source.offsetToLine(tree.span.point) + 1
if (nr != lastEmittedLineNr) {
if nr != lastEmittedLineNr then
lastEmittedLineNr = nr
getNonLabelNode(lastInsn) match {
getNonLabelNode(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())
}
}
}
end emitNr

def lineNumber(tree: Tree): Unit = {
if (!emitLines || !tree.span.exists) return;
// Use JSR-45 mapping for inlined trees defined outside of the current compilation unit
if tree.source != cunit.source then
sourceMap.lineFor(tree) match
case Some(nr) =>
emitNr(nr)
case None => ()
else
val nr = ctx.source.offsetToLine(tree.span.point) + 1
emitNr(nr)
}

// on entering a method
def resetMethodBookkeeping(dd: DefDef) = {
Expand Down
172 changes: 172 additions & 0 deletions compiler/src/dotty/tools/backend/jvm/InlinedSourceMaps.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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.core.Symbols.Symbol
import dotc.report
import dotc.inlines.Inlines.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(inline: Inlined, 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)(internalNameProvider: Symbol => String)(using Context): InlinedSourceMap =
val requests = mutable.ListBuffer.empty[Inlined]
var internalNames = Map.empty[SourceFile, String]

class RequestCollector(enclosingFile: SourceFile) extends TreeTraverser:
override def traverse(tree: Tree)(using Context): Unit =
tree match
case inlined @ Inlined(call, bindings, expansion) =>
if expansion.source != enclosingFile && expansion.source != cunit.source then
requests += inlined
val topLevelClass = Option.when(!call.isEmpty)(call.symbol.topLevelClass)

topLevelClass 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 _ => ()

traverseChildren(tree)
case _ => traverseChildren(tree)
end RequestCollector

// Don't generate mappings for the quotes compiled at runtime by the staging compiler
if cunit.source.file.isVirtual then InlinedSourceMap(cunit, Nil, Map.empty[SourceFile, String])
else
var lastLine = cunit.tpdTree.sourcePos.endLine
// returns the first fake line (starting from 0)
def allocate(origPos: SourcePosition): Int =
val line = lastLine + 1
lastLine += origPos.lines.length
line

RequestCollector(cunit.source).traverse(cunit.tpdTree)

val allocated = requests.map(r => Request(r, allocate(r.expansion.sourcePos)))

InlinedSourceMap(cunit, allocated.toList, internalNames)
end sourceMapFor

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(_.inline.expansion.source).distinct.filter(_ != cunit.source)
val mappings = requests.map { case Request(inline, firstFakeLine) =>
Mapping(inline.expansion.sourcePos.startLine,
files.indexOf(inline.expansion.source) + 1,
inline.expansion.sourcePos.lines.length,
firstFakeLine, 1)
}
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(inline, firstFakeLine) =>
Mapping(inline.sourcePos.startLine, 1, 1, firstFakeLine, inline.expansion.sourcePos.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(tree: Tree): Option[Int] =

val sourcePos = tree.sourcePos
requests.findLast(r => r.inline.expansion.contains(tree)) match
case Some(request) =>
val offset = sourcePos.startLine - request.inline.expansion.sourcePos.startLine
val virtualLine = request.firstFakeLine + offset
if requests.filter(_.inline.expansion.contains(tree)).size > 1 then None
else Some(virtualLine + 1) // + 1 because the first line is 1 in the LineNumberTable
case None =>
// report.warning(s"${sourcePos.show} was inlined in ${cunit.source} but its inlining position was not recorded.")
None
4 changes: 4 additions & 0 deletions compiler/src/dotty/tools/backend/sjs/JSCodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import dotty.tools.dotc.transform.sjs.JSSymUtils._

import JSEncoding._
import ScopedVar.withScopedVars
import dotty.tools.dotc.inlines.Inlines

/** Main codegen for Scala.js IR.
*
Expand Down Expand Up @@ -1930,6 +1931,9 @@ class JSCodeGen()(using genCtx: Context) {
case EmptyTree =>
js.Skip()

case inlined @ Inlined(_, _, _) =>
genStatOrExpr(Inlines.dropInlined(inlined), isStat)

case _ =>
throw new FatalError("Unexpected tree in genExpr: " +
tree + "/" + tree.getClass + " at: " + (tree.span: Position))
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class Compiler {
List(new Parser) :: // Compiler frontend: scanner, parser
List(new TyperPhase) :: // Compiler frontend: namer, typer
List(new CheckUnused.PostTyper) :: // Check for unused elements
List(new YCheckPositions) :: // YCheck positions
//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
Expand Down
17 changes: 12 additions & 5 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1341,10 +1341,17 @@ object Trees {
case tree: SeqLiteral if (elems eq tree.elems) && (elemtpt eq tree.elemtpt) => tree
case _ => finalize(tree, untpd.SeqLiteral(elems, elemtpt)(sourceFile(tree)))
}
def Inlined(tree: Tree)(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = tree match {
case tree: Inlined if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) => tree
case _ => finalize(tree, untpd.Inlined(call, bindings, expansion)(sourceFile(tree)))
}
// Positions of trees are automatically pushed down except when we reach an Inlined tree. Therefore, we
// make sure the new expansion has a position by copying the one of the original Inlined tree.
def Inlined(tree: Inlined)(call: tpd.Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined =
if (call eq tree.call) && (bindings eq tree.bindings) && (expansion eq tree.expansion) then tree
else
// Copy the span from the original Inlined tree if the new expansion doesn't have a span.
val expansionWithSpan =
if expansion.span.exists then expansion
else expansion.withSpan(tree.expansion.span)
finalize(tree, untpd.Inlined(call, bindings, expansionWithSpan)(sourceFile(tree)))

def Quote(tree: Tree)(body: Tree, tags: List[Tree])(using Context): Quote = tree match {
case tree: Quote if (body eq tree.body) && (tags eq tree.tags) => tree
case _ => finalize(tree, untpd.Quote(body, tags)(sourceFile(tree)))
Expand Down Expand Up @@ -1549,7 +1556,7 @@ object Trees {
cpy.Try(tree)(transform(block), transformSub(cases), transform(finalizer))
case SeqLiteral(elems, elemtpt) =>
cpy.SeqLiteral(tree)(transform(elems), transform(elemtpt))
case Inlined(call, bindings, expansion) =>
case tree @ Inlined(call, bindings, expansion) =>
cpy.Inlined(tree)(call, transformSub(bindings), transform(expansion)(using inlineContext(call)))
case TypeTree() =>
tree
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
}
}

override def Inlined(tree: Tree)(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = {
override def Inlined(tree: Inlined)(call: Tree, bindings: List[MemberDef], expansion: Tree)(using Context): Inlined = {
val tree1 = untpdCpy.Inlined(tree)(call, bindings, expansion)
tree match {
case tree: Inlined if sameTypes(bindings, tree.bindings) && (expansion.tpe eq tree.expansion.tpe) =>
Expand Down
Loading