Author: Nikita Popov Date: 2025-06-30T11:56:32+02:00 New Revision: de6b8cdc41123b71c2306384fd177a18a504a187
URL: https://github.com/llvm/llvm-project/commit/de6b8cdc41123b71c2306384fd177a18a504a187 DIFF: https://github.com/llvm/llvm-project/commit/de6b8cdc41123b71c2306384fd177a18a504a187.diff LOG: [EarlyCSE] Add support for writeonly call CSE (#145474) Add support for CSE of writeonly calls, similar to the existing support for readonly calls. Added: Modified: clang/test/CodeGenCXX/auto-var-init.cpp llvm/lib/Transforms/Scalar/EarlyCSE.cpp llvm/test/Transforms/EarlyCSE/writeonly.ll Removed: ################################################################################ diff --git a/clang/test/CodeGenCXX/auto-var-init.cpp b/clang/test/CodeGenCXX/auto-var-init.cpp index 94386e44573b5..67bc5d417bce9 100644 --- a/clang/test/CodeGenCXX/auto-var-init.cpp +++ b/clang/test/CodeGenCXX/auto-var-init.cpp @@ -1342,6 +1342,7 @@ TEST_UNINIT(base, base); // ZERO-O1-NOT: !annotation TEST_BRACES(base, base); +// ZERO-LABEL: @test_base_braces() // CHECK-LABEL: @test_base_braces() // CHECK: %braces = alloca %struct.base, align [[ALIGN:[0-9]*]] // CHECK-NEXT: call void @llvm.memset{{.*}}(ptr align [[ALIGN]] %{{.*}}, i8 0, i64 8, i1 false) diff --git a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp index 381de60fcface..b6cb987c0423f 100644 --- a/llvm/lib/Transforms/Scalar/EarlyCSE.cpp +++ b/llvm/lib/Transforms/Scalar/EarlyCSE.cpp @@ -493,7 +493,7 @@ struct CallValue { static bool canHandle(Instruction *Inst) { CallInst *CI = dyn_cast<CallInst>(Inst); - if (!CI || !CI->onlyReadsMemory() || + if (!CI || (!CI->onlyReadsMemory() && !CI->onlyWritesMemory()) || // FIXME: Currently the calls which may access the thread id may // be considered as not accessing the memory. But this is // problematic for coroutines, since coroutines may resume in a @@ -1626,14 +1626,19 @@ bool EarlyCSE::processNode(DomTreeNode *Node) { !(MemInst.isValid() && !MemInst.mayReadFromMemory())) LastStore = nullptr; - // If this is a read-only call, process it. - if (CallValue::canHandle(&Inst)) { + // If this is a read-only or write-only call, process it. Skip store + // MemInsts, as they will be more precisely handled later on. Also skip + // memsets, as DSE may be able to optimize them better by removing the + // earlier rather than later store. + if (CallValue::canHandle(&Inst) && + (!MemInst.isValid() || !MemInst.isStore()) && !isa<MemSetInst>(&Inst)) { // If we have an available version of this call, and if it is the right // generation, replace this instruction. std::pair<Instruction *, unsigned> InVal = AvailableCalls.lookup(&Inst); if (InVal.first != nullptr && isSameMemGeneration(InVal.second, CurrentGeneration, InVal.first, - &Inst)) { + &Inst) && + InVal.first->mayReadFromMemory() == Inst.mayReadFromMemory()) { LLVM_DEBUG(dbgs() << "EarlyCSE CSE CALL: " << Inst << " to: " << *InVal.first << '\n'); if (!DebugCounter::shouldExecute(CSECounter)) { @@ -1651,6 +1656,11 @@ bool EarlyCSE::processNode(DomTreeNode *Node) { continue; } + // Increase memory generation for writes. Do this before inserting + // the call, so it has the generation after the write occurred. + if (Inst.mayWriteToMemory()) + ++CurrentGeneration; + // Otherwise, remember that we have this instruction. AvailableCalls.insert(&Inst, std::make_pair(&Inst, CurrentGeneration)); continue; diff --git a/llvm/test/Transforms/EarlyCSE/writeonly.ll b/llvm/test/Transforms/EarlyCSE/writeonly.ll index 0bfffa3c825a3..c09b913f9ff2b 100644 --- a/llvm/test/Transforms/EarlyCSE/writeonly.ll +++ b/llvm/test/Transforms/EarlyCSE/writeonly.ll @@ -1,5 +1,6 @@ ; NOTE: Assertions have been autogenerated by utils/update_test_checks.py -; RUN: opt -S -passes=early-cse -earlycse-debug-hash < %s | FileCheck %s +; RUN: opt -S -passes=early-cse -earlycse-debug-hash < %s | FileCheck %s --check-prefixes=CHECK,NO-MSSA +; RUN: opt -S -passes='early-cse<memssa>' < %s | FileCheck %s --check-prefixes=CHECK,MSSA @var = global i32 undef declare void @foo() nounwind @@ -15,3 +16,148 @@ define void @test() { store i32 2, ptr @var ret void } + +declare void @writeonly_void() memory(write) + +; Can CSE writeonly calls, including non-nounwind/willreturn. +define void @writeonly_cse() { +; CHECK-LABEL: @writeonly_cse( +; CHECK-NEXT: call void @writeonly_void() +; CHECK-NEXT: ret void +; + call void @writeonly_void() + call void @writeonly_void() + ret void +} + +; Can CSE, loads do not matter. +define i32 @writeonly_cse_intervening_load(ptr %p) { +; CHECK-LABEL: @writeonly_cse_intervening_load( +; CHECK-NEXT: call void @writeonly_void() +; CHECK-NEXT: [[V:%.*]] = load i32, ptr [[P:%.*]], align 4 +; CHECK-NEXT: ret i32 [[V]] +; + call void @writeonly_void() + %v = load i32, ptr %p + call void @writeonly_void() + ret i32 %v +} + +; Cannot CSE, the store may be to the same memory. +define void @writeonly_cse_intervening_store(ptr %p) { +; CHECK-LABEL: @writeonly_cse_intervening_store( +; CHECK-NEXT: call void @writeonly_void() +; CHECK-NEXT: store i32 0, ptr [[P:%.*]], align 4 +; CHECK-NEXT: call void @writeonly_void() +; CHECK-NEXT: ret void +; + call void @writeonly_void() + store i32 0, ptr %p + call void @writeonly_void() + ret void +} + +; Can CSE, the store does not alias the writeonly call. +define void @writeonly_cse_intervening_noalias_store(ptr noalias %p) { +; NO-MSSA-LABEL: @writeonly_cse_intervening_noalias_store( +; NO-MSSA-NEXT: call void @writeonly_void() +; NO-MSSA-NEXT: store i32 0, ptr [[P:%.*]], align 4 +; NO-MSSA-NEXT: call void @writeonly_void() +; NO-MSSA-NEXT: ret void +; +; MSSA-LABEL: @writeonly_cse_intervening_noalias_store( +; MSSA-NEXT: call void @writeonly_void() +; MSSA-NEXT: store i32 0, ptr [[P:%.*]], align 4 +; MSSA-NEXT: ret void +; + call void @writeonly_void() + store i32 0, ptr %p + call void @writeonly_void() + ret void +} + +; Cannot CSE loads across writeonly call. +define i32 @load_cse_across_writeonly(ptr %p) { +; CHECK-LABEL: @load_cse_across_writeonly( +; CHECK-NEXT: [[V1:%.*]] = load i32, ptr [[P:%.*]], align 4 +; CHECK-NEXT: call void @writeonly_void() +; CHECK-NEXT: [[V2:%.*]] = load i32, ptr [[P]], align 4 +; CHECK-NEXT: [[RES:%.*]] = sub i32 [[V1]], [[V2]] +; CHECK-NEXT: ret i32 [[RES]] +; + %v1 = load i32, ptr %p + call void @writeonly_void() + %v2 = load i32, ptr %p + %res = sub i32 %v1, %v2 + ret i32 %res +} + +; Can CSE loads across eliminated writeonly call. +define i32 @load_cse_across_csed_writeonly(ptr %p) { +; CHECK-LABEL: @load_cse_across_csed_writeonly( +; CHECK-NEXT: call void @writeonly_void() +; CHECK-NEXT: [[V2:%.*]] = load i32, ptr [[P:%.*]], align 4 +; CHECK-NEXT: ret i32 0 +; + call void @writeonly_void() + %v1 = load i32, ptr %p + call void @writeonly_void() + %v2 = load i32, ptr %p + %res = sub i32 %v1, %v2 + ret i32 %res +} + +declare i32 @writeonly(ptr %p) memory(write) + +; Can CSE writeonly calls with arg and return. +define i32 @writeonly_ret_cse(ptr %p) { +; CHECK-LABEL: @writeonly_ret_cse( +; CHECK-NEXT: [[V2:%.*]] = call i32 @writeonly(ptr [[P:%.*]]) +; CHECK-NEXT: ret i32 0 +; + %v1 = call i32 @writeonly(ptr %p) + %v2 = call i32 @writeonly(ptr %p) + %res = sub i32 %v1, %v2 + ret i32 %res +} + +; Cannot CSE writeonly calls with diff erent arguments. +define i32 @writeonly_ diff erent_args(ptr %p1, ptr %p2) { +; CHECK-LABEL: @writeonly_ diff erent_args( +; CHECK-NEXT: [[V1:%.*]] = call i32 @writeonly(ptr [[P1:%.*]]) +; CHECK-NEXT: [[V2:%.*]] = call i32 @writeonly(ptr [[P2:%.*]]) +; CHECK-NEXT: [[RES:%.*]] = sub i32 [[V1]], [[V2]] +; CHECK-NEXT: ret i32 [[RES]] +; + %v1 = call i32 @writeonly(ptr %p1) + %v2 = call i32 @writeonly(ptr %p2) + %res = sub i32 %v1, %v2 + ret i32 %res +} + +declare void @callee() + +; These are weird cases where the same call is both readonly and writeonly +; based on call-site attributes. I believe this implies that both calls are +; actually readnone and safe to CSE, but leave them alone to be conservative. +define void @readonly_and_writeonly() { +; CHECK-LABEL: @readonly_and_writeonly( +; CHECK-NEXT: call void @callee() #[[ATTR2:[0-9]+]] +; CHECK-NEXT: call void @callee() #[[ATTR1]] +; CHECK-NEXT: ret void +; + call void @callee() memory(read) + call void @callee() memory(write) + ret void +} + +define void @writeonly_and_readonly() { +; CHECK-LABEL: @writeonly_and_readonly( +; CHECK-NEXT: call void @callee() #[[ATTR1]] +; CHECK-NEXT: call void @callee() #[[ATTR2]] +; CHECK-NEXT: ret void +; + call void @callee() memory(write) + call void @callee() memory(read) + ret void +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits