Skip to content
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

[WebAssembly] Fix phi handling for Wasm SjLj #99730

Merged
merged 1 commit into from
Jul 23, 2024
Merged

Conversation

aheejin
Copy link
Member

@aheejin aheejin commented Jul 20, 2024

In Wasm SjLj, longjmpable calls that in functions that call setjmp are converted into invokes. Those invokes are meant to unwind to catch.dispatch.longjmp to figure out which setjmp those longjmp buffers belong to:

/// catch.dispatch.longjmp:
/// %0 = catchswitch within none [label %catch.longjmp] unwind to caller
///
/// catch.longjmp:
/// %longjmp.args = wasm.catch() ;; struct __WasmLongjmpArgs
/// %env = load 'env' field from __WasmLongjmpArgs
/// %val = load 'val' field from __WasmLongjmpArgs
/// %label = __wasm_setjmp_test(%env, functionInvocationId);
/// if (%label == 0)
/// __wasm_longjmp(%env, %val)
/// catchret to %setjmp.dispatch

But in case a longjmpable call is within another catchpad or cleanuppad scope, to maintain the nested scope structure, we should make them unwind to the scope's next unwind destination and not directly to catch.dispatch.longjmp:

// Change it to an invoke and make it unwind to the catch.dispatch.longjmp
// BB. If the call is enclosed in another catchpad/cleanuppad scope, unwind
// to its parent pad's unwind destination instead to preserve the scope
// structure. It will eventually unwind to the catch.dispatch.longjmp.
SmallVector<OperandBundleDef, 1> Bundles;
BasicBlock *UnwindDest = nullptr;
if (auto Bundle = CI->getOperandBundle(LLVMContext::OB_funclet)) {
Instruction *FromPad = cast<Instruction>(Bundle->Inputs[0]);
while (!UnwindDest) {
if (auto *CPI = dyn_cast<CatchPadInst>(FromPad)) {
UnwindDest = CPI->getCatchSwitch()->getUnwindDest();
break;
}
if (auto *CPI = dyn_cast<CleanupPadInst>(FromPad)) {
// getCleanupRetUnwindDest() can return nullptr when
// 1. This cleanuppad's matching cleanupret uwninds to caller
// 2. There is no matching cleanupret because it ends with
// unreachable.
// In case of 2, we need to traverse the parent pad chain.
UnwindDest = getCleanupRetUnwindDest(CPI);
Value *ParentPad = CPI->getParentPad();
if (isa<ConstantTokenNone>(ParentPad))
break;
FromPad = cast<Instruction>(ParentPad);
}
}
}
if (!UnwindDest)
UnwindDest = CatchDispatchLongjmpBB;
changeToInvokeAndSplitBasicBlock(CI, UnwindDest);
In this case the longjmps will eventually unwind to catch.dispatch.longjmp and be handled there.

In this case, it is possible that the unwind destination (which is an existing catchpad or cleanuppad) may already have phis. And because the unwind destinations get new predecessors because of the newly created invokes, those phis need to have new entries for those new predecessors.

This adds new preds as new incoming blocks to those phis, and we use a separate SSAUpdater to calculate the correct incoming values to those blocks.

I have assumed SSAUpdaterBulk used in rebuildSSA would take care of these things, but apparently it doesn't. It takes available defs and adds phis in the defs' dominance frontiers, i.e., where each def's dominance ends, and rewrites other uses based on the newly added phis. But it doesn't add entries to existing phis, and the case in this bug may not even involve dominance frontiers; this bug is simply about existing phiss that have gained new preds need new entries for them. It is kind of surprising that this bug was only reported recently, given that this pass has not been changed much in years.

Fixes #97496 and fixes
emscripten-core/emscripten#22170.

In Wasm SjLj, longjmpable `call`s that in functions that call `setjmp`
are converted into `invoke`s. Those `invoke`s are meant to unwind to
`catch.dispatch.longjmp` to figure out which `setjmp` those `longjmp`
buffers belong to:
https://github.com/llvm/llvm-project/blob/fada9227325b3eaa0bdc09a486f29a7f08b7b3fb/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L250-L260

But in case a longjmpable call is within another `catchpad` or
`cleanuppad` scope, to maintain the nested scope structure, we should
make them unwind to the scope's next unwind destination and not directly
to `catch.dispatch.longjmp`:
https://github.com/llvm/llvm-project/blob/fada9227325b3eaa0bdc09a486f29a7f08b7b3fb/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L1698-L1727
In this case the longjmps will eventually unwind to
`catch.dispatch.longjmp` and be handled there.

In this case, it is possible that the unwind destination (which is an
existing `catchpad` or `cleanuppad`) may already have `phi`s. And
because the unwind destinations get new predecessors because of the
newly created `invoke`s, those `phi`s need to have new entries for those
new predecessors.

This adds new preds as new incoming blocks to those `phi`s, and we use a
separate `SSAUpdater` to calculate the correct incoming values to those
blocks.

I have assumed `SSAUpdaterBulk` used in `rebuildSSA` would take care of
these things, but apparently it doesn't. It takes available defs and
adds `phi`s in the defs' dominance frontiers, i.e., where each def's
dominance ends, and rewrites other uses based on the newly added `phi`s.
But it doesn't add entries to existing `phi`s, and the case in this bug
may not even involve dominance frontiers; this bug is simply about
existing `phis`s that have gained new preds need new entries for
them. It is kind of surprising that this bug was only reported recently,
given that this pass has not been changed much in years.

Fixes llvm#97496 and fixes
emscripten-core/emscripten#22170.
@llvmbot
Copy link

llvmbot commented Jul 20, 2024

@llvm/pr-subscribers-backend-webassembly

Author: Heejin Ahn (aheejin)

Changes

In Wasm SjLj, longjmpable calls that in functions that call setjmp are converted into invokes. Those invokes are meant to unwind to catch.dispatch.longjmp to figure out which setjmp those longjmp buffers belong to:

/// catch.dispatch.longjmp:
/// %0 = catchswitch within none [label %catch.longjmp] unwind to caller
///
/// catch.longjmp:
/// %longjmp.args = wasm.catch() ;; struct __WasmLongjmpArgs
/// %env = load 'env' field from __WasmLongjmpArgs
/// %val = load 'val' field from __WasmLongjmpArgs
/// %label = __wasm_setjmp_test(%env, functionInvocationId);
/// if (%label == 0)
/// __wasm_longjmp(%env, %val)
/// catchret to %setjmp.dispatch

But in case a longjmpable call is within another catchpad or cleanuppad scope, to maintain the nested scope structure, we should make them unwind to the scope's next unwind destination and not directly to catch.dispatch.longjmp:

// Change it to an invoke and make it unwind to the catch.dispatch.longjmp
// BB. If the call is enclosed in another catchpad/cleanuppad scope, unwind
// to its parent pad's unwind destination instead to preserve the scope
// structure. It will eventually unwind to the catch.dispatch.longjmp.
SmallVector<OperandBundleDef, 1> Bundles;
BasicBlock *UnwindDest = nullptr;
if (auto Bundle = CI->getOperandBundle(LLVMContext::OB_funclet)) {
Instruction *FromPad = cast<Instruction>(Bundle->Inputs[0]);
while (!UnwindDest) {
if (auto *CPI = dyn_cast<CatchPadInst>(FromPad)) {
UnwindDest = CPI->getCatchSwitch()->getUnwindDest();
break;
}
if (auto *CPI = dyn_cast<CleanupPadInst>(FromPad)) {
// getCleanupRetUnwindDest() can return nullptr when
// 1. This cleanuppad's matching cleanupret uwninds to caller
// 2. There is no matching cleanupret because it ends with
// unreachable.
// In case of 2, we need to traverse the parent pad chain.
UnwindDest = getCleanupRetUnwindDest(CPI);
Value *ParentPad = CPI->getParentPad();
if (isa<ConstantTokenNone>(ParentPad))
break;
FromPad = cast<Instruction>(ParentPad);
}
}
}
if (!UnwindDest)
UnwindDest = CatchDispatchLongjmpBB;
changeToInvokeAndSplitBasicBlock(CI, UnwindDest);
In this case the longjmps will eventually unwind to catch.dispatch.longjmp and be handled there.

In this case, it is possible that the unwind destination (which is an existing catchpad or cleanuppad) may already have phis. And because the unwind destinations get new predecessors because of the newly created invokes, those phis need to have new entries for those new predecessors.

This adds new preds as new incoming blocks to those phis, and we use a separate SSAUpdater to calculate the correct incoming values to those blocks.

I have assumed SSAUpdaterBulk used in rebuildSSA would take care of these things, but apparently it doesn't. It takes available defs and adds phis in the defs' dominance frontiers, i.e., where each def's dominance ends, and rewrites other uses based on the newly added phis. But it doesn't add entries to existing phis, and the case in this bug may not even involve dominance frontiers; this bug is simply about existing phiss that have gained new preds need new entries for them. It is kind of surprising that this bug was only reported recently, given that this pass has not been changed much in years.

Fixes #97496 and fixes
emscripten-core/emscripten#22170.


Full diff: https://github.com/llvm/llvm-project/pull/99730.diff

2 Files Affected:

  • (modified) llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp (+50)
  • (added) llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll (+126)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index 7cc030460e30f..9e97f6b38d0e1 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -777,6 +777,8 @@ void WebAssemblyLowerEmscriptenEHSjLj::rebuildSSA(Function &F) {
   SSAUpdaterBulk SSA;
   for (BasicBlock &BB : F) {
     for (Instruction &I : BB) {
+      if (I.getType()->isVoidTy())
+        continue;
       unsigned VarID = SSA.AddVariable(I.getName(), I.getType());
       // If a value is defined by an invoke instruction, it is only available in
       // its normal destination and not in its unwind destination.
@@ -1687,6 +1689,8 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
   }
 
+  SmallDenseMap<BasicBlock *, SmallPtrSet<BasicBlock *, 4>, 4>
+      UnwindDestToNewPreds;
   for (auto *CI : LongjmpableCalls) {
     // Even if the callee function has attribute 'nounwind', which is true for
     // all C functions, it can longjmp, which means it can throw a Wasm
@@ -1724,6 +1728,11 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
     if (!UnwindDest)
       UnwindDest = CatchDispatchLongjmpBB;
+    // Because we are changing a longjmpable call to an invoke, its unwind
+    // destination can be an existing EH pad that already have phis, and the BB
+    // with the newly created invoke will become a new predecessor of that EH
+    // pad. In this case we need to add the new predecessor to those phis.
+    UnwindDestToNewPreds[UnwindDest].insert(CI->getParent());
     changeToInvokeAndSplitBasicBlock(CI, UnwindDest);
   }
 
@@ -1752,4 +1761,45 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
 
   for (Instruction *I : ToErase)
     I->eraseFromParent();
+
+  // Add entries for new predecessors to phis in unwind destinations. We use
+  // 'undef' as a placeholder value. We should make sure the phis have a valid
+  // set of predecessors before running SSAUpdater, because SSAUpdater
+  // internally can use existing phis to gather predecessor info rather than
+  // scanning the actual CFG (See FindPredecessorBlocks in SSAUpdater.cpp for
+  // details).
+  for (auto &[UnwindDest, NewPreds] : UnwindDestToNewPreds) {
+    for (PHINode &PN : UnwindDest->phis()) {
+      for (auto *NewPred : NewPreds) {
+        assert(PN.getBasicBlockIndex(NewPred) == -1);
+        PN.addIncoming(UndefValue::get(PN.getType()), NewPred);
+      }
+    }
+  }
+
+  // For unwind destinations for newly added invokes to longjmpable functions,
+  // calculate incoming values for the newly added predecessors using
+  // SSAUpdater. We add existing values in the phis to SSAUpdater as available
+  // values and let it calculate what the value should be at the end of new
+  // incoming blocks.
+  for (auto &[UnwindDest, NewPreds] : UnwindDestToNewPreds) {
+    for (PHINode &PN : UnwindDest->phis()) {
+      SSAUpdater SSA;
+      SSA.Initialize(PN.getType(), PN.getName());
+      for (unsigned Idx = 0, E = PN.getNumIncomingValues(); Idx != E; ++Idx) {
+        if (NewPreds.contains(PN.getIncomingBlock(Idx)))
+          continue;
+        Value *V = PN.getIncomingValue(Idx);
+        if (auto *II = dyn_cast<InvokeInst>(V))
+          SSA.AddAvailableValue(II->getNormalDest(), II);
+        else if (auto *I = dyn_cast<Instruction>(V))
+          SSA.AddAvailableValue(I->getParent(), I);
+        else
+          SSA.AddAvailableValue(PN.getIncomingBlock(Idx), V);
+      }
+      for (auto *NewPred : NewPreds)
+        PN.setIncomingValueForBlock(NewPred, SSA.GetValueAtEndOfBlock(NewPred));
+      assert(PN.isComplete());
+    }
+  }
 }
diff --git a/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll
new file mode 100644
index 0000000000000..97f6d797a63bd
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll
@@ -0,0 +1,126 @@
+; RUN: opt < %s -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S | FileCheck %s
+
+target triple = "wasm32-unknown-emscripten"
+
+%struct.__jmp_buf_tag = type { [6 x i32], i32, [32 x i32] }
+@buf = internal global [1 x %struct.__jmp_buf_tag] zeroinitializer, align 16
+
+; When longjmpable calls are coverted into invokes in Wasm SjLj transformation
+; and their unwind destination is an existing catchpad or cleanuppad due to
+; maintain the scope structure, the new pred BBs created by invokes and the
+; correct incoming values should be added the existing phis in those unwind
+; destinations.
+
+; When longjmpable calls are within a cleanuppad.
+define void @longjmpable_invoke_phi0() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: @longjmpable_invoke_phi0
+entry:
+  %val.entry = call i32 @llvm.wasm.memory.size.i32(i32 0)
+  %0 = call i32 @setjmp(ptr @buf) #2
+  invoke void @foo()
+          to label %bb1 unwind label %ehcleanup1
+
+bb1:                                              ; preds = %entry
+  ; We use llvm.wasm.memory.size intrinsic just to get/use an i32 value. The
+  ; reason we use an intrinsic here is to make it not longjmpable. If this can
+  ; longjmp, the result will be more complicated and hard to check.
+  %val.bb1 = call i32 @llvm.wasm.memory.size.i32(i32 0)
+  invoke void @foo()
+          to label %bb2 unwind label %ehcleanup0
+
+bb2:                                              ; preds = %bb1
+  unreachable
+
+ehcleanup0:                                       ; preds = %bb1
+  %1 = cleanuppad within none []
+  call void @longjmpable() [ "funclet"(token %1) ]
+; CHECK:      ehcleanup0
+; CHECK:        invoke void @longjmpable
+; CHECK-NEXT:           to label %.noexc unwind label %ehcleanup1
+  invoke void @foo() [ "funclet"(token %1) ]
+          to label %bb3 unwind label %ehcleanup1
+
+bb3:                                              ; preds = %ehcleanup0
+  %val.bb3 = call i32 @llvm.wasm.memory.size.i32(i32 0)
+  call void @longjmpable() [ "funclet"(token %1) ]
+; CHECK:      bb3:
+; CHECK:        invoke void @longjmpable
+; CHECK-NEXT:           to label %.noexc1 unwind label %ehcleanup1
+  cleanupret from %1 unwind label %ehcleanup1
+
+ehcleanup1:                                       ; preds = %bb3, %ehcleanup0, %entry
+  %phi = phi i32 [ %val.entry, %entry ], [ %val.bb1, %ehcleanup0 ], [ %val.bb3, %bb3 ]
+; CHECK:      ehcleanup1:
+; CHECK-NEXT:   %phi = phi i32 [ %val.entry2, %entry.split.split ], [ %val.bb1, %.noexc ], [ %val.bb3, %.noexc1 ], [ %val.bb1, %ehcleanup0 ], [ %val.bb3, %bb3 ]
+  %2 = cleanuppad within none []
+  %3 = call i32 @llvm.wasm.memory.size.i32(i32 %phi)
+  cleanupret from %2 unwind to caller
+}
+
+; When longjmpable calls are within a catchpad.
+define void @longjmpable_invoke_phi1() personality ptr @__gxx_wasm_personality_v0 {
+; CHECK-LABEL: @longjmpable_invoke_phi1
+entry:
+  %val.entry = call i32 @llvm.wasm.memory.size.i32(i32 0)
+  %0 = call i32 @setjmp(ptr @buf) #2
+  invoke void @foo()
+          to label %bb1 unwind label %ehcleanup
+
+bb1:                                              ; preds = %entry
+  %val.bb1 = call i32 @llvm.wasm.memory.size.i32(i32 0)
+  invoke void @foo()
+          to label %bb2 unwind label %catch.dispatch
+
+bb2:                                              ; preds = %bb1
+  unreachable
+
+catch.dispatch:                                   ; preds = %bb1
+  %1 = catchswitch within none [label %catch.start] unwind label %ehcleanup
+
+catch.start:                                      ; preds = %catch.dispatch
+  %2 = catchpad within %1 [ptr null]
+  %3 = call ptr @llvm.wasm.get.exception(token %2)
+  %4 = call i32 @llvm.wasm.get.ehselector(token %2)
+  call void @longjmpable() [ "funclet"(token %2) ]
+; CHECK:      catch.start:
+; CHECK:        invoke void @longjmpable
+; CHECK-NEXT:           to label %.noexc unwind label %ehcleanup
+  invoke void @foo() [ "funclet"(token %2) ]
+          to label %bb3 unwind label %ehcleanup
+
+bb3:                                              ; preds = %catch.start
+  %val.bb3 = call i32 @llvm.wasm.memory.size.i32(i32 0)
+  call void @longjmpable() [ "funclet"(token %2) ]
+; CHECK:      bb3:
+; CHECK:        invoke void @longjmpable
+; CHECK-NEXT:           to label %.noexc1 unwind label %ehcleanup
+  invoke void @foo() [ "funclet"(token %2) ]
+          to label %bb4 unwind label %ehcleanup
+
+bb4:                                              ; preds = %bb3
+  unreachable
+
+ehcleanup:                                        ; preds = %bb3, %catch.start, %catch.dispatch, %entry
+  %phi = phi i32 [ %val.entry, %entry ], [ %val.bb1, %catch.dispatch ], [ %val.bb1, %catch.start ], [ %val.bb3, %bb3 ]
+; CHECK:      ehcleanup:
+; CHECK-NEXT:   %phi = phi i32 [ %val.entry2, %entry.split.split ], [ %val.bb1, %catch.dispatch ], [ %val.bb1, %.noexc ], [ %val.bb3, %.noexc1 ], [ %val.bb1, %catch.start ], [ %val.bb3, %bb3 ]
+  %5 = cleanuppad within none []
+  %6 = call i32 @llvm.wasm.memory.size.i32(i32 %phi)
+  cleanupret from %5 unwind to caller
+}
+
+declare i32 @setjmp(ptr)
+declare i32 @__gxx_wasm_personality_v0(...)
+declare void @foo()
+declare void @longjmpable()
+declare void @use_i32(i32)
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare i32 @llvm.wasm.get.ehselector(token) #0
+; Function Attrs: nocallback nofree nosync nounwind willreturn
+declare ptr @llvm.wasm.get.exception(token) #0
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(read)
+declare i32 @llvm.wasm.memory.size.i32(i32) #1
+
+attributes #0 = { nocallback nofree nosync nounwind willreturn }
+attributes #1 = { nocallback nofree nosync nounwind willreturn memory(read) }
+attributes #2 = { returns_twice }

Comment on lines +780 to +781
if (I.getType()->isVoidTy())
continue;
Copy link
Member Author

Choose a reason for hiding this comment

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

This is not related to this PR and a NFC drive-by small fix. Because we are adding defs, void type instructions don't have any effects at all when added to SSAUpdaterBulk anyway.

@aheejin
Copy link
Member Author

aheejin commented Jul 23, 2024

The CI failure doesn't seem to be related. Merging.

@aheejin aheejin merged commit 2bf71b8 into llvm:main Jul 23, 2024
7 of 9 checks passed
@aheejin aheejin deleted the sjlj_fix branch July 23, 2024 23:06
@llvm-ci
Copy link
Collaborator

llvm-ci commented Jul 24, 2024

LLVM Buildbot has detected a new failure on builder reverse-iteration running on hexagon-build-03 while building llvm at step 6 "check_all".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/110/builds/474

Here is the relevant piece of the build log for the reference:

Step 6 (check_all) failure: test (failure)
******************** TEST 'LLVM :: CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll' FAILED ********************
Exit Code: 1

Command Output (stderr):
--
RUN: at line 1: /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.obj/bin/opt < /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.src/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S | /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.obj/bin/FileCheck /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.src/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll
+ /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.obj/bin/FileCheck /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.src/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll
+ /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.obj/bin/opt -wasm-lower-em-ehsjlj -wasm-enable-eh -wasm-enable-sjlj -S
/local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.src/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll:54:15: error: CHECK-NEXT: expected string not found in input
; CHECK-NEXT: %phi = phi i32 [ %val.entry2, %entry.split.split ], [ %val.bb1, %.noexc ], [ %val.bb3, %.noexc1 ], [ %val.bb1, %ehcleanup0 ], [ %val.bb3, %bb3 ]
              ^
<stdin>:59:12: note: scanning from here
ehcleanup1: ; preds = %bb3, %ehcleanup0, %.noexc1, %.noexc, %entry.split.split
           ^
<stdin>:60:2: note: possible intended match here
 %phi = phi i32 [ %val.entry2, %entry.split.split ], [ %val.bb1, %.noexc ], [ %val.bb3, %.noexc1 ], [ %val.bb3, %bb3 ], [ %val.bb1, %ehcleanup0 ]
 ^
/local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.src/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll:106:15: error: CHECK-NEXT: expected string not found in input
; CHECK-NEXT: %phi = phi i32 [ %val.entry2, %entry.split.split ], [ %val.bb1, %catch.dispatch ], [ %val.bb1, %.noexc ], [ %val.bb3, %.noexc1 ], [ %val.bb1, %catch.start ], [ %val.bb3, %bb3 ]
              ^
<stdin>:145:11: note: scanning from here
ehcleanup: ; preds = %bb3, %catch.start, %.noexc1, %.noexc, %catch.dispatch, %entry.split.split
          ^
<stdin>:146:2: note: possible intended match here
 %phi = phi i32 [ %val.entry2, %entry.split.split ], [ %val.bb1, %catch.dispatch ], [ %val.bb1, %.noexc ], [ %val.bb3, %.noexc1 ], [ %val.bb3, %bb3 ], [ %val.bb1, %catch.start ]
 ^

Input file: <stdin>
Check file: /local/mnt/workspace/bots/hexagon-build-03/reverse-iteration/llvm.src/llvm/test/CodeGen/WebAssembly/lower-wasm-ehsjlj-phi.ll

-dump-input=help explains the following input dump.

Input was:
<<<<<<
            .
            .
            .
           54:  to label %.noexc1 unwind label %ehcleanup1 
           55:  
           56: .noexc1: ; preds = %bb3 
           57:  cleanupret from %0 unwind label %ehcleanup1 
           58:  
           59: ehcleanup1: ; preds = %bb3, %ehcleanup0, %.noexc1, %.noexc, %entry.split.split 
next:54'0                 X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
           60:  %phi = phi i32 [ %val.entry2, %entry.split.split ], [ %val.bb1, %.noexc ], [ %val.bb3, %.noexc1 ], [ %val.bb3, %bb3 ], [ %val.bb1, %ehcleanup0 ] 
next:54'0      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
next:54'1       ?                                                                                                                                                 possible intended match
           61:  %1 = cleanuppad within none [] 
next:54'0      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
           62:  %2 = call i32 @llvm.wasm.memory.size.i32(i32 %phi) 
...

aheejin added a commit to aheejin/llvm-project that referenced this pull request Jul 24, 2024
aheejin added a commit that referenced this pull request Jul 24, 2024
aheejin added a commit that referenced this pull request Jul 25, 2024
This reapplies #99730. #99730 contained a nondeterministic iteration
which failed the reverse-iteration bot
(https://lab.llvm.org/buildbot/#/builders/110/builds/474) and reverted
in
f3f0d99.

The fix is make the order of iteration of new predecessors
determintistic by using `SmallSetVector`.
```diff
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -1689,7 +1689,7 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
   }

-  SmallDenseMap<BasicBlock *, SmallPtrSet<BasicBlock *, 4>, 4>
+  SmallDenseMap<BasicBlock *, SmallSetVector<BasicBlock *, 4>, 4>
       UnwindDestToNewPreds;
   for (auto *CI : LongjmpableCalls) {
     // Even if the callee function has attribute 'nounwind', which is true for
```
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
Summary:
In Wasm SjLj, longjmpable `call`s that in functions that call `setjmp`
are converted into `invoke`s. Those `invoke`s are meant to unwind to
`catch.dispatch.longjmp` to figure out which `setjmp` those `longjmp`
buffers belong to:

https://github.com/llvm/llvm-project/blob/fada9227325b3eaa0bdc09a486f29a7f08b7b3fb/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L250-L260

But in case a longjmpable call is within another `catchpad` or
`cleanuppad` scope, to maintain the nested scope structure, we should
make them unwind to the scope's next unwind destination and not directly
to `catch.dispatch.longjmp`:

https://github.com/llvm/llvm-project/blob/fada9227325b3eaa0bdc09a486f29a7f08b7b3fb/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp#L1698-L1727
In this case the longjmps will eventually unwind to
`catch.dispatch.longjmp` and be handled there.

In this case, it is possible that the unwind destination (which is an
existing `catchpad` or `cleanuppad`) may already have `phi`s. And
because the unwind destinations get new predecessors because of the
newly created `invoke`s, those `phi`s need to have new entries for those
new predecessors.

This adds new preds as new incoming blocks to those `phi`s, and we use a
separate `SSAUpdater` to calculate the correct incoming values to those
blocks.

I have assumed `SSAUpdaterBulk` used in `rebuildSSA` would take care of
these things, but apparently it doesn't. It takes available defs and
adds `phi`s in the defs' dominance frontiers, i.e., where each def's
dominance ends, and rewrites other uses based on the newly added `phi`s.
But it doesn't add entries to existing `phi`s, and the case in this bug
may not even involve dominance frontiers; this bug is simply about
existing `phis`s that have gained new preds need new entries for them.
It is kind of surprising that this bug was only reported recently, given
that this pass has not been changed much in years.

Fixes #97496 and fixes
emscripten-core/emscripten#22170.

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60251080
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
Summary:
This reverts commit 2bf71b8.
This broke the builbot at
https://lab.llvm.org/buildbot/#/builders/110/builds/474.

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60250757
yuxuanchen1997 pushed a commit that referenced this pull request Jul 25, 2024
Summary:
This reapplies #99730. #99730 contained a nondeterministic iteration
which failed the reverse-iteration bot
(https://lab.llvm.org/buildbot/#/builders/110/builds/474) and reverted
in
f3f0d99.

The fix is make the order of iteration of new predecessors
determintistic by using `SmallSetVector`.
```diff
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -1689,7 +1689,7 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
   }

-  SmallDenseMap<BasicBlock *, SmallPtrSet<BasicBlock *, 4>, 4>
+  SmallDenseMap<BasicBlock *, SmallSetVector<BasicBlock *, 4>, 4>
       UnwindDestToNewPreds;
   for (auto *CI : LongjmpableCalls) {
     // Even if the callee function has attribute 'nounwind', which is true for
```

Test Plan: 

Reviewers: 

Subscribers: 

Tasks: 

Tags: 


Differential Revision: https://phabricator.intern.facebook.com/D60250660
Harini0924 pushed a commit to Harini0924/llvm-project that referenced this pull request Aug 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Clang frontend command failed with exit code 139 when compiling to webassembly
4 participants