[llvm] [CodeGen][SafeStack] Successful InlineFunction should force a recalculation of potentially stale DominatorTree (PR #222820)

via llvm-commits llvm-commits at lists.llvm.org
Thu Sep 10 17:59:32 PDT 2026


https://github.com/dgg5503 created https://github.com/llvm/llvm-project/pull/222820

In commit 51a25846c198, Safe Stack began preserving the `DominatorTree` by the end of the pass' run. Updates to the `DominatorTree` were then made lazily via inclusion of `DomTreeUpdater` and its use with `SplitBlockAndInsertIfThen`. Preservation was maintained when updated to the new pass manager in 3bd517205799. However, there was an overlooked case where the `DominatorTree` would remain stale by the end of the Safe Stack pass if the actions under `TryInlinePointerAddress` completed successfully and modified the caller's CFG.

The conditions for a stale `DominatorTree` to exist are roughly:
1. Safe Stack obtains the Unsafe Stack pointer location as a `CallInst` (i.e. one way is `-safestack-use-pointer-address`)
2. `__safestack_pointer_address` is defined and inlined successfully.
3. The inlining changes the CFG such that a recalculated `DominatorTree` would differ.
4. `DominatorTree` remains unchanged since it is preserved and currently not recalculated.

Here's a simple example that demonstrates the issue:
```
@unsafe_stack_pointer_a = thread_local global ptr null
@unsafe_stack_pointer_b = thread_local global ptr null

declare i32 @get_runtime_mode() nounwind willreturn memory(none)
declare void @escape(ptr)

define ptr @__safestack_pointer_address() alwaysinline {
entry:
  %first = call i32 @get_runtime_mode()
  %use_a = icmp eq i32 %first, 0
  br i1 %use_a, label %a, label %b

a:
  ret ptr @unsafe_stack_pointer_a

b:
  ret ptr @unsafe_stack_pointer_b
}

define i32 @caller() safestack {
entry:
  %unsafe = alloca i32, align 4
  call void @escape(ptr %unsafe)
  %second = call i32 @get_runtime_mode()
  ret i32 %second
}
```

```
> opt -mtriple=x86_64-pc-linux-gnu -safestack-use-pointer-address -passes=require<libcall-lowering-info>,function(safe-stack,verify<domtree>) -disable-output example.ll
DominatorTree is different than a freshly computed one!
	Current:
=============================--------------------------------
Inorder Dominator Tree: DFSNumbers invalid: 0 slow queries.
  [1] %entry {4294967295,4294967295} [0]
Roots: %entry

	Freshly computed tree:
=============================--------------------------------
Inorder Dominator Tree: DFSNumbers invalid: 0 slow queries.
  [1] %entry {4294967295,4294967295} [0]
    [2] %a.i {4294967295,4294967295} [1]
    [2] %__safestack_pointer_address.exit {4294967295,4294967295} [1]
    [2] %b.i {4294967295,4294967295} [1]
Roots: %entry
```

To avoid a stale `DominatorTree` by the end of the Safe Stack pass, I propose calling `recalculate` on the `DominatorTree` for the function which will have the call to `__safestack_pointer_address` inlined assuming the inlining was successful and `DomTreeUpdater` is non-NULL.

>From c39d0a02ca5a8991d3205f91bd53f9e2c353ed44 Mon Sep 17 00:00:00 2001
From: Douglas Gliner <Douglas.Gliner at sony.com>
Date: Thu, 10 Sep 2026 17:18:24 -0700
Subject: [PATCH] [CodeGen][SafeStack] Successful InlineFunction should force a
 recalculation of potentially stale DominatorTree

In commit 51a25846c198, Safe Stack began preserving the `DominatorTree` by the
end of the pass' run. Updates to the `DominatorTree` were then made lazily via
inclusion of `DomTreeUpdater` and its use with `SplitBlockAndInsertIfThen`.
Preservation was maintained when updated to the new pass manager in 3bd517205799.
However, there was an overlooked case where the `DominatorTree` would remain
stale by the end of the Safe Stack pass if the actions under
`TryInlinePointerAddress` completed successfully and modified the caller's CFG.

The conditions for a stale `DominatorTree` to exist are roughly:
1. Safe Stack obtains the Unsafe Stack pointer location as a `CallInst` (i.e. one way is `-safestack-use-pointer-address`)
2. `__safestack_pointer_address` is defined and inlined successfully.
3. The inlining changes the CFG such that a recalculated `DominatorTree` would differ.
4. `DominatorTree` remains unchanged since it is preserved and currently not recalculated.

Here's a simple example that demonstrates the issue:
```
@unsafe_stack_pointer_a = thread_local global ptr null
@unsafe_stack_pointer_b = thread_local global ptr null

declare i32 @get_runtime_mode() nounwind willreturn memory(none)
declare void @escape(ptr)

define ptr @__safestack_pointer_address() alwaysinline {
entry:
  %first = call i32 @get_runtime_mode()
  %use_a = icmp eq i32 %first, 0
  br i1 %use_a, label %a, label %b

a:
  ret ptr @unsafe_stack_pointer_a

b:
  ret ptr @unsafe_stack_pointer_b
}

define i32 @caller() safestack {
entry:
  %unsafe = alloca i32, align 4
  call void @escape(ptr %unsafe)
  %second = call i32 @get_runtime_mode()
  ret i32 %second
}
```

```
> opt -mtriple=x86_64-pc-linux-gnu -safestack-use-pointer-address -passes=require<libcall-lowering-info>,function(safe-stack,verify<domtree>) -disable-output example.ll
DominatorTree is different than a freshly computed one!
	Current:
=============================--------------------------------
Inorder Dominator Tree: DFSNumbers invalid: 0 slow queries.
  [1] %entry {4294967295,4294967295} [0]
Roots: %entry

	Freshly computed tree:
=============================--------------------------------
Inorder Dominator Tree: DFSNumbers invalid: 0 slow queries.
  [1] %entry {4294967295,4294967295} [0]
    [2] %a.i {4294967295,4294967295} [1]
    [2] %__safestack_pointer_address.exit {4294967295,4294967295} [1]
    [2] %b.i {4294967295,4294967295} [1]
Roots: %entry
```

To avoid a stale `DominatorTree` by the end of the Safe Stack pass, I propose
calling `recalculate` on the `DominatorTree` for the function which will have
the call to `__safestack_pointer_address` inlined assuming the inlining was
successful and `DomTreeUpdater` is non-NULL.
---
 llvm/lib/CodeGen/SafeStack.cpp                |  6 +++-
 .../SafeStack/X86/pointer-address-domtree.ll  | 33 +++++++++++++++++++
 2 files changed, 38 insertions(+), 1 deletion(-)
 create mode 100644 llvm/test/Transforms/SafeStack/X86/pointer-address-domtree.ll

diff --git a/llvm/lib/CodeGen/SafeStack.cpp b/llvm/lib/CodeGen/SafeStack.cpp
index ca20503024524..75a8762cc7090 100644
--- a/llvm/lib/CodeGen/SafeStack.cpp
+++ b/llvm/lib/CodeGen/SafeStack.cpp
@@ -748,8 +748,12 @@ void SafeStack::TryInlinePointerAddress() {
   if (!ShouldInlinePointerAddress(*CI))
     return;
 
+  // InlineFunction can modify the callers CFG, but it has no DomTreeUpdater
+  // hook. Since SafeStack preserves the DominatorTree, we must rebuild it
+  // after a successful inline instead of leaving the cached tree stale.
   InlineFunctionInfo IFI;
-  InlineFunction(*CI, IFI);
+  if (InlineFunction(*CI, IFI).isSuccess() && DTU)
+    DTU->recalculate(F);
 }
 
 bool SafeStack::run() {
diff --git a/llvm/test/Transforms/SafeStack/X86/pointer-address-domtree.ll b/llvm/test/Transforms/SafeStack/X86/pointer-address-domtree.ll
new file mode 100644
index 0000000000000..84ef9bf618676
--- /dev/null
+++ b/llvm/test/Transforms/SafeStack/X86/pointer-address-domtree.ll
@@ -0,0 +1,33 @@
+; RUN: opt -mtriple=x86_64-pc-linux-gnu -safestack-use-pointer-address \
+; RUN:   -passes='require<libcall-lowering-info>,function(require<domtree>,safe-stack,verify<domtree>)' \
+; RUN:   -disable-output < %s
+; RUN: opt -mtriple=x86_64-pc-linux-gnu -safestack-use-pointer-address \
+; RUN:   -domtree -safe-stack -loops -verify-dom-info \
+; RUN:   -disable-output < %s
+
+ at unsafe_stack_pointer_a = thread_local global ptr null
+ at unsafe_stack_pointer_b = thread_local global ptr null
+
+declare i32 @get_runtime_mode()
+declare void @escape(ptr)
+
+define ptr @__safestack_pointer_address() alwaysinline {
+entry:
+  %first = call i32 @get_runtime_mode()
+  %use_a = icmp eq i32 %first, 0
+  br i1 %use_a, label %a, label %b
+
+a:
+  ret ptr @unsafe_stack_pointer_a
+
+b:
+  ret ptr @unsafe_stack_pointer_b
+}
+
+define i32 @caller() safestack {
+entry:
+  %unsafe = alloca i32, align 4
+  call void @escape(ptr %unsafe)
+  %second = call i32 @get_runtime_mode()
+  ret i32 %second
+}



More information about the llvm-commits mailing list