From ad85ecd0c5c1a2d84d938e15ab029d5ab5fbfab9 Mon Sep 17 00:00:00 2001 From: Dmitry Borisenkov Date: Sun, 7 Sep 2025 19:39:13 +0200 Subject: [PATCH 1/3] [EVM] Combine signextend --- llvm/lib/Target/EVM/EVMInstrInfo.td | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index 45727d4b7e2f..4c3c3082eea7 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -1210,3 +1210,26 @@ let isCodeGenOnly = 1, hasSideEffects = 1, isNotDuplicable = 1 in def PUSHDEPLOYADDRESS : NRI<(outs GPR:$res), (ins), [(set GPR:$res, (int_evm_pushdeployaddress))], "PUSHDEPLOYADDRESS $res">; + +// Constant predicate: C % 8 == 0 and C <= 255 +def ShAmtMul8U255 : PatLeaf<(imm), [{ + auto *CN = dyn_cast(N); + if (!CN) return false; + const APInt &V = CN->getAPIntValue(); + return V.ule(255) && (V.urem(8) == 0); +}]>; + +// Transform shift-bits C -> byteIndex = (C/8) - 1 +def CToByteIndex : SDNodeXForm(N); + uint64_t C = CN->getZExtValue(); + uint64_t idx = (256 - C) / 8 - 1; + return CurDAG->getTargetConstant(idx, SDLoc(N), MVT::i256); +}]>; + +// Fold: (sra C, (shl C, x)) ==> SIGNEXTEND(byteIndex(C), x) +// Note: generic DAG uses 'sra'/'shl'; EVM op is SIGNEXTEND(byteIndex, value). +def : Pat< + (sra (shl GPR:$x, ShAmtMul8U255:$C), ShAmtMul8U255:$C), + (SIGNEXTEND (CONST_I256 (CToByteIndex ShAmtMul8U255:$C)), GPR:$x) +>; From 5927bedc417918d5dcb1abf912ecc2200a194944 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 1 Aug 2025 12:22:32 +0200 Subject: [PATCH 2/3] [EVM] Unfold signextend(c, x) -> ashr(shl(x, 256 - (c + 1) * 8), 256 - (c + 1) * 8) This transform does unfold to canonical InstCombine form, instead to sext(trunc(x, (c + 1) * 8), 256). Unfold signextend to LLVM instructions, iff c is constant, so LLVM can optimize it better. Do the folding back just before ISel, since we can do cross BB boundaries. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMCodegenPrepare.cpp | 38 ++++++++++++++++++- llvm/lib/Target/EVM/EVMTargetMachine.cpp | 2 +- .../lib/Target/EVM/EVMTargetTransformInfo.cpp | 22 +++++++++++ llvm/test/CodeGen/EVM/O3-pipeline.ll | 4 +- llvm/test/CodeGen/EVM/fold-signextend.ll | 7 ++-- 5 files changed, 65 insertions(+), 8 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp b/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp index 4923d58e231c..8a9d8bebcde2 100644 --- a/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp +++ b/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp @@ -20,11 +20,14 @@ #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/Intrinsics.h" #include "llvm/IR/IntrinsicsEVM.h" +#include "llvm/IR/PatternMatch.h" #include "llvm/Pass.h" +#include "llvm/Transforms/InstCombine/InstCombiner.h" #include "EVM.h" using namespace llvm; +using namespace llvm::PatternMatch; #define DEBUG_TYPE "evm-codegen-prepare" @@ -102,14 +105,47 @@ void EVMCodegenPrepare::processMemTransfer(MemTransferInst *M) { M->setCalledFunction(Intrinsic::getDeclaration(M->getModule(), IntrID)); } +static bool optimizeAShrInst(Instruction *I) { + auto *Ty = I->getType(); + unsigned BitWidth = Ty->getIntegerBitWidth(); + if (BitWidth != 256) + return false; + + // Fold ashr(shl(x, c), c) -> signextend(((256 - c) / 8) - 1, x) + // where c is a constant and divisible by 8. + Value *X = nullptr; + ConstantInt *ShiftAmt = nullptr; + if (match(I->getOperand(0), + m_OneUse(m_Shl(m_Value(X), m_ConstantInt(ShiftAmt)))) && + match(I->getOperand(1), m_Specific(ShiftAmt)) && + ShiftAmt->getZExtValue() % 8 == 0) { + IRBuilder<> Builder(I); + unsigned ByteIdx = ((BitWidth - ShiftAmt->getZExtValue()) / 8) - 1; + auto *B = ConstantInt::get(Ty, ByteIdx); + auto *SignExtend = + Builder.CreateIntrinsic(Ty, Intrinsic::evm_signextend, {B, X}); + SignExtend->takeName(I); + I->replaceAllUsesWith(SignExtend); + + // Remove shl after ashr. If to do otherwise, assert will be triggered. + auto *ToRemove = cast(I->getOperand(0)); + I->eraseFromParent(); + ToRemove->eraseFromParent(); + return true; + } + return false; +} + bool EVMCodegenPrepare::runOnFunction(Function &F) { bool Changed = false; for (auto &BB : F) { - for (auto &I : BB) { + for (auto &I : make_early_inc_range(BB)) { if (auto *M = dyn_cast(&I)) { processMemTransfer(M); Changed = true; } + if (I.getOpcode() == Instruction::AShr) + Changed |= optimizeAShrInst(&I); } } diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.cpp b/llvm/lib/Target/EVM/EVMTargetMachine.cpp index f1cb29550769..02d46ae25d53 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.cpp +++ b/llvm/lib/Target/EVM/EVMTargetMachine.cpp @@ -233,8 +233,8 @@ bool EVMPassConfig::addPreISel() { } void EVMPassConfig::addCodeGenPrepare() { - addPass(createEVMCodegenPreparePass()); TargetPassConfig::addCodeGenPrepare(); + addPass(createEVMCodegenPreparePass()); } bool EVMPassConfig::addInstSelector() { diff --git a/llvm/lib/Target/EVM/EVMTargetTransformInfo.cpp b/llvm/lib/Target/EVM/EVMTargetTransformInfo.cpp index ad7c259757f8..9acb7e9c17e6 100644 --- a/llvm/lib/Target/EVM/EVMTargetTransformInfo.cpp +++ b/llvm/lib/Target/EVM/EVMTargetTransformInfo.cpp @@ -21,6 +21,28 @@ using namespace llvm::PatternMatch; static std::optional instCombineSignExtend(InstCombiner &IC, IntrinsicInst &II) { + unsigned BitWidth = II.getType()->getIntegerBitWidth(); + if (BitWidth != 256) + return std::nullopt; + + // Unfold signextend(c, x) -> + // ashr(shl(x, 256 - (c + 1) * 8), 256 - (c + 1) * 8) + // where c is a constant integer. + ConstantInt *C = nullptr; + if (match(II.getArgOperand(0), m_ConstantInt(C))) { + const APInt &B = C->getValue(); + + // If the signextend is larger than 31 bits, leave constant + // folding to handle it. + if (B.uge(APInt(BitWidth, (BitWidth / 8) - 1))) + return std::nullopt; + + unsigned ShiftAmt = BitWidth - ((B.getZExtValue() + 1) * 8); + auto *Shl = IC.Builder.CreateShl(II.getArgOperand(1), ShiftAmt); + auto *Ashr = IC.Builder.CreateAShr(Shl, ShiftAmt); + return IC.replaceInstUsesWith(II, Ashr); + } + // Fold signextend(b, signextend(b, x)) -> signextend(b, x) Value *B = nullptr, *X = nullptr; if (match(&II, m_Intrinsic( diff --git a/llvm/test/CodeGen/EVM/O3-pipeline.ll b/llvm/test/CodeGen/EVM/O3-pipeline.ll index c6f2605162ba..7cd473057c37 100644 --- a/llvm/test/CodeGen/EVM/O3-pipeline.ll +++ b/llvm/test/CodeGen/EVM/O3-pipeline.ll @@ -55,10 +55,8 @@ target triple = "evm" ; CHECK-NEXT: Expand reduction intrinsics ; CHECK-NEXT: Natural Loop Information ; CHECK-NEXT: TLS Variable Hoist -; CHECK-NEXT: Final transformations before code generation -; CHECK-NEXT: Dominator Tree Construction -; CHECK-NEXT: Natural Loop Information ; CHECK-NEXT: CodeGen Prepare +; CHECK-NEXT: Final transformations before code generation ; CHECK-NEXT: Lower invoke and unwind, for unwindless code generators ; CHECK-NEXT: Remove unreachable blocks from the CFG ; CHECK-NEXT: CallGraph Construction diff --git a/llvm/test/CodeGen/EVM/fold-signextend.ll b/llvm/test/CodeGen/EVM/fold-signextend.ll index 559d46381321..015fe1deb1a1 100644 --- a/llvm/test/CodeGen/EVM/fold-signextend.ll +++ b/llvm/test/CodeGen/EVM/fold-signextend.ll @@ -7,7 +7,8 @@ target triple = "evm" define i256 @test_const(i256 %x) { ; CHECK-LABEL: define i256 @test_const( ; CHECK-SAME: i256 [[X:%.*]]) { -; CHECK-NEXT: [[SIGNEXT1:%.*]] = call i256 @llvm.evm.signextend(i256 15, i256 [[X]]) +; CHECK-NEXT: [[TMP1:%.*]] = shl i256 [[X]], 128 +; CHECK-NEXT: [[SIGNEXT1:%.*]] = ashr exact i256 [[TMP1]], 128 ; CHECK-NEXT: ret i256 [[SIGNEXT1]] ; %signext1 = call i256 @llvm.evm.signextend(i256 15, i256 %x) @@ -18,8 +19,8 @@ define i256 @test_const(i256 %x) { define i256 @test_const_ne(i256 %x) { ; CHECK-LABEL: define i256 @test_const_ne( ; CHECK-SAME: i256 [[X:%.*]]) { -; CHECK-NEXT: [[SIGNEXT1:%.*]] = call i256 @llvm.evm.signextend(i256 15, i256 [[X]]) -; CHECK-NEXT: [[SIGNEXT2:%.*]] = call i256 @llvm.evm.signextend(i256 10, i256 [[SIGNEXT1]]) +; CHECK-NEXT: [[TMP1:%.*]] = shl i256 [[X]], 168 +; CHECK-NEXT: [[SIGNEXT2:%.*]] = ashr exact i256 [[TMP1]], 168 ; CHECK-NEXT: ret i256 [[SIGNEXT2]] ; %signext1 = call i256 @llvm.evm.signextend(i256 15, i256 %x) From 954047250508a03cd6eca439d58cc2f6c2fad625 Mon Sep 17 00:00:00 2001 From: Dmitry Borisenkov Date: Sun, 7 Sep 2025 23:09:43 +0200 Subject: [PATCH 3/3] - optimizeAshr --- llvm/lib/Target/EVM/EVMCodegenPrepare.cpp | 33 ----------------------- 1 file changed, 33 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp b/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp index 8a9d8bebcde2..b5bdd3613204 100644 --- a/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp +++ b/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp @@ -105,37 +105,6 @@ void EVMCodegenPrepare::processMemTransfer(MemTransferInst *M) { M->setCalledFunction(Intrinsic::getDeclaration(M->getModule(), IntrID)); } -static bool optimizeAShrInst(Instruction *I) { - auto *Ty = I->getType(); - unsigned BitWidth = Ty->getIntegerBitWidth(); - if (BitWidth != 256) - return false; - - // Fold ashr(shl(x, c), c) -> signextend(((256 - c) / 8) - 1, x) - // where c is a constant and divisible by 8. - Value *X = nullptr; - ConstantInt *ShiftAmt = nullptr; - if (match(I->getOperand(0), - m_OneUse(m_Shl(m_Value(X), m_ConstantInt(ShiftAmt)))) && - match(I->getOperand(1), m_Specific(ShiftAmt)) && - ShiftAmt->getZExtValue() % 8 == 0) { - IRBuilder<> Builder(I); - unsigned ByteIdx = ((BitWidth - ShiftAmt->getZExtValue()) / 8) - 1; - auto *B = ConstantInt::get(Ty, ByteIdx); - auto *SignExtend = - Builder.CreateIntrinsic(Ty, Intrinsic::evm_signextend, {B, X}); - SignExtend->takeName(I); - I->replaceAllUsesWith(SignExtend); - - // Remove shl after ashr. If to do otherwise, assert will be triggered. - auto *ToRemove = cast(I->getOperand(0)); - I->eraseFromParent(); - ToRemove->eraseFromParent(); - return true; - } - return false; -} - bool EVMCodegenPrepare::runOnFunction(Function &F) { bool Changed = false; for (auto &BB : F) { @@ -144,8 +113,6 @@ bool EVMCodegenPrepare::runOnFunction(Function &F) { processMemTransfer(M); Changed = true; } - if (I.getOpcode() == Instruction::AShr) - Changed |= optimizeAShrInst(&I); } }