[llvm] [IR][DSE] Support non-malloc functions in malloc+memset->calloc fold (PR #138299)
via llvm-commits
llvm-commits at lists.llvm.org
Mon May 12 10:06:11 PDT 2025
https://github.com/clubby789 updated https://github.com/llvm/llvm-project/pull/138299
>From eba5e9e025826d8ca14c4056028c142c0506ca0f Mon Sep 17 00:00:00 2001
From: Jamie <jamie at osec.io>
Date: Fri, 2 May 2025 19:00:13 +0100
Subject: [PATCH] Support non-malloc functions in `malloc`+`memset`->`calloc`
fold
---
llvm/docs/LangRef.rst | 4 ++
llvm/include/llvm/IR/Attributes.h | 1 +
llvm/lib/IR/Attributes.cpp | 4 ++
llvm/lib/IR/Verifier.cpp | 26 ++++++++++++
.../Scalar/DeadStoreElimination.cpp | 41 +++++++++++++++----
.../DeadStoreElimination/noop-stores.ll | 13 ++++++
.../DeadStoreElimination/zeroed-missing.ll | 18 ++++++++
llvm/test/Verifier/alloc-variant-zeroed.ll | 19 +++++++++
8 files changed, 118 insertions(+), 8 deletions(-)
create mode 100644 llvm/test/Transforms/DeadStoreElimination/zeroed-missing.ll
create mode 100644 llvm/test/Verifier/alloc-variant-zeroed.ll
diff --git a/llvm/docs/LangRef.rst b/llvm/docs/LangRef.rst
index 568843a4486e5..78718f102e19c 100644
--- a/llvm/docs/LangRef.rst
+++ b/llvm/docs/LangRef.rst
@@ -1954,6 +1954,10 @@ For example:
The first three options are mutually exclusive, and the remaining options
describe more details of how the function behaves. The remaining options
are invalid for "free"-type functions.
+``"alloc-variant-zeroed"="FUNCTION"``
+ This attribute indicates that another function is equivalent to an allocator function,
+ but returns zeroed memory. The function must have "zeroed" allocation behavior,
+ the same ``alloc-family``, and take exactly the same arguments.
``allocsize(<EltSizeParam>[, <NumEltsParam>])``
This attribute indicates that the annotated function will always return at
least a given number of bytes (or null). Its arguments are zero-indexed
diff --git a/llvm/include/llvm/IR/Attributes.h b/llvm/include/llvm/IR/Attributes.h
index 0e8e31715acd7..b7a00ad93a7e8 100644
--- a/llvm/include/llvm/IR/Attributes.h
+++ b/llvm/include/llvm/IR/Attributes.h
@@ -156,6 +156,7 @@ class Attribute {
static Attribute getWithAllocSizeArgs(
LLVMContext &Context, unsigned ElemSizeArg,
const std::optional<unsigned> &NumElemsArg);
+ static Attribute getWithAllocKind(LLVMContext &Context, AllocFnKind Kind);
static Attribute getWithVScaleRangeArgs(LLVMContext &Context,
unsigned MinValue, unsigned MaxValue);
static Attribute getWithByValType(LLVMContext &Context, Type *Ty);
diff --git a/llvm/lib/IR/Attributes.cpp b/llvm/lib/IR/Attributes.cpp
index 33ac8bfaf4e7c..d76bdcfebcb9a 100644
--- a/llvm/lib/IR/Attributes.cpp
+++ b/llvm/lib/IR/Attributes.cpp
@@ -300,6 +300,10 @@ Attribute::getWithAllocSizeArgs(LLVMContext &Context, unsigned ElemSizeArg,
return get(Context, AllocSize, packAllocSizeArgs(ElemSizeArg, NumElemsArg));
}
+Attribute Attribute::getWithAllocKind(LLVMContext &Context, AllocFnKind Kind) {
+ return get(Context, AllocKind, static_cast<uint64_t>(Kind));
+}
+
Attribute Attribute::getWithVScaleRangeArgs(LLVMContext &Context,
unsigned MinValue,
unsigned MaxValue) {
diff --git a/llvm/lib/IR/Verifier.cpp b/llvm/lib/IR/Verifier.cpp
index a798808d79656..2c5857e6f4fb8 100644
--- a/llvm/lib/IR/Verifier.cpp
+++ b/llvm/lib/IR/Verifier.cpp
@@ -2377,6 +2377,32 @@ void Verifier::verifyFunctionAttrs(FunctionType *FT, AttributeList Attrs,
CheckFailed("'allockind()' can't be both zeroed and uninitialized");
}
+ if (Attribute A = Attrs.getFnAttr("alloc-variant-zeroed"); A.isValid()) {
+ StringRef S = A.getValueAsString();
+ Check(!S.empty(), "'alloc-variant-zeroed' must not be empty");
+ auto *GV = dyn_cast<GlobalValue>(V);
+ Function *Variant = GV ? GV->getParent()->getFunction(S) : nullptr;
+ if (Variant) {
+ Attribute Family = Attrs.getFnAttr("alloc-family");
+ Attribute VariantFamily = Variant->getFnAttribute("alloc-family");
+ if (Family.isValid())
+ Check(VariantFamily.isValid() &&
+ VariantFamily.getValueAsString() == Family.getValueAsString(),
+ "'alloc-variant-zeroed' must name a function belonging to the "
+ "same 'alloc-family'");
+
+ Check(Variant->hasFnAttribute(Attribute::AllocKind) &&
+ (Variant->getFnAttribute(Attribute::AllocKind).getAllocKind() &
+ AllocFnKind::Zeroed) != AllocFnKind::Unknown,
+ "'alloc-variant-zeroed' must name a function with "
+ "'allockind(\"zeroed\")'");
+
+ Check(FT == Variant->getFunctionType(),
+ "'alloc-variant-zeroed' must name a function with the same "
+ "signature");
+ }
+ }
+
if (Attrs.hasFnAttr(Attribute::VScaleRange)) {
unsigned VScaleMin = Attrs.getFnAttrs().getVScaleRangeMin();
if (VScaleMin == 0)
diff --git a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
index e318ec94db4c3..5e90866a995f0 100644
--- a/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
+++ b/llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp
@@ -2028,9 +2028,17 @@ struct DSEState {
if (!InnerCallee)
return false;
LibFunc Func;
+ StringRef ZeroedVariantName;
if (!TLI.getLibFunc(*InnerCallee, Func) || !TLI.has(Func) ||
- Func != LibFunc_malloc)
- return false;
+ Func != LibFunc_malloc) {
+ Attribute Attr = Malloc->getFnAttr("alloc-variant-zeroed");
+ if (!Attr.isValid())
+ return false;
+ ZeroedVariantName = Attr.getValueAsString();
+ if (ZeroedVariantName.empty())
+ return false;
+ }
+
// Gracefully handle malloc with unexpected memory attributes.
auto *MallocDef = dyn_cast_or_null<MemoryDef>(MSSA.getMemoryAccess(Malloc));
if (!MallocDef)
@@ -2057,15 +2065,32 @@ struct DSEState {
if (Malloc->getOperand(0) != MemSet->getLength())
return false;
- if (!shouldCreateCalloc(Malloc, MemSet) ||
- !DT.dominates(Malloc, MemSet) ||
+ if (!shouldCreateCalloc(Malloc, MemSet) || !DT.dominates(Malloc, MemSet) ||
!memoryIsNotModifiedBetween(Malloc, MemSet, BatchAA, DL, &DT))
return false;
IRBuilder<> IRB(Malloc);
- Type *SizeTTy = Malloc->getArgOperand(0)->getType();
- auto *Calloc =
- emitCalloc(ConstantInt::get(SizeTTy, 1), Malloc->getArgOperand(0), IRB,
- TLI, Malloc->getType()->getPointerAddressSpace());
+ assert(Func == LibFunc_malloc || !ZeroedVariantName.empty());
+ Value *Calloc = nullptr;
+ if (!ZeroedVariantName.empty()) {
+ LLVMContext &Ctx = Malloc->getContext();
+ AttributeList Attrs = InnerCallee->getAttributes();
+ AllocFnKind AllocKind =
+ Attrs.getFnAttr(Attribute::AllocKind).getAllocKind() |
+ AllocFnKind::Zeroed;
+ Attrs =
+ Attrs.addFnAttribute(Ctx, Attribute::getWithAllocKind(Ctx, AllocKind))
+ .removeFnAttribute(Ctx, "alloc-variant-zeroed");
+ FunctionCallee ZeroedVariant = Malloc->getModule()->getOrInsertFunction(
+ ZeroedVariantName, InnerCallee->getFunctionType(), Attrs);
+ SmallVector<Value *, 3> Args;
+ Args.append(Malloc->arg_begin(), Malloc->arg_end());
+ Calloc = IRB.CreateCall(ZeroedVariant, Args, ZeroedVariantName);
+ } else {
+ Type *SizeTTy = Malloc->getArgOperand(0)->getType();
+ Calloc =
+ emitCalloc(ConstantInt::get(SizeTTy, 1), Malloc->getArgOperand(0),
+ IRB, TLI, Malloc->getType()->getPointerAddressSpace());
+ }
if (!Calloc)
return false;
diff --git a/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll b/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll
index 9fc20d76da5eb..59a1cc180a194 100644
--- a/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll
+++ b/llvm/test/Transforms/DeadStoreElimination/noop-stores.ll
@@ -374,6 +374,19 @@ define ptr @notmalloc_memset(i64 %size, ptr %notmalloc) {
ret ptr %call1
}
+; This should create a customalloc_zeroed call and eliminate the memset
+define ptr @customalloc_memset(i64 %size, i64 %align) {
+; CHECK-LABEL: @customalloc_memset
+; CHECK-NEXT: [[CALL:%.*]] = call ptr @customalloc_zeroed(i64 [[SIZE:%.*]], i64 [[ALIGN:%.*]])
+; CHECK-NEXT: ret ptr [[CALL]]
+ %call = call ptr @customalloc(i64 %size, i64 %align)
+ call void @llvm.memset.p0.i64(ptr %call, i8 0, i64 %size, i1 false)
+ ret ptr %call
+}
+
+declare ptr @customalloc(i64, i64) allockind("alloc") "alloc-family"="customalloc" "alloc-variant-zeroed"="customalloc_zeroed"
+declare ptr @customalloc_zeroed(i64, i64) allockind("alloc,zeroed") "alloc-family"="customalloc"
+
; This should not create recursive call to calloc.
define ptr @calloc(i64 %nmemb, i64 %size) inaccessiblememonly {
; CHECK-LABEL: @calloc(
diff --git a/llvm/test/Transforms/DeadStoreElimination/zeroed-missing.ll b/llvm/test/Transforms/DeadStoreElimination/zeroed-missing.ll
new file mode 100644
index 0000000000000..0ebd8089ae7cc
--- /dev/null
+++ b/llvm/test/Transforms/DeadStoreElimination/zeroed-missing.ll
@@ -0,0 +1,18 @@
+; RUN: opt < %s -aa-pipeline=basic-aa -passes='dse,verify<memoryssa>' -S | FileCheck %s
+target datalayout = "E-p:64:64:64-a0:0:8-f32:32:32-f64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:32:64-v64:64:64-v128:128:128"
+
+declare void @llvm.memset.p0.i64(ptr nocapture, i8, i64, i1) nounwind
+
+; This should create a declaration for the named variant
+define ptr @undeclared_customalloc(i64 %size, i64 %align) {
+; CHECK-LABEL: @undeclared_customalloc
+; CHECK-NEXT: [[CALL:%.*]] = call ptr @customalloc2_zeroed(i64 [[SIZE:%.*]], i64 [[ALIGN:%.*]])
+; CHECK-NEXT: ret ptr [[CALL]]
+ %call = call ptr @customalloc2(i64 %size, i64 %align)
+ call void @llvm.memset.p0.i64(ptr %call, i8 0, i64 %size, i1 false)
+ ret ptr %call
+}
+
+declare ptr @customalloc2(i64, i64) allockind("alloc") "alloc-family"="customalloc2" "alloc-variant-zeroed"="customalloc2_zeroed"
+; CHECK-DAG: declare ptr @customalloc2_zeroed(i64, i64) #[[CA2ATTR:[0-9]+]]
+; CHECK-DAG: attributes #[[CA2ATTR]] = { allockind("alloc,zeroed") "alloc-family"="customalloc2" }
diff --git a/llvm/test/Verifier/alloc-variant-zeroed.ll b/llvm/test/Verifier/alloc-variant-zeroed.ll
new file mode 100644
index 0000000000000..03cd7be4d9acf
--- /dev/null
+++ b/llvm/test/Verifier/alloc-variant-zeroed.ll
@@ -0,0 +1,19 @@
+; RUN: not llvm-as %s -o /dev/null 2>&1 | FileCheck %s
+
+; CHECK: 'alloc-variant-zeroed' must not be empty
+declare ptr @a(i64) "alloc-variant-zeroed"=""
+
+; CHECK: 'alloc-variant-zeroed' must not be empty
+declare ptr @b(i64) "alloc-variant-zeroed"=""
+
+; CHECK: 'alloc-variant-zeroed' must name a function belonging to the same 'alloc-family'
+declare ptr @c(i64) "alloc-variant-zeroed"="c_zeroed" "alloc-family"="C"
+declare ptr @c_zeroed(i64)
+
+; CHECK: 'alloc-variant-zeroed' must name a function with 'allockind("zeroed")'
+declare ptr @d(i64) "alloc-variant-zeroed"="d_zeroed" "alloc-family"="D"
+declare ptr @d_zeroed(i64) "alloc-family"="D"
+
+; CHECK: 'alloc-variant-zeroed' must name a function with the same signature
+declare ptr @e(i64) "alloc-variant-zeroed"="e_zeroed" "alloc-family"="E"
+declare ptr @e_zeroed(i64, i64) "alloc-family"="E" allockind("zeroed")
More information about the llvm-commits
mailing list