Skip to content

Debug info handling for new EH try-catch #3496

New issue

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

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

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 25, 2021
Merged

Debug info handling for new EH try-catch #3496

merged 12 commits into from
Jan 25, 2021

Conversation

kripken
Copy link
Member

@kripken kripken commented Jan 15, 2021

We now have multiple catches in each try, and a possible catch-all.

This changes our "extra delimiter" storage to store either an "else"
(unchanged from before) or an arbitrary list of things - we use that
for try-catch.

I'm not sure how to test this. We can't synthesize DWARF in binaryen
itself, so it would be easiest if we can emit a small testcase from clang
using the new EH stuff?

@kripken kripken requested review from dschuff and aheejin January 15, 2021 19:48
Comment on lines -3070 to -3111
// We are called after parsing the byte, so we need to subtract one to
// get its position.
Copy link
Member

Choose a reason for hiding this comment

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

This is old code, but what does this subtracting one mean? Where do we do it?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh, I think the comment about subtracting one was just stale. A refactoring I did must have removed it, and forgot to update the comment...

@aheejin
Copy link
Member

aheejin commented Jan 17, 2021

I tried to generate a wasm file with debug info. I attached it here. (Github doesn't support 'wasm' extension so I compressed it)
test_eh_debuginfo.tar.gz

break;
case BinaryConsts::Catch:
case BinaryConsts::CatchAll: {
Copy link
Member Author

Choose a reason for hiding this comment

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

@aheejin actually this fails to compile, since the binary code for CatchAll is 5, the same as for Else, so it says we have a duplicate entry in the switch.

Is it correct that CatchAll has the same code as Else?

Copy link
Member

Choose a reason for hiding this comment

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

Yes that's correct; we are doing that to reuse the opcode because else cannot occur with a try.

@kripken
Copy link
Member Author

kripken commented Jan 19, 2021

@aheejin Thanks for the testcase!

That showed something was missing here. I added code in Print to log out delimiters in try-catch. (We have also been missing it in if-else... I'll add that later to keep this small.) With that, looks like everything works properly. For example, the first catch

   ;; code offset: 0x18
   (catch $event$0

is the third line entry in the debug_line dwarfdump,

            0x0000000000000018      5      3      1   0             0  is_stmt

After the roundtrip, that line entry is

            0x0000000000000012      5      3      1   0             0  is_stmt

So we moved from 0x18 to 0x12. And indeed the catch is printed as being at that binary offset,

   ;; code offset: 0x12
   (catch $event$0

Base automatically changed from master to main January 19, 2021 21:59
case BinaryConsts::CatchAll: {
case BinaryConsts::Catch: {
// TODO: add CatchAll, but the value of the constant is identical to Else
// case BinaryConsts::CatchAll: {
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we can add CatchAll handling in case BinaryConsts::Else:? We will know if that is not an Else but a CatchAll if we encountered a try but not an end yet. But because control flow sturctures can be nested, we would need a stack to track this, something like I used as procesStack in #3494...?

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, yeah, a stack seems unavoidable. Looks like we already have a control flow stack here, luckily, which I was able to use, PTAL.

Copy link
Member

@aheejin aheejin left a comment

Choose a reason for hiding this comment

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

Thank you!

@kripken kripken merged commit c74acc0 into main Jan 25, 2021
@kripken kripken deleted the temp branch January 25, 2021 18:12
aheejin added a commit to aheejin/binaryen that referenced this pull request Feb 17, 2021
We decided to change `catch_all`'s opcode from 0x05, which is the same
as `else`, to 0x19, to avoid some complicated handling in the tools.

See: WebAssembly/exception-handling#147

---

dwarf_with_exceptions.wasm was added in WebAssembly#3496 but we didn't comment on
how that file was created, so I'll add some comment on that here, in
case we need to regenerate this file with some modifications. I
regenerated dwarf_with_exceptions.wasm, so some variable names and such
have changed.

cpp file:
```cpp
void foo();

void test_debuginfo() {
  try {
    foo();
  } catch (...) {
    foo();
  }
}
```

Run:
```
$ clang++ -std=c++14 -stdlib=libc++ --target=wasm32-unknown-unknown -fwasm-exceptions -Xclang -disable-O0-optnone -c -S -emit-llvm test_debuginfo.cpp -o temp.ll
$ opt -S -mem2reg -simplifycfg temp.ll -o test_debuginfo.ll
```
(`opt -mem2reg -simplifycfg` was run to make the code little tidier.
This basically promotes stack loads/saves to registers and simplifies
the CFG.)

Resulting ll file, after modifying some function names and removing
personal info in directory names:
```llvm
source_filename = "test_debuginfo.cpp"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"

$__clang_call_terminate = comdat any

; Function Attrs: noinline mustprogress
define hidden void @test_debuginfo() #0 personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) !dbg !7 {
entry:
  invoke void @foo()
          to label %try.cont unwind label %catch.dispatch, !dbg !10

catch.dispatch:                                   ; preds = %entry
  %0 = catchswitch within none [label %catch.start] unwind to caller, !dbg !12

catch.start:                                      ; preds = %catch.dispatch
  %1 = catchpad within %0 [i8* null], !dbg !12
  %2 = call i8* @llvm.wasm.get.exception(token %1), !dbg !12
  %3 = call i32 @llvm.wasm.get.ehselector(token %1), !dbg !12
  %4 = call i8* @__cxa_begin_catch(i8* %2) WebAssembly#2 [ "funclet"(token %1) ], !dbg !12
  invoke void @foo() [ "funclet"(token %1) ]
          to label %invoke.cont1 unwind label %ehcleanup, !dbg !13

invoke.cont1:                                     ; preds = %catch.start
  call void @__cxa_end_catch() [ "funclet"(token %1) ], !dbg !15
  catchret from %1 to label %try.cont, !dbg !15

try.cont:                                         ; preds = %entry, %invoke.cont1
  ret void, !dbg !16

ehcleanup:                                        ; preds = %catch.start
  %5 = cleanuppad within %1 [], !dbg !15
  invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
          to label %invoke.cont2 unwind label %terminate, !dbg !15

invoke.cont2:                                     ; preds = %ehcleanup
  cleanupret from %5 unwind to caller, !dbg !15

terminate:                                        ; preds = %ehcleanup
  %6 = cleanuppad within %5 [], !dbg !15
  %7 = call i8* @llvm.wasm.get.exception(token %6), !dbg !15
  call void @__clang_call_terminate(i8* %7) WebAssembly#5 [ "funclet"(token %6) ], !dbg !15
  unreachable, !dbg !15
}

declare void @foo() WebAssembly#1

declare i32 @__gxx_wasm_personality_v0(...)

; Function Attrs: nounwind
declare i8* @llvm.wasm.get.exception(token) WebAssembly#2

; Function Attrs: nounwind
declare i32 @llvm.wasm.get.ehselector(token) WebAssembly#2

; Function Attrs: nounwind readnone
declare i32 @llvm.eh.typeid.for(i8*) WebAssembly#3

declare i8* @__cxa_begin_catch(i8*)

declare void @__cxa_end_catch()

; Function Attrs: noinline noreturn nounwind
define linkonce_odr hidden void @__clang_call_terminate(i8* %0) WebAssembly#4 comdat {
  %2 = call i8* @__cxa_begin_catch(i8* %0) WebAssembly#2
  call void @_ZSt9terminatev() WebAssembly#5
  unreachable
}

declare void @_ZSt9terminatev()

attributes #0 = { noinline mustprogress "disable-tail-calls"="false" "frame-pointer"="none" "min-legal-vector-width"="0" "no-jump-tables"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+exception-handling" }
attributes WebAssembly#1 = { "disable-tail-calls"="false" "frame-pointer"="none" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+exception-handling" }
attributes WebAssembly#2 = { nounwind }
attributes WebAssembly#3 = { nounwind readnone }
attributes WebAssembly#4 = { noinline noreturn nounwind }
attributes WebAssembly#5 = { noreturn nounwind }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!3, !4, !5}
!llvm.ident = !{!6}

!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 13.0.0 (https://github.com/llvm/llvm-project.git 3c4c205060c9398da705eb71b63ddd8a04999de9)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, nameTableKind: None)
!1 = !DIFile(filename: "test_debuginfo.cpp", directory: "/")
!2 = !{}
!3 = !{i32 7, !"Dwarf Version", i32 4}
!4 = !{i32 2, !"Debug Info Version", i32 3}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{!"clang version 13.0.0 (https://github.com/llvm/llvm-project.git 3c4c205060c9398da705eb71b63ddd8a04999de9)"}
!7 = distinct !DISubprogram(name: "test_debuginfo", linkageName: "test_debuginfo", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
!8 = !DISubroutineType(types: !9)
!9 = !{null}
!10 = !DILocation(line: 5, column: 5, scope: !11)
!11 = distinct !DILexicalBlock(scope: !7, file: !1, line: 4, column: 7)
!12 = !DILocation(line: 6, column: 3, scope: !11)
!13 = !DILocation(line: 7, column: 5, scope: !14)
!14 = distinct !DILexicalBlock(scope: !7, file: !1, line: 6, column: 17)
!15 = !DILocation(line: 8, column: 3, scope: !14)
!16 = !DILocation(line: 9, column: 1, scope: !7)
```

Run:
```
llc -exception-model=wasm -mattr=+exception-handling -filetype=obj test_debuginfo.ll -o test_debuginfo.o
wasm-ld --no-entry --no-gc-sections --allow-undefined test_debuginfo.o -o test_debuginfo.wasm
```
aheejin added a commit to aheejin/binaryen that referenced this pull request Feb 17, 2021
We decided to change `catch_all`'s opcode from 0x05, which is the same
as `else`, to 0x19, to avoid some complicated handling in the tools.

See: WebAssembly/exception-handling#147

---

dwarf_with_exceptions.wasm was added in WebAssembly#3496 but we didn't comment on
how that file was created, so I'll add some comment on that here, in
case we need to regenerate this file with some modifications. I
regenerated dwarf_with_exceptions.wasm, so some variable names and such
have changed.

cpp file:
```cpp
void foo();

void test_debuginfo() {
  try {
    foo();
  } catch (...) {
    foo();
  }
}
```

Run:
```
$ clang++ -std=c++14 -stdlib=libc++ --target=wasm32-unknown-unknown -fwasm-exceptions -Xclang -disable-O0-optnone -c -S -emit-llvm test_debuginfo.cpp -o temp.ll
$ opt -S -mem2reg -simplifycfg temp.ll -o test_debuginfo.ll
```
(`opt -mem2reg -simplifycfg` was run to make the code little tidier.
This basically promotes stack loads/saves to registers and simplifies
the CFG.)

Resulting ll file, after modifying some function names and removing
personal info in directory names:
```llvm
source_filename = "test_debuginfo.cpp"
target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
target triple = "wasm32-unknown-unknown"

$__clang_call_terminate = comdat any

; Function Attrs: noinline mustprogress
define hidden void @test_debuginfo() #0 personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) !dbg !7 {
entry:
  invoke void @foo()
          to label %try.cont unwind label %catch.dispatch, !dbg !10

catch.dispatch:                                   ; preds = %entry
  %0 = catchswitch within none [label %catch.start] unwind to caller, !dbg !12

catch.start:                                      ; preds = %catch.dispatch
  %1 = catchpad within %0 [i8* null], !dbg !12
  %2 = call i8* @llvm.wasm.get.exception(token %1), !dbg !12
  %3 = call i32 @llvm.wasm.get.ehselector(token %1), !dbg !12
  %4 = call i8* @__cxa_begin_catch(i8* %2) WebAssembly#2 [ "funclet"(token %1) ], !dbg !12
  invoke void @foo() [ "funclet"(token %1) ]
          to label %invoke.cont1 unwind label %ehcleanup, !dbg !13

invoke.cont1:                                     ; preds = %catch.start
  call void @__cxa_end_catch() [ "funclet"(token %1) ], !dbg !15
  catchret from %1 to label %try.cont, !dbg !15

try.cont:                                         ; preds = %entry, %invoke.cont1
  ret void, !dbg !16

ehcleanup:                                        ; preds = %catch.start
  %5 = cleanuppad within %1 [], !dbg !15
  invoke void @__cxa_end_catch() [ "funclet"(token %5) ]
          to label %invoke.cont2 unwind label %terminate, !dbg !15

invoke.cont2:                                     ; preds = %ehcleanup
  cleanupret from %5 unwind to caller, !dbg !15

terminate:                                        ; preds = %ehcleanup
  %6 = cleanuppad within %5 [], !dbg !15
  %7 = call i8* @llvm.wasm.get.exception(token %6), !dbg !15
  call void @__clang_call_terminate(i8* %7) WebAssembly#5 [ "funclet"(token %6) ], !dbg !15
  unreachable, !dbg !15
}

declare void @foo() WebAssembly#1

declare i32 @__gxx_wasm_personality_v0(...)

; Function Attrs: nounwind
declare i8* @llvm.wasm.get.exception(token) WebAssembly#2

; Function Attrs: nounwind
declare i32 @llvm.wasm.get.ehselector(token) WebAssembly#2

; Function Attrs: nounwind readnone
declare i32 @llvm.eh.typeid.for(i8*) WebAssembly#3

declare i8* @__cxa_begin_catch(i8*)

declare void @__cxa_end_catch()

; Function Attrs: noinline noreturn nounwind
define linkonce_odr hidden void @__clang_call_terminate(i8* %0) WebAssembly#4 comdat {
  %2 = call i8* @__cxa_begin_catch(i8* %0) WebAssembly#2
  call void @_ZSt9terminatev() WebAssembly#5
  unreachable
}

declare void @_ZSt9terminatev()

attributes #0 = { noinline mustprogress "disable-tail-calls"="false" "frame-pointer"="none" "min-legal-vector-width"="0" "no-jump-tables"="false" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+exception-handling" }
attributes WebAssembly#1 = { "disable-tail-calls"="false" "frame-pointer"="none" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="generic" "target-features"="+exception-handling" }
attributes WebAssembly#2 = { nounwind }
attributes WebAssembly#3 = { nounwind readnone }
attributes WebAssembly#4 = { noinline noreturn nounwind }
attributes WebAssembly#5 = { noreturn nounwind }

!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!3, !4, !5}
!llvm.ident = !{!6}

!0 = distinct !DICompileUnit(language: DW_LANG_C_plus_plus_14, file: !1, producer: "clang version 13.0.0 (https://github.com/llvm/llvm-project.git 3c4c205060c9398da705eb71b63ddd8a04999de9)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2, nameTableKind: None)
!1 = !DIFile(filename: "test_debuginfo.cpp", directory: "/")
!2 = !{}
!3 = !{i32 7, !"Dwarf Version", i32 4}
!4 = !{i32 2, !"Debug Info Version", i32 3}
!5 = !{i32 1, !"wchar_size", i32 4}
!6 = !{!"clang version 13.0.0 (https://github.com/llvm/llvm-project.git 3c4c205060c9398da705eb71b63ddd8a04999de9)"}
!7 = distinct !DISubprogram(name: "test_debuginfo", linkageName: "test_debuginfo", scope: !1, file: !1, line: 3, type: !8, scopeLine: 3, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0, retainedNodes: !2)
!8 = !DISubroutineType(types: !9)
!9 = !{null}
!10 = !DILocation(line: 5, column: 5, scope: !11)
!11 = distinct !DILexicalBlock(scope: !7, file: !1, line: 4, column: 7)
!12 = !DILocation(line: 6, column: 3, scope: !11)
!13 = !DILocation(line: 7, column: 5, scope: !14)
!14 = distinct !DILexicalBlock(scope: !7, file: !1, line: 6, column: 17)
!15 = !DILocation(line: 8, column: 3, scope: !14)
!16 = !DILocation(line: 9, column: 1, scope: !7)
```

Run:
```
$ llc -exception-model=wasm -mattr=+exception-handling -filetype=obj test_debuginfo.ll -o test_debuginfo.o
$ wasm-ld --no-entry --no-gc-sections --allow-undefined test_debuginfo.o -o test_debuginfo.wasm
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants