Skip to content

Fix #4947: Do not replace positions of inlined arguments #4949

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

Merged
merged 19 commits into from
Aug 24, 2018

Conversation

nicolasstucki
Copy link
Contributor

No description provided.

@Blaisorblade Blaisorblade self-assigned this Aug 15, 2018
pos = last
res
case tree =>
if (pos.exists) super.transform(tree).withPos(pos)
Copy link
Contributor

Choose a reason for hiding this comment

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

@nicolasstucki says !pos.exists is only possible if inlined.call is emptyTree, which means that this is not a call to an inline method but an argument to it — and then bindings should be empty.
We should update docs for Inlined about this.

Copy link
Contributor

Choose a reason for hiding this comment

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

This does not look like the right algorithm. We have enclosingInlineds, which is interpreted as follows:

  • An Inlined node with a non-empty call is a plain inlined node
  • An inlined node with an empty tree as call in an inlined argument which cancels the enclosing inlined call.

So one should start with the outermost InlinedCall and update all positions in its expansion to the call position, except in those regions where there are as many empty as non-empty calls in enclosingInlineds.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That sounds like the algorithm I implemented though I use the pos state to know if it has been cancelled or not. I will change it to use enclosingInlineds directly.

@smarter
Copy link
Member

smarter commented Aug 16, 2018

Please add documentation, I have no idea what this does from looking at it.

pos = last
res
case tree =>
if (pos.exists) super.transform(tree).withPos(pos)
Copy link
Contributor

Choose a reason for hiding this comment

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

This does not look like the right algorithm. We have enclosingInlineds, which is interpreted as follows:

  • An Inlined node with a non-empty call is a plain inlined node
  • An inlined node with an empty tree as call in an inlined argument which cancels the enclosing inlined call.

So one should start with the outermost InlinedCall and update all positions in its expansion to the call position, except in those regions where there are as many empty as non-empty calls in enclosingInlineds.

@smarter
Copy link
Member

smarter commented Aug 16, 2018

I think this also needs more extensive tests covering various nesting levels of inlined/non-inlined across one or more files and probably more low-level tests too (like https://github.com/scala/scala/blob/2.13.x/test/junit/scala/tools/nsc/backend/jvm/BytecodeTest.scala#L32-L77, https://github.com/scala/scala/blob/2.13.x/test/junit/scala/tools/nsc/backend/jvm/opt/InlinerTest.scala#L1515, etc)

Only reposition positions of inlined trees when they come from
another file.
@nicolasstucki
Copy link
Contributor Author

@Blaisorblade I when a bit than #4947 and kept the positions of trees inlined in the same file. Those already have the correct position and can be used in the debugger or stack traces.

@Blaisorblade
Copy link
Contributor

Blaisorblade commented Aug 17, 2018

I went a bit further than #4947 and kept the positions of trees inlined in the same file.

Sounds like what I hoped for!* Line numbers in debugging now work like a charm (minus, of course, setting breakpoints inside track, but that requires JSR-45). The only weird thing is that IntelliJ thinks you can set breakpoint in track, which suggests it is output at all, but that's an unfortunate UX for the semantics of transparent/@forceInline.

*EDIT: oh, I see that you preserve line numbers even for track if it's defined in the same file. Nice!

Copy link
Contributor

@Blaisorblade Blaisorblade left a comment

Choose a reason for hiding this comment

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

Cool, but I'm taking a look and changing a couple things which can be improved, and will reassign you for re-review.

@@ -0,0 +1,4 @@
track: Test$.main(i4947.scala:4)
Copy link
Contributor

Choose a reason for hiding this comment

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

These testcases are good and go beyond #4947.

@Test def i4947 = {
val source = """class Foo {
| transparent def track[T](f: => T): T = {
| println("tracking") // line 3
Copy link
Contributor

Choose a reason for hiding this comment

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

What about we call a new virtual method m1(String) in Foo, rather than println, a bit like the existing test? This test will break if any code figures that Predef$.MODULE$ needn't be reloaded.

Do these tests ever break? (Guess: yes). I don't know and I'm curious about the existing guidelines, but people maintaining them might like info on what bits matter and which don't (beyond what's already in this PR).

Nicolas mentioned he already tried this but the resulting test was more complex (which... well, ALOAD 0 seems pretty stable to me), and it's easy to fix the test by pasting the output.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Changed

val transformed = super.transform(tree)
enclosingInlineds match {
case call :: _ if call.symbol.topLevelClass != ctx.owner.topLevelClass =>
// reposition tree inlined from some other file
Copy link
Contributor

Choose a reason for hiding this comment

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

The comment makes sense, but the code is stricter, and resets the position even if the toplevel class is different but the source file is the same. In that case, it appears we don't need to reset the position. Fixing.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also, this is an expensive (O(n)) way to test if enclosingInlineds.nonEmpty and get the head, and that could be expensive, but that's really enclosingInlineds's fault and I think can be fixed separately without affecting this code.

val transformed = super.transform(tree)
enclosingInlineds match {
case call :: _ if call.symbol.sourceFile != ctx.owner.sourceFile =>
// reposition tree inlined from some other file
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the documentation is too complicated. We're dropping Inlined nodes, and repositioning nodes from other files to the call position because we can't express their position otherwise in their debug info, pending JSR-45. The stuff about "empty call" is confusing and expressed in terms of the implementation.

Other issues that I think are there and that I'm trying to address:

  • The comment makes sense, but the code is stricter, and resets the position even if the toplevel class is different but the source file is the same. In that case, it appears we don't need to reset the position. Addressed by one commit I pushed.

  • Also, this is an expensive (O(n)) way to test if enclosingInlineds.nonEmpty and get the head, and that could be expensive, but that's really enclosingInlineds's fault and I think can be fixed separately without affecting this code.

Copy link
Contributor

Choose a reason for hiding this comment

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

So #4990 addresses performance.

@Blaisorblade
Copy link
Contributor

Blaisorblade commented Aug 23, 2018

@nicolasstucki The current algorithm is fishy; if I keep the full stack as described in #4990 (comment) (not the PR but the comment), the new tests break.

What we need is to compare the current source file ctx.owner.sourceFile with the source file of parameters, which is call.symbol.sourceFile for the enclosing call (not theirs).

I'm not sure Martin's suggested algorithm is correct either, or if so I don't get why:

So one should start with the outermost InlinedCall and update all positions in its expansion to the call position, except in those regions where there are as many empty as non-empty calls in enclosingInlineds.

It seems to keep unchanged the position of parameters (as many empty as non-empty calls), but that's wrong for parameters from another file (also featuring in the latest testcases).

EDIT: BTW, tests keep working with #4990 in.

EDIT2: I conjecture this algorithm works only assuming that enclosingInlineds is empty for parameters.

@nicolasstucki nicolasstucki dismissed Blaisorblade’s stale review August 24, 2018 09:39

All the requested changes where made

@nicolasstucki
Copy link
Contributor Author

@Blaisorblade I'll let you merge if all is in order for you.

@Blaisorblade
Copy link
Contributor

@nicolasstucki I think I figured what's going on and why the code is correct. I'll update the comments and merge everything.

@Blaisorblade
Copy link
Contributor

I'm merging the PRs as-is and pushing comment clarifications to followup PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants