scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

inliner destroys line number information #7518

Closed scabug closed 6 years ago

scabug commented 11 years ago

In the attached screenshot, debugging Scala 2.10.2-RC1, you can see the call stack shows

resetAllAttrs():37, Global {scala.tools.nsc}
adaptToImplicitMethod$1():870, Typers$Typer {scala.tools.nsc.typechecker}

I expect:

resetAllAttrs():37, Global {scala.tools.nsc}
adaptToImplicitMethod$1():881, Typers$Typer {scala.tools.nsc.typechecker}

Where line 881 is in the inlined lambda.

scabug commented 11 years ago

Imported From: https://issues.scala-lang.org/browse/SI-7518?orig=1 Reporter: @retronym Affected Versions: 2.10.0 Attachments:

scabug commented 11 years ago

@JamesIry said: Is this a 2.10.2-RC1 regression?

scabug commented 11 years ago

@retronym said: I don't believe so. I've pushed the fix version out.

scabug commented 11 years ago

@retronym said (edited on May 29, 2013 7:07:58 AM UTC): Here's a standalone test case. The same line number information is emitted in 2.10.0 as 2.10.2-RC1.

class Test {
  @inline final def foo[A](a: => A) = a

  def client {
    foo {
      println("line 6")
    }
  }
}

With -optimize, nothing on line 6.

  scala> :javap -v Test

     public void client();
  Code:
   Stack=2, Locals=2, Args_size=1
   0:  getstatic  #28; //Field scala/Predef$.MODULE$:Lscala/Predef$;
   3:  astore_1
   4:  getstatic  #33; //Field scala/Console$.MODULE$:Lscala/Console$;
   7:  ldc  #35; //String line 6
   9:  invokevirtual  #39; //Method scala/Console$.println:(Ljava/lang/Object;)V
   12:  return
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      13      0    this       LTest;

  LineNumberTable:
   line 5: 0
   */

Without -optimize, closure creation and apply on line 6, as expected.

  scala> :javap -v Test

    public void client();
  Code:
   Stack=4, Locals=1, Args_size=1
   0:  aload_0
   1:  new  #24; //class Test$$anonfun$client$1
   4:  dup
   5:  aload_0
   6:  invokespecial  #28; //Method Test$$anonfun$client$1."<init>":(LTest;)V
   9:  invokevirtual  #30; //Method foo:(Lscala/Function0;)Ljava/lang/Object;
   12:  pop
   13:  return
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      14      0    this       LTest;

  LineNumberTable:
   line 5: 0
   line 6: 1
   line 5: 9

  scala> :javap -v Test$$anonfun$client$1

   public final java.lang.Object apply();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:  aload_0
   1:  invokevirtual  #37; //Method apply:()V
   4:  getstatic  #43; //Field scala/runtime/BoxedUnit.UNIT:Lscala/runtime/BoxedUnit;
   7:  areturn
  LocalVariableTable:
   Start  Length  Slot  Name   Signature
   0      8      0    this       LTest$$anonfun$client$1;

  LineNumberTable:
   line 6: 0
scabug commented 11 years ago

@retronym said: Here's a patch that fixes this. But I'm not familiar enough with the intent of the original code to be sure its a) appropriate and b) complete.

https://github.com/retronym/scala/compare/scala:2.10.x...ticket/7518

WIP SI-7518 Position retention for inlined code …
The inliner was positioning all inlined instructions
at the method call (`targetPos). This makes debugging
of code compiled under `-optimize` difficult.

This commit retains the original position of the
inlined instruction if it is in the same source file
as the method call.

TODO: This still seems aribtrary. What was the intent
of using `targetPos`? Should we expand the above to
any source position? What about code inlined from class
files: should they be treated any differently?

James, assigning this to you for an opinion.

scabug commented 11 years ago

@retronym said: After thinking about this for a bit longer, its now obvious that we can only retain the line numbers in the limited (but useful!) condition used in my patch.

scabug commented 11 years ago

@retronym said: Just added a commit with an additional test case that shows a bug in my patch.

scabug commented 11 years ago

@JamesIry said: One of the stupid limitations of the JVM is that every .class file has only one associated source file name and chunks of bytecode are assigned to lines within that source file. See http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html#79868 and http://docs.oracle.com/javase/specs/jvms/se5.0/html/ClassFile.doc.html#22856 .

TL;DR Inlining from one source file into another source file must lose debug information.

scabug commented 11 years ago

@JamesIry said (edited on May 29, 2013 4:17:03 PM UTC): Ah, but wait, Java 7 has an answer. There's a new attribute http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.11 and JSR-45 specifies what goes into that attribute http://download.oracle.com/otndocs/jcp/dsol-1.0-fr-spec-oth-JSpec/. Because the attribute is invisible to JVMs prior to 7, it should be safe to add this functionality.

scabug commented 11 years ago

@retronym said: It would be great to start with the dumb-but-Java-6-compatible version to at least retain line number info from the same file. Inlining a closure or by-name argument is so common, I'd really like to be able to set a break point in them.

But I got a bit lost in the code trying to implement this correctly.

scabug commented 11 years ago

@JamesIry said: Agreed. Even if/when we can do the super-duper-extended-attribute version, we probably mostly don't want to except in cases where it's necessary as it will make class files that much larger. I'll take a look at the code.

scabug commented 11 years ago

@magarciaEPFL said: The new optimizer http://magarciaepfl.github.io/scala/ isn't prone to this bug. In the example, the closure body (which is invoked from the inlined higher-order method) contains the correct line number information:

  // access flags 0x1A
  private final static dlgt$27b8b21$1()V
   L0
    LINENUMBER 6 L0
    GETSTATIC scala/Predef$.MODULE$ : Lscala/Predef$;
    LDC "line 6"
    INVOKEVIRTUAL scala/Predef$.println (Ljava/lang/Object;)V
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0

A sidenote: Why aren't the instructions above also inlined alongside the instructions inlined for the higher-order method? Inlining a static method (e.g., the delegate above) is better done by the VM. Moreover, that's what the current optimizer does, with the consequence that:

the current optimizer duplicates a closure body whenever that closure's apply() is invoked, the resulting code size taxes the JIT compiler. 

With the new optimizer, the only way to avoid the code-duplication above is to write "optimizer-quirks-aware" code, as was done for immutable.Range in: https://github.com/scala/scala/commit/7abb0c911a7c3d60057fbcab6fc3687322a67082

scabug commented 11 years ago

@retronym said: @Miguel: Just to confirm: does the new backend take care not to attach line number information from other sourcefiles, if the inlined method and inline target are in different compilation units?

scabug commented 11 years ago

@magarciaEPFL said: I guess the new backend currently doesn't pay attention to that distinction (debug info originating in other source files). I'll fix it after finding out.

scabug commented 10 years ago

@retronym said: Updating fix-by version to 2.11.5.

SethTisue commented 6 years ago

@lrytz should this stay open?

lrytz commented 6 years ago

no, let's track the rest at https://github.com/scala/scala-dev/issues/3