[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