Skip to content

Commit d3e3e7c

Browse files
committed
[EVM] Extend DSE to remove dead store at the end of a function.
1 parent 8db1d19 commit d3e3e7c

File tree

2 files changed

+101
-2
lines changed

2 files changed

+101
-2
lines changed

llvm/lib/Transforms/Scalar/DeadStoreElimination.cpp

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@
6363
#include "llvm/IR/Instruction.h"
6464
#include "llvm/IR/Instructions.h"
6565
#include "llvm/IR/IntrinsicInst.h"
66+
// EVM local begin
67+
#include "llvm/IR/IntrinsicsEVM.h"
68+
// EVM local begin
6669
#include "llvm/IR/Module.h"
6770
#include "llvm/IR/PassManager.h"
6871
#include "llvm/IR/PatternMatch.h"
@@ -73,6 +76,9 @@
7376
#include "llvm/Support/DebugCounter.h"
7477
#include "llvm/Support/ErrorHandling.h"
7578
#include "llvm/Support/raw_ostream.h"
79+
// EVM local begin
80+
#include "llvm/TargetParser/Triple.h"
81+
// EVM local end
7682
#include "llvm/Transforms/Utils/AssumeBundleBuilder.h"
7783
#include "llvm/Transforms/Utils/BuildLibCalls.h"
7884
#include "llvm/Transforms/Utils/Local.h"
@@ -1801,6 +1807,29 @@ struct DSEState {
18011807
return false;
18021808
}
18031809

1810+
/// Verify whether there is a path from the block containing the store to a
1811+
/// return block, i.e., a block that does not end with 'unreachable'.
1812+
bool isOnPathToFunctionReturn(const MemoryDef *MaybeDeadDef) {
1813+
const BasicBlock *BB = MaybeDeadDef->getBlock();
1814+
// Handle the trivial case where the store resides in a block that ends
1815+
// with 'unreachable'.
1816+
if (isa<UnreachableInst>(BB->getTerminator()))
1817+
return false;
1818+
1819+
// Handle the general case by performing a depth-first traversal on the
1820+
// inverse control flow graph (CFG), starting from a return block and
1821+
// continuing until the block containing the store is reached.
1822+
for (const BasicBlock *R : PDT.roots()) {
1823+
if (isa<UnreachableInst>(R->getTerminator()))
1824+
continue;
1825+
1826+
for (auto I = idf_begin(R), E = idf_end(R); I != E; ++I)
1827+
if (*I == BB)
1828+
return true;
1829+
}
1830+
return false;
1831+
}
1832+
18041833
/// Eliminate writes to objects that are not visible in the caller and are not
18051834
/// accessed before returning from the function.
18061835
bool eliminateDeadWritesAtEndOfFunction() {
@@ -1828,8 +1857,23 @@ struct DSEState {
18281857
// underlying objects is very uncommon. If it turns out to be important,
18291858
// we can use getUnderlyingObjects here instead.
18301859
const Value *UO = getUnderlyingObject(DefLoc->Ptr);
1831-
if (!isInvisibleToCallerAfterRet(UO))
1832-
continue;
1860+
if (!isInvisibleToCallerAfterRet(UO)) {
1861+
// EVM local begin
1862+
// For EVM verify that all paths originating from the basic block
1863+
// containing the store eventually lead to blocks that terminate with
1864+
// an 'unreachable' instruction. If, in addition, the store is not
1865+
// read before the function returns (as determined by
1866+
// isWriteAtEndOfFunction), then the store can be safely eliminated.
1867+
// TODO: Evaluate this transformation across all targets
1868+
// (i.e., without limiting it to EVM) to assess whether it could be
1869+
// upstreamed. At first glance, this appears to be EVM-specific,
1870+
// since in the general case it is not guaranteed that memory side
1871+
// effects preceding an unreachable can be safely ignored.
1872+
Triple TT(DefI->getFunction()->getParent()->getTargetTriple());
1873+
if (!TT.isEVM() || isOnPathToFunctionReturn(Def))
1874+
continue;
1875+
// EVM local end
1876+
}
18331877

18341878
if (isWriteAtEndOfFunction(Def, *DefLoc)) {
18351879
// See through pointer-to-pointer bitcasts

llvm/test/CodeGen/EVM/evm-dse.ll

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
2+
; RUN: opt -aa-pipeline=basic-aa,evm-aa -passes=dse -S < %s | FileCheck %s
3+
target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256"
4+
target triple = "evm-unknown-unknown"
5+
6+
declare i256 @llvm.evm.calldatasize()
7+
declare void @llvm.evm.return(ptr addrspace(1) readonly, i256)
8+
declare void @llvm.evm.revert(ptr addrspace(1) readonly, i256)
9+
declare void @llvm.memcpy.p1.p3.i256(ptr addrspace(1) noalias nocapture writeonly, ptr addrspace(3) noalias nocapture readonly, i256, i1 immarg)
10+
11+
; This test verifies that unused heap stores can be safely eliminated before revert or return intrinsics.
12+
define void @test(ptr addrspace(3) %src) noreturn {
13+
; CHECK-LABEL: define void @test(
14+
; CHECK-SAME: ptr addrspace(3) [[SRC:%.*]]) #[[ATTR3:[0-9]+]] {
15+
; CHECK-NEXT: [[ENTRY:.*:]]
16+
; CHECK-NEXT: [[CALLDATASIZE:%.*]] = tail call i256 @llvm.evm.calldatasize()
17+
; CHECK-NEXT: br label %[[IF_JOIN22:.*]]
18+
; CHECK: [[IF_JOIN22]]:
19+
; CHECK-NEXT: store i256 1, ptr addrspace(5) null, align 32
20+
; CHECK-NEXT: [[COMPARISON_RESULT_I:%.*]] = icmp ugt i256 [[CALLDATASIZE]], 4
21+
; CHECK-NEXT: br i1 [[COMPARISON_RESULT_I]], label %[[FUN_EXIT:.*]], label %[[IF_JOIN:.*]]
22+
; CHECK: [[IF_JOIN]]:
23+
; CHECK-NEXT: store i256 7, ptr addrspace(6) null, align 32
24+
; CHECK-NEXT: tail call void @llvm.evm.return(ptr addrspace(1) noalias nocapture nofree noundef nonnull align 32 null, i256 32)
25+
; CHECK-NEXT: unreachable
26+
; CHECK: [[FUN_EXIT]]:
27+
; CHECK-NEXT: store i256 2, ptr addrspace(5) null, align 32
28+
; CHECK-NEXT: tail call void @llvm.evm.revert(ptr addrspace(1) noalias nocapture nofree noundef nonnull align 32 null, i256 0)
29+
; CHECK-NEXT: unreachable
30+
;
31+
entry:
32+
; To be removed
33+
store i256 128, ptr addrspace(1) inttoptr (i256 64 to ptr addrspace(1)), align 32
34+
%calldatasize = tail call i256 @llvm.evm.calldatasize()
35+
br label %if_join22
36+
37+
if_join22:
38+
store i256 1, ptr addrspace(5) null, align 32
39+
%comparison_result.i = icmp ugt i256 %calldatasize, 4
40+
br i1 %comparison_result.i, label %fun_exit, label %if_join
41+
42+
if_join:
43+
store i256 7, ptr addrspace(6) null, align 32
44+
; To be removed
45+
call void @llvm.memcpy.p1.p3.i256(ptr addrspace(1) inttoptr (i256 128 to ptr addrspace(1)), ptr addrspace(3) %src, i256 32, i1 false)
46+
tail call void @llvm.evm.return(ptr addrspace(1) noalias nocapture nofree noundef nonnull align 32 null, i256 32)
47+
unreachable
48+
49+
fun_exit:
50+
; To be removed
51+
store i256 10, ptr addrspace(1) null, align 32
52+
store i256 2, ptr addrspace(5) null, align 32
53+
tail call void @llvm.evm.revert(ptr addrspace(1) noalias nocapture nofree noundef nonnull align 32 null, i256 0)
54+
unreachable
55+
}

0 commit comments

Comments
 (0)