[llvm] [InstCombine] Merge mallocs followed directly by reallocs (PR #207513)
Nikolas Klauser via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 4 06:42:52 PDT 2026
https://github.com/philnik777 created https://github.com/llvm/llvm-project/pull/207513
None
>From 1a2a08c9b057c14d3550bc75b4c69273dd9b22fc Mon Sep 17 00:00:00 2001
From: Nikolas Klauser <nikolasklauser at berlin.de>
Date: Sat, 4 Jul 2026 15:42:18 +0200
Subject: [PATCH] [InstCombine] Merge mallocs followed directly by reallocs
---
llvm/include/llvm/Analysis/MemoryBuiltins.h | 5 +-
llvm/lib/Analysis/MemoryBuiltins.cpp | 11 ++-
.../InstCombine/InstCombineCalls.cpp | 36 ++++++++
.../InstCombine/InstCombineInternal.h | 1 +
llvm/test/Transforms/InstCombine/realloc.ll | 90 ++++++++++++++++++-
5 files changed, 139 insertions(+), 4 deletions(-)
diff --git a/llvm/include/llvm/Analysis/MemoryBuiltins.h b/llvm/include/llvm/Analysis/MemoryBuiltins.h
index a42d662146563..2e9e7120524a9 100644
--- a/llvm/include/llvm/Analysis/MemoryBuiltins.h
+++ b/llvm/include/llvm/Analysis/MemoryBuiltins.h
@@ -69,7 +69,7 @@ LLVM_ABI bool isAllocLikeFn(const Value *V, const TargetLibraryInfo *TLI);
/// Tests if a function is a call or invoke to a library function that
/// reallocates memory (e.g., realloc).
-LLVM_ABI bool isReallocLikeFn(const Function *F);
+LLVM_ABI bool isReallocLikeFn(const Value *F);
/// If this is a call to a realloc function, return the reallocated operand.
LLVM_ABI Value *getReallocatedOperand(const CallBase *CB);
@@ -118,6 +118,9 @@ LLVM_ABI std::optional<APInt> getAllocSize(
return V;
});
+/// Return the Use of the allocation size argument if there exists one.
+LLVM_ABI Use *getAllocSizeArg(CallBase *CB, const TargetLibraryInfo *TLI);
+
/// If this is a call to an allocation function that initializes memory to a
/// fixed value, return said value in the requested type. Otherwise, return
/// nullptr.
diff --git a/llvm/lib/Analysis/MemoryBuiltins.cpp b/llvm/lib/Analysis/MemoryBuiltins.cpp
index 0364a8bff9e53..a78a8d8cc4632 100644
--- a/llvm/lib/Analysis/MemoryBuiltins.cpp
+++ b/llvm/lib/Analysis/MemoryBuiltins.cpp
@@ -308,7 +308,7 @@ bool llvm::isAllocLikeFn(const Value *V, const TargetLibraryInfo *TLI) {
/// Tests if a functions is a call or invoke to a library function that
/// reallocates memory (e.g., realloc).
-bool llvm::isReallocLikeFn(const Function *F) {
+bool llvm::isReallocLikeFn(const Value *F) {
return checkFnAllocKind(F, AllocFnKind::Realloc);
}
@@ -354,6 +354,15 @@ static bool checkedZextOrTrunc(APInt &I, unsigned IntTyBits) {
return true;
}
+Use *llvm::getAllocSizeArg(CallBase *CB, const TargetLibraryInfo *TLI) {
+ std::optional<AllocFnsTy> FnData = getAllocationSize(CB, TLI);
+ if (!FnData)
+ return nullptr;
+ if (FnData->FstParam == -1 || FnData->SndParam != -1)
+ return nullptr;
+ return &CB->getArgOperandUse(FnData->FstParam);
+}
+
std::optional<APInt>
llvm::getAllocSize(const CallBase *CB, const TargetLibraryInfo *TLI,
function_ref<const Value *(const Value *)> Mapper) {
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
index dcda2f57648d1..6c9780a1bf9ff 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
+++ b/llvm/lib/Transforms/InstCombine/InstCombineCalls.cpp
@@ -4882,6 +4882,39 @@ bool InstCombinerImpl::annotateAnyAllocSite(CallBase &Call,
return Changed;
}
+Instruction *InstCombinerImpl::visitReallocSite(CallBase &Call) {
+ assert(isReallocLikeFn(&Call));
+
+ CallBase *Realloced =
+ dyn_cast_or_null<CallBase>(getReallocatedOperand(&Call));
+
+ if (!Realloced || Realloced->getParent() != Call.getParent() ||
+ !isAllocLikeFn(Realloced, &TLI))
+ return nullptr;
+
+ if (getAllocationFamily(&Call, &TLI) != getAllocationFamily(Realloced, &TLI))
+ return nullptr;
+
+ if (getAllocAlignment(&Call, &TLI) != getAllocAlignment(Realloced, &TLI))
+ return nullptr;
+
+ for (Instruction &Inst :
+ make_range(++Realloced->getIterator(), Call.getIterator())) {
+ if (Inst.mayReadOrWriteMemory())
+ return nullptr;
+ }
+
+ Realloced->moveBefore(Call.getIterator());
+ Use *AllocSizeArg = getAllocSizeArg(Realloced, &TLI);
+ Use *ReallocSizeArg = getAllocSizeArg(&Call, &TLI);
+ if (!AllocSizeArg || !ReallocSizeArg)
+ return nullptr;
+
+ replaceUse(*AllocSizeArg, *ReallocSizeArg);
+ replaceInstUsesWith(Call, Realloced);
+ return eraseInstFromFunction(Call);
+}
+
/// Improvements for call, callbr and invoke instructions.
Instruction *InstCombinerImpl::visitCallBase(CallBase &Call) {
bool Changed = annotateAnyAllocSite(Call, &TLI);
@@ -5066,6 +5099,9 @@ Instruction *InstCombinerImpl::visitCallBase(CallBase &Call) {
if (isRemovableAlloc(&Call, &TLI))
return visitAllocSite(Call);
+ if (isReallocLikeFn(&Call))
+ return visitReallocSite(Call);
+
// Handle intrinsics which can be used in both call and invoke context.
switch (Call.getIntrinsicID()) {
case Intrinsic::experimental_gc_statepoint: {
diff --git a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
index 8b759e701da60..81f0b73160cf9 100644
--- a/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
+++ b/llvm/lib/Transforms/InstCombine/InstCombineInternal.h
@@ -171,6 +171,7 @@ class LLVM_LIBRARY_VISIBILITY InstCombinerImpl final
Instruction *visitGEPOfGEP(GetElementPtrInst &GEP, GEPOperator *Src);
Instruction *visitAllocaInst(AllocaInst &AI);
Instruction *visitAllocSite(Instruction &FI);
+ Instruction *visitReallocSite(CallBase &);
Instruction *visitFree(CallInst &FI, Value *FreedOp);
Instruction *visitLoadInst(LoadInst &LI);
Instruction *visitStoreInst(StoreInst &SI);
diff --git a/llvm/test/Transforms/InstCombine/realloc.ll b/llvm/test/Transforms/InstCombine/realloc.ll
index 1542707d79c1a..ab751533af4fc 100644
--- a/llvm/test/Transforms/InstCombine/realloc.ll
+++ b/llvm/test/Transforms/InstCombine/realloc.ll
@@ -1,9 +1,14 @@
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
; RUN: opt < %s -passes=instcombine -S | FileCheck %s
-declare ptr @realloc(ptr allocptr, i64) allockind("realloc") allocsize(1)
-declare noalias ptr @malloc(i64) allockind("alloc,uninitialized")
+declare ptr @realloc(ptr allocptr, i64) allockind("realloc") allocsize(1) "alloc-family"="malloc"
+declare noalias ptr @malloc(i64) allockind("alloc,uninitialized") "alloc-family"="malloc"
+declare noalias ptr @aligned_alloc(i64, i64 allocalign) allockind("alloc,uninitialized,aligned") "alloc-family"="malloc"
+declare ptr @realloc2(ptr allocptr, i64) allockind("realloc") allocsize(1) "alloc-family"="malloc2"
+declare noalias ptr @malloc2(i64) allockind("alloc,uninitialized") "alloc-family"="malloc2"
+
+declare void @use_i64(i64)
define ptr @realloc_null_ptr() #0 {
; CHECK-LABEL: @realloc_null_ptr(
@@ -22,3 +27,84 @@ define ptr @realloc_unknown_ptr(ptr %ptr) #0 {
%call = call ptr @realloc(ptr %ptr, i64 100) #2
ret ptr %call
}
+
+define ptr @realloc_directly_after_alloc_shrinking() {
+; CHECK-LABEL: @realloc_directly_after_alloc_shrinking(
+; CHECK-NEXT: [[ALLOC:%.*]] = call dereferenceable_or_null(1) ptr @malloc(i64 1)
+; CHECK-NEXT: ret ptr [[ALLOC]]
+;
+ %alloc = call ptr @malloc(i64 3)
+ %realloc = call ptr @realloc(ptr %alloc, i64 1)
+ ret ptr %realloc
+}
+
+define ptr @realloc_directly_after_alloc_growing() {
+; CHECK-LABEL: @realloc_directly_after_alloc_growing(
+; CHECK-NEXT: [[ALLOC:%.*]] = call dereferenceable_or_null(3) ptr @malloc(i64 3)
+; CHECK-NEXT: ret ptr [[ALLOC]]
+;
+ %alloc = call ptr @malloc(i64 1)
+ %realloc = call ptr @realloc(ptr %alloc, i64 3)
+ ret ptr %realloc
+}
+
+define ptr @realloc_directly_after_alloc_identity() {
+; CHECK-LABEL: @realloc_directly_after_alloc_identity(
+; CHECK-NEXT: [[ALLOC:%.*]] = call dereferenceable_or_null(1) ptr @malloc(i64 1)
+; CHECK-NEXT: ret ptr [[ALLOC]]
+;
+ %alloc = call ptr @malloc(i64 1)
+ %realloc = call ptr @realloc(ptr %alloc, i64 1)
+ ret ptr %realloc
+}
+
+define ptr @realloc_directly_after_alloc_family_mismatch() {
+; CHECK-LABEL: @realloc_directly_after_alloc_family_mismatch(
+; CHECK-NEXT: [[ALLOC:%.*]] = call dereferenceable_or_null(1) ptr @malloc(i64 1)
+; CHECK-NEXT: [[REALLOC:%.*]] = call dereferenceable_or_null(1) ptr @realloc2(ptr [[ALLOC]], i64 1)
+; CHECK-NEXT: ret ptr [[REALLOC]]
+;
+ %alloc = call ptr @malloc(i64 1)
+ %realloc = call ptr @realloc2(ptr %alloc, i64 1)
+ ret ptr %realloc
+}
+
+define ptr @realloc_directly_after_alloc_alignment_mismatch() {
+; CHECK-LABEL: @realloc_directly_after_alloc_alignment_mismatch(
+; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @aligned_alloc(i64 1, i64 1)
+; CHECK-NEXT: [[REALLOC:%.*]] = call dereferenceable_or_null(1) ptr @realloc(ptr [[ALLOC]], i64 1)
+; CHECK-NEXT: ret ptr [[REALLOC]]
+;
+ %alloc = call ptr @aligned_alloc(i64 1, i64 1)
+ %realloc = call ptr @realloc(ptr %alloc, i64 1)
+ ret ptr %realloc
+}
+
+define ptr @realloc_with_non_memory_instructions(ptr %ptr) {
+; CHECK-LABEL: @realloc_with_non_memory_instructions(
+; CHECK-NEXT: [[INT:%.*]] = ptrtoint ptr [[PTR:%.*]] to i64
+; CHECK-NEXT: [[REALLOC:%.*]] = call dereferenceable_or_null(1) ptr @malloc(i64 1)
+; CHECK-NEXT: call void @use_i64(i64 [[INT]])
+; CHECK-NEXT: ret ptr [[REALLOC]]
+;
+ %alloc = call ptr @malloc(i64 1)
+ %int = ptrtoint ptr %ptr to i64
+ %realloc = call ptr @realloc(ptr %alloc, i64 1)
+ call void @use_i64(i64 %int)
+ ret ptr %realloc
+}
+
+define ptr @realloc_with_memory_instructions(ptr %ptr) {
+; CHECK-LABEL: @realloc_with_memory_instructions(
+; CHECK-NEXT: [[ALLOC:%.*]] = call dereferenceable_or_null(1) ptr @malloc(i64 1)
+; CHECK-NEXT: [[INT:%.*]] = ptrtoint ptr [[PTR:%.*]] to i64
+; CHECK-NEXT: call void @use_i64(i64 [[INT]])
+; CHECK-NEXT: [[REALLOC:%.*]] = call dereferenceable_or_null(1) ptr @realloc(ptr [[ALLOC]], i64 1)
+; CHECK-NEXT: ret ptr [[REALLOC]]
+;
+ %alloc = call ptr @malloc(i64 1)
+ %int = ptrtoint ptr %ptr to i64
+ call void @use_i64(i64 %int)
+ %realloc = call ptr @realloc(ptr %alloc, i64 1)
+ ret ptr %realloc
+}
More information about the llvm-commits
mailing list