[libcxx-commits] [libcxxabi] [libcxxabi] prevent __cxa_free_exception from being inlined when called in __cxa_decrement_exception_refcount (PR #222754)
via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Sep 11 06:32:37 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxxabi
Author: rickymolodiez
<details>
<summary>Changes</summary>
## Summary
When the function `__cxa_free_exception` is called from inside `__cxa_decrement_exception_refcount`, the call to `__cxa_free_exception` gets **inlined.** Due to this inlining, `__cxa_free_exception` is not a function call and cannot be interposed by the application.
## Details
In platforms where memory control is tight, being able to interpose `__cxa_allocate_exception` and `__cxa_free_exception` can be important. In addition, applications may want to track all dynamic allocations, and exception allocators are an important part of that. `__cxa_decrement_exception_refcount` calls `__cxa_free_exception` when the exception reference count reaches 0. With the inlining, it is impossible to interpose `__cxa_free_exception` from this specific call site. This fix also improves compatibility with libstdc++, since their implementation already allows this
### Before
```asm
___cxa_decrement_exception_refcount:
000000000002a720 stp x20, x19, [sp, #-0x20]!
000000000002a724 stp x29, x30, [sp, #<!-- -->0x10]
000000000002a728 add x29, sp, #<!-- -->0x10
000000000002a72c cbz x0, 0x2a760
000000000002a730 sub x8, x0, #<!-- -->0x78
000000000002a734 mov x9, #-0x1
000000000002a738 ldaddal x9, x8, [x8]
000000000002a73c cmp x8, #<!-- -->0x1
000000000002a740 b.ne 0x2a760
000000000002a744 ldur x8, [x0, #-0x68]
000000000002a748 cbz x8, 0x2a758
000000000002a74c mov x19, x0
000000000002a750 blr x8
000000000002a754 mov x0, x19
000000000002a758 sub x0, x0, #<!-- -->0x80
000000000002a75c bl __ZN10__cxxabiv128__aligned_free_with_fallbackEPv
```
There is no branching `bl ___cxa_free_exception` instruction. The body of `__cxa_free_exception` has been inlined into `__cxa_decrement_exception_refcount`.
### After
```asm
___cxa_decrement_exception_refcount:
...
000000000002a78c ldp x29, x30, [sp, #<!-- -->0x10]
000000000002a790 ldp x20, x19, [sp], #<!-- -->0x20
000000000002a794 b ___cxa_free_exception
```
With `__cxa_free_exception` marked `noinline`, the function is called instead of being inlined. The function is reached through a tail branch, allowing `__cxa_free_exception` to still be an interposable function.
## Testing
I added a regression test that verifies that `__cxa_free_exception` is interposed and called when
`__cxa_decrement_exception_refcount`'s reference count reaches 0.
This test passes with `check-cxxabi`.
I would appreciate any suggestions on whether there is a prefered way to structure the unit test in this case.
Disclaimer: AI has been used to understand the codebase, implementation and reviewing the patch. However, everything was written by me.
---
Full diff: https://github.com/llvm/llvm-project/pull/222754.diff
2 Files Affected:
- (modified) libcxxabi/src/cxa_exception.cpp (+1)
- (added) libcxxabi/test/cxa_free_exception_interpose.pass.cpp (+26)
``````````diff
diff --git a/libcxxabi/src/cxa_exception.cpp b/libcxxabi/src/cxa_exception.cpp
index f56ecc248eefb..68b8358d3ee29 100644
--- a/libcxxabi/src/cxa_exception.cpp
+++ b/libcxxabi/src/cxa_exception.cpp
@@ -200,6 +200,7 @@ void *__cxa_allocate_exception(size_t thrown_size) throw() {
// Free a __cxa_exception object allocated with __cxa_allocate_exception.
+__attribute__((noinline))
void __cxa_free_exception(void *thrown_object) throw() {
// Compute the size of the padding before the header.
size_t header_offset = get_cxa_exception_offset();
diff --git a/libcxxabi/test/cxa_free_exception_interpose.pass.cpp b/libcxxabi/test/cxa_free_exception_interpose.pass.cpp
new file mode 100644
index 0000000000000..072e0ff150ba2
--- /dev/null
+++ b/libcxxabi/test/cxa_free_exception_interpose.pass.cpp
@@ -0,0 +1,26 @@
+// UNSUPPORTED: target={{.*-apple-darwin.*}}
+// RUN: %{cxx} %{flags} %{compile_flags} -O3 %s %{link_flags} -o %t.exe
+// RUN: %{exec} %t.exe
+
+#include <cassert>
+#include <cstddef>
+
+static bool freed = false;
+
+extern "C" void __cxa_free_exception(void*) {
+ freed = true;
+}
+
+extern "C" void* __cxa_allocate_exception(std::size_t);
+
+extern "C" void __cxa_decrement_exception_refcount(void*);
+
+int main() {
+ void* p = __cxa_allocate_exception(sizeof(int));
+
+ assert(p != nullptr);
+
+ __cxa_decrement_exception_refcount(p);
+
+ assert(freed);
+}
\ No newline at end of file
``````````
</details>
https://github.com/llvm/llvm-project/pull/222754
More information about the libcxx-commits
mailing list