[llvm] [Inline] Allow inlining with null_pointer_is_valid mismatch (PR #190510)

Nikita Popov via llvm-commits llvm-commits at lists.llvm.org
Sun Apr 5 01:30:03 PDT 2026


https://github.com/nikic created https://github.com/llvm/llvm-project/pull/190510

If the callee has null_pointer_is_valid but the caller does not, we should still inline and add null_pointer_is_valid to the caller (which is handled by an already existing inline adjustment rule).

This does mean that optimizations in the caller may be reduced by unnecessarily preserving null checks, but that's still better than not inlining at all. In particular, this check causes issues with LTO in the Linux kernel, as the C portions are compiled with null_pointer_is_valid, but the Rust portions are not.

The test is modified to show that the previous alwaysinline behavior now always holds.

>From e8cae9f7b7a20956c7506bb487f15a423dde38e4 Mon Sep 17 00:00:00 2001
From: Nikita Popov <nikita.ppv at gmail.com>
Date: Sun, 5 Apr 2026 10:26:34 +0200
Subject: [PATCH] [Inline] Allow inlining with null_pointer_is_valid mismatch

If the callee has null_pointer_is_valid but the caller does not,
we should still inline and add null_pointer_is_valid to the
caller (which is handled by an already existing inline adjustment
rule).

This does mean that optimizations in the caller may be reduced
by unnecessarily preserving null checks, but that's still better
than not inlining at all. In particular, this check causes issues
with LTO in the Linux kernel, as the C portions are compiled with
null_pointer_is_valid, but the Rust portions are not.

The test is modified to show that the previous alwaysinline
behavior now always holds.
---
 llvm/lib/Analysis/InlineCost.cpp          |  5 ----
 llvm/test/Transforms/Inline/attributes.ll | 36 +++++------------------
 2 files changed, 8 insertions(+), 33 deletions(-)

diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp
index f0ce00b24704f..e1e47b6bc0db6 100644
--- a/llvm/lib/Analysis/InlineCost.cpp
+++ b/llvm/lib/Analysis/InlineCost.cpp
@@ -3240,11 +3240,6 @@ std::optional<InlineResult> llvm::getAttributeBasedInliningDecision(
   if (Caller->hasOptNone())
     return InlineResult::failure("optnone attribute");
 
-  // Don't inline a function that treats null pointer as valid into a caller
-  // that does not have this attribute.
-  if (!Caller->nullPointerIsDefined() && Callee->nullPointerIsDefined())
-    return InlineResult::failure("nullptr definitions incompatible");
-
   // Don't inline functions which can be interposed at link-time.
   if (Callee->isInterposable())
     return InlineResult::failure("interposable");
diff --git a/llvm/test/Transforms/Inline/attributes.ll b/llvm/test/Transforms/Inline/attributes.ll
index cfc393ac54faa..9052d27304886 100644
--- a/llvm/test/Transforms/Inline/attributes.ll
+++ b/llvm/test/Transforms/Inline/attributes.ll
@@ -429,10 +429,9 @@ define i32 @test_no-use-jump-tables3(i32 %i) "no-jump-tables"="true" {
 ; CHECK-NEXT: ret i32
 }
 
-; Callee with null_pointer_is_valid attribute should not be inlined
-; into a caller without this attribute.
-; Exception: alwaysinline callee can still be inlined but
-; null_pointer_is_valid should get copied to caller.
+; Callee with null_pointer_is_valid attribute should be inlined
+; into a caller without this attribute, and set the attribute on
+; the caller.
 
 define i32 @null-pointer-is-valid_callee0(i32 %i) null_pointer_is_valid {
   ret i32 %i
@@ -440,43 +439,24 @@ define i32 @null-pointer-is-valid_callee0(i32 %i) null_pointer_is_valid {
 ; CHECK-NEXT: ret i32
 }
 
-define i32 @null-pointer-is-valid_callee1(i32 %i) alwaysinline null_pointer_is_valid {
+define i32 @null-pointer-is-valid_callee1(i32 %i)  {
   ret i32 %i
 ; CHECK: @null-pointer-is-valid_callee1(i32 %i)
 ; CHECK-NEXT: ret i32
 }
 
-define i32 @null-pointer-is-valid_callee2(i32 %i)  {
-  ret i32 %i
-; CHECK: @null-pointer-is-valid_callee2(i32 %i)
-; CHECK-NEXT: ret i32
-}
-
-; No inlining since caller does not have null_pointer_is_valid attribute.
 define i32 @test_null-pointer-is-valid0(i32 %i) {
   %1 = call i32 @null-pointer-is-valid_callee0(i32 %i)
   ret i32 %1
-; CHECK: @test_null-pointer-is-valid0(
-; CHECK: call i32 @null-pointer-is-valid_callee0
+; CHECK: @test_null-pointer-is-valid0(i32 %i) [[NULLPOINTERISVALID:#[0-9]+]] {
 ; CHECK-NEXT: ret i32
 }
 
-; alwaysinline should force inlining even when caller does not have
-; null_pointer_is_valid attribute. However, the attribute should be
-; copied to caller.
-define i32 @test_null-pointer-is-valid1(i32 %i) {
+; Nothing to do since calleer already has null_pointer_is_valid attribute.
+define i32 @test_null-pointer-is-valid1(i32 %i) null_pointer_is_valid {
   %1 = call i32 @null-pointer-is-valid_callee1(i32 %i)
   ret i32 %1
-; CHECK: @test_null-pointer-is-valid1(i32 %i) [[NULLPOINTERISVALID:#[0-9]+]] {
-; CHECK-NEXT: ret i32
-}
-
-; Can inline since both caller and callee have null_pointer_is_valid
-; attribute.
-define i32 @test_null-pointer-is-valid2(i32 %i) null_pointer_is_valid {
-  %1 = call i32 @null-pointer-is-valid_callee2(i32 %i)
-  ret i32 %1
-; CHECK: @test_null-pointer-is-valid2(i32 %i) [[NULLPOINTERISVALID]] {
+; CHECK: @test_null-pointer-is-valid1(i32 %i) [[NULLPOINTERISVALID]] {
 ; CHECK-NEXT: ret i32
 }
 



More information about the llvm-commits mailing list