diff --git a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/InvokeWithExceptionNode.java b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/InvokeWithExceptionNode.java index ca01af2b655d..642d5fd15c40 100644 --- a/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/InvokeWithExceptionNode.java +++ b/compiler/src/org.graalvm.compiler.nodes/src/org/graalvm/compiler/nodes/InvokeWithExceptionNode.java @@ -248,6 +248,8 @@ public InvokeNode replaceWithInvoke() { AbstractBeginNode oldException = this.exceptionEdge; graph().replaceSplitWithFixed(this, newInvoke, this.next()); GraphUtil.killCFG(oldException); + // copy across any original node source position + newInvoke.setNodeSourcePosition(getNodeSourcePosition()); return newInvoke; } diff --git a/substratevm/mx.substratevm/testhello.py b/substratevm/mx.substratevm/testhello.py index 1861b8958520..32346d942bd3 100644 --- a/substratevm/mx.substratevm/testhello.py +++ b/substratevm/mx.substratevm/testhello.py @@ -87,6 +87,7 @@ def check(self, text, skip_fails=True): print(self) print(text) sys.exit(1) + return matches else: matches.append(match) line_idx += 1 @@ -95,6 +96,7 @@ def check(self, text, skip_fails=True): print(self) print(text) sys.exit(1) + return matches print(text) return matches @@ -124,6 +126,7 @@ def test(): # define some useful patterns address_pattern = '0x[0-9a-f]+' + hex_digits_pattern = '[0-9a-f]+' spaces_pattern = '[ \t]+' maybe_spaces_pattern = '[ \t]*' digits_pattern = '[0-9]+' @@ -168,29 +171,32 @@ def test(): # enable pretty printing of structures execute("set print pretty on") # set a break point at hello.Hello::main - # expect "Breakpoint 1 at 0x[0-9a-f]+: file hello.Hello.java, line 67." + # expect "Breakpoint 1 at 0x[0-9a-f]+: file hello.Hello.java, line 70." exec_string = execute("break hello.Hello::main") - rexp = r"Breakpoint 1 at %s: file hello/Hello\.java, line 67\."%address_pattern + rexp = r"Breakpoint 1 at %s: file hello/Hello\.java, line 70\."%address_pattern checker = Checker('break main', rexp) checker.check(exec_string) - # run the program + # run the program till the breakpoint execute("run") + execute("delete breakpoints") # list the line at the breakpoint - # expect "67 Greeter greeter = Greeter.greeter(args);" + # expect "70 Greeter greeter = Greeter.greeter(args);" exec_string = execute("list") - checker = Checker(r"list bp 1", "67%sGreeter greeter = Greeter\.greeter\(args\);"%spaces_pattern) + checker = Checker(r"list bp 1", "70%sGreeter greeter = Greeter\.greeter\(args\);"%spaces_pattern) checker.check(exec_string, skip_fails=False) # run a backtrace - # expect "#0 hello.Hello.main(java.lang.String[] *).* at hello.Hello.java:67" + # expect "#0 hello.Hello.main(java.lang.String[] *).* at hello.Hello.java:70" # expect "#1 0x[0-9a-f]+ in com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" exec_string = execute("backtrace") checker = Checker("backtrace hello.Hello::main", - [r"#0%shello\.Hello::main\(java\.lang\.String\[\] \*\)%s at hello/Hello\.java:67"%(spaces_pattern, wildcard_pattern), - r"#1%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, wildcard_pattern, package_pattern) - ]) + [r"#0%shello\.Hello::main\(java\.lang\.String\[\] \*\)%s at hello/Hello\.java:70"%(spaces_pattern, wildcard_pattern), + r"#1%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, wildcard_pattern, package_pattern), + r"#2%s com\.oracle\.svm\.core\.JavaMainWrapper::run%s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, wildcard_pattern, package_pattern), + r"#3%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s"%(spaces_pattern, address_pattern, hex_digits_pattern, wildcard_pattern) + ]) checker.check(exec_string, skip_fails=False) if can_print_data: @@ -263,14 +269,6 @@ def test(): r"%s = 1000"%(wildcard_pattern)) checker.check(exec_string, skip_fails=False) - - # set a break point at hello.Hello$DefaultGreeter::greet - # expect "Breakpoint 2 at 0x[0-9a-f]+: file hello/Target_Hello_DefaultGreeter.java, line [0-9]+." - exec_string = execute("break hello.Hello$DefaultGreeter::greet") - rexp = r"Breakpoint 2 at %s: file hello/Target_hello_Hello_DefaultGreeter\.java, line %s\."%(address_pattern, digits_pattern) - checker = Checker("break on substituted method", rexp) - checker.check(exec_string, skip_fails=False) - # look up greet methods # expect "All functions matching regular expression "greet":" # expect "" @@ -287,40 +285,26 @@ def test(): r"File hello/Target_hello_Hello_DefaultGreeter\.java:", r"%svoid hello.Hello\$DefaultGreeter::greet\(void\);"%maybe_spaces_pattern] checker = Checker("info func greet", rexp) - checker.check(exec_string, skip_fails=True) + checker.check(exec_string) # look up PrintStream.println methods # expect "All functions matching regular expression "java.io.PrintStream.println":" # expect "" # expect "File java.base/java/io/PrintStream.java:" - # expect " void java.io.PrintStream::println(java.lang.Object);" - # expect " void java.io.PrintStream::println(java.lang.String);" + # expect " void java.io.PrintStream::println(java.lang.Object *);" + # expect " void java.io.PrintStream::println(java.lang.String *);" exec_string = execute("info func java.io.PrintStream::println") - # checker = Checker("info func java.io.PrintStream::println", - # ["All functions matching regular expression \"java\\.io\\.PrintStream::println\":", - # "", - # "File .*java/io/PrintStream.java:", - # "[ \t]*void java.io.PrintStream::println\\(java\\.lang\\.Object \\*\\);", - # "[ \t]*void java.io.PrintStream::println\\(java\\.lang\\.String \\*\\);", - # ]) rexp = r"%svoid java.io.PrintStream::println\(java\.lang\.String \*\)"%maybe_spaces_pattern checker = Checker("info func java.io.PrintStream::println", rexp) checker.check(exec_string) - # set a break point at PrintStream.println(String) - # expect "Breakpoint 3 at 0x[0-9a-f]+: java.base/java/io/PrintStream.java, line [0-9]+." - exec_string = execute("break java.io.PrintStream::println(java.lang.String *)") - rexp = r"Breakpoint 3 at %s: file .*java/io/PrintStream\.java, line %s\."%(address_pattern, digits_pattern) - checker = Checker('break println', rexp) - checker.check(exec_string, skip_fails=False) - # step into method call execute("step") # list current line - # expect "34 if (args.length == 0) {" + # expect "37 if (args.length == 0) {" exec_string = execute("list") - rexp = r"34%sif \(args\.length == 0\) {"%spaces_pattern + rexp = r"37%sif \(args\.length == 0\) {"%spaces_pattern checker = Checker('list hello.Hello$Greeter.greeter', rexp) checker.check(exec_string, skip_fails=False) @@ -399,15 +383,14 @@ def test(): checker.check(exec_string, skip_fails=True) # run a backtrace - # expect "#0 _hello.Hello$Greeter::greeter(java.lang.String[]).* at hello.Hello.java:34" - # expect "#1 0x[0-9a-f]+ in _hello.Hello::main(java.lang.String[]).* at hello.Hello.java:67" - # expect "#2 0x[0-9a-f]+ in _com.oracle.svm.core.code.IsolateEnterStub.JavaMainWrapper_run_.* at [a-z/]+/JavaMainWrapper.java:[0-9]+" exec_string = execute("backtrace") checker = Checker("backtrace hello.Hello.Greeter::greeter", - [r"#0%shello\.Hello\$Greeter::greeter\(java\.lang\.String\[\] \*\)%s at hello/Hello\.java:34"%(spaces_pattern, wildcard_pattern), - r"#1%s%s in hello\.Hello::main\(java\.lang\.String\[\] \*\)%s at hello/Hello\.java:67"%(spaces_pattern, address_pattern, wildcard_pattern), - r"#2%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s at [a-z/]+/JavaMainWrapper\.java:%s"%(spaces_pattern, address_pattern, wildcard_pattern, digits_pattern) - ]) + [r"#0%shello\.Hello\$Greeter::greeter\(java\.lang\.String\[\] \*\)%s at hello/Hello\.java:37"%(spaces_pattern, wildcard_pattern), + r"#1%s%s in hello\.Hello::main\(java\.lang\.String\[\] \*\)%s at hello/Hello\.java:70"%(spaces_pattern, address_pattern, wildcard_pattern), + r"#2%s%s in com\.oracle\.svm\.core\.JavaMainWrapper::runCore%s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, address_pattern, wildcard_pattern, package_pattern), + r"#3%scom\.oracle\.svm\.core\.JavaMainWrapper::run%s at %sJavaMainWrapper\.java:[0-9]+"%(spaces_pattern, wildcard_pattern, package_pattern), + r"#4%s%s in com\.oracle\.svm\.core\.code\.IsolateEnterStub::JavaMainWrapper_run_%s%s"%(spaces_pattern, address_pattern, hex_digits_pattern, wildcard_pattern) + ]) checker.check(exec_string, skip_fails=False) # now step into inlined code @@ -426,16 +409,34 @@ def test(): print(checker) sys.exit(1) - # continue to next 2 breakpoints - execute("continue") + # set breakpoint at substituted method hello.Hello$DefaultGreeter::greet + # expect "Breakpoint 2 at 0x[0-9a-f]+: file hello/Target_Hello_DefaultGreeter.java, line [0-9]+." + exec_string = execute("break hello.Hello$DefaultGreeter::greet") + rexp = r"Breakpoint %s at %s: file hello/Target_hello_Hello_DefaultGreeter\.java, line %s\."%(digits_pattern, address_pattern, digits_pattern) + checker = Checker("break on substituted method", rexp) + checker.check(exec_string, skip_fails=False) + execute("delete breakpoints") + + # set a break point at standard library PrintStream.println(String) + # expect "Breakpoint 3 at 0x[0-9a-f]+: java.base/java/io/PrintStream.java, line [0-9]+." + exec_string = execute("break java.io.PrintStream::println(java.lang.String *)") + rexp = r"Breakpoint %s at %s: file .*java/io/PrintStream\.java, line %s\."%(digits_pattern, address_pattern, digits_pattern) + checker = Checker('break println', rexp) + checker.check(exec_string, skip_fails=False) + execute("continue") # run backtrace to check we are in java.io.PrintStream::println(java.lang.String) # expect "#0 java.io.PrintStream::println(java.lang.String).* at java.base/java/io/PrintStream.java:[0-9]+" - exec_string = execute("backtrace 1") - checker = Checker("backtrace 1 PrintStream::println", - [r"#0%sjava\.io\.PrintStream::println\(java\.lang\.String \*\)%s at %sjava/io/PrintStream.java:%s"%(spaces_pattern, wildcard_pattern, wildcard_pattern, digits_pattern)]) - checker.check(exec_string, skip_fails=False) + exec_string = execute("backtrace 6") + rexp = [r"#0%sjava\.io\.PrintStream::println\(java\.lang\.String \*\)%s at %sjava/io/PrintStream.java:%s"%(spaces_pattern, wildcard_pattern, wildcard_pattern, digits_pattern), + r"#1%s%s in hello\.SubstituteHelperClass::nestedGreet\(void\) \(\) at hello/Target_hello_Hello_DefaultGreeter\.java:59"%(spaces_pattern, address_pattern), + r"#2%s%s in hello\.SubstituteHelperClass::staticInlineGreet \(\) at hello/Target_hello_Hello_DefaultGreeter\.java:53"%(spaces_pattern, address_pattern), + r"#3%s hello\.SubstituteHelperClass::inlineGreet \(\) at hello/Target_hello_Hello_DefaultGreeter\.java:48"%(spaces_pattern), + r"#4%s hello\.Hello\$DefaultGreeter::greet\(void\) \(\) at hello/Target_hello_Hello_DefaultGreeter\.java:40"%(spaces_pattern), + r"#5%s%s in hello\.Hello::main\(java\.lang\.String\[\] \*\) \(\) at hello/Hello\.java:71"%(spaces_pattern, address_pattern)] + checker = Checker("backtrace PrintStream::println", rexp) + checker.check(exec_string) # list current line # expect "[0-9]+ synchronized (this) {" @@ -491,6 +492,196 @@ def test(): r"%s:%s\"java.io.PrintStream.*\""%(address_pattern, spaces_pattern)) checker.check(exec_string, skip_fails=False) + ### + # Tests for inlined methods + ### + + # print details of Hello type + exec_string = execute("ptype 'hello.Hello'") + rexp = [r"type = class hello\.Hello : public java\.lang\.Object {", + # ptype lists inlined methods although they are not listed with info func + r"%sprivate:"%spaces_pattern, + r"%sstatic void inlineA\(void\);"%spaces_pattern, + r"%sstatic void inlineCallChain\(void\);"%spaces_pattern, + r"%sstatic void inlineFrom\(void\);"%spaces_pattern, + r"%sstatic void inlineHere\(int\);"%spaces_pattern, + r"%sstatic void inlineIs\(void\);"%spaces_pattern, + r"%sstatic void inlineMee\(void\);"%spaces_pattern, + r"%sstatic void inlineMixTo\(int\);"%spaces_pattern, + r"%sstatic void inlineMoo\(void\);"%spaces_pattern, + r"%sstatic void inlineTailRecursion\(int\);"%spaces_pattern, + r"%sstatic void inlineTo\(int\);"%spaces_pattern, + r"%spublic:"%spaces_pattern, + r"%sstatic void main\(java\.lang\.String\[\] \*\);"%spaces_pattern, + r"%sprivate:"%spaces_pattern, + r"%sstatic void noInlineFoo\(void\);"%spaces_pattern, + r"%sstatic void noInlineHere\(int\);"%spaces_pattern, + r"%sstatic void noInlineTest\(void\);"%spaces_pattern, + r"%sstatic void noInlineThis\(void\);"%spaces_pattern, + r"}"] + checker = Checker('ptype hello.Hello', rexp) + checker.check(exec_string, skip_fails=False) + + # list methods matching regural expression "nlined", inline methods are not listed + exec_string = execute("info func nline") + rexp = [r"All functions matching regular expression \"nline\":", + r"File hello/Hello\.java:", + r"%svoid hello\.Hello::noInlineFoo\(void\);"%spaces_pattern, + r"%svoid hello\.Hello::noInlineHere\(int\);"%spaces_pattern, + r"%svoid hello\.Hello::noInlineTest\(void\);"%spaces_pattern, + r"%svoid hello\.Hello::noInlineThis\(void\);"%spaces_pattern] + checker = Checker('ptype info func nline', rexp) + checker.check(exec_string) + + # list inlineMee and inlineMoo and check that the listing maps to the inlined code instead of the actual code, + # although not ideal this is how GDB treats inlined code in C/C++ as well + rexp = [r"109%sSystem\.out\.println\(\"This is a cow\"\);"%spaces_pattern] + checker = Checker('list inlineMee', rexp) + checker.check(execute("list inlineMee")) + checker = Checker('list inlineMoo', rexp) + checker.check(execute("list inlineMoo")) + + execute("delete breakpoints") + # Set breakpoint at inlined method and step through its nested inline methods + exec_string = execute("break hello.Hello::inlineMee") + rexp = r"Breakpoint %s at %s: hello\.Hello::inlineMee\. \(3 locations\)"%(digits_pattern, address_pattern) + checker = Checker('break inlineMee', rexp) + checker.check(exec_string, skip_fails=False) + + exec_string = execute("info break 4") + # The breakpoint will be set to the inlined code instead of the actual code, + # although not ideal this is how GDB treats inlined code in C/C++ as well + rexp = [r"4.1%sy%s%s in hello\.Hello::inlineMee at hello/Hello\.java:109"%(spaces_pattern, spaces_pattern, address_pattern), + r"4.2%sy%s%s in hello\.Hello::inlineMee at hello/Hello\.java:109"%(spaces_pattern, spaces_pattern, address_pattern), + r"4.3%sy%s%s in hello\.Hello::inlineMee at hello/Hello\.java:109"%(spaces_pattern, spaces_pattern, address_pattern)] + checker = Checker('info break inlineMee', rexp) + checker.check(exec_string) + + execute("continue") + exec_string = execute("list") + rexp = [r"104%sinlineMoo\(\);"%spaces_pattern] + checker = Checker('hit break at inlineMee', rexp) + checker.check(exec_string, skip_fails=False) + execute("step") + exec_string = execute("list") + rexp = [r"109%sSystem\.out\.println\(\"This is a cow\"\);"%spaces_pattern] + checker = Checker('step in inlineMee', rexp) + checker.check(exec_string, skip_fails=False) + exec_string = execute("backtrace 4") + rexp = [r"#0%shello\.Hello::inlineMoo \(\) at hello/Hello\.java:109"%spaces_pattern, + r"#1%shello\.Hello::inlineMee \(\) at hello/Hello\.java:104"%spaces_pattern, + r"#2%shello\.Hello::noInlineFoo\(void\) \(\) at hello/Hello\.java:94"%spaces_pattern, + r"#3%s%s in hello\.Hello::main\(java\.lang\.String\[\] \*\) \(\) at hello/Hello\.java:85"%(spaces_pattern, address_pattern)] + checker = Checker('backtrace inlineMee', rexp) + checker.check(exec_string, skip_fails=False) + + execute("continue") + exec_string = execute("list") + rexp = [r"104%sinlineMoo\(\);"%spaces_pattern] + checker = Checker('hit break at inlineMee', rexp) + checker.check(exec_string, skip_fails=False) + execute("step") + exec_string = execute("list") + rexp = [r"109%sSystem\.out\.println\(\"This is a cow\"\);"%spaces_pattern] + checker = Checker('step in inlineMee', rexp) + checker.check(exec_string, skip_fails=False) + exec_string = execute("backtrace 4") + rexp = [r"#0%shello\.Hello::inlineMoo \(\) at hello/Hello\.java:109"%spaces_pattern, + r"#1%shello\.Hello::inlineMee \(\) at hello/Hello\.java:104"%spaces_pattern, + r"#2%shello\.Hello::inlineCallChain \(\) at hello/Hello\.java:99"%spaces_pattern, + r"#3%shello\.Hello::main\(java\.lang\.String\[\] \*\) \(\) at hello/Hello\.java:86"%spaces_pattern] + checker = Checker('backtrace inlineMee 2', rexp) + checker.check(exec_string, skip_fails=False) + + execute("delete breakpoints") + exec_string = execute("break hello.Hello::noInlineTest") + rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 129\."%(digits_pattern, address_pattern) + checker = Checker('break noInlineTest', rexp) + checker.check(exec_string, skip_fails=False) + + execute("continue") + exec_string = execute("list") + rexp = r"129%sSystem.out.println\(\"This is a test\"\);"%spaces_pattern + checker = Checker('hit breakpoint in noInlineTest', rexp) + checker.check(exec_string, skip_fails=False) + exec_string = execute("backtrace 5") + rexp = [r"#0%shello\.Hello::noInlineTest\(void\) \(\) at hello/Hello\.java:129"%(spaces_pattern), + r"#1%s%s in hello\.Hello::inlineA \(\) at hello/Hello\.java:124"%(spaces_pattern, address_pattern), + r"#2%shello\.Hello::inlineIs \(\) at hello/Hello\.java:119"%(spaces_pattern), + r"#3%shello\.Hello::noInlineThis\(void\) \(\) at hello/Hello\.java:114"%(spaces_pattern), + r"#4%s%s in hello\.Hello::main\(java\.lang\.String\[\] \*\) \(\) at hello/Hello\.java:87"%(spaces_pattern, address_pattern)] + checker = Checker('backtrace in inlineMethod', rexp) + checker.check(exec_string, skip_fails=False) + + execute("delete breakpoints") + exec_string = execute("break Hello.java:149") + rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 149\."%(digits_pattern, address_pattern) + checker = Checker('break Hello.java:149', rexp) + checker.check(exec_string) + + execute("continue 5") + exec_string = execute("backtrace 14") + rexp = [r"#0%shello\.Hello::inlineMixTo \(\) at hello/Hello\.java:149"%(spaces_pattern), + r"#1%shello\.Hello::noInlineHere\(int\) \(\) at hello/Hello\.java:141"%(spaces_pattern), + r"#2%s%s in hello\.Hello::inlineMixTo \(\) at hello/Hello\.java:147"%(spaces_pattern, address_pattern), + r"#3%shello\.Hello::noInlineHere\(int\) \(\) at hello/Hello\.java:141"%(spaces_pattern), + r"#4%s%s in hello\.Hello::inlineMixTo \(\) at hello/Hello\.java:147"%(spaces_pattern, address_pattern), + r"#5%shello\.Hello::noInlineHere\(int\) \(\) at hello/Hello\.java:141"%(spaces_pattern), + r"#6%s%s in hello\.Hello::inlineMixTo \(\) at hello/Hello\.java:147"%(spaces_pattern, address_pattern), + r"#7%shello\.Hello::noInlineHere\(int\) \(\) at hello/Hello\.java:141"%(spaces_pattern), + r"#8%s%s in hello\.Hello::inlineMixTo \(\) at hello/Hello\.java:147"%(spaces_pattern, address_pattern), + r"#9%shello\.Hello::noInlineHere\(int\) \(\) at hello/Hello\.java:141"%(spaces_pattern), + r"#10%s%s in hello\.Hello::inlineMixTo \(\) at hello/Hello\.java:147"%(spaces_pattern, address_pattern), + r"#11%shello\.Hello::noInlineHere\(int\) \(\) at hello/Hello\.java:141"%(spaces_pattern), + r"#12%s%s in hello\.Hello::inlineFrom \(\) at hello/Hello\.java:134"%(spaces_pattern, address_pattern), + r"#13%shello\.Hello::main\(java\.lang\.String\[\] \*\) \(\) at hello/Hello\.java:88"%(spaces_pattern)] + checker = Checker('backtrace in recursive inlineMixTo', rexp) + checker.check(exec_string, skip_fails=False) + + execute("delete breakpoints") + exec_string = execute("break Hello.java:162") + rexp = r"Breakpoint %s at %s: Hello\.java:162\. \(2 locations\)"%(digits_pattern, address_pattern) + checker = Checker('break Hello.java:162', rexp) + checker.check(exec_string) + + execute("continue 5") + exec_string = execute("backtrace 14") + rexp = [r"#0%shello\.Hello::inlineTo\(int\) \(\) at hello/Hello\.java:162"%(spaces_pattern), + r"#1%s%s in hello\.Hello::inlineHere \(\) at hello/Hello\.java:154"%(spaces_pattern, address_pattern), + r"#2%shello\.Hello::inlineTo\(int\) \(\) at hello/Hello\.java:160"%(spaces_pattern), + r"#3%s%s in hello\.Hello::inlineHere \(\) at hello/Hello\.java:154"%(spaces_pattern, address_pattern), + r"#4%shello\.Hello::inlineTo\(int\) \(\) at hello/Hello\.java:160"%(spaces_pattern), + r"#5%s%s in hello\.Hello::inlineHere \(\) at hello/Hello\.java:154"%(spaces_pattern, address_pattern), + r"#6%shello\.Hello::inlineTo\(int\) \(\) at hello/Hello\.java:160"%(spaces_pattern), + r"#7%s%s in hello\.Hello::inlineHere \(\) at hello/Hello\.java:154"%(spaces_pattern, address_pattern), + r"#8%shello\.Hello::inlineTo\(int\) \(\) at hello/Hello\.java:160"%(spaces_pattern), + r"#9%s%s in hello\.Hello::inlineHere \(\) at hello/Hello\.java:154"%(spaces_pattern, address_pattern), + r"#10%shello\.Hello::inlineTo \(\) at hello/Hello\.java:160"%(spaces_pattern), + r"#11%shello\.Hello::inlineHere \(\) at hello/Hello\.java:154"%(spaces_pattern), + r"#12%shello\.Hello::inlineFrom \(\) at hello/Hello\.java:135"%(spaces_pattern), + r"#13%shello\.Hello::main\(java\.lang\.String\[\] \*\) \(\) at hello/Hello\.java:88"%(spaces_pattern)] + checker = Checker('backtrace in recursive inlineTo', rexp) + checker.check(exec_string, skip_fails=False) + + execute("delete breakpoints") + exec_string = execute("break Hello.java:168") + rexp = r"Breakpoint %s at %s: file hello/Hello\.java, line 168\."%(digits_pattern, address_pattern) + checker = Checker('break Hello.java:168', rexp) + checker.check(exec_string) + + execute("continue 5") + exec_string = execute("backtrace 8") + rexp = [r"#0%shello\.Hello::inlineTailRecursion\(int\) \(\) at hello/Hello\.java:168"%(spaces_pattern), + r"#1%s%s in hello\.Hello::inlineTailRecursion\(int\) \(\) at hello/Hello\.java:171"%(spaces_pattern, address_pattern), + r"#2%s%s in hello\.Hello::inlineTailRecursion\(int\) \(\) at hello/Hello\.java:171"%(spaces_pattern, address_pattern), + r"#3%s%s in hello\.Hello::inlineTailRecursion\(int\) \(\) at hello/Hello\.java:171"%(spaces_pattern, address_pattern), + r"#4%s%s in hello\.Hello::inlineTailRecursion\(int\) \(\) at hello/Hello\.java:171"%(spaces_pattern, address_pattern), + r"#5%s%s in hello\.Hello::inlineTailRecursion \(\) at hello/Hello\.java:171"%(spaces_pattern, address_pattern), + r"#6%shello\.Hello::inlineFrom \(\) at hello/Hello\.java:136"%(spaces_pattern), + r"#7%shello\.Hello::main\(java\.lang\.String\[\] \*\) \(\) at hello/Hello\.java:88"%(spaces_pattern)] + checker = Checker('backtrace in recursive inlineTo', rexp) + checker.check(exec_string, skip_fails=False) + print(execute("quit 0")) test() diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java index 2057fde4a297..643ad61ec936 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/ClassEntry.java @@ -30,7 +30,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; -import java.util.ListIterator; import java.util.Map; import org.graalvm.compiler.debug.DebugContext; @@ -63,6 +62,10 @@ public class ClassEntry extends StructureTypeEntry { * Details of methods located in this instance. */ protected List methods; + /** + * An index of all currently known methods keyed by the unique local symbol name of the method. + */ + private Map methodsIndex; /** * A list recording details of all primary ranges included in this class sorted by ascending * address range. @@ -98,6 +101,7 @@ public ClassEntry(String className, FileEntry fileEntry, int size) { this.interfaces = new ArrayList<>(); this.fileEntry = fileEntry; this.methods = new ArrayList<>(); + this.methodsIndex = new HashMap<>(); this.primaryEntries = new ArrayList<>(); this.primaryIndex = new HashMap<>(); this.localFiles = new ArrayList<>(); @@ -137,9 +141,7 @@ public void addDebugInfo(DebugInfoBase debugInfoBase, DebugTypeInfo debugTypeInf /* Add details of fields and field types */ debugInstanceTypeInfo.fieldInfoProvider().forEach(debugFieldInfo -> this.processField(debugFieldInfo, debugInfoBase, debugContext)); /* Add details of methods and method types */ - debugInstanceTypeInfo.methodInfoProvider().forEach(methodFieldInfo -> this.methods.add(this.processMethod(methodFieldInfo, debugInfoBase, debugContext, false))); - /* Sort methods to improve lookup speed */ - this.methods.sort(MethodEntry::compareTo); + debugInstanceTypeInfo.methodInfoProvider().forEach(debugMethodInfo -> this.processMethod(debugMethodInfo, debugInfoBase, debugContext)); } public void indexPrimary(Range primary, List frameSizeInfos, int frameSize) { @@ -168,13 +170,19 @@ public void indexSubRange(Range subrange) { /* We should already have seen the primary range. */ assert primaryEntry != null; assert primaryEntry.getClassEntry() == this; - primaryEntry.addSubRange(subrange); FileEntry subFileEntry = subrange.getFileEntry(); if (subFileEntry != null) { indexLocalFileEntry(subFileEntry); } } + private void indexMethodEntry(MethodEntry methodEntry) { + String methodName = methodEntry.getSymbolName(); + assert methodsIndex.get(methodName) == null : methodName; + methods.add(methodEntry); + methodsIndex.put(methodName, methodEntry); + } + private void indexLocalFileEntry(FileEntry localFileEntry) { if (localFilesIndex.get(localFileEntry) == null) { localFiles.add(localFileEntry); @@ -273,8 +281,8 @@ private void processInterface(String interfaceName, DebugInfoBase debugInfoBase, interfaceClassEntry.addImplementor(this, debugContext); } - protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBase debugInfoBase, DebugContext debugContext, boolean fromRangeInfo) { - String methodName = debugInfoBase.uniqueDebugString(debugMethodInfo.name()); + protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { + String methodName = debugMethodInfo.name(); String resultTypeName = TypeEntry.canonicalize(debugMethodInfo.valueType()); int modifiers = debugMethodInfo.modifiers(); List paramTypes = debugMethodInfo.paramTypes(); @@ -297,8 +305,11 @@ protected MethodEntry processMethod(DebugMethodInfo debugMethodInfo, DebugInfoBa * substitution */ FileEntry methodFileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo); - return new MethodEntry(methodFileEntry, debugMethodInfo.symbolNameForMethod(), methodName, this, resultType, - paramTypeArray, paramNameArray, modifiers, debugMethodInfo.isDeoptTarget(), fromRangeInfo); + MethodEntry methodEntry = new MethodEntry(debugInfoBase, debugMethodInfo, methodFileEntry, methodName, + this, resultType, paramTypeArray, paramNameArray); + indexMethodEntry(methodEntry); + + return methodEntry; } @Override @@ -340,35 +351,19 @@ public ClassEntry getSuperClass() { } public MethodEntry ensureMethodEntryForDebugRangeInfo(DebugRangeInfo debugRangeInfo, DebugInfoBase debugInfoBase, DebugContext debugContext) { - assert listIsSorted(methods); - ListIterator methodIterator = methods.listIterator(); - String methodName = debugInfoBase.uniqueDebugString(debugRangeInfo.name()); - String paramSignature = debugRangeInfo.paramSignature(); - String returnTypeName = debugRangeInfo.valueType(); - while (methodIterator.hasNext()) { - MethodEntry methodEntry = methodIterator.next(); - int comparisonResult = methodEntry.compareTo(methodName, paramSignature, returnTypeName); - if (comparisonResult == 0) { - methodEntry.setInRangeAndUpdateFileEntry(debugInfoBase, debugRangeInfo); - if (methodEntry.fileEntry != null) { - /* Ensure that the methodEntry's fileEntry is present in the localsFileIndex */ - indexLocalFileEntry(methodEntry.fileEntry); - } - return methodEntry; - } else if (comparisonResult > 0) { - methodIterator.previous(); - break; + + MethodEntry methodEntry = methodsIndex.get(debugRangeInfo.symbolNameForMethod()); + if (methodEntry == null) { + methodEntry = processMethod(debugRangeInfo, debugInfoBase, debugContext); + } else { + methodEntry.updateRangeInfo(debugInfoBase, debugRangeInfo); + /* Ensure that the methodEntry's fileEntry is present in the localsFileIndex */ + FileEntry methodFileEntry = methodEntry.fileEntry; + if (methodFileEntry != null) { + indexLocalFileEntry(methodFileEntry); } } - MethodEntry newMethodEntry = processMethod(debugRangeInfo, debugInfoBase, debugContext, true); - methodIterator.add(newMethodEntry); - return newMethodEntry; - } - - private static boolean listIsSorted(List list) { - List copy = new ArrayList<>(list); - copy.sort(MethodEntry::compareTo); - return list.equals(copy); + return methodEntry; } public List getMethods() { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java index e328446d3b00..9e336bbcd562 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/DebugInfoBase.java @@ -35,6 +35,7 @@ import java.util.Map; import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFileInfo; +import jdk.vm.ci.meta.ResolvedJavaType; import org.graalvm.compiler.debug.DebugContext; import com.oracle.objectfile.debuginfo.DebugInfoProvider; @@ -103,7 +104,7 @@ public abstract class DebugInfoBase { /** * index of already seen classes. */ - private Map primaryClassesIndex = new HashMap<>(); + private Map primaryClassesIndex = new HashMap<>(); /** * Index of files which contain primary or secondary ranges. */ @@ -238,38 +239,24 @@ public void installDebugInfo(DebugInfoProvider debugInfoProvider) { */ String fileName = debugCodeInfo.fileName(); Path filePath = debugCodeInfo.filePath(); - String className = TypeEntry.canonicalize(debugCodeInfo.ownerType()); + ResolvedJavaType ownerType = debugCodeInfo.ownerType(); String methodName = debugCodeInfo.name(); int lo = debugCodeInfo.addressLo(); int hi = debugCodeInfo.addressHi(); int primaryLine = debugCodeInfo.line(); /* Search for a method defining this primary range. */ - ClassEntry classEntry = ensureClassEntry(className); + ClassEntry classEntry = ensureClassEntry(ownerType); MethodEntry methodEntry = classEntry.ensureMethodEntryForDebugRangeInfo(debugCodeInfo, this, debugContext); Range primaryRange = new Range(stringTable, methodEntry, lo, hi, primaryLine); - debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", className, methodName, filePath, fileName, primaryLine, lo, hi); + debugContext.log(DebugContext.INFO_LEVEL, "PrimaryRange %s.%s %s %s:%d [0x%x, 0x%x]", ownerType.toJavaName(), methodName, filePath, fileName, primaryLine, lo, hi); classEntry.indexPrimary(primaryRange, debugCodeInfo.getFrameSizeChanges(), debugCodeInfo.getFrameSize()); - debugCodeInfo.lineInfoProvider().forEach(debugLineInfo -> { - String fileNameAtLine = debugLineInfo.fileName(); - Path filePathAtLine = debugLineInfo.filePath(); - String classNameAtLine = TypeEntry.canonicalize(debugLineInfo.ownerType()); - String methodNameAtLine = debugLineInfo.name(); - int loAtLine = lo + debugLineInfo.addressLo(); - int hiAtLine = lo + debugLineInfo.addressHi(); - int line = debugLineInfo.line(); - /* - * Record all subranges even if they have no line or file so we at least get a - * symbol for them and don't see a break in the address range. - */ - ClassEntry subClassEntry = ensureClassEntry(classNameAtLine); - MethodEntry subMethodEntry = subClassEntry.ensureMethodEntryForDebugRangeInfo(debugLineInfo, this, debugContext); - Range subRange = new Range(stringTable, subMethodEntry, loAtLine, hiAtLine, line, primaryRange); - classEntry.indexSubRange(subRange); - try (DebugContext.Scope s = debugContext.scope("Subranges")) { - debugContext.log(DebugContext.VERBOSE_LEVEL, "SubRange %s.%s %s %s:%d 0x%x, 0x%x]", classNameAtLine, methodNameAtLine, filePathAtLine, fileNameAtLine, line, loAtLine, hiAtLine); - } - }); + /* + * Record all subranges even if they have no line or file so we at least get a symbol + * for them and don't see a break in the address range. + */ + debugCodeInfo.lineInfoProvider().forEach(debugLineInfo -> recursivelyAddSubRanges(debugLineInfo, primaryRange, classEntry, debugContext)); + primaryRange.mergeSubranges(debugContext); })); debugInfoProvider.dataInfoProvider().forEach(debugDataInfo -> debugDataInfo.debugContext((debugContext) -> { @@ -350,17 +337,60 @@ ClassEntry lookupClassEntry(String typeName) { return (ClassEntry) typeEntry; } - private ClassEntry ensureClassEntry(String className) { + /** + * Recursively creates subranges based on DebugLineInfo including, and appropriately linking, + * nested inline subranges. + * + * @param lineInfo + * @param primaryRange + * @param classEntry + * @param debugContext + * @return the subrange for {@code lineInfo} linked with all its caller subranges up to the + * primaryRange + */ + @SuppressWarnings("try") + private Range recursivelyAddSubRanges(DebugInfoProvider.DebugLineInfo lineInfo, Range primaryRange, ClassEntry classEntry, DebugContext debugContext) { + if (lineInfo == null) { + return primaryRange; + } + /* + * We still insert subranges for the primary method but they don't actually count as inline. + * we only need a range so that subranges for inline code can refer to the top level line + * number + */ + boolean isInline = lineInfo.getCaller() != null; + assert (isInline || (lineInfo.name().equals(primaryRange.getMethodName()) && TypeEntry.canonicalize(lineInfo.ownerType().toJavaName()).equals(primaryRange.getClassName()))); + + Range caller = recursivelyAddSubRanges(lineInfo.getCaller(), primaryRange, classEntry, debugContext); + final String fileName = lineInfo.fileName(); + final Path filePath = lineInfo.filePath(); + final ResolvedJavaType ownerType = lineInfo.ownerType(); + final String methodName = lineInfo.name(); + final int lo = primaryRange.getLo() + lineInfo.addressLo(); + final int hi = primaryRange.getLo() + lineInfo.addressHi(); + final int line = lineInfo.line(); + ClassEntry subRangeClassEntry = ensureClassEntry(ownerType); + MethodEntry subRangeMethodEntry = subRangeClassEntry.ensureMethodEntryForDebugRangeInfo(lineInfo, this, debugContext); + Range subRange = new Range(stringTable, subRangeMethodEntry, lo, hi, line, primaryRange, isInline, caller); + classEntry.indexSubRange(subRange); + try (DebugContext.Scope s = debugContext.scope("Subranges")) { + debugContext.log(DebugContext.VERBOSE_LEVEL, "SubRange %s.%s %s %s:%d 0x%x, 0x%x]", + ownerType.toJavaName(), methodName, filePath, fileName, line, lo, hi); + } + return subRange; + } + + private ClassEntry ensureClassEntry(ResolvedJavaType type) { /* See if we already have an entry. */ - ClassEntry classEntry = primaryClassesIndex.get(className); + ClassEntry classEntry = primaryClassesIndex.get(type); if (classEntry == null) { - TypeEntry typeEntry = typesIndex.get(className); + TypeEntry typeEntry = typesIndex.get(TypeEntry.canonicalize(type.toJavaName())); assert (typeEntry != null && typeEntry.isClass()); classEntry = (ClassEntry) typeEntry; primaryClasses.add(classEntry); - primaryClassesIndex.put(className, classEntry); + primaryClassesIndex.put(type, classEntry); } - assert (classEntry.getTypeName().equals(className)); + assert (classEntry.getTypeName().equals(TypeEntry.canonicalize(type.toJavaName()))); return classEntry; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java index ab8471a6a348..a5ab87a66717 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/MethodEntry.java @@ -26,31 +26,33 @@ package com.oracle.objectfile.debugentry; -import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugRangeInfo; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugCodeInfo; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugLineInfo; +import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugMethodInfo; -import java.util.Arrays; -import java.util.stream.Collectors; - -public class MethodEntry extends MemberEntry implements Comparable { +public class MethodEntry extends MemberEntry { final TypeEntry[] paramTypes; final String[] paramNames; - public final boolean isDeoptTarget; - boolean isInRange; - + static final int DEOPT = 1 << 0; + static final int IN_RANGE = 1 << 1; + static final int INLINED = 1 << 2; + int flags; final String symbolName; - private String signature; - public MethodEntry(FileEntry fileEntry, String symbolName, String methodName, ClassEntry ownerType, - TypeEntry valueType, TypeEntry[] paramTypes, String[] paramNames, int modifiers, - boolean isDeoptTarget, boolean isInRange) { - super(fileEntry, methodName, ownerType, valueType, modifiers); + public MethodEntry(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo, + FileEntry fileEntry, String methodName, ClassEntry ownerType, + TypeEntry valueType, TypeEntry[] paramTypes, String[] paramNames) { + super(fileEntry, methodName, ownerType, valueType, debugMethodInfo.modifiers()); assert ((paramTypes == null && paramNames == null) || (paramTypes != null && paramNames != null && paramTypes.length == paramNames.length)); this.paramTypes = paramTypes; this.paramNames = paramNames; - this.isDeoptTarget = isDeoptTarget; - this.isInRange = isInRange; - this.symbolName = symbolName; + this.symbolName = debugMethodInfo.symbolNameForMethod(); + this.flags = 0; + if (debugMethodInfo.isDeoptTarget()) { + setIsDeopt(); + } + updateRangeInfo(debugInfoBase, debugMethodInfo); } public String methodName() { @@ -91,8 +93,28 @@ public String getParamName(int idx) { return paramNames[idx]; } + private void setIsDeopt() { + flags |= DEOPT; + } + + public boolean isDeopt() { + return (flags & DEOPT) != 0; + } + + private void setIsInRange() { + flags |= IN_RANGE; + } + public boolean isInRange() { - return isInRange; + return (flags & IN_RANGE) != 0; + } + + private void setIsInlined() { + flags |= INLINED; + } + + public boolean isInlined() { + return (flags & INLINED) != 0; } /** @@ -101,59 +123,37 @@ public boolean isInRange() { * to the original source file, thus it will be wrong for substituted methods. As a result when * setting a MethodEntry as isInRange we also make sure that its fileEntry reflects the file * info associated with the corresponding Range. - * + * * @param debugInfoBase - * @param debugRangeInfo + * @param debugMethodInfo */ - public void setInRangeAndUpdateFileEntry(DebugInfoBase debugInfoBase, DebugRangeInfo debugRangeInfo) { - if (isInRange) { - assert fileEntry == debugInfoBase.ensureFileEntry(debugRangeInfo); - return; + public void updateRangeInfo(DebugInfoBase debugInfoBase, DebugMethodInfo debugMethodInfo) { + if (debugMethodInfo instanceof DebugLineInfo) { + DebugLineInfo lineInfo = (DebugLineInfo) debugMethodInfo; + if (lineInfo.getCaller() != null) { + /* this is a real inlined method not just a top level primary range */ + setIsInlined(); + } + } else if (debugMethodInfo instanceof DebugCodeInfo) { + /* this method has been seen in a primary range */ + if (isInRange()) { + /* it has already been seen -- just check for consistency */ + assert fileEntry == debugInfoBase.ensureFileEntry(debugMethodInfo); + } else { + /* + * If the MethodEntry was added by traversing the DeclaredMethods of a Class its + * fileEntry may point to the original source file, which will be wrong for + * substituted methods. As a result when setting a MethodEntry as isInRange we also + * make sure that its fileEntry reflects the file info associated with the + * corresponding Range. + */ + setIsInRange(); + fileEntry = debugInfoBase.ensureFileEntry(debugMethodInfo); + } } - isInRange = true; - /* - * If the MethodEntry was added by traversing the DeclaredMethods of a Class its fileEntry - * will point to the original source file, thus it will be wrong for substituted methods. As - * a result when setting a MethodEntry as isInRange we also make sure that its fileEntry - * reflects the file info associated with the corresponding Range. - */ - fileEntry = debugInfoBase.ensureFileEntry(debugRangeInfo); } public String getSymbolName() { return symbolName; } - - private String getSignature() { - if (signature == null) { - signature = Arrays.stream(paramTypes).map(TypeEntry::getTypeName).collect(Collectors.joining(", ")); - } - return signature; - } - - public int compareTo(String methodName, String paramSignature, String returnTypeName) { - int nameComparison = memberName.compareTo(methodName); - if (nameComparison != 0) { - return nameComparison; - } - int typeComparison = valueType.getTypeName().compareTo(returnTypeName); - if (typeComparison != 0) { - return typeComparison; - } - return getSignature().compareTo(paramSignature); - } - - @Override - public int compareTo(MethodEntry other) { - assert other != null; - int nameComparison = methodName().compareTo(other.methodName()); - if (nameComparison != 0) { - return nameComparison; - } - int typeComparison = valueType.getTypeName().compareTo(other.valueType.getTypeName()); - if (typeComparison != 0) { - return typeComparison; - } - return getSignature().compareTo(other.getSignature()); - } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimaryEntry.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimaryEntry.java index c61e3428d140..7c7ed80295e6 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimaryEntry.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/PrimaryEntry.java @@ -28,7 +28,8 @@ import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; -import java.util.ArrayList; +import java.util.ArrayDeque; +import java.util.Iterator; import java.util.List; /** @@ -43,10 +44,6 @@ public class PrimaryEntry { * Details of the class owning this range. */ private ClassEntry classEntry; - /** - * A list of subranges associated with the primary range. - */ - private List subranges; /** * Details of of compiled method frame size changes. */ @@ -59,22 +56,10 @@ public class PrimaryEntry { public PrimaryEntry(Range primary, List frameSizeInfos, int frameSize, ClassEntry classEntry) { this.primary = primary; this.classEntry = classEntry; - this.subranges = new ArrayList<>(); this.frameSizeInfos = frameSizeInfos; this.frameSize = frameSize; } - public void addSubRange(Range subrange) { - /* - * We should not see a subrange more than once. - */ - assert !subranges.contains(subrange); - /* - * We need to generate a file table entry for all ranges. - */ - subranges.add(subrange); - } - public Range getPrimary() { return primary; } @@ -83,8 +68,88 @@ public ClassEntry getClassEntry() { return classEntry; } - public List getSubranges() { - return subranges; + /** + * Returns an iterator that traverses all the callees of the primary range associated with this + * entry. The iterator performs a depth-first pre-order traversal of the call tree. + * + * @return the iterator + */ + public Iterator topDownRangeIterator() { + return new Iterator() { + final ArrayDeque workStack = new ArrayDeque<>(); + Range current = primary.getFirstCallee(); + + @Override + public boolean hasNext() { + return current != null; + } + + @Override + public Range next() { + assert hasNext(); + Range result = current; + forward(); + return result; + } + + private void forward() { + Range sibling = current.getSiblingCallee(); + assert sibling == null || (current.getHi() <= sibling.getLo()) : current.getHi() + " > " + sibling.getLo(); + if (!current.isLeaf()) { + /* save next sibling while we process the children */ + if (sibling != null) { + workStack.push(sibling); + } + current = current.getFirstCallee(); + } else if (sibling != null) { + current = sibling; + } else { + /* + * Return back up to parents' siblings, use pollFirst instead of pop to return + * null in case the work stack is empty + */ + current = workStack.pollFirst(); + } + } + }; + } + + /** + * Returns an iterator that traverses the callees of the primary range associated with this + * entry and returns only the leafs. The iterator performs a depth-first pre-order traversal of + * the call tree returning only ranges with no callees. + * + * @return the iterator + */ + public Iterator leafRangeIterator() { + final Iterator iter = topDownRangeIterator(); + return new Iterator() { + Range current = forwardLeaf(iter); + + @Override + public boolean hasNext() { + return current != null; + } + + @Override + public Range next() { + assert hasNext(); + Range result = current; + current = forwardLeaf(iter); + return result; + } + + private Range forwardLeaf(Iterator t) { + if (t.hasNext()) { + Range next = t.next(); + while (next != null && !next.isLeaf()) { + next = t.next(); + } + return next; + } + return null; + } + }; } public List getFrameSizeInfos() { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/Range.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/Range.java index 5f7f3fe3bdab..43c64a540e9d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/Range.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debugentry/Range.java @@ -26,46 +26,99 @@ package com.oracle.objectfile.debugentry; +import org.graalvm.compiler.debug.DebugContext; + /** * Details of a specific address range in a compiled method either a primary range identifying a * whole method or a sub-range identifying a sequence of instructions that belong to an inlined - * method. + * method. Each sub-range is linked with its caller and its callees, forming a call tree. */ - public class Range { private static final String CLASS_DELIMITER = "."; + private Range caller; private final MethodEntry methodEntry; + private final String fullMethodName; private final String fullMethodNameWithParams; private final int lo; - private final int hi; + private int hi; private final int line; - /* - * This is null for a primary range. + private final boolean isInlined; + private final int depth; + /** + * This is null for a primary range. For sub ranges it holds the root of the call tree they + * belong to. */ private final Range primary; + /* + * Support for tree of nested inline callee ranges + */ + + /** + * The first direct callee whose range is wholly contained in this range. + */ + private Range firstCallee; + + /** + * The last direct callee whose range is wholly contained in this range. + */ + private Range lastCallee; + + /** + * A link to a sibling callee, i.e., a range sharing the same caller with this range. + */ + private Range siblingCallee; + /* * Create a primary range. */ public Range(StringTable stringTable, MethodEntry methodEntry, int lo, int hi, int line) { - this(stringTable, methodEntry, lo, hi, line, null); + this(stringTable, methodEntry, lo, hi, line, null, false, null); } /* * Create a primary or secondary range. */ - public Range(StringTable stringTable, MethodEntry methodEntry, int lo, int hi, int line, Range primary) { + public Range(StringTable stringTable, MethodEntry methodEntry, int lo, int hi, int line, Range primary, boolean isInline, Range caller) { assert methodEntry != null; if (methodEntry.fileEntry != null) { stringTable.uniqueDebugString(methodEntry.fileEntry.getFileName()); stringTable.uniqueDebugString(methodEntry.fileEntry.getPathName()); } this.methodEntry = methodEntry; + this.fullMethodName = isInline ? stringTable.uniqueDebugString(constructClassAndMethodName()) : stringTable.uniqueString(constructClassAndMethodName()); this.fullMethodNameWithParams = stringTable.uniqueString(constructClassAndMethodNameWithParams()); this.lo = lo; this.hi = hi; this.line = line; + this.isInlined = isInline; this.primary = primary; + this.firstCallee = null; + this.lastCallee = null; + this.siblingCallee = null; + this.caller = caller; + if (caller != null) { + caller.addCallee(this); + } + if (this.isPrimary()) { + this.depth = -1; + } else { + this.depth = caller.depth + 1; + } + } + + private void addCallee(Range callee) { + assert this.lo <= callee.lo; + assert this.hi >= callee.hi; + assert callee.caller == this; + assert callee.siblingCallee == null; + if (this.firstCallee == null) { + assert this.lastCallee == null; + this.firstCallee = this.lastCallee = callee; + } else { + this.lastCallee.siblingCallee = callee; + this.lastCallee = callee; + } } public boolean contains(Range other) { @@ -105,7 +158,7 @@ public int getLine() { } public String getFullMethodName() { - return constructClassAndMethodName(); + return fullMethodName; } public String getFullMethodNameWithParams() { @@ -113,7 +166,7 @@ public String getFullMethodNameWithParams() { } public boolean isDeoptTarget() { - return methodEntry.isDeoptTarget; + return methodEntry.isDeopt(); } private String getExtendedMethodName(boolean includeClass, boolean includeParams, boolean includeReturnType) { @@ -167,4 +220,120 @@ public String getFileName() { public MethodEntry getMethodEntry() { return methodEntry; } + + public boolean isInlined() { + return isInlined; + } + + public Range getCaller() { + return caller; + } + + public Range getFirstCallee() { + return firstCallee; + } + + public Range getSiblingCallee() { + return siblingCallee; + } + + public Range getLastCallee() { + return lastCallee; + } + + public boolean isLeaf() { + return firstCallee == null; + } + + public int getDepth() { + return depth; + } + + /** + * Minimizes the nodes in the tree that track the inline call hierarchy and associated code + * ranges. The initial range tree models the call hierarchy as presented in the original debug + * line info. It consists of a root node each of whose children is a sequence of linear call + * chains, either a single leaf node for some given file and line or a series of inline calls to + * such a leaf node. In this initial tree all node ranges in a given chain have the same lo and + * hi address and chains are properly ordered by range The merge algorithm works across siblings + * at successive depths starting at depth 1. Once all possible nodes at a given depth have been + * merged their children can then be merged. A successor node may only be merged into its + * predecessor if the nodes have contiguous ranges and idenitfy the same method, line and file. + * The range and children of the merged node are, respectively, the union of the input ranges + * and children. This preserves the invariant that child ranges lie within their parent range. + * + * @param debugContext + */ + public void mergeSubranges(DebugContext debugContext) { + Range next = getFirstCallee(); + if (next == null) { + return; + } + debugContext.log(DebugContext.INFO_LEVEL, "Merge subranges [0x%x, 0x%x] %s", lo, hi, getFullMethodNameWithParams()); + /* merge siblings together if possible, reparenting children to the merged node */ + while (next != null) { + next = next.maybeMergeSibling(debugContext); + } + /* now recurse down to merge children of whatever nodes remain */ + next = getFirstCallee(); + /* now this level is merged recursively merge children of each child node. */ + while (next != null) { + next.mergeSubranges(debugContext); + next = next.getSiblingCallee(); + } + } + + /** + * Removes and merges the next sibling returning the current node or it skips past the current + * node as is and returns the next sibling or null if no sibling exists. + */ + private Range maybeMergeSibling(DebugContext debugContext) { + Range sibling = getSiblingCallee(); + debugContext.log(DebugContext.INFO_LEVEL, "Merge subrange (maybe) [0x%x, 0x%x] %s", lo, hi, getFullMethodNameWithParams()); + if (sibling == null) { + /* all child nodes at this level have been merged */ + return null; + } + if (hi < sibling.lo) { + /* cannot merge non-contiguous ranges, move on. */ + return sibling; + } + if (getMethodEntry() != sibling.getMethodEntry()) { + /* cannot merge distinct callers, move on. */ + return sibling; + } + if (getLine() != sibling.getLine()) { + /* cannot merge callers with different line numbers, move on. */ + return sibling; + } + /* splice out the sibling from the chain and update this one to include it. */ + unlink(debugContext, sibling); + /* relocate the siblings children to this node. */ + reparentChildren(debugContext, sibling); + /* return the merged node so we can maybe merge it again. */ + return this; + } + + private void unlink(DebugContext debugContext, Range sibling) { + assert hi == sibling.lo : String.format("gap in range [0x%x,0x%x] %s [0x%x,0x%x] %s", + lo, hi, getFullMethodNameWithParams(), sibling.getLo(), sibling.getHi(), sibling.getFullMethodNameWithParams()); + assert this.isInlined == sibling.isInlined : String.format("change in inlined [0x%x,0x%x] %s %s [0x%x,0x%x] %s %s", + lo, hi, getFullMethodNameWithParams(), Boolean.valueOf(this.isInlined), sibling.lo, sibling.hi, sibling.getFullMethodNameWithParams(), Boolean.valueOf(sibling.isInlined)); + debugContext.log(DebugContext.INFO_LEVEL, "Combining [0x%x, 0x%x] %s into [0x%x, 0x%x] %s", sibling.lo, sibling.hi, sibling.getFullMethodName(), lo, hi, getFullMethodNameWithParams()); + this.hi = sibling.hi; + this.siblingCallee = sibling.siblingCallee; + } + + private void reparentChildren(DebugContext debugContext, Range sibling) { + Range siblingNext = sibling.getFirstCallee(); + while (siblingNext != null) { + debugContext.log(DebugContext.INFO_LEVEL, "Reparenting [0x%x, 0x%x] %s to [0x%x, 0x%x] %s", siblingNext.lo, siblingNext.hi, siblingNext.getFullMethodName(), lo, hi, + getFullMethodNameWithParams()); + siblingNext.caller = this; + Range newSiblingNext = siblingNext.siblingCallee; + siblingNext.siblingCallee = null; + addCallee(siblingNext); + siblingNext = newSiblingNext; + } + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java index 00ef51179038..b7af9cc7f695 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/debuginfo/DebugInfoProvider.java @@ -31,6 +31,7 @@ import java.util.function.Consumer; import java.util.stream.Stream; +import jdk.vm.ci.meta.ResolvedJavaType; import org.graalvm.compiler.debug.DebugContext; /** @@ -187,8 +188,6 @@ interface DebugMemberInfo extends DebugFileInfo { String name(); - String ownerType(); - String valueType(); int modifiers(); @@ -201,11 +200,6 @@ interface DebugFieldInfo extends DebugMemberInfo { } interface DebugMethodInfo extends DebugMemberInfo { - /** - * @return a string identifying the method parameters. - */ - String paramSignature(); - /** * @return an array of Strings identifying the method parameters. */ @@ -232,6 +226,7 @@ interface DebugMethodInfo extends DebugMemberInfo { * {@link com.oracle.objectfile.debugentry.Range}. */ interface DebugRangeInfo extends DebugMethodInfo { + ResolvedJavaType ownerType(); } /** @@ -315,6 +310,11 @@ interface DebugLineInfo extends DebugRangeInfo { * @return the line number for the outer or inlined segment. */ int line(); + + /** + * @return the {@link DebugLineInfo} of the nested inline caller-line + */ + DebugLineInfo getCaller(); } interface DebugFrameSizeChange { diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java index bf8a1035ff50..26916fc8cec1 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfAbbrevSectionImpl.java @@ -98,7 +98,7 @@ public void createContent() { *
  • code = builtin_unit, TAG = compile_unit - Java primitive and header type * compile unit * - *
  • code = class_unit1/2, tag = compile_unit - Java instance type compile + *
  • code = class_unit1/2/3, tag = compile_unit - Java instance type compile * unit * *
  • code = array_unit, tag = compile_unit - Java array type compile unit @@ -124,6 +124,10 @@ public void createContent() { *
  • code = method_location, tag = subprogram , parent = class_unit - Java * method code definition (i.e. location of code) * + *
  • code = abstract_inline_method, tag = subprogram , parent = class_unit - + * Java abstract inline method (i.e. proxy for method definition referenced by concrete + * inline instance) + * *
  • code = static_field_location, tag = variable, parent = class_unit - Java * static field definition (i.e. location of data) * @@ -172,12 +176,17 @@ public void createContent() { *
  • code == interface_implementor, tag == member, parent = interface_layout * - union member typed using class layout of a given implementing class * + *
  • code = inlined_subroutine/inlined_subroutine_with_children, tag = subprogram, + * parent = method_location/inlined_subroutine_with_children - provides range and + * abstract origin for a concrete inline method + * * * *
  • Level 2/3 DIEs * *
  • code == method_parameter_declaration1/2/3, tag == formal_parameter, parent = - * method_declaration1/2, method_location - details of method parameters + * method_declaration1/2, method_location, abstract_inline_method - details of method + * parameters * * Details of each specific DIE contents are as follows: * @@ -245,11 +254,13 @@ public void createContent() { * * * Instance Classes: For each class there is a level 0 DIE defining the class compilation - * unit + * unit. low_pc and hi_pc are only included if the class has compiled methods i.e. for + * variants 1 and 2. stmt_list is is only included if the class has an associated source + * file and may therefore have line info i.e. for variant 1. * *
      * - *
    • abbrev_code == class_unit1/2, tag == DW_TAG_compilation_unit, + *
    • abbrev_code == class_unit1/2/3, tag == DW_TAG_compilation_unit, * has_children * *
    • DW_AT_language : ... DW_FORM_data1 @@ -258,9 +269,11 @@ public void createContent() { * *
    • DW_AT_comp_dir : ... DW_FORM_strp * - *
    • DW_AT_low_pc : ..... DW_FORM_address + *
    • DW_AT_low_pc : ..... DW_FORM_address n.b only for abbrev-code == + * class_unit1/2 * - *
    • DW_AT_hi_pc : ...... DW_FORM_address + *
    • DW_AT_hi_pc : ...... DW_FORM_address n.b only for abbrev-code == + * class_unit1/2 * *
    • DW_AT_use_UTF8 : ... DW_FORM_flag * @@ -482,13 +495,16 @@ public void createContent() { * *
    * - * Method Code Locations: For each method within a class there is a corresponding level 1 - * DIE providing details of the location of the compiled code for the method. This DIE - * should inherit attributes from the method_definition DIE referenced from its - * specification attribute without the need to repeat them, including attributes specified - * in child DIEs of the method_definition. However, it is actually necessary to replicate - * the method_parameter DIEs as children of this DIE because gdb does not carry these - * attributes across from the specification DIE. + * Method Code Locations: For each method within a class there will normally be a + * corresponding level 1 DIE providing details of the location of the compiled code for the + * method. This DIE should inherit attributes from the method_definition DIE referenced from + * its specification attribute without the need to repeat them, including attributes + * specified in child DIEs of the method_definition. However, it is actually necessary to + * replicate the method_parameter DIEs as children of this DIE because gdb does not carry + * these attributes across from the specification DIE. + * + * Note that for methods which only occur as inlined code rather than as a top-level + * compiles method the method location DIE will be omitted * *
      * @@ -505,6 +521,73 @@ public void createContent() { * *
    * + * Abstract Inline Methods: For any method which has been inlined into another compiled + * method there will be a corresponding level 1 DIE that identifies the method declaration + * and serves as the target reference for concrete inlined method DIEs. This DIE should + * inherit attributes from the method_definition DIE referenced from its specification + * attribute without the need to repeat them, including attributes specified in child DIEs + * of the method_definition. However, it is actually necessary to replicate the + * method_parameter DIEs as children of this DIE because gdb does not carry these attributes + * across from the specification DIE. + * + * Note that an abstract inline method DIE is generated in the compile unit of the class + * which declares the inlined method whereas a concrete inlined method DIE is generated in + * the compile unit of the class which declares method into which code has been inlined. + * + *
      + * + *
    • abbrev_code == DW_ABBREV_CODE_abstract_inline_method, tag == DW_TAG_subprogram, + * has_children + * + *
    • DW_AT_inline : .......... DW_FORM_data1 + * + *
    • DW_AT_external : ........ DW_FORM_flag + * + *
    • DW_AT_specification : ... DW_FORM_ref_addr + * + *
    + * + * Concrete Inlined Methods: Concrete inlined methods are nested as a tree of children under + * the method_location DIE for the method into which they have been inlined. Each inlined + * method DIE defines an address range that is a subrange of its parent DIE. A + * method_location DIE occurs at depth 1 in a compile unit (class_unit). So, this means that + * for any method which has been inlined into a compiled method at depth K in the inline + * frame stack there will be a corresponding level 2+K DIE that identifies the method that + * was inlined (by referencing the corresponding abstract inline method DIE) and locates the + * call point by citing the file index and line number of its caller. So, if compiled method + * M inlines a call to m1 at source position f0:l0, m1 inlines a call to method m2 at source + * position f1:l1 and m2 inlines a call to m3 at source position f2:l2 then there will be a + * level 2 DIE for the inline code range derived from m1 referencing the abstract entry for + * m1 with f0 and l0 as file and line, a level 3 DIE for the inline code range derived from + * m2 referencing the abstract entry for m2 with f1 and l1 as file and line and a level 3 + * DIE for the inline code range derived from m3 referencing the abstract entry for m3 with + * f2 and l2 as file and line. + * + * Note that a concrete inlined method DIE is generated in the compile unit of the class + * which declares the method into which code has been inlined whereas an abstract inlined + * method DIE is generated in the compile unit of the class which declares of the inlined + * method. + * + *
      + * + *
    • abbrev_code == DW_ABBREV_CODE_inlined_subroutine, tag == DW_TAG_subprogram, + * no_children + * + *
    • abbrev_code == DW_ABBREV_CODE_inlined_subroutine_with_children, tag == + * DW_TAG_subprogram, has_children + * + *
    • DW_AT_abstract_origin : ... DW_FORM_ref_addr + * + *
    • DW_AT_low_pc : ............ DW_FORM_addr + * + *
    • DW_AT_hi_pc : ............. DW_FORM_addr + * + *
    • DW_AT_call_file : ......... DW_FORM_data4 + * + *
    • DW_AT_call_line : ......... DW_FORM_data4 + * + *
    + * * Static Field Locations: For each static field within the class there is a level 1 DIE * providing details of the static field location * @@ -711,10 +794,14 @@ public int writeAbbrevs(DebugContext context, byte[] buffer, int p) { pos = writeHeaderFieldAbbrev(context, buffer, pos); pos = writeArrayDataTypeAbbrev(context, buffer, pos); pos = writeMethodLocationAbbrev(context, buffer, pos); + pos = writeAbstractInlineMethodAbbrev(context, buffer, pos); pos = writeStaticFieldLocationAbbrev(context, buffer, pos); pos = writeSuperReferenceAbbrev(context, buffer, pos); pos = writeInterfaceImplementorAbbrev(context, buffer, pos); + pos = writeInlinedSubroutineAbbrev(buffer, pos, false); + pos = writeInlinedSubroutineAbbrev(buffer, pos, true); + /* * if we address rebasing is required then then we need to use indirect layout types * supplied with a suitable data_location attribute and indirect pointer types to ensure @@ -769,10 +856,12 @@ private int writeBuiltInUnitAbbrev(@SuppressWarnings("unused") DebugContext cont private int writeClassUnitAbbrevs(DebugContext context, byte[] buffer, int p) { int pos = p; - /* class compile unit no line info */ + /* class compile unit with compiled methods and line info */ pos = writeClassUnitAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_class_unit1, buffer, pos); - /* class compile unit with line info */ + /* class compile unit with compiled methods but without line info */ pos = writeClassUnitAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_class_unit2, buffer, pos); + /* class compile unit without compiled methods and without line info */ + pos = writeClassUnitAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_class_unit3, buffer, pos); return pos; } @@ -789,10 +878,12 @@ private int writeClassUnitAbbrev(@SuppressWarnings("unused") DebugContext contex pos = writeAttrForm(DwarfDebugInfo.DW_FORM_strp, buffer, pos); pos = writeAttrType(DwarfDebugInfo.DW_AT_comp_dir, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_strp, buffer, pos); - pos = writeAttrType(DwarfDebugInfo.DW_AT_low_pc, buffer, pos); - pos = writeAttrForm(DwarfDebugInfo.DW_FORM_addr, buffer, pos); - pos = writeAttrType(DwarfDebugInfo.DW_AT_hi_pc, buffer, pos); - pos = writeAttrForm(DwarfDebugInfo.DW_FORM_addr, buffer, pos); + if (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_class_unit1 || abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_class_unit2) { + pos = writeAttrType(DwarfDebugInfo.DW_AT_low_pc, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_addr, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_hi_pc, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_addr, buffer, pos); + } if (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_class_unit1) { pos = writeAttrType(DwarfDebugInfo.DW_AT_stmt_list, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data4, buffer, pos); @@ -936,8 +1027,8 @@ private int writeClassReferenceAbbrev(@SuppressWarnings("unused") DebugContext c private int writeMethodDeclarationAbbrevs(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; - pos = writeMethodDeclarationAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_method_declaration1, buffer, pos); - pos = writeMethodDeclarationAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_method_declaration2, buffer, pos); + pos = writeMethodDeclarationAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_method_declaration, buffer, pos); + pos = writeMethodDeclarationAbbrev(context, DwarfDebugInfo.DW_ABBREV_CODE_method_declaration_static, buffer, pos); return pos; } @@ -971,7 +1062,7 @@ private int writeMethodDeclarationAbbrev(@SuppressWarnings("unused") DebugContex // pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data1, buffer, pos); pos = writeAttrType(DwarfDebugInfo.DW_AT_containing_type, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); - if (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_method_declaration1) { + if (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_method_declaration) { pos = writeAttrType(DwarfDebugInfo.DW_AT_object_pointer, buffer, pos); pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); } @@ -1186,6 +1277,25 @@ private int writeMethodLocationAbbrev(@SuppressWarnings("unused") DebugContext c return pos; } + private int writeAbstractInlineMethodAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { + int pos = p; + pos = writeAbbrevCode(DwarfDebugInfo.DW_ABBREV_CODE_abstract_inline_method, buffer, pos); + pos = writeTag(DwarfDebugInfo.DW_TAG_subprogram, buffer, pos); + pos = writeFlag(DwarfDebugInfo.DW_CHILDREN_yes, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_inline, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data1, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_external, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_flag, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_specification, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); + /* + * Now terminate. + */ + pos = writeAttrType(DwarfDebugInfo.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_null, buffer, pos); + return pos; + } + private int writeStaticFieldLocationAbbrev(@SuppressWarnings("unused") DebugContext context, byte[] buffer, int p) { int pos = p; @@ -1326,6 +1436,27 @@ private int writeNullAbbrev(@SuppressWarnings("unused") DebugContext context, by return pos; } + private int writeInlinedSubroutineAbbrev(byte[] buffer, int p, boolean withChildren) { + int pos = p; + pos = writeAbbrevCode(withChildren ? DwarfDebugInfo.DW_ABBREV_CODE_inlined_subroutine_with_children : DwarfDebugInfo.DW_ABBREV_CODE_inlined_subroutine, buffer, pos); + pos = writeTag(DwarfDebugInfo.DW_TAG_inlined_subroutine, buffer, pos); + pos = writeFlag(withChildren ? DwarfDebugInfo.DW_CHILDREN_yes : DwarfDebugInfo.DW_CHILDREN_no, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_abstract_origin, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_ref_addr, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_low_pc, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_addr, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_hi_pc, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_addr, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_call_file, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data4, buffer, pos); + pos = writeAttrType(DwarfDebugInfo.DW_AT_call_line, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_data4, buffer, pos); + /* Now terminate. */ + pos = writeAttrType(DwarfDebugInfo.DW_AT_null, buffer, pos); + pos = writeAttrForm(DwarfDebugInfo.DW_FORM_null, buffer, pos); + return pos; + } + /** * The debug_abbrev section depends on debug_aranges section. */ diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java index 48c651ba1ee5..8fcad6f32b1d 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfDebugInfo.java @@ -69,37 +69,43 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final int DW_ABBREV_CODE_builtin_unit = 1; public static final int DW_ABBREV_CODE_class_unit1 = 2; public static final int DW_ABBREV_CODE_class_unit2 = 3; - public static final int DW_ABBREV_CODE_array_unit = 4; + public static final int DW_ABBREV_CODE_class_unit3 = 4; + public static final int DW_ABBREV_CODE_array_unit = 5; /* Level 1 DIEs. */ - public static final int DW_ABBREV_CODE_primitive_type = 5; - public static final int DW_ABBREV_CODE_void_type = 6; - public static final int DW_ABBREV_CODE_object_header = 7; - public static final int DW_ABBREV_CODE_class_layout1 = 8; - public static final int DW_ABBREV_CODE_class_layout2 = 9; - public static final int DW_ABBREV_CODE_class_pointer = 10; - public static final int DW_ABBREV_CODE_method_location = 11; - public static final int DW_ABBREV_CODE_static_field_location = 12; - public static final int DW_ABBREV_CODE_array_layout = 13; - public static final int DW_ABBREV_CODE_array_pointer = 14; - public static final int DW_ABBREV_CODE_interface_layout = 15; - public static final int DW_ABBREV_CODE_interface_pointer = 16; - public static final int DW_ABBREV_CODE_indirect_layout = 17; - public static final int DW_ABBREV_CODE_indirect_pointer = 18; + public static final int DW_ABBREV_CODE_primitive_type = 6; + public static final int DW_ABBREV_CODE_void_type = 7; + public static final int DW_ABBREV_CODE_object_header = 8; + public static final int DW_ABBREV_CODE_class_layout1 = 9; + public static final int DW_ABBREV_CODE_class_layout2 = 10; + public static final int DW_ABBREV_CODE_class_pointer = 11; + public static final int DW_ABBREV_CODE_method_location = 12; + public static final int DW_ABBREV_CODE_abstract_inline_method = 13; + public static final int DW_ABBREV_CODE_static_field_location = 14; + public static final int DW_ABBREV_CODE_array_layout = 15; + public static final int DW_ABBREV_CODE_array_pointer = 16; + public static final int DW_ABBREV_CODE_interface_layout = 17; + public static final int DW_ABBREV_CODE_interface_pointer = 18; + public static final int DW_ABBREV_CODE_indirect_layout = 19; + public static final int DW_ABBREV_CODE_indirect_pointer = 20; /* Level 2 DIEs. */ - public static final int DW_ABBREV_CODE_method_declaration1 = 19; - public static final int DW_ABBREV_CODE_method_declaration2 = 20; - public static final int DW_ABBREV_CODE_field_declaration1 = 21; - public static final int DW_ABBREV_CODE_field_declaration2 = 22; - public static final int DW_ABBREV_CODE_field_declaration3 = 23; - public static final int DW_ABBREV_CODE_field_declaration4 = 24; - public static final int DW_ABBREV_CODE_header_field = 25; - public static final int DW_ABBREV_CODE_array_data_type = 26; - public static final int DW_ABBREV_CODE_super_reference = 27; - public static final int DW_ABBREV_CODE_interface_implementor = 28; + public static final int DW_ABBREV_CODE_method_declaration = 21; + public static final int DW_ABBREV_CODE_method_declaration_static = 22; + public static final int DW_ABBREV_CODE_field_declaration1 = 23; + public static final int DW_ABBREV_CODE_field_declaration2 = 24; + public static final int DW_ABBREV_CODE_field_declaration3 = 25; + public static final int DW_ABBREV_CODE_field_declaration4 = 26; + public static final int DW_ABBREV_CODE_header_field = 27; + public static final int DW_ABBREV_CODE_array_data_type = 28; + public static final int DW_ABBREV_CODE_super_reference = 29; + public static final int DW_ABBREV_CODE_interface_implementor = 30; + /* Level 2+K DIEs (where inline depth K >= 0) */ + public static final int DW_ABBREV_CODE_inlined_subroutine = 31; + public static final int DW_ABBREV_CODE_inlined_subroutine_with_children = 32; /* Level 3 DIEs. */ - public static final int DW_ABBREV_CODE_method_parameter_declaration1 = 29; - public static final int DW_ABBREV_CODE_method_parameter_declaration2 = 30; - public static final int DW_ABBREV_CODE_method_parameter_declaration3 = 31; + public static final int DW_ABBREV_CODE_method_parameter_declaration1 = 33; + public static final int DW_ABBREV_CODE_method_parameter_declaration2 = 34; + public static final int DW_ABBREV_CODE_method_parameter_declaration3 = 35; + /* * Define all the Dwarf tags we need for our DIEs. */ @@ -116,6 +122,7 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final int DW_TAG_subprogram = 0x2e; public static final int DW_TAG_variable = 0x34; public static final int DW_TAG_unspecified_type = 0x3b; + public static final int DW_TAG_inlined_subroutine = 0x1d; /* * Define all the Dwarf attributes we need for our DIEs. @@ -131,6 +138,8 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final int DW_AT_language = 0x13; public static final int DW_AT_comp_dir = 0x1b; public static final int DW_AT_containing_type = 0x1d; + public static final int DW_AT_inline = 0x20; + public static final int DW_AT_abstract_origin = 0x31; public static final int DW_AT_accessibility = 0x32; public static final int DW_AT_artificial = 0x34; public static final int DW_AT_data_member_location = 0x38; @@ -146,6 +155,8 @@ public class DwarfDebugInfo extends DebugInfoBase { public static final int DW_AT_type = 0x49; public static final int DW_AT_data_location = 0x50; public static final int DW_AT_use_UTF8 = 0x53; + public static final int DW_AT_call_file = 0x58; + public static final int DW_AT_call_line = 0x59; public static final int DW_AT_object_pointer = 0x64; /* @@ -158,6 +169,10 @@ public class DwarfDebugInfo extends DebugInfoBase { @SuppressWarnings("unused") public static final int DW_FORM_data8 = 0x7; @SuppressWarnings("unused") private static final int DW_FORM_string = 0x8; @SuppressWarnings("unused") public static final int DW_FORM_block1 = 0x0a; + @SuppressWarnings("unused") public static final int DW_FORM_ref1 = 0x11; + @SuppressWarnings("unused") public static final int DW_FORM_ref2 = 0x12; + @SuppressWarnings("unused") public static final int DW_FORM_ref4 = 0x13; + @SuppressWarnings("unused") public static final int DW_FORM_ref8 = 0x14; public static final int DW_FORM_ref_addr = 0x10; public static final int DW_FORM_data1 = 0x0b; public static final int DW_FORM_flag = 0xc; @@ -181,6 +196,13 @@ public class DwarfDebugInfo extends DebugInfoBase { * Value for DW_AT_language attribute with form DATA1. */ public static final byte DW_LANG_Java = 0xb; + /* + * Values for {@link DW_AT_inline} attribute with form DATA1. + */ + @SuppressWarnings("unused") public static final byte DW_INL_not_inlined = 0; + public static final byte DW_INL_inlined = 1; + @SuppressWarnings("unused") public static final byte DW_INL_declared_not_inlined = 2; + @SuppressWarnings("unused") public static final byte DW_INL_declared_inlined = 3; /* * DW_AT_Accessibility attribute values. @@ -436,6 +458,10 @@ static class DwarfClassProperties extends DwarfTypeProperties { * Map from method names to info section index for the field declaration. */ private HashMap methodDeclarationIndex; + /** + * Map from method names to info section index for the field declaration. + */ + private HashMap abstractInlineMethodIndex; DwarfClassProperties(StructureTypeEntry entry) { super(entry); @@ -448,6 +474,7 @@ static class DwarfClassProperties extends DwarfTypeProperties { this.lineSectionSize = -1; fieldDeclarationIndex = null; methodDeclarationIndex = null; + abstractInlineMethodIndex = null; } } @@ -660,7 +687,7 @@ public void setFieldDeclarationIndex(StructureTypeEntry entry, String fieldName, classProperties.fieldDeclarationIndex = fieldDeclarationIndex = new HashMap<>(); } if (fieldDeclarationIndex.get(fieldName) != null) { - assert fieldDeclarationIndex.get(fieldName) == pos; + assert fieldDeclarationIndex.get(fieldName) == pos : entry.getTypeName() + fieldName; } else { fieldDeclarationIndex.put(fieldName, pos); } @@ -671,8 +698,8 @@ public int getFieldDeclarationIndex(StructureTypeEntry entry, String fieldName) classProperties = lookupClassProperties(entry); assert classProperties.getTypeEntry() == entry; HashMap fieldDeclarationIndex = classProperties.fieldDeclarationIndex; - assert fieldDeclarationIndex != null; - assert fieldDeclarationIndex.get(fieldName) != null; + assert fieldDeclarationIndex != null : fieldName; + assert fieldDeclarationIndex.get(fieldName) != null : entry.getTypeName() + fieldName; return fieldDeclarationIndex.get(fieldName); } @@ -685,7 +712,7 @@ public void setMethodDeclarationIndex(ClassEntry classEntry, String methodName, classProperties.methodDeclarationIndex = methodDeclarationIndex = new HashMap<>(); } if (methodDeclarationIndex.get(methodName) != null) { - assert methodDeclarationIndex.get(methodName) == pos; + assert methodDeclarationIndex.get(methodName) == pos : classEntry.getTypeName() + methodName; } else { methodDeclarationIndex.put(methodName, pos); } @@ -696,8 +723,33 @@ public int getMethodDeclarationIndex(ClassEntry classEntry, String methodName) { classProperties = lookupClassProperties(classEntry); assert classProperties.getTypeEntry() == classEntry; HashMap methodDeclarationIndex = classProperties.methodDeclarationIndex; - assert methodDeclarationIndex != null; - assert methodDeclarationIndex.get(methodName) != null; + assert methodDeclarationIndex != null : classEntry.getTypeName() + methodName; + assert methodDeclarationIndex.get(methodName) != null : classEntry.getTypeName() + methodName; return methodDeclarationIndex.get(methodName); } + + public void setAbstractInlineMethodIndex(ClassEntry classEntry, String methodName, int pos) { + DwarfClassProperties classProperties; + classProperties = lookupClassProperties(classEntry); + assert classProperties.getTypeEntry() == classEntry; + HashMap abstractInlineMethodIndex = classProperties.abstractInlineMethodIndex; + if (abstractInlineMethodIndex == null) { + classProperties.abstractInlineMethodIndex = abstractInlineMethodIndex = new HashMap<>(); + } + if (abstractInlineMethodIndex.get(methodName) != null) { + assert abstractInlineMethodIndex.get(methodName) == pos : classEntry.getTypeName() + methodName; + } else { + abstractInlineMethodIndex.put(methodName, pos); + } + } + + public int getAbstractInlineMethodIndex(ClassEntry classEntry, String methodName) { + DwarfClassProperties classProperties; + classProperties = lookupClassProperties(classEntry); + assert classProperties.getTypeEntry() == classEntry; + HashMap abstractInlineMethodIndex = classProperties.abstractInlineMethodIndex; + assert abstractInlineMethodIndex != null : classEntry.getTypeName() + methodName; + assert abstractInlineMethodIndex.get(methodName) != null : classEntry.getTypeName() + methodName; + return abstractInlineMethodIndex.get(methodName); + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java index 51cc50b33fb0..0df2f7862289 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfInfoSectionImpl.java @@ -27,6 +27,7 @@ package com.oracle.objectfile.elf.dwarf; import java.lang.reflect.Modifier; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -360,7 +361,7 @@ private int writeNonPrimaryClassUnit(DebugContext context, ClassEntry classEntry pos = writeCUHeader(buffer, pos); assert pos == lengthPos + DW_DIE_HEADER_SIZE; /* Non-primary classes have no compiled methods so they also have no line section entry. */ - int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_class_unit2; + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_class_unit3; log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] language %s", pos, "DW_LANG_Java"); @@ -372,17 +373,6 @@ private int writeNonPrimaryClassUnit(DebugContext context, ClassEntry classEntry String compilationDirectory = classEntry.getCachePath(); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeAttrStrp(compilationDirectory, buffer, pos); - /* Writing of lo and hi should really be optional. */ - int lo = 0; - log(context, " [0x%08x] lo_pc 0x%08x", pos, lo); - pos = writeAttrAddress(lo, buffer, pos); - int hi = 0; - log(context, " [0x%08x] hi_pc 0x%08x", pos, hi); - pos = writeAttrAddress(hi, buffer, pos); - /* - * Note, there is no need to write a stmt_list (line section idx) for this class unit as the - * class has no code. - */ /* Now write the child DIEs starting with the layout and pointer type. */ @@ -397,6 +387,10 @@ private int writeNonPrimaryClassUnit(DebugContext context, ClassEntry classEntry /* Note, for a non-primary there are no method definitions to write. */ + /* Write abstract inline methods. */ + + pos = writeAbstractInlineMethods(context, classEntry, buffer, pos); + /* Write all static field definitions. */ pos = writeStaticFieldLocations(context, classEntry, buffer, pos); @@ -426,7 +420,15 @@ private int writePrimaryClassUnit(DebugContext context, ClassEntry classEntry, b int pos = p; int lineIndex = getLineIndex(classEntry); String fileName = classEntry.getFileName(); - /* Primary classes only have a line section entry if they have an associated file. */ + /* + * Primary classes only have a line section entry if they have method and an associated + * file. + */ + List classPrimaryEntries = classEntry.getPrimaryEntries(); + int lo = findLo(classPrimaryEntries, false); + int hi = findHi(classPrimaryEntries, classEntry.includesDeoptTarget(), false); + // we must have at least one compiled method + assert hi > 0; int abbrevCode = (fileName.length() > 0 ? DwarfDebugInfo.DW_ABBREV_CODE_class_unit1 : DwarfDebugInfo.DW_ABBREV_CODE_class_unit2); setCUIndex(classEntry, pos); int lengthPos = pos; @@ -444,15 +446,12 @@ private int writePrimaryClassUnit(DebugContext context, ClassEntry classEntry, b String compilationDirectory = classEntry.getCachePath(); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeAttrStrp(compilationDirectory, buffer, pos); - List classPrimaryEntries = classEntry.getPrimaryEntries(); /* * Specify hi and lo for the compile unit which means we also need to ensure methods within * it are listed in ascending address order. */ - int lo = findLo(classPrimaryEntries, false); log(context, " [0x%08x] lo_pc 0x%08x", pos, lo); pos = writeAttrAddress(lo, buffer, pos); - int hi = findHi(classPrimaryEntries, classEntry.includesDeoptTarget(), false); log(context, " [0x%08x] hi_pc 0x%08x", pos, hi); pos = writeAttrAddress(hi, buffer, pos); /* Only write stmt_list if the entry actually has line number info. */ @@ -474,7 +473,11 @@ private int writePrimaryClassUnit(DebugContext context, ClassEntry classEntry, b /* Write all method locations. */ - pos = writeMethodLocations(context, classEntry, buffer, pos); + pos = writeMethodLocations(context, classEntry, false, buffer, pos); + + /* Write abstract inline method locations. */ + + pos = writeAbstractInlineMethods(context, classEntry, buffer, pos); /* Write all static field definitions. */ @@ -660,7 +663,7 @@ private int writeField(DebugContext context, StructureTypeEntry entry, FieldEntr private int writeMethodDeclarations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { int pos = p; for (MethodEntry method : classEntry.getMethods()) { - if (method.isInRange()) { + if (method.isInRange() || method.isInlined()) { /* * Declare all methods including deopt targets even though they are written in * separate CUs. @@ -679,7 +682,7 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, int modifiers = method.getModifiers(); boolean isStatic = Modifier.isStatic(modifiers); log(context, " [0x%08x] method declaration %s", pos, methodKey); - int abbrevCode = (isStatic ? DwarfDebugInfo.DW_ABBREV_CODE_method_declaration2 : DwarfDebugInfo.DW_ABBREV_CODE_method_declaration1); + int abbrevCode = (isStatic ? DwarfDebugInfo.DW_ABBREV_CODE_method_declaration_static : DwarfDebugInfo.DW_ABBREV_CODE_method_declaration); log(context, " [0x%08x] <2> Abbrev Number %d", pos, abbrevCode); pos = writeAbbrevCode(abbrevCode, buffer, pos); log(context, " [0x%08x] external true", pos); @@ -699,8 +702,8 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, int retTypeIdx = getTypeIndex(returnTypeName); log(context, " [0x%08x] type 0x%x (%s)", pos, retTypeIdx, returnTypeName); pos = writeAttrRefAddr(retTypeIdx, buffer, pos); - log(context, " [0x%08x] artificial %s", pos, method.isDeoptTarget ? "true" : "false"); - pos = writeFlag((method.isDeoptTarget ? (byte) 1 : (byte) 0), buffer, pos); + log(context, " [0x%08x] artificial %s", pos, method.isDeopt() ? "true" : "false"); + pos = writeFlag((method.isDeopt() ? (byte) 1 : (byte) 0), buffer, pos); log(context, " [0x%08x] accessibility %s", pos, "public"); pos = writeAttrAccessibility(modifiers, buffer, pos); log(context, " [0x%08x] declaration true", pos); @@ -708,7 +711,7 @@ private int writeMethodDeclaration(DebugContext context, ClassEntry classEntry, int typeIdx = getLayoutIndex(classEntry); log(context, " [0x%08x] containing_type 0x%x (%s)", pos, typeIdx, classEntry.getTypeName()); pos = writeAttrRefAddr(typeIdx, buffer, pos); - if (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_method_declaration1) { + if (abbrevCode == DwarfDebugInfo.DW_ABBREV_CODE_method_declaration) { /* Record the current position so we can back patch the object pointer. */ int objectPointerIndex = pos; /* @@ -921,16 +924,81 @@ private int writeInterfaceType(DebugContext context, InterfaceClassEntry interfa return pos; } - private int writeMethodLocations(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + private int writeMethodLocations(DebugContext context, ClassEntry classEntry, boolean deoptTargets, byte[] buffer, int p) { int pos = p; List classPrimaryEntries = classEntry.getPrimaryEntries(); + + /* The primary file entry should always be first in the local files list. */ + assert classEntry.localFilesIdx(classEntry.getFileEntry()) == 1; + for (PrimaryEntry primaryEntry : classPrimaryEntries) { - Range range = primaryEntry.getPrimary(); - if (!range.isDeoptTarget()) { - pos = writeMethodLocation(context, classEntry, range, buffer, pos); + Range primary = primaryEntry.getPrimary(); + if (primary.isDeoptTarget() != deoptTargets) { + continue; } + pos = writeMethodLocation(context, classEntry, primaryEntry, buffer, pos); } + return pos; + } + private int writeAbstractInlineMethods(DebugContext context, ClassEntry classEntry, byte[] buffer, int p) { + int pos = p; + for (MethodEntry method : classEntry.getMethods()) { + if (method.isInlined()) { + String methodKey = method.getSymbolName(); + setAbstractInlineMethodIndex(classEntry, methodKey, pos); + pos = writeAbstractInlineMethod(context, classEntry, method, buffer, pos); + } + } + return pos; + } + + /** + * Go through the subranges and generate concrete debug entries for inlined methods. + */ + private int generateConcreteInlinedMethods(DebugContext context, ClassEntry classEntry, + PrimaryEntry primaryEntry, byte[] buffer, int p) { + Range primary = primaryEntry.getPrimary(); + if (primary.isLeaf()) { + return p; + } + int pos = p; + log(context, " [0x%08x] concrete entries [0x%x,0x%x] %s", pos, primary.getLo(), primary.getHi(), primary.getFullMethodName()); + int depth = 1; + Iterator iterator = primaryEntry.topDownRangeIterator(); + while (iterator.hasNext()) { + Range subrange = iterator.next(); + /* + * Top level subranges don't need concrete methods. They just provide a file and line + * for their callee. + */ + if (!subrange.isInlined()) { + // only happens if the subrange is for the top-level compiled method + assert subrange.getCaller() == primaryEntry.getPrimary(); + assert subrange.getDepth() == 0; + continue; + } + // if we just stepped out of a child range write nulls for each step up + while (depth > subrange.getDepth()) { + pos = writeAttrNull(buffer, pos); + depth--; + } + MethodEntry method = subrange.getMethodEntry(); + ClassEntry methodClassEntry = method.ownerType(); + String methodKey = method.getSymbolName(); + /* the abstract index was written in the method's class entry */ + int specificationIndex = getAbstractInlineMethodIndex(methodClassEntry, methodKey); + pos = writeInlineSubroutine(context, classEntry, subrange, specificationIndex, depth, buffer, pos); + if (!subrange.isLeaf()) { + // increment depth before writing the children + depth++; + } + } + // if we just stepped out of a child range write nulls for each step up + while (depth > 1) { + pos = writeAttrNull(buffer, pos); + depth--; + } return pos; } @@ -1192,8 +1260,13 @@ private int writeDeoptMethodsCU(DebugContext context, ClassEntry classEntry, byt int pos = p; assert classEntry.includesDeoptTarget(); List classPrimaryEntries = classEntry.getPrimaryEntries(); + assert !classPrimaryEntries.isEmpty(); String fileName = classEntry.getFileName(); int lineIndex = getLineIndex(classEntry); + int lo = findLo(classPrimaryEntries, true); + int hi = findHi(classPrimaryEntries, true, true); + // we must have at least one compiled deopt method + assert hi > 0 : hi; int abbrevCode = (fileName.length() > 0 ? DwarfDebugInfo.DW_ABBREV_CODE_class_unit1 : DwarfDebugInfo.DW_ABBREV_CODE_class_unit2); log(context, " [0x%08x] <0> Abbrev Number %d", pos, abbrevCode); pos = writeAbbrevCode(abbrevCode, buffer, pos); @@ -1204,8 +1277,6 @@ private int writeDeoptMethodsCU(DebugContext context, ClassEntry classEntry, byt String compilationDirectory = classEntry.getCachePath(); log(context, " [0x%08x] comp_dir 0x%x (%s)", pos, debugStringIndex(compilationDirectory), compilationDirectory); pos = writeAttrStrp(compilationDirectory, buffer, pos); - int lo = findLo(classPrimaryEntries, true); - int hi = findHi(classPrimaryEntries, true, true); log(context, " [0x%08x] lo_pc 0x%08x", pos, lo); pos = writeAttrAddress(lo, buffer, pos); log(context, " [0x%08x] hi_pc 0x%08x", pos, hi); @@ -1215,44 +1286,118 @@ private int writeDeoptMethodsCU(DebugContext context, ClassEntry classEntry, byt pos = writeAttrData4(lineIndex, buffer, pos); } - for (PrimaryEntry primaryEntry : classPrimaryEntries) { - Range range = primaryEntry.getPrimary(); - if (range.isDeoptTarget()) { - pos = writeMethodLocation(context, classEntry, range, buffer, pos); - } - } + pos = writeMethodLocations(context, classEntry, true, buffer, pos); /* * Write a terminating null attribute. */ return writeAttrNull(buffer, pos); } - private int writeMethodLocation(DebugContext context, ClassEntry classEntry, Range range, byte[] buffer, int p) { + private int writeMethodLocation(DebugContext context, ClassEntry classEntry, PrimaryEntry primaryEntry, byte[] buffer, int p) { int pos = p; + Range primary = primaryEntry.getPrimary(); log(context, " [0x%08x] method location", pos); int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_method_location; - log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); pos = writeAbbrevCode(abbrevCode, buffer, pos); - log(context, " [0x%08x] lo_pc 0x%08x", pos, range.getLo()); - pos = writeAttrAddress(range.getLo(), buffer, pos); - log(context, " [0x%08x] hi_pc 0x%08x", pos, range.getHi()); - pos = writeAttrAddress(range.getHi(), buffer, pos); + log(context, " [0x%08x] lo_pc 0x%08x", pos, primary.getLo()); + pos = writeAttrAddress(primary.getLo(), buffer, pos); + log(context, " [0x%08x] hi_pc 0x%08x", pos, primary.getHi()); + pos = writeAttrAddress(primary.getHi(), buffer, pos); + /* + * Should pass true only if method is non-private. + */ + log(context, " [0x%08x] external true", pos); + pos = writeFlag(DwarfDebugInfo.DW_FLAG_true, buffer, pos); + String methodKey = primary.getSymbolName(); + int methodSpecOffset = getMethodDeclarationIndex(classEntry, methodKey); + log(context, " [0x%08x] specification 0x%x (%s)", pos, methodSpecOffset, methodKey); + pos = writeAttrRefAddr(methodSpecOffset, buffer, pos); + pos = writeMethodParameterDeclarations(context, classEntry, primary.getMethodEntry(), false, buffer, pos); + if (!primary.isLeaf()) { + /* + * the method has inlined ranges so write concrete inlined method entries as its + * children + */ + pos = generateConcreteInlinedMethods(context, classEntry, primaryEntry, buffer, pos); + } + /* + * Write a terminating null attribute. + */ + return writeAttrNull(buffer, pos); + } + + private int writeAbstractInlineMethod(DebugContext context, ClassEntry classEntry, MethodEntry method, byte[] buffer, int p) { + int pos = p; + String methodKey = method.getSymbolName(); + log(context, " [0x%08x] abstract inline method %s", pos, method.getSymbolName()); + int abbrevCode = DwarfDebugInfo.DW_ABBREV_CODE_abstract_inline_method; + log(context, " [0x%08x] <1> Abbrev Number %d", pos, abbrevCode); + pos = writeAbbrevCode(abbrevCode, buffer, pos); + log(context, " [0x%08x] inline 0x%x", pos, DwarfDebugInfo.DW_INL_inlined); + pos = writeAttrData1(DwarfDebugInfo.DW_INL_inlined, buffer, pos); /* * Should pass true only if method is non-private. */ log(context, " [0x%08x] external true", pos); pos = writeFlag(DwarfDebugInfo.DW_FLAG_true, buffer, pos); - String methodKey = range.getSymbolName(); int methodSpecOffset = getMethodDeclarationIndex(classEntry, methodKey); log(context, " [0x%08x] specification 0x%x (%s)", pos, methodSpecOffset, methodKey); pos = writeAttrRefAddr(methodSpecOffset, buffer, pos); - pos = writeMethodParameterDeclarations(context, classEntry, range.getMethodEntry(), false, buffer, pos); + pos = writeMethodParameterDeclarations(context, classEntry, method, false, buffer, pos); /* * Write a terminating null attribute. */ return writeAttrNull(buffer, pos); } + private int writeInlineSubroutine(DebugContext context, ClassEntry classEntry, Range range, int subprogramOffset, int depth, byte[] buffer, int p) { + assert range.isInlined(); + int pos = p; + log(context, " [0x%08x] concrete inline subroutine [0x%x, 0x%x] %s", pos, range.getLo(), range.getHi(), range.getSymbolName()); + final Range callerSubrange = range.getCaller(); + assert callerSubrange != null; + int callLine = callerSubrange.getLine(); + assert callLine >= -1 : callLine; + if (callLine == -1) { + log(context, " Unable to retrieve call line for inlined method %s", range.getFullMethodName()); + /* continue with line 0 as we must insert a tree node */ + callLine = 0; + } + Integer fileIndex; + if (callerSubrange == range) { + fileIndex = 1; + } else { + FileEntry subFileEntry = callerSubrange.getFileEntry(); + assert subFileEntry != null; + fileIndex = classEntry.localFilesIdx(subFileEntry); + assert fileIndex != null; + } + final int code; + if (range.isLeaf()) { + code = DwarfDebugInfo.DW_ABBREV_CODE_inlined_subroutine; + } else { + code = DwarfDebugInfo.DW_ABBREV_CODE_inlined_subroutine_with_children; + } + log(context, " [0x%08x] <%d> Abbrev Number %d", pos, depth + 1, code); + pos = writeAbbrevCode(code, buffer, pos); + log(context, " [0x%08x] abstract_origin 0x%x", pos, subprogramOffset); + pos = writeAttrRef4(subprogramOffset, buffer, pos); + log(context, " [0x%08x] lo_pc 0x%08x", pos, range.getLo()); + pos = writeAttrAddress(range.getLo(), buffer, pos); + log(context, " [0x%08x] hi_pc 0x%08x", pos, range.getHi()); + pos = writeAttrAddress(range.getHi(), buffer, pos); + log(context, " [0x%08x] call_file %d", pos, fileIndex); + pos = writeAttrData4(fileIndex, buffer, pos); + log(context, " [0x%08x] call_line %d", pos, callLine); + pos = writeAttrData4(callLine, buffer, pos); + return pos; + } + + private int writeAttrRef4(int reference, byte[] buffer, int p) { + return writeAttrData4(reference, buffer, p); + } + private int writeCUHeader(byte[] buffer, int p) { int pos = p; if (buffer == null) { @@ -1290,12 +1435,13 @@ private static int findLo(List classPrimaryEntries, boolean isDeop } } /* We should never get here. */ - assert false; + assert false : "should not reach"; return 0; } private static int findHi(List classPrimaryEntries, boolean includesDeoptTarget, boolean isDeoptTargetCU) { if (isDeoptTargetCU || !includesDeoptTarget) { + assert classPrimaryEntries.size() > 0 : "expected to find primary methods"; /* Either way the last entry is the one we want. */ return classPrimaryEntries.get(classPrimaryEntries.size() - 1).getPrimary().getHi(); } else { @@ -1311,7 +1457,7 @@ private static int findHi(List classPrimaryEntries, boolean includ } } /* We should never get here. */ - assert false; + assert false : "should not reach"; return 0; } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java index 23e206f91d27..bbd2479796ef 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfLineSectionImpl.java @@ -37,6 +37,7 @@ import com.oracle.objectfile.debuginfo.DebugInfoProvider.DebugFrameSizeChange; import org.graalvm.compiler.debug.DebugContext; +import java.util.Iterator; import java.util.Map; /** @@ -454,8 +455,22 @@ private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, by * end_sequence when we finish all the subranges in the method. */ long line = primaryRange.getLine(); - if (line < 0 && primaryEntry.getSubranges().size() > 0) { - line = primaryEntry.getSubranges().get(0).getLine(); + if (line < 0) { + Iterator iterator = primaryEntry.leafRangeIterator(); + if (iterator.hasNext()) { + final Range subRange = iterator.next(); + line = subRange.getLine(); + /* + * If line gets successfully retrieved from subrange get file index from there + * since the line might be from a different file for inlined methods + */ + if (line > 0) { + FileEntry subFileEntry = subRange.getFileEntry(); + if (subFileEntry != null) { + fileIdx = classEntry.localFilesIdx(subFileEntry); + } + } + } } if (line < 0) { line = 0; @@ -515,7 +530,9 @@ private int writeLineNumberTable(DebugContext context, ClassEntry classEntry, by /* * Now write a row for each subrange lo and hi. */ - for (Range subrange : primaryEntry.getSubranges()) { + Iterator iterator = primaryEntry.leafRangeIterator(); + while (iterator.hasNext()) { + Range subrange = iterator.next(); assert subrange.getLo() >= primaryRange.getLo(); assert subrange.getHi() <= primaryRange.getHi(); FileEntry subFileEntry = subrange.getFileEntry(); diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java index fdaf82261afe..ce45876549e2 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/elf/dwarf/DwarfSectionImpl.java @@ -598,4 +598,15 @@ protected int getMethodDeclarationIndex(ClassEntry classEntry, String methodName } return dwarfSections.getMethodDeclarationIndex(classEntry, methodName); } + + protected void setAbstractInlineMethodIndex(ClassEntry classEntry, String methodName, int pos) { + dwarfSections.setAbstractInlineMethodIndex(classEntry, methodName, pos); + } + + protected int getAbstractInlineMethodIndex(ClassEntry classEntry, String methodName) { + if (!contentByteArrayCreated()) { + return 0; + } + return dwarfSections.getAbstractInlineMethodIndex(classEntry, methodName); + } } diff --git a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java index 29a245d904fe..37daba6cda7b 100644 --- a/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java +++ b/substratevm/src/com.oracle.objectfile/src/com/oracle/objectfile/pecoff/cv/CVLineRecordBuilder.java @@ -31,6 +31,8 @@ import com.oracle.objectfile.debugentry.PrimaryEntry; import com.oracle.objectfile.debugentry.Range; +import java.util.Iterator; + /* * In CV4, the line table consists of a series of file headers followed by line number entries. * If this is a different file, then update the length of the previous file header, write the @@ -67,7 +69,9 @@ CVLineRecord build(PrimaryEntry entry) { debug("CVLineRecord.computeContents: processing primary range %s\n", primaryRange); processRange(primaryRange); - for (Range subRange : primaryEntry.getSubranges()) { + Iterator iterator = primaryEntry.leafRangeIterator(); + while (iterator.hasNext()) { + Range subRange = iterator.next(); debug("CVLineRecord.computeContents: processing range %s\n", subRange); processRange(subRange); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java index b80b3b507750..4997385e88af 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/image/NativeImageDebugInfoProvider.java @@ -364,7 +364,7 @@ private class NativeImageHeaderTypeInfo implements DebugHeaderTypeInfo { } void addField(String name, String valueType, int offset, @SuppressWarnings("hiding") int size) { - NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, typeName, valueType, offset, size); + NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size); fieldInfos.add(fieldinfo); } @@ -416,15 +416,13 @@ public Stream fieldInfoProvider() { private class NativeImageDebugHeaderFieldInfo implements DebugFieldInfo { private final String name; - private final String ownerType; private final String valueType; private final int offset; private final int size; private final int modifiers; - NativeImageDebugHeaderFieldInfo(String name, String ownerType, String valueType, int offset, int size) { + NativeImageDebugHeaderFieldInfo(String name, String valueType, int offset, int size) { this.name = name; - this.ownerType = ownerType; this.valueType = valueType; this.offset = offset; this.size = size; @@ -436,11 +434,6 @@ public String name() { return name; } - @Override - public String ownerType() { - return ownerType; - } - @Override public String valueType() { return valueType; @@ -585,11 +578,6 @@ public String name() { return field.getName(); } - @Override - public String ownerType() { - return typeName(); - } - @Override public String valueType() { HostedType valueType = field.getType(); @@ -655,21 +643,11 @@ public String name() { return name; } - @Override - public String ownerType() { - return typeName(); - } - @Override public String valueType() { return hostedMethod.getSignature().getReturnType(null).toJavaName(); } - @Override - public String paramSignature() { - return hostedMethod.format("%P"); - } - @Override public List paramTypes() { Signature signature = hostedMethod.getSignature(); @@ -740,7 +718,7 @@ private class NativeImageDebugArrayTypeInfo extends NativeImageDebugTypeInfo imp } void addField(String name, String valueType, int offset, @SuppressWarnings("hiding") int size) { - NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, typeName(), valueType, offset, size); + NativeImageDebugHeaderFieldInfo fieldinfo = new NativeImageDebugHeaderFieldInfo(name, valueType, offset, size); fieldInfos.add(fieldinfo); } @@ -861,8 +839,8 @@ public void debugContext(Consumer action) { } @Override - public String ownerType() { - return getDeclaringClass(hostedMethod, true).toJavaName(); + public ResolvedJavaType ownerType() { + return getDeclaringClass(hostedMethod, true); } @Override @@ -891,11 +869,6 @@ public String symbolNameForMethod() { return NativeImage.localSymbolNameForMethod(hostedMethod); } - @Override - public String paramSignature() { - return hostedMethod.format("%P"); - } - @Override public String valueType() { return hostedMethod.format("%R"); @@ -995,15 +968,21 @@ public int modifiers() { } private static boolean filterLineInfoSourceMapping(SourceMapping sourceMapping) { - if (!SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue()) { - return true; + NodeSourcePosition sourcePosition = sourceMapping.getSourcePosition(); + /* Don't report line info for zero length ranges. */ + if (sourceMapping.getStartOffset() == sourceMapping.getEndOffset()) { + return false; + } + /* Don't report inline line info unless the user has configured it. */ + if (SubstrateOptions.OmitInlinedMethodDebugLineInfo.getValue() && sourcePosition.getCaller() != null) { + return false; } - return sourceMapping.getSourcePosition().getCaller() == null; + return true; } /** - * Implementation of the DebugLineInfo API interface that allows line number info to be passed - * to an ObjectFile when generation of debug info is enabled. + * Implementation of the DebugLineInfo API interface that allows line number info (and more) to + * be passed to an ObjectFile when generation of debug info is enabled. */ private class NativeImageDebugLineInfo implements DebugLineInfo { private final int bci; @@ -1012,15 +991,32 @@ private class NativeImageDebugLineInfo implements DebugLineInfo { private final int hi; private Path cachePath; private Path fullFilePath; + private DebugLineInfo callersLineInfo; NativeImageDebugLineInfo(SourceMapping sourceMapping) { - NodeSourcePosition position = sourceMapping.getSourcePosition(); - int posbci = position.getBCI(); - this.bci = (posbci >= 0 ? posbci : 0); + this(sourceMapping.getSourcePosition(), sourceMapping.getStartOffset(), sourceMapping.getEndOffset()); + } + + NativeImageDebugLineInfo(DebugLineInfo lineInfo, NodeSourcePosition position) { + this(position, lineInfo.addressLo(), lineInfo.addressHi()); + } + + NativeImageDebugLineInfo(NodeSourcePosition position, int lo, int hi) { + this.bci = position.getBCI(); this.method = position.getMethod(); - this.lo = sourceMapping.getStartOffset(); - this.hi = sourceMapping.getEndOffset(); + this.lo = lo; + this.hi = hi; this.cachePath = SubstrateOptions.getDebugInfoSourceCacheRoot(); + NodeSourcePosition callerPosition = position.getCaller(); + /* Skip substitutions with bytecode index -1 */ + while (callerPosition != null && callerPosition.isSubstitution() && callerPosition.getBCI() == -1) { + callerPosition = callerPosition.getCaller(); + } + if (callerPosition != null) { + callersLineInfo = new NativeImageDebugLineInfo(this, callerPosition); + } else { + callersLineInfo = null; + } computeFullFilePath(); } @@ -1049,12 +1045,11 @@ public Path cachePath() { } @Override - public String ownerType() { + public ResolvedJavaType ownerType() { if (method instanceof HostedMethod) { - return getDeclaringClass((HostedMethod) method, true).toJavaName(); - } else { - return method.getDeclaringClass().toJavaName(); + return getDeclaringClass((HostedMethod) method, true); } + return method.getDeclaringClass(); } @Override @@ -1093,11 +1088,6 @@ public String valueType() { return method.format("%R"); } - @Override - public String paramSignature() { - return method.format("%P"); - } - @Override public String symbolNameForMethod() { return NativeImage.localSymbolNameForMethod(method); @@ -1124,7 +1114,7 @@ public int addressHi() { @Override public int line() { LineNumberTable lineNumberTable = method.getLineNumberTable(); - if (lineNumberTable != null) { + if (lineNumberTable != null && bci >= 0) { return lineNumberTable.getLineNumber(bci); } return -1; @@ -1159,6 +1149,11 @@ public int modifiers() { return method.getModifiers(); } + @Override + public DebugLineInfo getCaller() { + return callersLineInfo; + } + @SuppressWarnings("try") private void computeFullFilePath() { ResolvedJavaType declaringClass; diff --git a/substratevm/src/com.oracle.svm.test/src/hello/Hello.java b/substratevm/src/com.oracle.svm.test/src/hello/Hello.java index 0482024ce1ac..e70d133ee8ea 100644 --- a/substratevm/src/com.oracle.svm.test/src/hello/Hello.java +++ b/substratevm/src/com.oracle.svm.test/src/hello/Hello.java @@ -28,6 +28,9 @@ // Checkstyle: stop +import com.oracle.svm.core.annotate.AlwaysInline; +import com.oracle.svm.core.annotate.NeverInline; + public class Hello { public abstract static class Greeter { static Greeter greeter(String[] args) { @@ -53,7 +56,7 @@ public void greet() { public static class NamedGreeter extends Greeter { private String name; - NamedGreeter(String name) { + public NamedGreeter(String name) { this.name = name; } @@ -66,6 +69,105 @@ public void greet() { public static void main(String[] args) { Greeter greeter = Greeter.greeter(args); greeter.greet(); + /*- + * Perform the following call chains + * + * main --no-inline--> noInlineFoo --inline--> inlineMee --inline--> inlineMoo + * main --inline--> inlineCallChain --inline--> inlineMee --inline--> inlineMoo + * main --no-inline--> noInlineThis --inline--> inlineIs --inline--> inlineA --no-inline--> noInlineTest + * main --inline--> inlineFrom --no-inline--> noInlineHere --inline--> inlineMixTo --no-inline--+ + * ^ | + * +-------------(rec call n-times)-------------+ + * main --inline--> inlineFrom --inline--> inlineHere --inline--> inlineTo --inline--+ + * ^ | + * +---------(rec call n-times)----------+ + */ + noInlineFoo(); + inlineCallChain(); + noInlineThis(); + inlineFrom(); System.exit(0); } + + @NeverInline("For testing purposes") + private static void noInlineFoo() { + inlineMee(); + } + + @AlwaysInline("For testing purposes") + private static void inlineCallChain() { + inlineMee(); + } + + @AlwaysInline("For testing purposes") + private static void inlineMee() { + inlineMoo(); + } + + @AlwaysInline("For testing purposes") + private static void inlineMoo() { + System.out.println("This is a cow"); + } + + @NeverInline("For testing purposes") + private static void noInlineThis() { + inlineIs(); + } + + @AlwaysInline("For testing purposes") + private static void inlineIs() { + inlineA(); + } + + @AlwaysInline("For testing purposes") + private static void inlineA() { + noInlineTest(); + } + + @NeverInline("For testing purposes") + private static void noInlineTest() { + System.out.println("This is a test"); + } + + @AlwaysInline("For testing purposes") + private static void inlineFrom() { + noInlineHere(5); + inlineHere(5); + inlineTailRecursion(5); + } + + @NeverInline("For testing purposes") + private static void noInlineHere(int n) { + inlineMixTo(n); + } + + @AlwaysInline("For testing purposes") + private static void inlineMixTo(int n) { + if (n > 0) { + noInlineHere(n - 1); + } + System.out.println("Recursive mixed calls!"); + } + + @AlwaysInline("For testing purposes") + private static void inlineHere(int n) { + inlineTo(n); + } + + @AlwaysInline("For testing purposes") + private static void inlineTo(int n) { + if (n > 0) { + inlineHere(n - 1); + } + System.out.println("Recursive inline calls!"); + } + + @AlwaysInline("For testing purposes") + private static void inlineTailRecursion(int n) { + if (n <= 0) { + System.out.println("Recursive inline calls!"); + return; + } + inlineTailRecursion(n - 1); + } } diff --git a/substratevm/src/com.oracle.svm.test/src/hello/Target_hello_Hello_DefaultGreeter.java b/substratevm/src/com.oracle.svm.test/src/hello/Target_hello_Hello_DefaultGreeter.java index db73478cd50d..1fee4b7e22ca 100644 --- a/substratevm/src/com.oracle.svm.test/src/hello/Target_hello_Hello_DefaultGreeter.java +++ b/substratevm/src/com.oracle.svm.test/src/hello/Target_hello_Hello_DefaultGreeter.java @@ -26,6 +26,8 @@ package hello; +import com.oracle.svm.core.annotate.AlwaysInline; +import com.oracle.svm.core.annotate.NeverInline; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @@ -34,6 +36,25 @@ final class Target_hello_Hello_DefaultGreeter { @SuppressWarnings("static-method") @Substitute public void greet() { + SubstituteHelperClass substituteHelperClass = new SubstituteHelperClass(); + substituteHelperClass.inlineGreet(); + } + +} + +class SubstituteHelperClass { + @AlwaysInline("For testing purposes") + void inlineGreet() { + staticInlineGreet(); + } + + @AlwaysInline("For testing purposes") + private static void staticInlineGreet() { + nestedGreet(); + } + + @NeverInline("For testing purposes") + private static void nestedGreet() { // Checkstyle: stop System.out.println("Hello, substituted world!"); // Checkstyle: resume