[llvm] [WebAssembly] Fix nondeterminism by using MapVector for pointer-keyed maps [NFC] (PR #205184)

Derek Schuff via llvm-commits llvm-commits at lists.llvm.org
Fri Jun 26 16:47:16 PDT 2026


https://github.com/dschuff updated https://github.com/llvm/llvm-project/pull/205184

>From 9bea55d106ef7ffc1902a9268b03c0f45031784b Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Mon, 22 Jun 2026 19:53:10 +0000
Subject: [PATCH 1/5] [WebAssembly] Fix nondeterminism by using MapVector for
 pointer-keyed maps [NFC]

Several DenseMaps in the WebAssembly backend keyed by pointers were being
iterated over, potentially leading to nondeterministiccodegen (differing
try/delegate nesting, virtual register allocation, or PHI node insertion)
due to nondeterministsic pointer values.

This patch replaces these DenseMaps with MapVectors to guarantee
deterministic iteration order: - UnwindDestToTryRanges in
WebAssemblyCFGStackify.cpp - EHPadToUnwindDest in
WebAssemblyCFGStackify.cpp - EHPadToRethrows in
WebAssemblyLateEHPrepare.cpp - UnwindDestToNewPreds in
WebAssemblyLowerEmscriptenEHSjLj.cpp

Fixes: #204883

Assisted-by: Antigravity
---
 llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp       | 5 +++--
 llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp     | 3 ++-
 .../Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp  | 3 ++-
 3 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
index 7d39e1074b026..f69c73b111891 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
@@ -29,6 +29,7 @@
 #include "WebAssemblySubtarget.h"
 #include "WebAssemblyTargetMachine.h"
 #include "WebAssemblyUtilities.h"
+#include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/Statistic.h"
 #include "llvm/BinaryFormat/Wasm.h"
 #include "llvm/CodeGen/MachineDominators.h"
@@ -1837,7 +1838,7 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
   // multiple BBs.
   using TryRange = std::pair<MachineInstr *, MachineInstr *>;
   // In original CFG, <unwind destination BB, a vector of try/try_table ranges>
-  DenseMap<MachineBasicBlock *, SmallVector<TryRange, 4>> UnwindDestToTryRanges;
+  MapVector<MachineBasicBlock *, SmallVector<TryRange, 4>> UnwindDestToTryRanges;
 
   // Gather possibly throwing calls (i.e., previously invokes) whose current
   // unwind destination is not the same as the original CFG. (Case 1)
@@ -2203,7 +2204,7 @@ bool WebAssemblyCFGStackify::fixCatchUnwindMismatches(MachineFunction &MF) {
   SmallVector<const MachineBasicBlock *, 8> EHPadStack;
   // For EH pads that have catch unwind mismatches, a map of <EH pad, its
   // correct unwind destination>.
-  DenseMap<MachineBasicBlock *, MachineBasicBlock *> EHPadToUnwindDest;
+  MapVector<MachineBasicBlock *, MachineBasicBlock *> EHPadToUnwindDest;
 
   for (auto &MBB : reverse(MF)) {
     for (auto &MI : reverse(MBB)) {
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
index 548624e26d869..5e05aa1a1f6e9 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
@@ -15,6 +15,7 @@
 #include "WebAssemblySubtarget.h"
 #include "WebAssemblyTargetMachine.h"
 #include "WebAssemblyUtilities.h"
+#include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/SmallPtrSet.h"
 #include "llvm/CodeGen/MachineFunctionPass.h"
 #include "llvm/CodeGen/MachineInstrBuilder.h"
@@ -297,7 +298,7 @@ bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
 bool WebAssemblyLateEHPrepare::addCatchRefsAndThrowRefs(MachineFunction &MF) {
   const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
   auto &MRI = MF.getRegInfo();
-  DenseMap<MachineBasicBlock *, SmallVector<MachineInstr *, 2>> EHPadToRethrows;
+  MapVector<MachineBasicBlock *, SmallVector<MachineInstr *, 2>> EHPadToRethrows;
 
   // Create a map of <EH pad, a vector of RETHROWs rethrowing its exception>
   for (auto &MBB : MF)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index 51b71ba646b42..dd75e226509e2 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -263,6 +263,7 @@
 
 #include "WebAssembly.h"
 #include "WebAssemblyTargetMachine.h"
+#include "llvm/ADT/MapVector.h"
 #include "llvm/ADT/StringExtras.h"
 #include "llvm/CodeGen/TargetPassConfig.h"
 #include "llvm/CodeGen/WasmEHInfo.h"
@@ -1745,7 +1746,7 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
   }
 
-  SmallDenseMap<BasicBlock *, SmallSetVector<BasicBlock *, 4>, 4>
+  MapVector<BasicBlock *, SmallSetVector<BasicBlock *, 4>>
       UnwindDestToNewPreds;
   for (auto *CI : LongjmpableCalls) {
     // Even if the callee function has attribute 'nounwind', which is true for

>From e77a07a2db812d9ab63c7fbd5919974f538a291f Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Mon, 22 Jun 2026 21:33:17 +0000
Subject: [PATCH 2/5] clang-format

---
 llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp         | 3 ++-
 llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp       | 3 ++-
 .../Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp    | 3 +--
 3 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
index f69c73b111891..7e12b0861116e 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyCFGStackify.cpp
@@ -1838,7 +1838,8 @@ bool WebAssemblyCFGStackify::fixCallUnwindMismatches(MachineFunction &MF) {
   // multiple BBs.
   using TryRange = std::pair<MachineInstr *, MachineInstr *>;
   // In original CFG, <unwind destination BB, a vector of try/try_table ranges>
-  MapVector<MachineBasicBlock *, SmallVector<TryRange, 4>> UnwindDestToTryRanges;
+  MapVector<MachineBasicBlock *, SmallVector<TryRange, 4>>
+      UnwindDestToTryRanges;
 
   // Gather possibly throwing calls (i.e., previously invokes) whose current
   // unwind destination is not the same as the original CFG. (Case 1)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
index 5e05aa1a1f6e9..56f74dce630df 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLateEHPrepare.cpp
@@ -298,7 +298,8 @@ bool WebAssemblyLateEHPrepare::replaceFuncletReturns(MachineFunction &MF) {
 bool WebAssemblyLateEHPrepare::addCatchRefsAndThrowRefs(MachineFunction &MF) {
   const auto &TII = *MF.getSubtarget<WebAssemblySubtarget>().getInstrInfo();
   auto &MRI = MF.getRegInfo();
-  MapVector<MachineBasicBlock *, SmallVector<MachineInstr *, 2>> EHPadToRethrows;
+  MapVector<MachineBasicBlock *, SmallVector<MachineInstr *, 2>>
+      EHPadToRethrows;
 
   // Create a map of <EH pad, a vector of RETHROWs rethrowing its exception>
   for (auto &MBB : MF)
diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index dd75e226509e2..e294ae7025af6 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -1746,8 +1746,7 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
   }
 
-  MapVector<BasicBlock *, SmallSetVector<BasicBlock *, 4>>
-      UnwindDestToNewPreds;
+  MapVector<BasicBlock *, SmallSetVector<BasicBlock *, 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

>From 7385b669ba0e79b7275db32da227b8842aade793 Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Mon, 22 Jun 2026 21:54:12 +0000
Subject: [PATCH 3/5] use SmallMapVector

---
 .../lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index e294ae7025af6..9a2f658091946 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -1746,7 +1746,7 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
   }
 
-  MapVector<BasicBlock *, SmallSetVector<BasicBlock *, 4>> UnwindDestToNewPreds;
+  SmallMapVector<BasicBlock *, SmallSetVector<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

>From a3c491b9ce3e6a16a01b704078da6bb72decbde7 Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Tue, 23 Jun 2026 16:31:25 +0000
Subject: [PATCH 4/5] format

---
 .../Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp    | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
index 9a2f658091946..8c5917fab90a2 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyLowerEmscriptenEHSjLj.cpp
@@ -1746,7 +1746,8 @@ void WebAssemblyLowerEmscriptenEHSjLj::handleLongjmpableCallsForWasmSjLj(
     }
   }
 
-  SmallMapVector<BasicBlock *, SmallSetVector<BasicBlock *, 4>, 4> UnwindDestToNewPreds;
+  SmallMapVector<BasicBlock *, SmallSetVector<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

>From ace0d5d6a191ce032e0133f6297f01c941237e47 Mon Sep 17 00:00:00 2001
From: Derek Schuff <dschuff at chromium.org>
Date: Fri, 26 Jun 2026 23:46:35 +0000
Subject: [PATCH 5/5] Add test Co-authored-by: Ammar Askar <aaskar at google.com>

---
 .../CodeGen/WebAssembly/cfg-stackify-eh.ll    | 57 ++++++++++++++++++-
 1 file changed, 56 insertions(+), 1 deletion(-)

diff --git a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
index e872ceec4ea41..a1e7616711ab9 100644
--- a/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
+++ b/llvm/test/CodeGen/WebAssembly/cfg-stackify-eh.ll
@@ -1561,13 +1561,68 @@ bb21:                                             ; preds = %bb10
           to label %common.ret unwind label %bb16
 }
 
+; NOSORT-LABEL: nested_cleanup_unwind_to_caller:
+; NOSORT:      block     exnref
+; NOSORT:        try_table    (catch_all_ref 0)             # 0: down to label[[L0:[0-9]+]]
+; NOSORT:          block     exnref
+; NOSORT:            block     exnref
+; NOSORT:              try_table    (catch_all_ref 0)       # 0: down to label[[L1:[0-9]+]]
+; NOSORT:                call  throwing_func
+; NOSORT:                try_table    (catch_all_ref 2)     # 2: down to label[[L2:[0-9]+]]
+; NOSORT:                  call  throwing_func
+; NOSORT:                end_try_table
+; NOSORT:                try_table    (catch_all_ref 5)     # 5: down to label[[L4:[0-9]+]]
+; NOSORT:                  call  cleanup_dtor
+; NOSORT:                end_try_table
+; NOSORT:                return
+; NOSORT:              end_try_table
+; NOSORT:              unreachable
+; NOSORT:            end_block                              # label[[L1]]:
+; NOSORT:            try_table    (catch_all_ref 3)         # 3: down to label[[L4]]
+; NOSORT:              call  cleanup_dtor
+; NOSORT:              throw_ref
+; NOSORT:      .LBB{{[0-9]+}}_{{[0-9]+}}:
+; NOSORT-NEXT:       end_try_table
+; NOSORT-NEXT: .LBB{{[0-9]+}}_{{[0-9]+}}:
+; NOSORT-NEXT:       unreachable
+; NOSORT-NEXT: .LBB{{[0-9]+}}_{{[0-9]+}}:
+; NOSORT-NEXT:     end_block                           # label[[L2]]:
+define hidden void @nested_cleanup_unwind_to_caller() personality ptr @__gxx_wasm_personality_v0 {
+entry:
+  %c = alloca i8, align 1
+  %dummy1 = alloca i32, align 4
+  %dummy2 = alloca i32, align 4
+  invoke void @throwing_func()
+          to label %invoke.cont unwind label %ehcleanup
+
+invoke.cont:                                      ; preds = %entry
+  invoke void @throwing_func()
+          to label %invoke.cont2 unwind label %ehcleanup2
+
+invoke.cont2:                                     ; preds = %invoke.cont
+  %ignore1 = call ptr @cleanup_dtor(ptr %c)
+  ret void
+
+ehcleanup:                                        ; preds = %entry
+  %pad = cleanuppad within none []
+  %ignore2 = call ptr @cleanup_dtor(ptr %c) [ "funclet"(token %pad) ]
+  cleanupret from %pad unwind to caller
+
+ehcleanup2:                                       ; preds = %invoke.cont
+  %pad2 = cleanuppad within none []
+  %ignore3 = call ptr @cleanup_dtor(ptr %c) [ "funclet"(token %pad2) ]
+  cleanupret from %pad2 unwind to caller
+}
+
 ; Check if the unwind destination mismatch stats are correct
-; NOSORT: 25 wasm-cfg-stackify    - Number of call unwind mismatches found
+; NOSORT: 28 wasm-cfg-stackify    - Number of call unwind mismatches found
 ; NOSORT:  5 wasm-cfg-stackify    - Number of catch unwind mismatches found
 
 declare void @foo()
 declare void @bar()
 declare i32 @baz()
+declare void @throwing_func()
+declare ptr @cleanup_dtor(ptr)
 declare i32 @qux(i32)
 declare void @quux(i32)
 declare void @fun(i32)



More information about the llvm-commits mailing list