From c1908c55c4c0719e4ac8ba7fdbf4c66b2f246f2e Mon Sep 17 00:00:00 2001 From: LeeYoungJoon Date: Mon, 5 Jan 2026 15:26:50 +0900 Subject: [PATCH 1/4] Handle alloc/realloc calls without AllocSize If a function is marked as alloc or realloc but has no AllocSize attribute, do not try to compute the allocation size. Instead, model the size as a symbolic value returned by the call. --- ir/instr.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/ir/instr.cpp b/ir/instr.cpp index 806666725..92868e31d 100644 --- a/ir/instr.cpp +++ b/ir/instr.cpp @@ -2682,9 +2682,25 @@ StateValue FnCall::toSMT(State &s) const { }; if (attrs.has(AllocKind::Alloc) || - attrs.has(AllocKind::Realloc) || - attrs.has(FnAttrs::AllocSize)) { - auto [size, np_size] = attrs.computeAllocSize(s, args); + attrs.has(AllocKind::Realloc)) { + + smt::expr size; + smt::expr np_size; + + if (!attrs.has(FnAttrs::AllocSize)) { + has_write_fncall |= true; + static IntType sizet_type(string("size_t"), config::max_sizet_bits); + auto result = s.addFnCall(std::move(fnName_mangled).str(), std::move(inputs), + std::move(ptr_inputs), sizet_type, StateValue{}, + nullptr, std::vector{}, attrs, indirect_hash); + size = std::move(result.value); + np_size = expr(true); + } else { + auto result = attrs.computeAllocSize(s, args); + size = std::move(result.first); + np_size = std::move(result.second); + } + expr nonnull = attrs.isNonNull() ? expr(true) : expr::mkBoolVar("malloc_never_fails"); // FIXME: alloc-family below From 2e0640f03f3a0ae09965c831ce6ae5375fb994a9 Mon Sep 17 00:00:00 2001 From: LeeYoungJoon Date: Wed, 7 Jan 2026 11:34:40 +0900 Subject: [PATCH 2/4] Improve AllocSize handling following code review feedback - Replace hardcoded true with in instr.cpp - Move handling into the preprocessor stage (transform.cpp) - Keep AllocSize attribute checks for allocation functions - Add a test case covering allocations without allocsize --- ir/attrs.h | 1 + ir/instr.cpp | 6 +++--- tests/alive-tv/attrs/noallocsize-fn.srctgt.ll | 14 ++++++++++++++ tools/transform.cpp | 3 +++ 4 files changed, 21 insertions(+), 3 deletions(-) create mode 100644 tests/alive-tv/attrs/noallocsize-fn.srctgt.ll diff --git a/ir/attrs.h b/ir/attrs.h index 142815248..8e6fd4430 100644 --- a/ir/attrs.h +++ b/ir/attrs.h @@ -158,6 +158,7 @@ class FnAttrs final { void add(AllocKind k) { allockind |= (uint8_t)k; } bool has(AllocKind k) const { return allockind & (uint8_t)k; } bool isAlloc() const { return allockind != 0 || has(AllocSize); } + bool isAllocWithoutSize() const { return allockind != 0 && !has(AllocSize); } void inferImpliedAttributes(); diff --git a/ir/instr.cpp b/ir/instr.cpp index 92868e31d..9862474e1 100644 --- a/ir/instr.cpp +++ b/ir/instr.cpp @@ -2682,19 +2682,19 @@ StateValue FnCall::toSMT(State &s) const { }; if (attrs.has(AllocKind::Alloc) || - attrs.has(AllocKind::Realloc)) { + attrs.has(AllocKind::Realloc) || + attrs.has(FnAttrs::AllocSize)) { smt::expr size; smt::expr np_size; if (!attrs.has(FnAttrs::AllocSize)) { - has_write_fncall |= true; static IntType sizet_type(string("size_t"), config::max_sizet_bits); auto result = s.addFnCall(std::move(fnName_mangled).str(), std::move(inputs), std::move(ptr_inputs), sizet_type, StateValue{}, nullptr, std::vector{}, attrs, indirect_hash); size = std::move(result.value); - np_size = expr(true); + np_size = std::move(result.non_poison); } else { auto result = attrs.computeAllocSize(s, args); size = std::move(result.first); diff --git a/tests/alive-tv/attrs/noallocsize-fn.srctgt.ll b/tests/alive-tv/attrs/noallocsize-fn.srctgt.ll new file mode 100644 index 000000000..967a4f32c --- /dev/null +++ b/tests/alive-tv/attrs/noallocsize-fn.srctgt.ll @@ -0,0 +1,14 @@ +; ERROR: Source and target don't have the same return domain + +define i64 @src() { + %stack = call ptr @myalloc() + %sz = call i64 @llvm.objectsize.i64.p0(ptr %stack, i1 false, i1 false, i1 false) + ret i64 %sz +} + +define i64 @tgt() { + ret i64 -1 +} + +declare ptr @myalloc() allockind("alloc") +declare i64 @llvm.objectsize.i64.p0(ptr, i1, i1, i1) diff --git a/tools/transform.cpp b/tools/transform.cpp index 44bfd59e1..fd67b6d51 100644 --- a/tools/transform.cpp +++ b/tools/transform.cpp @@ -1163,6 +1163,9 @@ static void calculateAndInitConstants(Transform &t) { inaccessiblememonly_fns.emplace(call->getName()).second) ++num_inaccessiblememonly_fns; } + if(attrs.isAllocWithoutSize()) { + has_write_fncall |= attrs.mem.canWriteSomething(); + } if (call->isIndirect()) { has_indirect_fncalls = true; num_inaccessiblememonly_fns += is_src; From 77db5ddb344b2eac1a548371a377d416b3b89572 Mon Sep 17 00:00:00 2001 From: LeeYoungJoon Date: Wed, 21 Jan 2026 11:38:12 +0900 Subject: [PATCH 3/4] Add read-only attribute to addFnCall --- ir/instr.cpp | 4 +++- tools/transform.cpp | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/ir/instr.cpp b/ir/instr.cpp index 9862474e1..04a3573ba 100644 --- a/ir/instr.cpp +++ b/ir/instr.cpp @@ -2690,9 +2690,11 @@ StateValue FnCall::toSMT(State &s) const { if (!attrs.has(FnAttrs::AllocSize)) { static IntType sizet_type(string("size_t"), config::max_sizet_bits); + FnAttrs readonly_attrs = attrs; + readonly_attrs.mem.setCanOnlyRead(); auto result = s.addFnCall(std::move(fnName_mangled).str(), std::move(inputs), std::move(ptr_inputs), sizet_type, StateValue{}, - nullptr, std::vector{}, attrs, indirect_hash); + nullptr, std::vector{}, readonly_attrs, indirect_hash); size = std::move(result.value); np_size = std::move(result.non_poison); } else { diff --git a/tools/transform.cpp b/tools/transform.cpp index fd67b6d51..44bfd59e1 100644 --- a/tools/transform.cpp +++ b/tools/transform.cpp @@ -1163,9 +1163,6 @@ static void calculateAndInitConstants(Transform &t) { inaccessiblememonly_fns.emplace(call->getName()).second) ++num_inaccessiblememonly_fns; } - if(attrs.isAllocWithoutSize()) { - has_write_fncall |= attrs.mem.canWriteSomething(); - } if (call->isIndirect()) { has_indirect_fncalls = true; num_inaccessiblememonly_fns += is_src; From 183a78f67549a83f0be91405fbff81709a892cc4 Mon Sep 17 00:00:00 2001 From: LeeYoungJoon Date: Mon, 2 Feb 2026 16:31:39 +0900 Subject: [PATCH 4/4] Use addFnCall to mark allocator calls with inaccessible memory side effects. --- ir/instr.cpp | 12 +++++++++--- tools/transform.cpp | 3 +++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ir/instr.cpp b/ir/instr.cpp index d38f8a370..472c5174e 100644 --- a/ir/instr.cpp +++ b/ir/instr.cpp @@ -2774,14 +2774,20 @@ StateValue FnCall::toSMT(State &s) const { if (!attrs.has(FnAttrs::AllocSize)) { static IntType sizet_type(string("size_t"), config::max_sizet_bits); - FnAttrs readonly_attrs = attrs; - readonly_attrs.mem.setCanOnlyRead(); + FnAttrs new_attrs = attrs; + new_attrs.mem.setCanOnlyAccess(MemoryAccess::Inaccessible); auto result = s.addFnCall(std::move(fnName_mangled).str(), std::move(inputs), std::move(ptr_inputs), sizet_type, StateValue{}, - nullptr, std::vector{}, readonly_attrs, indirect_hash); + nullptr, std::vector{}, new_attrs, indirect_hash); size = std::move(result.value); np_size = std::move(result.non_poison); } else { + + FnAttrs new_attrs = attrs; + new_attrs.mem.setCanOnlyAccess(MemoryAccess::Inaccessible); + s.addFnCall(std::move(fnName_mangled).str(), std::move(inputs), + std::move(ptr_inputs), Type::voidTy, StateValue{}, + nullptr, std::vector{}, new_attrs, indirect_hash); auto result = attrs.computeAllocSize(s, args); size = std::move(result.first); np_size = std::move(result.second); diff --git a/tools/transform.cpp b/tools/transform.cpp index 8f705526d..b6f63cfb2 100644 --- a/tools/transform.cpp +++ b/tools/transform.cpp @@ -1162,6 +1162,9 @@ static void calculateAndInitConstants(Transform &t) { fn->getGlobalVar(string_view(call->getFnName()).substr(1)) && inaccessiblememonly_fns.emplace(call->getName()).second) ++num_inaccessiblememonly_fns; + } else { + if (inaccessiblememonly_fns.emplace(call->getName()).second) + ++num_inaccessiblememonly_fns; } if (call->isIndirect()) { has_indirect_fncalls = true;