[WebAssembly] Fix unwind destination mismatches in CFG stackify

Summary:
Linearing the control flow by placing `try`/`end_try` markers can create
mismatches in unwind destinations. This patch resolves these mismatches
by wrapping those instructions with an incorrect unwind destination with
a nested `try`/`catch`/`end_try` and branching to the right destination
within the new catch block.

Reviewers: dschuff

Subscribers: sunfish, sbc100, jgravelle-google, chrib, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D48345

llvm-svn: 357343
diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
index f11c4ff..9cb5a05 100644
--- a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
+++ b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
@@ -1,6 +1,6 @@
 ; RUN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling | FileCheck %s
 ; RUN: llc < %s -O0 -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -verify-machineinstrs -exception-model=wasm -mattr=+exception-handling | FileCheck %s --check-prefix=NOOPT
-; RUN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling -wasm-disable-ehpad-sort
+; RUN: llc < %s -disable-wasm-fallthrough-return-opt -wasm-disable-explicit-locals -wasm-keep-registers -disable-block-placement -verify-machineinstrs -fast-isel=false -machine-sink-split-probability-threshold=0 -cgp-freq-ratio-to-skip-merge=1000 -exception-model=wasm -mattr=+exception-handling -wasm-disable-ehpad-sort | FileCheck %s --check-prefix=NOSORT
 
 target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
 target triple = "wasm32-unknown-unknown"
@@ -368,6 +368,254 @@
   br label %loop
 }
 
+; Some of test cases below are hand-tweaked by deleting some library calls to
+; simplify tests and changing the order of basic blocks to cause unwind
+; destination mismatches. And we use -wasm-disable-ehpad-sort to create maximum
+; number of mismatches in several tests below.
+
+; 'call bar''s original unwind destination was 'catch14', but after control flow
+; linearization, its unwind destination incorrectly becomes 'catch15'. We fix
+; this by wrapping the call with a nested try/catch/end_try and branching to the
+; right destination (label32).
+
+; NOSORT-LABEL: test5
+; NOSORT:   block
+; NOSORT:     try
+; NOSORT:       try
+; NOSORT:         call      foo
+; --- Nested try/catch/end_try starts
+; NOSORT:         try
+; NOSORT:           call      bar
+; NOSORT:         catch     $drop=
+; NOSORT:           br        2                        # 2: down to label32
+; NOSORT:         end_try
+; --- Nested try/catch/end_try ends
+; NOSORT:         br        2                          # 2: down to label31
+; NOSORT:       catch     $drop=                       # catch15:
+; NOSORT:         br        2                          # 2: down to label31
+; NOSORT:       end_try
+; NOSORT:     catch     $drop=                         # catch14:
+; NOSORT:     end_try                                  # label32:
+; NOSORT:   end_block                                  # label31:
+; NOSORT:   return
+
+define void @test5() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+bb0:
+  invoke void @foo()
+          to label %bb1 unwind label %catch.dispatch0
+
+bb1:                                              ; preds = %bb0
+  invoke void @bar()
+          to label %try.cont unwind label %catch.dispatch1
+
+catch.dispatch0:                                  ; preds = %bb0
+  %0 = catchswitch within none [label %catch.start0] unwind to caller
+
+catch.start0:                                     ; preds = %catch.dispatch0
+  %1 = catchpad within %0 [i8* null]
+  %2 = call i8* @llvm.wasm.get.exception(token %1)
+  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
+  catchret from %1 to label %try.cont
+
+catch.dispatch1:                                  ; preds = %bb1
+  %4 = catchswitch within none [label %catch.start1] unwind to caller
+
+catch.start1:                                     ; preds = %catch.dispatch1
+  %5 = catchpad within %4 [i8* null]
+  %6 = call i8* @llvm.wasm.get.exception(token %5)
+  %7 = call i32 @llvm.wasm.get.ehselector(token %5)
+  catchret from %5 to label %try.cont
+
+try.cont:                                         ; preds = %catch.start1, %catch.start0, %bb1
+  ret void
+}
+
+; Two 'call bar''s original unwind destination was the caller, but after control
+; flow linearization, their unwind destination incorrectly becomes 'catch17'. We
+; fix this by wrapping the call with a nested try/catch/end_try and branching to
+; the right destination (label4), from which we rethrow the exception to the
+; caller.
+
+; NOSORT-LABEL: test6
+; NOSORT:   try
+; NOSORT:     call      foo
+; --- Nested try/catch/end_try starts
+; NOSORT:     try
+; NOSORT:       call      bar
+; NOSORT:       call      bar
+; NOSORT:     catch     $[[REG:[0-9]+]]=
+; NOSORT:       br        1                            # 1: down to label35
+; NOSORT:     end_try
+; --- Nested try/catch/end_try ends
+; NOSORT:     return
+; NOSORT:   catch     $drop=                           # catch17:
+; NOSORT:     return
+; NOSORT:   end_try                                    # label35:
+; NOSORT:   rethrow   $[[REG]]                         # to caller
+
+define void @test6() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+bb0:
+  invoke void @foo()
+          to label %bb1 unwind label %catch.dispatch0
+
+bb1:                                              ; preds = %bb0
+  call void @bar()
+  call void @bar()
+  ret void
+
+catch.dispatch0:                                  ; preds = %bb0
+  %0 = catchswitch within none [label %catch.start0] unwind to caller
+
+catch.start0:                                     ; preds = %catch.dispatch0
+  %1 = catchpad within %0 [i8* null]
+  %2 = call i8* @llvm.wasm.get.exception(token %1)
+  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
+  catchret from %1 to label %try.cont
+
+try.cont:                                         ; preds = %catch.start0
+  ret void
+}
+
+; If not for the unwind destination mismatch, the LOOP marker here would have an
+; i32 signature. But because we add a rethrow instruction at the end of the
+; appendix block, now the LOOP marker does not have a signature (= has a void
+; signature). Here the two calls two 'bar' are supposed to throw up to the
+; caller, but incorrectly unwind to 'catch19' after linearizing the CFG.
+
+; NOSORT-LABEL: test7
+; NOSORT: block
+; NOSORT-NOT: loop      i32
+; NOSORT:   loop                                       # label38:
+; NOSORT:     try
+; NOSORT:       call      foo
+; --- Nested try/catch/end_try starts
+; NOSORT:       try
+; NOSORT:         call      bar
+; NOSORT:         call      bar
+; NOSORT:       catch     $[[REG:[0-9]+]]=
+; NOSORT:         br        1                          # 1: down to label39
+; NOSORT:       end_try
+; --- Nested try/catch/end_try ends
+; NOSORT:       return    {{.*}}
+; NOSORT:     catch     $drop=                         # catch19:
+; NOSORT:       br        1                            # 1: up to label38
+; NOSORT:     end_try                                  # label39:
+; NOSORT:   end_loop
+; NOSORT: end_block
+; NOSORT: rethrow   $[[REG]]                           # to caller
+
+define i32 @test7(i32* %p) personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+entry:
+  store volatile i32 0, i32* %p
+  br label %loop
+
+loop:                                             ; preds = %try.cont, %entry
+  store volatile i32 1, i32* %p
+  invoke void @foo()
+          to label %bb unwind label %catch.dispatch
+
+bb:                                               ; preds = %loop
+  call void @bar()
+  call void @bar()
+  ret i32 0
+
+catch.dispatch:                                   ; preds = %loop
+  %0 = catchswitch within none [label %catch.start] unwind to caller
+
+catch.start:                                      ; preds = %catch.dispatch
+  %1 = catchpad within %0 [i8* null]
+  %2 = call i8* @llvm.wasm.get.exception(token %1)
+  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
+  catchret from %1 to label %try.cont
+
+try.cont:                                         ; preds = %catch.start
+  br label %loop
+}
+
+; When we have both kinds of EH pad unwind mismatches:
+; - A may-throw instruction unwinds to an incorrect EH pad after linearizing the
+;   CFG, when it is supposed to unwind to another EH pad.
+; - A may-throw instruction unwinds to an incorrect EH pad after linearizing the
+;   CFG, when it is supposed to unwind to the caller.
+
+; NOSORT-LABEL: test8
+; NOSORT: block
+; NOSORT:   block
+; NOSORT:     try
+; NOSORT:       try
+; NOSORT:         call      foo
+; --- Nested try/catch/end_try starts
+; NOSORT:         try
+; NOSORT:           call      bar
+; NOSORT:         catch     $[[REG0:[0-9]+]]=
+; NOSORT:           br        2                        # 2: down to label43
+; NOSORT:         end_try
+; --- Nested try/catch/end_try ends
+; NOSORT:         br        2                          # 2: down to label42
+; NOSORT:       catch     {{.*}}
+; NOSORT:         block     i32
+; NOSORT:           br_on_exn   0, {{.*}}              # 0: down to label46
+; --- Nested try/catch/end_try starts
+; NOSORT:           try
+; NOSORT:             rethrow   {{.*}}                 # down to catch24
+; NOSORT:           catch     $[[REG1:[0-9]+]]=        # catch24:
+; NOSORT:             br        5                      # 5: down to label41
+; NOSORT:           end_try
+; --- Nested try/catch/end_try ends
+; NOSORT:         end_block                            # label46:
+; NOSORT:         i32.call  $drop=, __cxa_begin_catch
+; --- Nested try/catch/end_try starts
+; NOSORT:         try
+; NOSORT:           call      __cxa_end_catch
+; NOSORT:         catch     $[[REG1]]=
+; NOSORT:           br        4                        # 4: down to label41
+; NOSORT:         end_try
+; --- Nested try/catch/end_try ends
+; NOSORT:         br        2                          # 2: down to label42
+; NOSORT:       end_try
+; NOSORT:     catch     $[[REG0]]=
+; NOSORT:     end_try                                  # label43:
+; NOSORT:     i32.call  $drop=, __cxa_begin_catch
+; NOSORT:     call      __cxa_end_catch
+; NOSORT:   end_block                                  # label42:
+; NOSORT:   return
+; NOSORT: end_block                                    # label41:
+; NOSORT: rethrow   $[[REG1]]                          # to caller
+define void @test8() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) {
+bb0:
+  invoke void @foo()
+          to label %bb1 unwind label %catch.dispatch0
+
+bb1:                                              ; preds = %bb0
+  invoke void @bar()
+          to label %try.cont unwind label %catch.dispatch1
+
+catch.dispatch0:                                  ; preds = %bb0
+  %0 = catchswitch within none [label %catch.start0] unwind to caller
+
+catch.start0:                                     ; preds = %catch.dispatch0
+  %1 = catchpad within %0 [i8* null]
+  %2 = call i8* @llvm.wasm.get.exception(token %1)
+  %3 = call i32 @llvm.wasm.get.ehselector(token %1)
+  %4 = call i8* @__cxa_begin_catch(i8* %2) [ "funclet"(token %1) ]
+  call void @__cxa_end_catch() [ "funclet"(token %1) ]
+  catchret from %1 to label %try.cont
+
+catch.dispatch1:                                  ; preds = %bb1
+  %5 = catchswitch within none [label %catch.start1] unwind to caller
+
+catch.start1:                                     ; preds = %catch.dispatch1
+  %6 = catchpad within %5 [i8* null]
+  %7 = call i8* @llvm.wasm.get.exception(token %6)
+  %8 = call i32 @llvm.wasm.get.ehselector(token %6)
+  %9 = call i8* @__cxa_begin_catch(i8* %7) [ "funclet"(token %6) ]
+  call void @__cxa_end_catch() [ "funclet"(token %6) ]
+  catchret from %6 to label %try.cont
+
+try.cont:                                         ; preds = %catch.start1, %catch.start0, %bb1
+  ret void
+}
+
 declare void @foo()
 declare void @bar()
 declare i32 @__gxx_wasm_personality_v0(...)