From 50230263eede611336aa717114b3d74b6d32bd14 Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Sat, 16 Nov 2024 09:14:44 +0100 Subject: [PATCH 01/42] [EVM] Fix creation of bundles with terminators --- llvm/lib/Target/EVM/EVMStackify.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMStackify.cpp b/llvm/lib/Target/EVM/EVMStackify.cpp index 3797c5fe7f98..722078251f8f 100644 --- a/llvm/lib/Target/EVM/EVMStackify.cpp +++ b/llvm/lib/Target/EVM/EVMStackify.cpp @@ -1065,10 +1065,12 @@ void StackModel::postProcess() { for (MachineBasicBlock &MBB : *MF) { MachineBasicBlock::instr_iterator I = MBB.instr_begin(), E = MBB.instr_end(); + // Skip the first instruction, as it's not interested anyway. + ++I; for (; I != E; ++I) { if (I->isBranch()) { - auto P = std::next(I); - if (P != E && P->getOpcode() == EVM::PUSH_LABEL) + auto P = std::prev(I); + if (P->getOpcode() == EVM::PUSH_LABEL) I->bundleWithPred(); } } From 608ec165f72252df8542b4f36eedfbd018e32f02 Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 15 Nov 2024 16:40:02 +0100 Subject: [PATCH 02/42] [EVM] Fix ordering of ARGUMENT instructions ARGUMENT instructions should always be located at the beginning of a MF`s entry basic block and be ordered in ascending order of their operand values. --- llvm/lib/Target/EVM/EVMArgumentMove.cpp | 32 ++--- llvm/lib/Target/EVM/EVMInstrFormats.td | 3 +- llvm/lib/Target/EVM/EVMInstrInfo.cpp | 2 +- llvm/lib/Target/EVM/EVMInstrInfo.td | 4 +- .../lib/Target/EVM/EVMSingleUseExpression.cpp | 4 + llvm/test/CodeGen/EVM/add.ll | 2 +- llvm/test/CodeGen/EVM/call.ll | 4 +- llvm/test/CodeGen/EVM/div.ll | 4 +- llvm/test/CodeGen/EVM/fallthrough.mir | 4 +- llvm/test/CodeGen/EVM/globals.ll | 2 +- llvm/test/CodeGen/EVM/intrinsic.ll | 112 +++++++++--------- llvm/test/CodeGen/EVM/logical.ll | 6 +- llvm/test/CodeGen/EVM/memory.ll | 4 +- llvm/test/CodeGen/EVM/mod.ll | 4 +- llvm/test/CodeGen/EVM/mul.ll | 2 +- llvm/test/CodeGen/EVM/select.ll | 6 +- llvm/test/CodeGen/EVM/storage.ll | 2 +- llvm/test/CodeGen/EVM/sub.ll | 2 +- llvm/test/CodeGen/EVM/tstorage.ll | 2 +- .../Generic/2008-08-07-PtrToInt-SmallerInt.ll | 3 +- 20 files changed, 106 insertions(+), 98 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMArgumentMove.cpp b/llvm/lib/Target/EVM/EVMArgumentMove.cpp index b76122b5336f..5da6aeb6caf2 100644 --- a/llvm/lib/Target/EVM/EVMArgumentMove.cpp +++ b/llvm/lib/Target/EVM/EVMArgumentMove.cpp @@ -6,7 +6,8 @@ // //===----------------------------------------------------------------------===// // -// This file moves ARGUMENT instructions after ScheduleDAG scheduling. +// This file moves and orders ARGUMENT instructions after ScheduleDAG +// scheduling. // // Arguments are really live-in registers, however, since we use virtual // registers and LLVM doesn't support live-in virtual registers, we're @@ -67,21 +68,24 @@ bool EVMArgumentMove::runOnMachineFunction(MachineFunction &MF) { bool Changed = false; MachineBasicBlock &EntryMBB = MF.front(); + SmallVector Args; + for (MachineInstr &MI : EntryMBB) { + if (EVM::ARGUMENT == MI.getOpcode()) + Args.push_back(&MI); + } - // Look for the first NonArg instruction. - const auto InsertPt = - std::find_if_not(EntryMBB.begin(), EntryMBB.end(), [](auto &MI) { - return EVM::ARGUMENT == MI.getOpcode(); - }); + // Sort ARGUMENT instructions in ascending order of their arguments. + std::sort(Args.begin(), Args.end(), + [](const MachineInstr *MI1, const MachineInstr *MI2) { + int64_t Arg1Idx = MI1->getOperand(1).getImm(); + int64_t Arg2Idx = MI2->getOperand(1).getImm(); + return Arg1Idx < Arg2Idx; + }); - // Now move any argument instructions later in the block - // to before our first NonArg instruction. - for (MachineInstr &MI : llvm::make_range(InsertPt, EntryMBB.end())) { - if (EVM::ARGUMENT == MI.getOpcode()) { - EntryMBB.insert(InsertPt, MI.removeFromParent()); - Changed = true; - } + for (MachineInstr *MI : reverse(Args)) { + MachineInstr *Arg = MI->removeFromParent(); + EntryMBB.insert(EntryMBB.begin(), Arg); + Changed = true; } - return Changed; } diff --git a/llvm/lib/Target/EVM/EVMInstrFormats.td b/llvm/lib/Target/EVM/EVMInstrFormats.td index bd2e6b8b7b0b..1c90c91c433c 100644 --- a/llvm/lib/Target/EVM/EVMInstrFormats.td +++ b/llvm/lib/Target/EVM/EVMInstrFormats.td @@ -53,6 +53,7 @@ class NI pattern, bit stack, let Opc = inst; let Inst{7-0} = Opc; let GasCost = cost; + let Defs = [ARGUMENTS]; } // Generates both register and stack based versions of one actual instruction. @@ -61,7 +62,7 @@ multiclass I pattern_r, int cost = 0, dag oops_s = (outs), dag iops_s = (ins), string argstr_s = ""> { let isCodeGenOnly = 1 in def "" : NI; - let BaseName = NAME in + let BaseName = NAME, Defs = [] in def _S : NI; } diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.cpp b/llvm/lib/Target/EVM/EVMInstrInfo.cpp index 340ecff24868..7e66f0a9e481 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.cpp +++ b/llvm/lib/Target/EVM/EVMInstrInfo.cpp @@ -174,7 +174,7 @@ unsigned EVMInstrInfo::insertBranch(MachineBasicBlock &MBB, MachineBasicBlock *FBB, ArrayRef Cond, const DebugLoc &DL, int *BytesAdded) const { - assert(!BytesAdded && "Code is size not handled"); + assert(!BytesAdded && "Code size not handled"); // The number of instructions inserted. unsigned InstrCount = 0; diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index bc8a1f0a4502..90aa86bcbd1c 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -200,8 +200,7 @@ def ADJCALLSTACKUP [(EVMcallseq_end timm:$amt1, timm:$amt2)]>; } -let isCodeGenOnly = 1 in { -let hasSideEffects = 1 in +let hasSideEffects = 1, Defs = [], Uses = [ARGUMENTS] in def ARGUMENT : NRI<(outs GPR:$res), (ins i256imm:$argno), [(set GPR:$res, (EVMargument timm:$argno))], @@ -219,7 +218,6 @@ def CONST_I256 let isAsCheapAsAMove = 1 in def COPY_I256 : NRI<(outs GPR:$res), (ins GPR:$src), [], "COPY_I256 $res, $src">; -} def : Pat<(i256 (EVMTargetAddrWrapper tglobaladdr:$addr)), (CONST_I256 tglobaladdr:$addr)>; diff --git a/llvm/lib/Target/EVM/EVMSingleUseExpression.cpp b/llvm/lib/Target/EVM/EVMSingleUseExpression.cpp index 8337dc7875d1..f31a49b53033 100644 --- a/llvm/lib/Target/EVM/EVMSingleUseExpression.cpp +++ b/llvm/lib/Target/EVM/EVMSingleUseExpression.cpp @@ -311,6 +311,10 @@ static bool isSafeToMove(const MachineOperand *Def, const MachineOperand *Use, if (NextI == Insert) return true; + // Don't move ARGUMENT instructions, as stackification pass relies on this. + if (DefI->getOpcode() == EVM::ARGUMENT) + return false; + // Check for register dependencies. SmallVector MutableRegisters; for (const MachineOperand &MO : DefI->operands()) { diff --git a/llvm/test/CodeGen/EVM/add.ll b/llvm/test/CodeGen/EVM/add.ll index a4a49063d0b9..c20432a02b05 100644 --- a/llvm/test/CodeGen/EVM/add.ll +++ b/llvm/test/CodeGen/EVM/add.ll @@ -5,8 +5,8 @@ target triple = "evm" define i256 @addrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @addrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ADD [[REG:\$[0-9]+]], [[IN1]], [[IN2]] %res = add i256 %rs1, %rs2 diff --git a/llvm/test/CodeGen/EVM/call.ll b/llvm/test/CodeGen/EVM/call.ll index ab6b309e16bc..4e57b307631c 100644 --- a/llvm/test/CodeGen/EVM/call.ll +++ b/llvm/test/CodeGen/EVM/call.ll @@ -8,8 +8,8 @@ declare void @foo2(i256) define i256 @call(i256 %a, i256 %b) nounwind { ; CHECK-LABEL: @call -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ADD [[TMP1:\$[0-9]+]], [[IN1]], [[IN2]] ; CHECK: FCALL 1 [[RES1:\$[0-9]+]], @foo, [[TMP1]] @@ -20,8 +20,8 @@ define i256 @call(i256 %a, i256 %b) nounwind { define void @call2(i256 %a, i256 %b) nounwind { ; CHECK-LABEL: @call2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ADD [[TMP1:\$[0-9]+]], [[IN1]], [[IN2]] ; CHECK: FCALL 0 @foo2, [[TMP1]] diff --git a/llvm/test/CodeGen/EVM/div.ll b/llvm/test/CodeGen/EVM/div.ll index af3296475505..f5965662751e 100644 --- a/llvm/test/CodeGen/EVM/div.ll +++ b/llvm/test/CodeGen/EVM/div.ll @@ -5,8 +5,8 @@ target triple = "evm" define i256 @udivrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @udivrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: DIV [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = udiv i256 %rs1, %rs2 @@ -15,8 +15,8 @@ define i256 @udivrrr(i256 %rs1, i256 %rs2) nounwind { define i256 @sdivrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @sdivrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SDIV [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = sdiv i256 %rs1, %rs2 diff --git a/llvm/test/CodeGen/EVM/fallthrough.mir b/llvm/test/CodeGen/EVM/fallthrough.mir index b5efb4f62911..1e578fb13513 100644 --- a/llvm/test/CodeGen/EVM/fallthrough.mir +++ b/llvm/test/CodeGen/EVM/fallthrough.mir @@ -22,10 +22,10 @@ machineFunctionInfo: body: | bb.0: JUMPDEST_S - PUSH_LABEL %bb.10 { + PUSH_LABEL %bb.10, implicit-def $arguments { JUMPI_S } - PUSH_LABEL %bb.13 { + PUSH_LABEL %bb.13, implicit-def $arguments { JUMP_S } diff --git a/llvm/test/CodeGen/EVM/globals.ll b/llvm/test/CodeGen/EVM/globals.ll index 996282f7b566..df70cab339df 100644 --- a/llvm/test/CodeGen/EVM/globals.ll +++ b/llvm/test/CodeGen/EVM/globals.ll @@ -57,8 +57,8 @@ define i256 @load.fromarray(i256 %i) nounwind { define void @store.toarray(i256 %val, i256 %i) nounwind { ; CHECK-LABEL: store.toarray -; CHECK: ARGUMENT [[IDX:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[VAL:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IDX:\$[0-9]+]], 1 ; CHECK: CONST_I256 [[C:\$[0-9]+]], 5 ; CHECK: SHL [[SHL:\$[0-9]+]], [[IDX]], [[C]] ; CHECK: CONST_I256 [[TMP:\$[0-9]+]], @val.arr diff --git a/llvm/test/CodeGen/EVM/intrinsic.ll b/llvm/test/CodeGen/EVM/intrinsic.ll index d871191f3ab5..8398c33cf70a 100644 --- a/llvm/test/CodeGen/EVM/intrinsic.ll +++ b/llvm/test/CodeGen/EVM/intrinsic.ll @@ -5,8 +5,8 @@ target triple = "evm" define i256 @sdiv(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @sdiv -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SDIV [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.sdiv(i256 %rs1, i256 %rs2) @@ -15,8 +15,8 @@ define i256 @sdiv(i256 %rs1, i256 %rs2) nounwind { define i256 @div(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @div -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: DIV [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.div(i256 %rs1, i256 %rs2) @@ -25,8 +25,8 @@ define i256 @div(i256 %rs1, i256 %rs2) nounwind { define i256 @smod(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @smod -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SMOD [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.smod(i256 %rs1, i256 %rs2) @@ -35,8 +35,8 @@ define i256 @smod(i256 %rs1, i256 %rs2) nounwind { define i256 @mod(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @mod -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: MOD [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.mod(i256 %rs1, i256 %rs2) @@ -45,8 +45,8 @@ define i256 @mod(i256 %rs1, i256 %rs2) nounwind { define i256 @shl(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @shl -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SHL [[TMP:\$[0-9]+]], [[IN2]], [[IN1]] %res = call i256 @llvm.evm.shl(i256 %rs1, i256 %rs2) @@ -55,8 +55,8 @@ define i256 @shl(i256 %rs1, i256 %rs2) nounwind { define i256 @shr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @shr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SHR [[TMP:\$[0-9]+]], [[IN2]], [[IN1]] %res = call i256 @llvm.evm.shr(i256 %rs1, i256 %rs2) @@ -65,8 +65,8 @@ define i256 @shr(i256 %rs1, i256 %rs2) nounwind { define i256 @sar(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @sar -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SAR [[TMP:\$[0-9]+]], [[IN2]], [[IN1]] %res = call i256 @llvm.evm.sar(i256 %rs1, i256 %rs2) @@ -75,9 +75,9 @@ define i256 @sar(i256 %rs1, i256 %rs2) nounwind { define i256 @addmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { ; CHECK-LABEL: @addmod -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 ; CHECK: ADDMOD [[TMP:\$[0-9]+]], [[IN1]], [[IN2]], [[IN3]] %res = call i256 @llvm.evm.addmod(i256 %rs1, i256 %rs2, i256 %rs3) @@ -86,9 +86,9 @@ define i256 @addmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { define i256 @mulmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { ; CHECK-LABEL: @mulmod -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 ; CHECK: MULMOD [[TMP:\$[0-9]+]], [[IN1]], [[IN2]], [[IN3]] %res = call i256 @llvm.evm.mulmod(i256 %rs1, i256 %rs2, i256 %rs3) @@ -97,8 +97,8 @@ define i256 @mulmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { define i256 @exp(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @exp -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: EXP [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.exp(i256 %rs1, i256 %rs2) @@ -107,8 +107,8 @@ define i256 @exp(i256 %rs1, i256 %rs2) nounwind { define i256 @sha3(ptr addrspace(1) %offset, i256 %size) nounwind { ; CHECK-LABEL: @sha3 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SHA3 [[RES1:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.sha3(ptr addrspace(1) %offset, i256 %size) @@ -117,8 +117,8 @@ define i256 @sha3(ptr addrspace(1) %offset, i256 %size) nounwind { define i256 @signextend(i256 %bytesize, i256 %val) nounwind { ; CHECK-LABEL: @signextend -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SIGNEXTEND [[RES1:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.signextend(i256 %bytesize, i256 %val) @@ -127,8 +127,8 @@ define i256 @signextend(i256 %bytesize, i256 %val) nounwind { define i256 @byte(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @byte -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: BYTE [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = call i256 @llvm.evm.byte(i256 %rs1, i256 %rs2) @@ -236,10 +236,10 @@ define i256 @extcodesize(i256 %rs1) nounwind { define void @extcodecopy(i256 %addr, ptr addrspace(1) %dst, ptr addrspace(4) %src, i256 %size) nounwind { ; CHECK-LABEL: @extcodecopy -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 ; CHECK: EXTCODECOPY [[IN1]], [[IN2]], [[IN3]], [[IN4]] call void @llvm.evm.extcodecopy(i256 %addr, ptr addrspace(1) %dst, ptr addrspace(4) %src, i256 %size) @@ -355,8 +355,8 @@ define i256 @blobbasefee() nounwind { define void @log0(ptr addrspace(1) %off, i256 %size) nounwind { ; CHECK-LABEL: @log0 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: LOG0 [[IN1]], [[IN2]] call void @llvm.evm.log0(ptr addrspace(1) %off, i256 %size) @@ -365,9 +365,9 @@ define void @log0(ptr addrspace(1) %off, i256 %size) nounwind { define void @log1(ptr addrspace(1) %off, i256 %size, i256 %t1) nounwind { ; CHECK-LABEL: @log1 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 ; CHECK: LOG1 [[IN1]], [[IN2]], [[IN3]] call void @llvm.evm.log1(ptr addrspace(1) %off, i256 %size, i256 %t1) @@ -376,10 +376,10 @@ define void @log1(ptr addrspace(1) %off, i256 %size, i256 %t1) nounwind { define void @log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) nounwind { ; CHECK-LABEL: @log2 -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 ; CHECK: LOG2 [[IN1]], [[IN2]], [[IN3]], [[IN4]] call void @llvm.evm.log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) @@ -388,11 +388,11 @@ define void @log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) nounwin define void @log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3) nounwind { ; CHECK-LABEL: @log3 -; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 +; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 ; CHECK: LOG3 [[IN1]], [[IN2]], [[IN3]], [[IN4]], [[IN5]] call void @llvm.evm.log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3) @@ -401,12 +401,12 @@ define void @log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t define void @log4(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3, i256 %t4) nounwind { ; CHECK-LABEL: @log4 -; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 -; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 +; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 +; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 ; CHECK: LOG4 [[IN1]], [[IN2]], [[IN3]], [[IN4]], [[IN5]], [[IN6]] call void @llvm.evm.log4(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3, i256 %t4) @@ -415,9 +415,9 @@ define void @log4(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t define i256 @create(i256 %val, ptr addrspace(1) %off, i256 %size) nounwind { ; CHECK-LABEL: @create -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 ; CHECK: CREATE [[RES1:\$[0-9]+]], [[IN1]], [[IN2]], [[IN3]] %ret = call i256 @llvm.evm.create(i256 %val, ptr addrspace(1) %off, i256 %size) @@ -426,13 +426,13 @@ define i256 @create(i256 %val, ptr addrspace(1) %off, i256 %size) nounwind { define i256 @call(i256 %gas, i256 %addr, i256 %val, ptr addrspace(1) %arg_off, i256 %arg_size, ptr addrspace(1) %ret_off, i256 %ret_size) nounwind { ; CHECK-LABEL: @call -; CHECK: ARGUMENT [[IN7:\$[0-9]+]], 6 -; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 -; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 +; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 +; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 +; CHECK: ARGUMENT [[IN7:\$[0-9]+]], 6 ; CHECK: CALL [[RES:\$[0-9]+]], [[IN1]], [[IN2]], [[IN3]], [[IN4]], [[IN5]], [[IN6]], [[IN7]] %ret = call i256 @llvm.evm.call(i256 %gas, i256 %addr, i256 %val, ptr addrspace(1) %arg_off, i256 %arg_size, ptr addrspace(1) %ret_off, i256 %ret_size) @@ -441,12 +441,12 @@ define i256 @call(i256 %gas, i256 %addr, i256 %val, ptr addrspace(1) %arg_off, i define i256 @delegatecall(i256 %gas, i256 %addr, ptr addrspace(1) %arg_off, i256 %arg_size, ptr addrspace(1) %ret_off, i256 %ret_size) nounwind { ; CHECK-LABEL: @delegatecall -; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 -; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 +; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 +; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 ; CHECK: DELEGATECALL [[RES:\$[0-9]+]], [[IN1]], [[IN2]], [[IN3]], [[IN4]], [[IN5]], [[IN6]] %ret = call i256 @llvm.evm.delegatecall(i256 %gas, i256 %addr, ptr addrspace(1) %arg_off, i256 %arg_size, ptr addrspace (1) %ret_off, i256 %ret_size) @@ -455,10 +455,10 @@ define i256 @delegatecall(i256 %gas, i256 %addr, ptr addrspace(1) %arg_off, i256 define i256 @create2(i256 %val, ptr addrspace(1) %off, i256 %size, i256 %salt) nounwind { ; CHECK-LABEL: @create2 -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 ; CHECK: CREATE2 [[RES1:\$[0-9]+]], [[IN1]], [[IN2]], [[IN3]], [[IN4]] %ret = call i256 @llvm.evm.create2(i256 %val, ptr addrspace(1) %off, i256 %size, i256 %salt) @@ -467,12 +467,12 @@ define i256 @create2(i256 %val, ptr addrspace(1) %off, i256 %size, i256 %salt) n define i256 @staticcall(i256 %gas, i256 %addr, ptr addrspace(1) %arg_off, i256 %arg_size, ptr addrspace(1) %ret_off, i256 %ret_size) nounwind { ; CHECK-LABEL: @staticcall -; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 -; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 +; CHECK: ARGUMENT [[IN5:\$[0-9]+]], 4 +; CHECK: ARGUMENT [[IN6:\$[0-9]+]], 5 ; CHECK: STATICCALL [[RES:\$[0-9]+]], [[IN1]], [[IN2]], [[IN3]], [[IN4]], [[IN5]], [[IN6]] %ret = call i256 @llvm.evm.staticcall(i256 %gas, i256 %addr, ptr addrspace(1) %arg_off, i256 %arg_size, ptr addrspace(1) %ret_off, i256 %ret_size) @@ -490,8 +490,8 @@ define void @selfdestruct(i256 %addr) nounwind { define void @return(ptr addrspace(1) %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @return -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: RETURN [[IN1]], [[IN2]] call void @llvm.evm.return(ptr addrspace(1) %rs1, i256 %rs2) @@ -500,8 +500,8 @@ define void @return(ptr addrspace(1) %rs1, i256 %rs2) nounwind { define void @revert(ptr addrspace(1) %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @revert -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: REVERT [[IN1]], [[IN2]] call void @llvm.evm.revert(ptr addrspace(1) %rs1, i256 %rs2) diff --git a/llvm/test/CodeGen/EVM/logical.ll b/llvm/test/CodeGen/EVM/logical.ll index 4839fb4e6ffa..43b75a30a4fe 100644 --- a/llvm/test/CodeGen/EVM/logical.ll +++ b/llvm/test/CodeGen/EVM/logical.ll @@ -5,8 +5,8 @@ target triple = "evm" define i256 @andrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @andrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: AND [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = and i256 %rs1, %rs2 @@ -15,8 +15,8 @@ define i256 @andrrr(i256 %rs1, i256 %rs2) nounwind { define i256 @orrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @orrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: OR [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = or i256 %rs1, %rs2 @@ -25,8 +25,8 @@ define i256 @orrrr(i256 %rs1, i256 %rs2) nounwind { define i256 @xorrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @xorrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: XOR [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = xor i256 %rs1, %rs2 diff --git a/llvm/test/CodeGen/EVM/memory.ll b/llvm/test/CodeGen/EVM/memory.ll index 2cfbf3f906c4..6c60d756ea85 100644 --- a/llvm/test/CodeGen/EVM/memory.ll +++ b/llvm/test/CodeGen/EVM/memory.ll @@ -5,8 +5,8 @@ target triple = "evm" define void @mstore8(ptr addrspace(1) %offset, i256 %val) nounwind { ; CHECK-LABEL: @mstore8 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: MSTORE8 [[IN1]], [[IN2]] call void @llvm.evm.mstore8(ptr addrspace(1) %offset, i256 %val) @@ -15,8 +15,8 @@ define void @mstore8(ptr addrspace(1) %offset, i256 %val) nounwind { define void @mstore(ptr addrspace(1) %offset, i256 %val) nounwind { ; CHECK-LABEL: @mstore -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: MSTORE [[IN1]], [[IN2]] store i256 %val, ptr addrspace(1) %offset, align 32 diff --git a/llvm/test/CodeGen/EVM/mod.ll b/llvm/test/CodeGen/EVM/mod.ll index c0b4e9976952..8ab6f36ba61a 100644 --- a/llvm/test/CodeGen/EVM/mod.ll +++ b/llvm/test/CodeGen/EVM/mod.ll @@ -5,8 +5,8 @@ target triple = "evm" define i256 @umodrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @umodrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: MOD [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = urem i256 %rs1, %rs2 @@ -15,8 +15,8 @@ define i256 @umodrrr(i256 %rs1, i256 %rs2) nounwind { define i256 @smodrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @smodrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SMOD [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = srem i256 %rs1, %rs2 diff --git a/llvm/test/CodeGen/EVM/mul.ll b/llvm/test/CodeGen/EVM/mul.ll index 9c286d256707..e2b89fc873bd 100644 --- a/llvm/test/CodeGen/EVM/mul.ll +++ b/llvm/test/CodeGen/EVM/mul.ll @@ -5,8 +5,8 @@ target triple = "evm" define i256 @mulrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @mulrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: MUL [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = mul i256 %rs1, %rs2 diff --git a/llvm/test/CodeGen/EVM/select.ll b/llvm/test/CodeGen/EVM/select.ll index 084ec33481a4..d5cb9af50ba7 100644 --- a/llvm/test/CodeGen/EVM/select.ll +++ b/llvm/test/CodeGen/EVM/select.ll @@ -5,10 +5,10 @@ target triple = "evm" define i256 @select(i256 %v1, i256 %v2, i256 %v3, i256 %v4) { ; CHECK-LABEL: @select -; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 -; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 +; CHECK: ARGUMENT [[IN3:\$[0-9]+]], 2 +; CHECK: ARGUMENT [[IN4:\$[0-9]+]], 3 ; CHECK: EQ [[TMP1:\$[0-9]+]], [[IN3]], [[IN4]] ; CHECK: ISZERO [[COND:\$[0-9]+]], [[TMP1]] ; CHECK: JUMPI @.BB0_2, [[COND]] diff --git a/llvm/test/CodeGen/EVM/storage.ll b/llvm/test/CodeGen/EVM/storage.ll index 24336cd0164f..e66d6adfa95d 100644 --- a/llvm/test/CodeGen/EVM/storage.ll +++ b/llvm/test/CodeGen/EVM/storage.ll @@ -5,8 +5,8 @@ target triple = "evm" define void @sstore(ptr addrspace(5) %key, i256 %val) nounwind { ; CHECK-LABEL: @sstore -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SSTORE [[IN1]], [[IN2]] store i256 %val, ptr addrspace(5) %key, align 32 diff --git a/llvm/test/CodeGen/EVM/sub.ll b/llvm/test/CodeGen/EVM/sub.ll index 6b88285ef48d..ef913e9a9434 100644 --- a/llvm/test/CodeGen/EVM/sub.ll +++ b/llvm/test/CodeGen/EVM/sub.ll @@ -5,8 +5,8 @@ target triple = "evm" define i256 @subrrr(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: @subrrr -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: SUB [[TMP:\$[0-9]+]], [[IN1]], [[IN2]] %res = sub i256 %rs1, %rs2 diff --git a/llvm/test/CodeGen/EVM/tstorage.ll b/llvm/test/CodeGen/EVM/tstorage.ll index c100d67d9992..8bb70f90e0e0 100644 --- a/llvm/test/CodeGen/EVM/tstorage.ll +++ b/llvm/test/CodeGen/EVM/tstorage.ll @@ -5,8 +5,8 @@ target triple = "evm" define void @tstore(ptr addrspace(6) %key, i256 %val) nounwind { ; CHECK-LABEL: @tstore -; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: ARGUMENT [[IN1:\$[0-9]+]], 0 +; CHECK: ARGUMENT [[IN2:\$[0-9]+]], 1 ; CHECK: TSTORE [[IN1]], [[IN2]] store i256 %val, ptr addrspace(6) %key, align 32 diff --git a/llvm/test/CodeGen/Generic/2008-08-07-PtrToInt-SmallerInt.ll b/llvm/test/CodeGen/Generic/2008-08-07-PtrToInt-SmallerInt.ll index c3fb54e3dd53..b3196227c86c 100644 --- a/llvm/test/CodeGen/Generic/2008-08-07-PtrToInt-SmallerInt.ll +++ b/llvm/test/CodeGen/Generic/2008-08-07-PtrToInt-SmallerInt.ll @@ -1,4 +1,5 @@ -; XFAIL: target=eravm{{.*}}, target=evm{{.*}} +; XFAIL: target=eravm{{.*}} +; UNSUPPORTED: target=evm{{.*}} ; TODO: CPR-920 support operators ; RUN: llc < %s ; PR2603 From 6b6acb796c3a5c50aee889e393ed0831ad40f0a9 Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 15 Nov 2024 18:28:45 +0100 Subject: [PATCH 03/42] [EVM] Add split critical edges pass --- llvm/lib/Target/EVM/CMakeLists.txt | 1 + llvm/lib/Target/EVM/EVM.h | 2 + llvm/lib/Target/EVM/EVMSplitCriticalEdges.cpp | 100 ++++++++++++++++++ llvm/lib/Target/EVM/EVMTargetMachine.cpp | 4 +- 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 llvm/lib/Target/EVM/EVMSplitCriticalEdges.cpp diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index ceed4bf575e0..048a2c4fd221 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -33,6 +33,7 @@ add_llvm_target(EVMCodeGen EVMRegColoring.cpp EVMRegisterInfo.cpp EVMSingleUseExpression.cpp + EVMSplitCriticalEdges.cpp EVMStackify.cpp EVMSubtarget.cpp EVMTargetMachine.cpp diff --git a/llvm/lib/Target/EVM/EVM.h b/llvm/lib/Target/EVM/EVM.h index 2ff8488a5089..ce7c33874994 100644 --- a/llvm/lib/Target/EVM/EVM.h +++ b/llvm/lib/Target/EVM/EVM.h @@ -50,6 +50,7 @@ ModulePass *createEVMLinkRuntimePass(); FunctionPass *createEVMOptimizeLiveIntervals(); FunctionPass *createEVMRegColoring(); FunctionPass *createEVMSingleUseExpression(); +FunctionPass *createEVMSplitCriticalEdges(); FunctionPass *createEVMStackify(); // PassRegistry initialization declarations. @@ -61,6 +62,7 @@ void initializeEVMLinkRuntimePass(PassRegistry &); void initializeEVMOptimizeLiveIntervalsPass(PassRegistry &); void initializeEVMRegColoringPass(PassRegistry &); void initializeEVMSingleUseExpressionPass(PassRegistry &); +void initializeEVMSplitCriticalEdgesPass(PassRegistry &); void initializeEVMStackifyPass(PassRegistry &); struct EVMLinkRuntimePass : PassInfoMixin { diff --git a/llvm/lib/Target/EVM/EVMSplitCriticalEdges.cpp b/llvm/lib/Target/EVM/EVMSplitCriticalEdges.cpp new file mode 100644 index 000000000000..f96d0deddca7 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMSplitCriticalEdges.cpp @@ -0,0 +1,100 @@ +//===----- EVMSplitCriticalEdges.cpp - Split Critical Edges ----*- C++ -*--===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file performs splitting of CFG critical edges. +// +//===----------------------------------------------------------------------===// + +#include "EVM.h" +#include "EVMMachineFunctionInfo.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/CodeGen/MachineLoopInfo.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/InitializePasses.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +#define DEBUG_TYPE "evm-split-critical-edges" + +namespace { +class EVMSplitCriticalEdges final : public MachineFunctionPass { +public: + static char ID; + + EVMSplitCriticalEdges() : MachineFunctionPass(ID) {} + + StringRef getPassName() const override { return "EVM split critical edges"; } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + MachineFunctionPass::getAnalysisUsage(AU); + } + + bool runOnMachineFunction(MachineFunction &MF) override; + +private: + MachineFunction *MF = nullptr; + + bool splitCriticalEdges(); +}; +} // end anonymous namespace + +char EVMSplitCriticalEdges::ID = 0; + +INITIALIZE_PASS_BEGIN(EVMSplitCriticalEdges, DEBUG_TYPE, "Split critical edges", + false, false) +INITIALIZE_PASS_DEPENDENCY(MachineLoopInfo) +INITIALIZE_PASS_END(EVMSplitCriticalEdges, DEBUG_TYPE, "Split critical edges", + false, false) + +FunctionPass *llvm::createEVMSplitCriticalEdges() { + return new EVMSplitCriticalEdges(); +} + +bool EVMSplitCriticalEdges::splitCriticalEdges() { + SetVector> ToSplit; + for (MachineBasicBlock &MBB : *MF) { + if (MBB.pred_size() > 1) { + for (MachineBasicBlock *Pred : MBB.predecessors()) { + if (Pred->succ_size() > 1) + ToSplit.insert(std::make_pair(Pred, &MBB)); + } + } + } + + bool Changed = false; + for (const auto &Pair : ToSplit) { + auto NewSucc = Pair.first->SplitCriticalEdge(Pair.second, *this); + if (NewSucc != nullptr) { + Pair.first->updateTerminator(NewSucc); + NewSucc->updateTerminator(Pair.second); + LLVM_DEBUG(dbgs() << " *** Splitting critical edge: " + << printMBBReference(*Pair.first) << " -- " + << printMBBReference(*NewSucc) << " -- " + << printMBBReference(*Pair.second) << '\n'); + Changed = true; + } else { + llvm_unreachable("Cannot break critical edge"); + } + } + return Changed; +} + +bool EVMSplitCriticalEdges::runOnMachineFunction(MachineFunction &Mf) { + MF = &Mf; + LLVM_DEBUG({ + dbgs() << "********** Splitting critical edges **********\n" + << "********** Function: " << Mf.getName() << '\n'; + }); + + bool Changed = splitCriticalEdges(); + return Changed; +} diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.cpp b/llvm/lib/Target/EVM/EVMTargetMachine.cpp index 8e28d24346b6..76111b648a8a 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.cpp +++ b/llvm/lib/Target/EVM/EVMTargetMachine.cpp @@ -51,6 +51,7 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeEVMTarget() { initializeEVMOptimizeLiveIntervalsPass(PR); initializeEVMRegColoringPass(PR); initializeEVMSingleUseExpressionPass(PR); + initializeEVMSplitCriticalEdgesPass(PR); initializeEVMStackifyPass(PR); } @@ -75,7 +76,6 @@ EVMTargetMachine::EVMTargetMachine(const Target &T, const Triple &TT, getEffectiveCodeModel(CM, CodeModel::Small), OL), TLOF(std::make_unique()), Subtarget(TT, std::string(CPU), std::string(FS), *this) { - setRequiresStructuredCFG(true); initAsmInfo(); } @@ -200,6 +200,8 @@ void EVMPassConfig::addPreEmitPass() { // FIXME: enable all the passes below, but the Stackify with EVMKeepRegisters. if (!EVMKeepRegisters) { + addPass(createEVMSplitCriticalEdges()); + addPass(&MachineBlockPlacementID); addPass(createEVMOptimizeLiveIntervals()); addPass(createEVMSingleUseExpression()); // Run the register coloring pass to reduce the total number of registers. From 09bb7600ac603dd89ca5edfaf45a1a85c8dafebf Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 15 Nov 2024 14:41:26 +0100 Subject: [PATCH 04/42] [EVM] Re-enable inlining --- llvm/lib/Target/EVM/EVMLinkRuntime.cpp | 6 ------ llvm/lib/Target/EVM/EVMTargetTransformInfo.h | 5 +---- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMLinkRuntime.cpp b/llvm/lib/Target/EVM/EVMLinkRuntime.cpp index a758131d9c7e..69d4c4b62709 100644 --- a/llvm/lib/Target/EVM/EVMLinkRuntime.cpp +++ b/llvm/lib/Target/EVM/EVMLinkRuntime.cpp @@ -81,12 +81,6 @@ static bool EVMLinkRuntimeImpl(Module &M, const char *ModuleToLink) { exit(1); } - for (auto &F : M.functions()) { - if (!F.isDeclaration()) { - F.addFnAttr(Attribute::NoInline); - } - } - bool LinkErr = false; LinkErr = L.linkInModule( std::move(RTM), Flags, [](Module &M, const StringSet<> &GVS) { diff --git a/llvm/lib/Target/EVM/EVMTargetTransformInfo.h b/llvm/lib/Target/EVM/EVMTargetTransformInfo.h index a0f0f62168b1..0f1b41d429f6 100644 --- a/llvm/lib/Target/EVM/EVMTargetTransformInfo.h +++ b/llvm/lib/Target/EVM/EVMTargetTransformInfo.h @@ -35,7 +35,7 @@ class EVMTTIImpl final : public BasicTTIImplBase { const EVMTargetLowering *getTLI() const { return TLI; } public: - enum SyncVMRegisterClass { Vector /* Unsupported */, GPR }; + enum EVMRegisterClass { Vector /* Unsupported */, GPR }; EVMTTIImpl(const EVMTargetMachine *TM, const Function &F) : BaseT(TM, F.getParent()->getDataLayout()), ST(TM->getSubtargetImpl(F)), @@ -100,9 +100,6 @@ class EVMTTIImpl final : public BasicTTIImplBase { OpsOut.push_back(Type::getIntNTy(Context, RemainingBytes * 8)); } - // TODO: The value is copied from SyncVM, needs to be checked. - unsigned getInliningThresholdMultiplier() const { return 11; } - void getUnrollingPreferences(Loop *L, ScalarEvolution &SE, TTI::UnrollingPreferences &UP, OptimizationRemarkEmitter *ORE); From 135a3ace19540b2a69689ea4f113deeed4dd5ddf Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 15 Nov 2024 16:36:40 +0100 Subject: [PATCH 05/42] [EVM] Add backward propagation (BP) stackification Original idea and some code parts were taken from the Ethereum`s compiler (solc) stackification algorithm. --- llvm/lib/Target/EVM/CMakeLists.txt | 6 + llvm/lib/Target/EVM/EVM.h | 2 + llvm/lib/Target/EVM/EVMAssembly.cpp | 283 ++++++ llvm/lib/Target/EVM/EVMAssembly.h | 100 +++ .../EVMBackwardPropagationStackification.cpp | 101 +++ llvm/lib/Target/EVM/EVMControlFlowGraph.h | 285 ++++++ .../Target/EVM/EVMControlFlowGraphBuilder.cpp | 435 +++++++++ .../Target/EVM/EVMControlFlowGraphBuilder.h | 56 ++ llvm/lib/Target/EVM/EVMHelperUtilities.h | 182 ++++ .../Target/EVM/EVMOptimizedCodeTransform.cpp | 456 ++++++++++ .../Target/EVM/EVMOptimizedCodeTransform.h | 90 ++ llvm/lib/Target/EVM/EVMStackDebug.cpp | 311 +++++++ llvm/lib/Target/EVM/EVMStackDebug.h | 69 ++ .../Target/EVM/EVMStackLayoutGenerator.cpp | 843 ++++++++++++++++++ llvm/lib/Target/EVM/EVMStackLayoutGenerator.h | 130 +++ llvm/lib/Target/EVM/EVMStackShuffler.h | 555 ++++++++++++ llvm/lib/Target/EVM/EVMTargetMachine.cpp | 15 +- .../Target/EVM/TargetInfo/EVMTargetInfo.cpp | 78 ++ .../lib/Target/EVM/TargetInfo/EVMTargetInfo.h | 2 + llvm/test/CodeGen/EVM/stack-ops-commutable.ll | 258 ++++++ llvm/test/CodeGen/EVM/stack-ops.ll | 340 +++++++ .../CodeGen/EVM/unused_function_arguments.ll | 52 ++ .../Generic/2007-01-15-LoadSelectCycle.ll | 1 + .../Generic/2009-04-28-i128-cmp-crash.ll | 1 + .../Generic/2011-07-07-ScheduleDAGCrash.ll | 1 + .../CodeGen/Generic/2012-06-08-APIntCrash.ll | 1 + llvm/test/CodeGen/Generic/i128-addsub.ll | 1 + ...e-return-values-cross-block-with-invoke.ll | 1 + llvm/test/CodeGen/Generic/undef-phi.ll | 1 + .../2007-10-19-InlineAsmDirectives.ll | 1 + .../Inputs/evm-basic.ll.expected | 13 +- llvm/unittests/Target/EVM/CMakeLists.txt | 23 + llvm/unittests/Target/EVM/StackShuffler.cpp | 196 ++++ 33 files changed, 4877 insertions(+), 12 deletions(-) create mode 100644 llvm/lib/Target/EVM/EVMAssembly.cpp create mode 100644 llvm/lib/Target/EVM/EVMAssembly.h create mode 100644 llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp create mode 100644 llvm/lib/Target/EVM/EVMControlFlowGraph.h create mode 100644 llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp create mode 100644 llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h create mode 100644 llvm/lib/Target/EVM/EVMHelperUtilities.h create mode 100644 llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp create mode 100644 llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h create mode 100644 llvm/lib/Target/EVM/EVMStackDebug.cpp create mode 100644 llvm/lib/Target/EVM/EVMStackDebug.h create mode 100644 llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp create mode 100644 llvm/lib/Target/EVM/EVMStackLayoutGenerator.h create mode 100644 llvm/lib/Target/EVM/EVMStackShuffler.h create mode 100644 llvm/test/CodeGen/EVM/stack-ops-commutable.ll create mode 100644 llvm/test/CodeGen/EVM/stack-ops.ll create mode 100644 llvm/test/CodeGen/EVM/unused_function_arguments.ll create mode 100644 llvm/unittests/Target/EVM/CMakeLists.txt create mode 100644 llvm/unittests/Target/EVM/StackShuffler.cpp diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index 048a2c4fd221..5f9d00e568c4 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -20,7 +20,10 @@ add_llvm_target(EVMCodeGen EVMAllocaHoisting.cpp EVMArgumentMove.cpp EVMAsmPrinter.cpp + EVMAssembly.cpp + EVMBackwardPropagationStackification.cpp EVMCodegenPrepare.cpp + EVMControlFlowGraphBuilder.cpp EVMFrameLowering.cpp EVMISelDAGToDAG.cpp EVMISelLowering.cpp @@ -30,10 +33,13 @@ add_llvm_target(EVMCodeGen EVMMachineFunctionInfo.cpp EVMMCInstLower.cpp EVMOptimizeLiveIntervals.cpp + EVMOptimizedCodeTransform.cpp EVMRegColoring.cpp EVMRegisterInfo.cpp EVMSingleUseExpression.cpp EVMSplitCriticalEdges.cpp + EVMStackDebug.cpp + EVMStackLayoutGenerator.cpp EVMStackify.cpp EVMSubtarget.cpp EVMTargetMachine.cpp diff --git a/llvm/lib/Target/EVM/EVM.h b/llvm/lib/Target/EVM/EVM.h index ce7c33874994..15bed82879b1 100644 --- a/llvm/lib/Target/EVM/EVM.h +++ b/llvm/lib/Target/EVM/EVM.h @@ -52,6 +52,7 @@ FunctionPass *createEVMRegColoring(); FunctionPass *createEVMSingleUseExpression(); FunctionPass *createEVMSplitCriticalEdges(); FunctionPass *createEVMStackify(); +FunctionPass *createEVMBPStackification(); // PassRegistry initialization declarations. void initializeEVMCodegenPreparePass(PassRegistry &); @@ -64,6 +65,7 @@ void initializeEVMRegColoringPass(PassRegistry &); void initializeEVMSingleUseExpressionPass(PassRegistry &); void initializeEVMSplitCriticalEdgesPass(PassRegistry &); void initializeEVMStackifyPass(PassRegistry &); +void initializeEVMBPStackificationPass(PassRegistry &); struct EVMLinkRuntimePass : PassInfoMixin { EVMLinkRuntimePass() = default; diff --git a/llvm/lib/Target/EVM/EVMAssembly.cpp b/llvm/lib/Target/EVM/EVMAssembly.cpp new file mode 100644 index 000000000000..21a7020e284c --- /dev/null +++ b/llvm/lib/Target/EVM/EVMAssembly.cpp @@ -0,0 +1,283 @@ +//===----------- EVMAssembly.cpp - EVM Assembly generator -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file creates Machine IR in stackified form. It provides different +// callbacks when the EVMOptimizedCodeTransform needs to emit operation, +// stack manipulation instruction, and so on. It the end, it walks over MIR +// instructions removing register operands. +// +//===----------------------------------------------------------------------===// + +#include "EVMAssembly.h" +#include "EVM.h" +#include "EVMMachineFunctionInfo.h" +#include "EVMSubtarget.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "TargetInfo/EVMTargetInfo.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineInstrBuilder.h" +#include "llvm/MC/MCContext.h" + +using namespace llvm; + +#define DEBUG_TYPE "evm-assembly" + +#ifndef NDEBUG +void EVMAssembly::dumpInst(const MachineInstr *MI) const { + LLVM_DEBUG(dbgs() << "Adding: " << *MI << "stack height: " << StackHeight + << "\n"); +} +#endif // NDEBUG + +int EVMAssembly::getStackHeight() const { return StackHeight; } + +void EVMAssembly::setStackHeight(int Height) { + StackHeight = Height; + LLVM_DEBUG(dbgs() << "Set stack height: " << StackHeight << "\n"); +} + +void EVMAssembly::setCurrentLocation(MachineBasicBlock *MBB) { + CurMBB = MBB; + CurMIIt = MBB->begin(); + LLVM_DEBUG(dbgs() << "Setting current location to: " << MBB->getNumber() + << "." << MBB->getName() << "\n"); +} + +void EVMAssembly::appendInstruction(MachineInstr *MI) { +#ifndef NDEBUG + unsigned Opc = MI->getOpcode(); + assert(Opc != EVM::JUMP && Opc != EVM::JUMPI && Opc != EVM::ARGUMENT && + Opc != EVM::RET && Opc != EVM::CONST_I256 && Opc != EVM::COPY_I256 && + Opc != EVM::FCALL); +#endif // NDEBUG + + [[maybe_unused]] auto Ret = AssemblyInstrs.insert(MI); + assert(Ret.second); + int StackAdj = (2 * static_cast(MI->getNumExplicitDefs())) - + static_cast(MI->getNumExplicitOperands()); + StackHeight += StackAdj; + LLVM_DEBUG(dumpInst(MI)); + CurMIIt = std::next(MIIter(MI)); +} + +void EVMAssembly::appendSWAPInstruction(unsigned Depth) { + unsigned Opc = EVM::getSWAPOpcode(Depth); + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opc)); + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendDUPInstruction(unsigned Depth) { + unsigned Opc = EVM::getDUPOpcode(Depth); + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opc)); + StackHeight += 1; + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendPOPInstruction() { + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::POP)); + assert(StackHeight > 0); + StackHeight -= 1; + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendConstant(const APInt &Val) { + unsigned Opc = EVM::getPUSHOpcode(Val); + MachineInstrBuilder Builder = + BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opc)); + if (Opc != EVM::PUSH0) { + LLVMContext &Ctx = MF->getFunction().getContext(); + Builder.addCImm(ConstantInt::get(Ctx, Val)); + } + StackHeight += 1; + CurMIIt = Builder; + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendSymbol(MCSymbol *Symbol, unsigned Opcode) { + // This is codegen-only instruction, that will be converted into PUSH4. + CurMIIt = + BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opcode)).addSym(Symbol); + StackHeight += 1; + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendConstant(uint64_t Val) { + appendConstant(APInt(256, Val)); +} + +void EVMAssembly::appendLabel() { + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::JUMPDEST)); + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendLabelReference(MCSymbol *Label) { + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::PUSH_LABEL)) + .addSym(Label); + StackHeight += 1; + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendMBBReference(MachineBasicBlock *MBB) { + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::PUSH_LABEL)) + .addMBB(MBB); + StackHeight += 1; + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +MCSymbol *EVMAssembly::createFuncRetSymbol() { + return MF->getContext().createTempSymbol("FUNC_RET", true); +} + +void EVMAssembly::appendFuncCall(const MachineInstr *MI, + const llvm::GlobalValue *Func, int StackAdj, + MCSymbol *RetSym) { + // Push the function label + assert(CurMBB == MI->getParent()); + CurMIIt = + BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PUSH_LABEL)) + .addGlobalAddress(Func); + // PUSH_LABEL technically increases the stack height on 1, but we don't + // increase it explicitly here, as the label will be consumed by the following + // JUMP. + AssemblyInstrs.insert(&*CurMIIt); + StackHeight += StackAdj; + LLVM_DEBUG(dumpInst(&*CurMIIt)); + + CurMIIt = std::next(CurMIIt); + // Create jump to the callee. Note, we don't add the 'target' operand to JUMP. + // This should be fine, unless we run MachineVerifier after this step + CurMIIt = BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::JUMP)); + if (RetSym) + CurMIIt->setPostInstrSymbol(*MF, RetSym); + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendJump(int StackAdj) { + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::JUMP)); + StackHeight += StackAdj; + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); +} + +void EVMAssembly::appendUncondJump(MachineInstr *MI, + MachineBasicBlock *Target) { + assert(MI->getOpcode() == EVM::JUMP); + appendMBBReference(Target); + [[maybe_unused]] auto It = AssemblyInstrs.insert(MI); + assert(It.second && StackHeight > 0); + StackHeight -= 1; + LLVM_DEBUG(dumpInst(MI)); + CurMIIt = std::next(MIIter(MI)); +} + +void EVMAssembly::appendCondJump(MachineInstr *MI, MachineBasicBlock *Target) { + assert(MI->getOpcode() == EVM::JUMPI); + appendMBBReference(Target); + [[maybe_unused]] auto It = AssemblyInstrs.insert(MI); + assert(It.second && StackHeight > 1); + StackHeight -= 2; + LLVM_DEBUG(dumpInst(MI)); + CurMIIt = std::next(MIIter(MI)); +} + +// Remove all registers operands of the \p MI and repaces the opcode with +// the stack variant variant. +void EVMAssembly::stackifyInstruction(MachineInstr *MI) { + if (MI->isDebugInstr() || MI->isLabel() || MI->isInlineAsm()) + return; + + unsigned RegOpcode = MI->getOpcode(); + if (RegOpcode == EVM::PUSH_LABEL) + return; + + // Remove register operands. + for (unsigned I = MI->getNumOperands(); I > 0; --I) { + auto &MO = MI->getOperand(I - 1); + if (MO.isReg()) { + MI->removeOperand(I - 1); + } + } + + // Transform 'register' instruction to the 'stack' one. + unsigned StackOpcode = EVM::getStackOpcode(RegOpcode); + MI->setDesc(TII->get(StackOpcode)); +} + +void EVMAssembly::finalize() { + // Collect and erase instructions that are not required in + // a stackified code. These are auxiliary codegn-only instructions. + SmallVector ToRemove; + for (MachineBasicBlock &MBB : *MF) { + for (MachineInstr &MI : MBB) { + if (!AssemblyInstrs.count(&MI) && MI.getOpcode() != EVM::JUMP && + MI.getOpcode() != EVM::JUMPI) + ToRemove.emplace_back(&MI); + } + } + + for (MachineInstr *MI : ToRemove) + MI->eraseFromParent(); + + // Remove register operands and replace instruction opcode with 'stack' one. + for (MachineBasicBlock &MBB : *MF) + for (MachineInstr &MI : MBB) + stackifyInstruction(&MI); + + auto *MFI = MF->getInfo(); + MFI->setIsStackified(); + + // In a stackified code register liveness has no meaning. + MachineRegisterInfo &MRI = MF->getRegInfo(); + MRI.invalidateLiveness(); + + // In EVM architecture jump target is set up using one of PUSH* instructions + // that come right before the jump instruction. + // For example: + + // PUSH_LABEL %bb.10 + // JUMPI_S + // PUSH_LABEL %bb.9 + // JUMP_S + // + // The problem here is that such MIR is not valid. There should not be + // non-terminator (PUSH) instructions between terminator (JUMP) ones. + // To overcome this issue, we bundle adjacent instructions + // together and unbundle them in the AsmPrinter. + for (MachineBasicBlock &MBB : *MF) { + MachineBasicBlock::instr_iterator I = MBB.instr_begin(), + E = MBB.instr_end(); + // Skip the first instruction, as it's not interested anyway. + ++I; + for (; I != E; ++I) { + if (I->isBranch()) { + auto P = std::prev(I); + if (P->getOpcode() == EVM::PUSH_LABEL) + I->bundleWithPred(); + } + } + } +} diff --git a/llvm/lib/Target/EVM/EVMAssembly.h b/llvm/lib/Target/EVM/EVMAssembly.h new file mode 100644 index 000000000000..8f40acfebe9e --- /dev/null +++ b/llvm/lib/Target/EVM/EVMAssembly.h @@ -0,0 +1,100 @@ +//===------------- EVMAssembly.h - EVM Assembly generator -------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file creates Machine IR in stackified form. It provides different +// callbacks when the EVMOptimizedCodeTransform needs to emit operation, +// stack manipulation instruction, and so on. It the end, it walks over MIR +// instructions removing register operands. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H +#define LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H + +#include "EVM.h" + +#include "EVMSubtarget.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/MachineFunction.h" + +namespace llvm { + +class MachineInstr; +class MCSymbol; + +class EVMAssembly { +private: + using MIIter = MachineBasicBlock::iterator; + + MachineFunction *MF; + const EVMInstrInfo *TII; + + int StackHeight = 0; + MIIter CurMIIt; + MachineBasicBlock *CurMBB; + DenseSet AssemblyInstrs; + +public: + EVMAssembly(MachineFunction *MF, const EVMInstrInfo *TII) + : MF(MF), TII(TII) {} + + // Retrieve the current height of the stack. + // This does not have to be zero at the MF beginning because of + // possible arguments. + int getStackHeight() const; + + void setStackHeight(int Height); + + void setCurrentLocation(MachineBasicBlock *MBB); + + void appendInstruction(MachineInstr *MI); + + void appendSWAPInstruction(unsigned Depth); + + void appendDUPInstruction(unsigned Depth); + + void appendPOPInstruction(); + + void appendConstant(const APInt &Val); + + void appendSymbol(MCSymbol *Symbol, unsigned Opcode); + + void appendConstant(uint64_t Val); + + void appendLabel(); + + void appendFuncCall(const MachineInstr *MI, const llvm::GlobalValue *Func, + int stackAdj, MCSymbol *RetSym = nullptr); + + void appendJump(int stackAdj); + + void appendCondJump(MachineInstr *MI, MachineBasicBlock *Target); + + void appendUncondJump(MachineInstr *MI, MachineBasicBlock *Target); + + void appendLabelReference(MCSymbol *Label); + + void appendMBBReference(MachineBasicBlock *MBB); + + MCSymbol *createFuncRetSymbol(); + + // Erases unused codegen-only instructions and removes register operands + // of the remaining ones. + void finalize(); + +private: + void stackifyInstruction(MachineInstr *MI); + +#ifndef NDEBUG + void dumpInst(const MachineInstr *MI) const; +#endif // NDEBUG +}; + +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H diff --git a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp new file mode 100644 index 000000000000..7a571204c899 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp @@ -0,0 +1,101 @@ +//===----- EVMBPStackification.cpp - BP stackification ---------*- C++ -*--===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements backward propagation (BP) stackification. +// Original idea was taken from the Ethereum's compiler (solc) stackification +// algorithm. +// The algorithm is broken into following components: +// - CFG (Control Flow Graph) and CFG builder. Stackification CFG has similar +// structure to LLVM CFG one, but employs wider notion of instruction. +// - Stack layout generator. Contains information about the stack layout at +// entry and exit of each CFG::BasicBlock. It also contains input/output +// stack layout for each operation. +// - Code transformation into stakified form. This component uses both CFG +// and the stack layout information to get stackified LLVM MIR. +// - Stack shuffler. Finds optimal (locally) transformation between two stack +// layouts using three primitives: POP, PUSHn, DUPn. The stack shuffler +// is used by the components above. +// +//===----------------------------------------------------------------------===// + +#include "EVM.h" +#include "EVMAssembly.h" +#include "EVMOptimizedCodeTransform.h" +#include "EVMSubtarget.h" +#include "llvm/ADT/SetVector.h" +#include "llvm/CodeGen/LiveIntervals.h" +#include "llvm/CodeGen/MachineLoopInfo.h" +#include "llvm/CodeGen/Passes.h" +#include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/InitializePasses.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +#define DEBUG_TYPE "evm-ethereum-stackify" + +namespace { +class EVMBPStackification final : public MachineFunctionPass { +public: + static char ID; // Pass identification, replacement for typeid + + EVMBPStackification() : MachineFunctionPass(ID) {} + +private: + StringRef getPassName() const override { + return "EVM Ethereum stackification"; + } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.addRequired(); + AU.addRequired(); + MachineFunctionPass::getAnalysisUsage(AU); + } + + bool runOnMachineFunction(MachineFunction &MF) override; + + MachineFunctionProperties getRequiredProperties() const override { + return MachineFunctionProperties().set( + MachineFunctionProperties::Property::TracksLiveness); + } +}; +} // end anonymous namespace + +char EVMBPStackification::ID = 0; + +INITIALIZE_PASS_BEGIN(EVMBPStackification, DEBUG_TYPE, + "Backward propagation stackification", false, false) +INITIALIZE_PASS_DEPENDENCY(MachineLoopInfo) +INITIALIZE_PASS_END(EVMBPStackification, DEBUG_TYPE, + "Backward propagation stackification", false, false) + +FunctionPass *llvm::createEVMBPStackification() { + return new EVMBPStackification(); +} + +bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { + LLVM_DEBUG({ + dbgs() << "********** Backward propagation stackification **********\n" + << "********** Function: " << MF.getName() << '\n'; + }); + + MachineRegisterInfo &MRI = MF.getRegInfo(); + const EVMInstrInfo *TII = MF.getSubtarget().getInstrInfo(); + LiveIntervals &LIS = getAnalysis(); + MachineLoopInfo *MLI = &getAnalysis(); + + // We don't preserve SSA form. + MRI.leaveSSA(); + + assert(MRI.tracksLiveness() && "Stackification expects liveness"); + + EVMAssembly Assembly(&MF, TII); + EVMOptimizedCodeTransform::run(Assembly, MF, LIS, MLI); + return true; +} diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraph.h b/llvm/lib/Target/EVM/EVMControlFlowGraph.h new file mode 100644 index 000000000000..904abf4f05c7 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMControlFlowGraph.h @@ -0,0 +1,285 @@ +//===----- EVMControlFlowGraph.h - CFG for BP stackification ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines Control Flow Graph used for the backward propagation +// stackification algorithm. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H +#define LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H + +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/CodeGen/MachineInstr.h" +#include "llvm/CodeGen/Register.h" +#include "llvm/MC/MCSymbol.h" + +#include +#include +#include + +namespace llvm { + +class MachineFunction; +class MachineBasicBlock; +class MachineInstr; +/// The following structs describe different kinds of stack slots. +/// Each stack slot is equality- and less-than-comparable and +/// specifies an attribute 'canBeFreelyGenerated' that is true, +/// if a slot of this kind always has a known value at compile time and +/// therefore can safely be removed from the stack at any time and then +/// regenerated later. + +/// The label pushed as return label before a function call, i.e. the label the +/// call is supposed to return to. +struct FunctionCallReturnLabelSlot { + const MachineInstr *Call = nullptr; + static constexpr bool canBeFreelyGenerated = true; + + bool operator==(FunctionCallReturnLabelSlot const &Rhs) const { + return Call == Rhs.Call; + } + + bool operator<(FunctionCallReturnLabelSlot const &Rhs) const { + return Call < Rhs.Call; + } +}; + +/// The return jump target of a function while generating the code of the +/// function body. I.e. the caller of a function pushes a +/// 'FunctionCallReturnLabelSlot' (see above) before jumping to the function +/// and this very slot is viewed as 'FunctionReturnLabelSlot' inside the +/// function body and jumped to when returning from the function. +struct FunctionReturnLabelSlot { + const MachineFunction *MF = nullptr; + static constexpr bool canBeFreelyGenerated = false; + + bool operator==(FunctionReturnLabelSlot const &Rhs) const { + // There can never be return label slots of different functions on stack + // simultaneously. + assert(MF == Rhs.MF); + return true; + } + + bool operator<(FunctionReturnLabelSlot const &Rhs) const { + // There can never be return label slots of different functions on stack + // simultaneously. + assert(MF == Rhs.MF); + return false; + } +}; + +/// A slot containing the current value of a particular variable. +struct VariableSlot { + Register VirtualReg; + static constexpr bool canBeFreelyGenerated = false; + + bool operator==(VariableSlot const &Rhs) const { + return VirtualReg == Rhs.VirtualReg; + } + + bool operator<(VariableSlot const &Rhs) const { + return VirtualReg < Rhs.VirtualReg; + } +}; + +/// A slot containing a literal value. +struct LiteralSlot { + APInt Value; + static constexpr bool canBeFreelyGenerated = true; + + bool operator==(LiteralSlot const &Rhs) const { return Value == Rhs.Value; } + + bool operator<(LiteralSlot const &Rhs) const { return Value.ult(Rhs.Value); } +}; + +/// A slot containing a MCSymbol. +struct SymbolSlot { + MCSymbol *Symbol; + const MachineInstr *MI = nullptr; + static constexpr bool canBeFreelyGenerated = true; + + bool operator==(SymbolSlot const &Rhs) const { + return Symbol == Rhs.Symbol && MI->getOpcode() == Rhs.MI->getOpcode(); + } + + bool operator<(SymbolSlot const &Rhs) const { + return std::make_pair(Symbol, MI->getOpcode()) < + std::make_pair(Rhs.Symbol, Rhs.MI->getOpcode()); + } +}; + +/// A slot containing the index-th return value of a previous call. +struct TemporarySlot { + /// The call that returned this slot. + const MachineInstr *MI = nullptr; + + Register VirtualReg; + /// Specifies to which of the values returned by the call this slot refers. + /// index == 0 refers to the slot deepest in the stack after the call. + size_t Index = 0; + static constexpr bool canBeFreelyGenerated = false; + + bool operator==(TemporarySlot const &Rhs) const { + return MI == Rhs.MI && Index == Rhs.Index; + } + + bool operator<(TemporarySlot const &Rhs) const { + return std::make_pair(MI, Index) < std::make_pair(Rhs.MI, Rhs.Index); + } +}; + +/// A slot containing an arbitrary value that is always eventually popped and +/// never used. Used to maintain stack balance on control flow joins. +struct JunkSlot { + static constexpr bool canBeFreelyGenerated = true; + + bool operator==(JunkSlot const &) const { return true; } + + bool operator<(JunkSlot const &) const { return false; } +}; + +using StackSlot = + std::variant; + +/// The stack top is usually the last element of the vector. +using Stack = std::vector; + +/// Returns true if Slot can be generated on the stack at any time. +inline bool canBeFreelyGenerated(StackSlot const &Slot) { + return std::visit( + [](auto const &TypedSlot) { + return std::decay_t::canBeFreelyGenerated; + }, + Slot); +} + +/// Control flow graph consisting of 'CFG::BasicBlock`s' connected by control +/// flow. +struct CFG { + explicit CFG() {} + CFG(CFG const &) = delete; + CFG(CFG &&) = delete; + CFG &operator=(CFG const &) = delete; + CFG &operator=(CFG &&) = delete; + ~CFG() = default; + + struct BuiltinCall { + MachineInstr *Builtin = nullptr; + bool TerminatesOrReverts = false; + }; + + struct FunctionCall { + const MachineInstr *Call; + /// True, if the call can return. + bool CanContinue = true; + size_t NumArguments = 0; + }; + + struct Assignment { + /// The variables being assigned to also occur as 'Output' in the + /// 'Operation' containing the assignment, but are also stored here for + /// convenience. + std::vector Variables; + }; + + struct Operation { + /// Stack slots this operation expects at the top of the stack and consumes. + Stack Input; + /// Stack slots this operation leaves on the stack as output. + Stack Output; + std::variant Operation; + }; + + struct FunctionInfo; + /// A basic control flow block containing 'Operation`s' acting on the stack. + /// Maintains a list of entry blocks and a typed exit. + struct BasicBlock { + struct InvalidExit {}; + + struct Unreachable {}; + + struct ConditionalJump { + StackSlot Condition; + BasicBlock *NonZero = nullptr; + BasicBlock *Zero = nullptr; + bool FallThrough = false; + MachineInstr *CondJump = nullptr; + MachineInstr *UncondJump = nullptr; + }; + + struct Jump { + BasicBlock *Target = nullptr; + bool FallThrough = false; + bool Backwards = false; + MachineInstr *UncondJump = nullptr; + }; + + struct FunctionReturn { + Stack RetValues; + CFG::FunctionInfo *Info = nullptr; + }; + + struct Terminated {}; + + MachineBasicBlock *MBB; + std::vector Entries; + std::vector Operations; + /// True, if the block is the beginning of a disconnected subgraph. That is, + /// if no block that is reachable from this block is an ancestor of this + /// block. In other words, this is true, if this block is the target of a + /// cut-edge/bridge in the CFG or if the block itself terminates. + bool IsStartOfSubGraph = false; + /// True, if there is a path from this block to a function return. + bool NeedsCleanStack = false; + /// If the block starts a sub-graph and does not lead to a function return, + /// we are free to add junk to it. + bool AllowsJunk() const { return IsStartOfSubGraph && !NeedsCleanStack; } + + std::variant + Exit = InvalidExit{}; + }; + + struct FunctionInfo { + MachineFunction *MF = nullptr; + BasicBlock *Entry = nullptr; + std::vector Parameters; + std::vector Exits; + bool CanContinue = true; + }; + + FunctionInfo FuncInfo; + + /// Container for blocks for explicit ownership. + std::list Blocks; + DenseMap MachineBBToBB; + + BasicBlock &getBlock(const MachineBasicBlock *MBB) { + auto It = MachineBBToBB.find(MBB); + assert(It != MachineBBToBB.end()); + return *It->second; + } + + void createBlock(MachineBasicBlock *MBB) { + auto It = MachineBBToBB.find(MBB); + if (It == MachineBBToBB.end()) { + BasicBlock &Block = Blocks.emplace_back(BasicBlock{MBB, {}, {}}); + MachineBBToBB[MBB] = &Block; + } + } +}; + +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp new file mode 100644 index 000000000000..dc359cc51965 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp @@ -0,0 +1,435 @@ +//===----- EVMControlFlowGraphBuilder.CPP - CFG builder ---------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file builds the Control Flow Graph used for the backward propagation +// stackification algorithm. +// +//===----------------------------------------------------------------------===// + +#include "EVM.h" + +#include "EVMControlFlowGraphBuilder.h" +#include "EVMHelperUtilities.h" +#include "EVMMachineFunctionInfo.h" +#include "EVMStackDebug.h" +#include "EVMSubtarget.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/MachineFunction.h" + +#include +#include + +using namespace llvm; + +#define DEBUG_TYPE "evm-control-flow-graph-builder" + +/// Marks each block that needs to maintain a clean stack. That is each block +/// that has an outgoing path to a function return. +static void markNeedsCleanStack(CFG &Cfg) { + for (CFG::BasicBlock *Exit : Cfg.FuncInfo.Exits) + EVMUtils::BreadthFirstSearch{{Exit}}.run( + [&](CFG::BasicBlock *Block, auto AddChild) { + Block->NeedsCleanStack = true; + // TODO: it seems this is not needed, as the return block has + // no childs. + for (CFG::BasicBlock *Entry : Block->Entries) + AddChild(Entry); + }); +} + +/// Marks each cut-vertex in the CFG, i.e. each block that begins a disconnected +/// sub-graph of the CFG. Entering such a block means that control flow will +/// never return to a previously visited block. +static void markStartsOfSubGraphs(CFG &Cfg) { + CFG::BasicBlock *Entry = Cfg.FuncInfo.Entry; + /** + * Detect bridges following Algorithm 1 in + * https://arxiv.org/pdf/2108.07346.pdf and mark the bridge targets as starts + * of sub-graphs. + */ + std::set Visited; + std::map Disc; + std::map Low; + std::map Parent; + size_t Time = 0; + auto Dfs = [&](CFG::BasicBlock *U, auto Recurse) -> void { + Visited.insert(U); + Disc[U] = Low[U] = Time; + Time++; + + std::vector Children = U->Entries; + std::visit(Overload{[&](CFG::BasicBlock::Jump const &Jump) { + Children.emplace_back(Jump.Target); + }, + [&](CFG::BasicBlock::ConditionalJump const &Jump) { + Children.emplace_back(Jump.Zero); + Children.emplace_back(Jump.NonZero); + }, + [&](CFG::BasicBlock::FunctionReturn const &) {}, + [&](CFG::BasicBlock::Terminated const &) { + U->IsStartOfSubGraph = true; + }, + [&](CFG::BasicBlock::Unreachable const &) { + U->IsStartOfSubGraph = true; + }, + [&](CFG::BasicBlock::InvalidExit const &) { + llvm_unreachable("Unexpected BB terminator"); + }}, + U->Exit); + + for (CFG::BasicBlock *V : Children) { + // Ignore the loop edge, as it cannot be the bridge. + if (V == U) + continue; + + if (!Visited.count(V)) { + Parent[V] = U; + Recurse(V, Recurse); + Low[U] = std::min(Low[U], Low[V]); + if (Low[V] > Disc[U]) { + // U <-> V is a cut edge in the undirected graph + bool EdgeVtoU = EVMUtils::contains(U->Entries, V); + bool EdgeUtoV = EVMUtils::contains(V->Entries, U); + if (EdgeVtoU && !EdgeUtoV) + // Cut edge V -> U + U->IsStartOfSubGraph = true; + else if (EdgeUtoV && !EdgeVtoU) + // Cut edge U -> v + V->IsStartOfSubGraph = true; + } + } else if (V != Parent[U]) + Low[U] = std::min(Low[U], Disc[V]); + } + }; + Dfs(Entry, Dfs); +} + +std::unique_ptr ControlFlowGraphBuilder::build(MachineFunction &MF, + const LiveIntervals &LIS, + MachineLoopInfo *MLI) { + auto Result = std::make_unique(); + ControlFlowGraphBuilder Builder(*Result, LIS, MLI); + + for (MachineBasicBlock &MBB : MF) + Result->createBlock(&MBB); + + Result->FuncInfo.MF = &MF; + Result->FuncInfo.Entry = &Result->getBlock(&MF.front()); + const Function &F = MF.getFunction(); + if (F.hasFnAttribute(Attribute::NoReturn)) + Result->FuncInfo.CanContinue = false; + + // Handle function parameters + auto *MFI = MF.getInfo(); + Result->FuncInfo.Parameters = + std::vector(MFI->getNumParams(), JunkSlot{}); + for (const MachineInstr &MI : MF.front()) { + if (MI.getOpcode() == EVM::ARGUMENT) { + int64_t ArgIdx = MI.getOperand(1).getImm(); + Result->FuncInfo.Parameters[ArgIdx] = + VariableSlot{MI.getOperand(0).getReg()}; + } + } + + for (MachineBasicBlock &MBB : MF) + Builder.handleBasicBlock(MBB); + + for (MachineBasicBlock &MBB : MF) + Builder.handleBasicBlockSuccessors(MBB); + + markStartsOfSubGraphs(*Result); + markNeedsCleanStack(*Result); + + LLVM_DEBUG({ + dbgs() << "************* CFG *************\n"; + ControlFlowGraphPrinter P(dbgs()); + P(*Result); + }); + + return Result; +} + +void ControlFlowGraphBuilder::handleBasicBlock(MachineBasicBlock &MBB) { + CurrentBlock = &Cfg.getBlock(&MBB); + for (MachineInstr &MI : MBB) + handleMachineInstr(MI); +} + +StackSlot ControlFlowGraphBuilder::getDefiningSlot(const MachineInstr &MI, + Register Reg) const { + StackSlot Slot = VariableSlot{Reg}; + SlotIndex Idx = LIS.getInstructionIndex(MI); + const LiveInterval *LI = &LIS.getInterval(Reg); + LiveQueryResult LRQ = LI->Query(Idx); + const VNInfo *VNI = LRQ.valueIn(); + assert(VNI && "Use of non-existing value"); + // If the virtual register defines a constant and this is the only + // definition, emit the literal slot as MI's input. + if (LI->containsOneValue()) { + assert(!VNI->isPHIDef()); + const MachineInstr *DefMI = LIS.getInstructionFromIndex(VNI->def); + assert(DefMI && "Dead valno in interval"); + if (DefMI->getOpcode() == EVM::CONST_I256) { + const APInt Imm = DefMI->getOperand(1).getCImm()->getValue(); + Slot = LiteralSlot{std::move(Imm)}; + } + } + + return Slot; +} + +void ControlFlowGraphBuilder::collectInstrOperands(const MachineInstr &MI, + Stack *Input, + Stack *Output) const { + if (Input) { + for (const auto &MO : reverse(MI.explicit_uses())) { + // All the non-register operands are handled in instruction specific + // handlers. + if (!MO.isReg()) + continue; + + const Register Reg = MO.getReg(); + // SP is not used anyhow. + if (Reg == EVM::SP) + continue; + + Input->push_back(getDefiningSlot(MI, Reg)); + } + } + + if (Output) { + unsigned ArgsNumber = 0; + for (const auto &MO : MI.defs()) + Output->push_back(TemporarySlot{&MI, MO.getReg(), ArgsNumber++}); + } +} + +void ControlFlowGraphBuilder::handleMachineInstr(MachineInstr &MI) { + bool TerminatesOrReverts = false; + unsigned Opc = MI.getOpcode(); + switch (Opc) { + case EVM::STACK_LOAD: + [[fallthrough]]; + case EVM::STACK_STORE: + llvm_unreachable("Unexpected stack memory instruction"); + return; + case EVM::ARGUMENT: + // Is handled above. + return; + case EVM::FCALL: + handleFunctionCall(MI); + break; + case EVM::RET: + handleReturn(MI); + return; + case EVM::JUMP: + [[fallthrough]]; + case EVM::JUMPI: + // Branch instructions are handled separately. + return; + case EVM::COPY_I256: + case EVM::DATASIZE: + case EVM::DATAOFFSET: + // The copy/data instructions just represent an assignment. This case is + // handled below. + break; + case EVM::CONST_I256: { + const LiveInterval *LI = &LIS.getInterval(MI.getOperand(0).getReg()); + // If the virtual register has the only definition, ignore this instruction, + // as we create literal slots from the immediate value at the register uses. + if (LI->containsOneValue()) + return; + } break; + case EVM::REVERT: + [[fallthrough]]; + case EVM::RETURN: + [[fallthrough]]; + case EVM::STOP: + [[fallthrough]]; + case EVM::INVALID: + CurrentBlock->Exit = CFG::BasicBlock::Terminated{}; + TerminatesOrReverts = true; + [[fallthrough]]; + default: { + Stack Input, Output; + collectInstrOperands(MI, &Input, &Output); + CurrentBlock->Operations.emplace_back( + CFG::Operation{std::move(Input), std::move(Output), + CFG::BuiltinCall{&MI, TerminatesOrReverts}}); + } break; + } + + // Cretae CFG::Assignment object for the MI. + Stack Input, Output; + std::vector Variables; + switch (MI.getOpcode()) { + case EVM::CONST_I256: { + const Register DefReg = MI.getOperand(0).getReg(); + const APInt Imm = MI.getOperand(1).getCImm()->getValue(); + Input.push_back(LiteralSlot{std::move(Imm)}); + Output.push_back(VariableSlot{DefReg}); + Variables.push_back(VariableSlot{DefReg}); + } break; + case EVM::DATASIZE: + case EVM::DATAOFFSET: { + const Register DefReg = MI.getOperand(0).getReg(); + MCSymbol *Sym = MI.getOperand(1).getMCSymbol(); + Input.push_back(SymbolSlot{Sym, &MI}); + Output.push_back(VariableSlot{DefReg}); + Variables.push_back(VariableSlot{DefReg}); + } break; + case EVM::COPY_I256: { + // Copy instruction corresponds to the assignment operator, so + // we do not need to create intermediate TmpSlots. + Stack In; + collectInstrOperands(MI, &In, nullptr); + Input = In; + const Register DefReg = MI.getOperand(0).getReg(); + Output.push_back(VariableSlot{DefReg}); + Variables.push_back(VariableSlot{DefReg}); + } break; + default: { + unsigned ArgsNumber = 0; + for (const auto &MO : MI.defs()) { + assert(MO.isReg()); + const Register Reg = MO.getReg(); + Input.push_back(TemporarySlot{&MI, Reg, ArgsNumber++}); + Output.push_back(VariableSlot{Reg}); + Variables.push_back(VariableSlot{Reg}); + } + } break; + } + // We don't need an assignment part of the instructions that do not write + // results. + if (!Input.empty() || !Output.empty()) + CurrentBlock->Operations.emplace_back( + CFG::Operation{std::move(Input), std::move(Output), + CFG::Assignment{std::move(Variables)}}); +} + +void ControlFlowGraphBuilder::handleFunctionCall(const MachineInstr &MI) { + Stack Input, Output; + const Function *Called = getCalledFunction(MI); + bool IsNoReturn = Called->hasFnAttribute(Attribute::NoReturn); + if (IsNoReturn) + CurrentBlock->Exit = CFG::BasicBlock::Terminated{}; + else + Input.push_back(FunctionCallReturnLabelSlot{&MI}); + collectInstrOperands(MI, &Input, &Output); + CurrentBlock->Operations.emplace_back( + CFG::Operation{Input, Output, + CFG::FunctionCall{&MI, !IsNoReturn, + Input.size() - (IsNoReturn ? 0 : 1)}}); +} + +void ControlFlowGraphBuilder::handleReturn(const MachineInstr &MI) { + Cfg.FuncInfo.Exits.emplace_back(CurrentBlock); + Stack Input; + collectInstrOperands(MI, &Input, nullptr); + // We need to reverse input operands to restore original ordering, + // as it is in the instruction. + // Calling convention: return values are passed in stack such that the + // last one specified in the RET instruction is passed on the stack TOP. + std::reverse(Input.begin(), Input.end()); + CurrentBlock->Exit = + CFG::BasicBlock::FunctionReturn{std::move(Input), &Cfg.FuncInfo}; +} + +static std::pair +getMBBJumps(MachineBasicBlock &MBB) { + MachineInstr *CondJump = nullptr; + MachineInstr *UncondJump = nullptr; + MachineBasicBlock::reverse_iterator I = MBB.rbegin(), E = MBB.rend(); + while (I != E) { + if (I->isUnconditionalBranch()) + UncondJump = &*I; + else if (I->isConditionalBranch()) + CondJump = &*I; + ++I; + } + return std::make_pair(CondJump, UncondJump); +} + +void ControlFlowGraphBuilder::handleBasicBlockSuccessors( + MachineBasicBlock &MBB) { + MachineBasicBlock *TBB = nullptr, *FBB = nullptr; + SmallVector Cond; + const TargetInstrInfo *TII = MBB.getParent()->getSubtarget().getInstrInfo(); + CurrentBlock = &Cfg.getBlock(&MBB); + if (TII->analyzeBranch(MBB, TBB, FBB, Cond)) { + if (!std::holds_alternative( + CurrentBlock->Exit) && + !std::holds_alternative( + CurrentBlock->Exit)) + llvm_unreachable("Unexpected MBB termination"); + return; + } + +#ifndef NDEBUG + // This corresponds to a noreturn functions at the end of the MBB. + if (std::holds_alternative(CurrentBlock->Exit)) { + CFG::FunctionCall *Call = std::get_if( + &CurrentBlock->Operations.back().Operation); + assert(Call && !Call->CanContinue); + return; + } +#endif // NDEBUG + + // This corresponds to 'unreachable' at the BB end. + if (!TBB && !FBB && MBB.succ_empty()) { + CurrentBlock->Exit = CFG::BasicBlock::Unreachable{}; + return; + } + + CurrentBlock = &Cfg.getBlock(&MBB); + bool IsLatch = false; + MachineLoop *ML = MLI->getLoopFor(&MBB); + if (ML) { + SmallVector Latches; + ML->getLoopLatches(Latches); + IsLatch = std::any_of( + Latches.begin(), Latches.end(), + [&MBB](const MachineBasicBlock *Latch) { return &MBB == Latch; }); + } + + std::pair MBBJumps = getMBBJumps(MBB); + if (!TBB || (TBB && Cond.empty())) { + // Fall through, or unconditional jump. + bool FallThrough = !TBB; + if (!TBB) { + assert(MBB.getSingleSuccessor()); + TBB = MBB.getFallThrough(); + assert(TBB); + } + CFG::BasicBlock &Target = Cfg.getBlock(TBB); + assert(!MBBJumps.first); + assert((FallThrough && !MBBJumps.second) || !FallThrough); + CurrentBlock->Exit = + CFG::BasicBlock::Jump{&Target, FallThrough, IsLatch, MBBJumps.second}; + EVMUtils::emplace_back_unique(Target.Entries, CurrentBlock); + } else if (TBB && !Cond.empty()) { + assert(!IsLatch); + // Conditional jump + fallthrough or unconditional jump. + bool FallThrough = !FBB; + if (!FBB) { + FBB = MBB.getFallThrough(); + assert(FBB); + } + + CFG::BasicBlock &NonZeroTarget = Cfg.getBlock(TBB); + CFG::BasicBlock &ZeroTarget = Cfg.getBlock(FBB); + assert(Cond[0].isReg()); + auto CondSlot = getDefiningSlot(*Cond[0].getParent(), Cond[0].getReg()); + CurrentBlock->Exit = CFG::BasicBlock::ConditionalJump{ + std::move(CondSlot), &NonZeroTarget, &ZeroTarget, + FallThrough, MBBJumps.first, MBBJumps.second}; + + EVMUtils::emplace_back_unique(NonZeroTarget.Entries, CurrentBlock); + EVMUtils::emplace_back_unique(ZeroTarget.Entries, CurrentBlock); + } +} diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h new file mode 100644 index 000000000000..efe9b97313d0 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h @@ -0,0 +1,56 @@ +//===----- EVMControlFlowGraphBuilder.h - CFG builder -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file builds the Control Flow Graph used for the backward propagation +// stackification algorithm. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPHBUILDER_H +#define LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPHBUILDER_H + +#include "EVMControlFlowGraph.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/CodeGen/LiveIntervals.h" +#include "llvm/CodeGen/MachineLoopInfo.h" + +namespace llvm { + +class MachineFunction; +class MachineBasicBlock; + +class ControlFlowGraphBuilder { +public: + ControlFlowGraphBuilder(ControlFlowGraphBuilder const &) = delete; + ControlFlowGraphBuilder &operator=(ControlFlowGraphBuilder const &) = delete; + static std::unique_ptr + build(MachineFunction &MF, const LiveIntervals &LIS, MachineLoopInfo *MLI); + +private: + ControlFlowGraphBuilder(CFG &Cfg, const LiveIntervals &LIS, + MachineLoopInfo *MLI) + : Cfg(Cfg), LIS(LIS), MLI(MLI) {} + + void handleBasicBlock(MachineBasicBlock &MBB); + void handleMachineInstr(MachineInstr &MI); + void handleFunctionCall(const MachineInstr &MI); + void handleReturn(const MachineInstr &MI); + void handleBasicBlockSuccessors(MachineBasicBlock &MBB); + StackSlot getDefiningSlot(const MachineInstr &MI, Register Reg) const; + void collectInstrOperands(const MachineInstr &MI, Stack *Input, + Stack *Output) const; + + CFG &Cfg; + const LiveIntervals &LIS; + MachineLoopInfo *MLI = nullptr; + CFG::BasicBlock *CurrentBlock = nullptr; +}; + +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H diff --git a/llvm/lib/Target/EVM/EVMHelperUtilities.h b/llvm/lib/Target/EVM/EVMHelperUtilities.h new file mode 100644 index 000000000000..6f137535cbcd --- /dev/null +++ b/llvm/lib/Target/EVM/EVMHelperUtilities.h @@ -0,0 +1,182 @@ +//===----- EVMHelperUtilities.h - CFG for stackification --------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines different utility templates. In particular, for a partial +// emulation of the C++ range-V3 library functionality; BFS graph traversal; +// the overload pattern support. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H +#define LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H + +#include "llvm/ADT/iterator_range.h" +#include +#include +#include +#include +#include +#include + +namespace llvm { + +template struct Overload : Ts... { + using Ts::operator()...; +}; +template Overload(Ts...) -> Overload; + +template static inline SmallVector iota17(T begin, T end) { + SmallVector R(end - begin); + std::iota(R.begin(), R.end(), begin); + return R; +} + +/// Concatenate the contents of a container into a vector +template +std::vector &operator+=(std::vector &lhs, U &rhs) { + for (auto const &elm : rhs) + lhs.push_back(T(elm)); + return lhs; +} + +template +std::vector &operator+=(std::vector &lhs, U &&rhs) { + std::move(rhs.begin(), rhs.end(), std::back_inserter(lhs)); + return lhs; +} + +/// Concatenate two vectors of elements. +template +inline std::vector operator+(std::vector const &lhs, + std::vector const &rhs) { + std::vector result(lhs); + result += rhs; + return result; +} + +/// Concatenate two vectors of elements, moving them. +template +inline std::vector operator+(std::vector &&lhs, std::vector &&rhs) { + std::vector result(std::move(lhs)); + assert(&lhs != &rhs); + result += std::move(rhs); + return result; +} + +namespace EVMUtils { + +template bool contains(const T &t, const V &v) { + return std::end(t) != std::find(std::begin(t), std::end(t), v); +} + +template +std::optional findOffset(T &&t, V &&v) { + auto begin = std::begin(t); + auto end = std::end(t); + auto it = std::find(begin, end, std::forward(v)); + if (it == end) + return std::nullopt; + return std::distance(begin, it); +} + +template +auto take_last(T &&t, size_t N) -> iterator_range { + auto it = t.end(); + std::advance(it, -N); + return make_range(it, t.end()); +} + +template +auto drop_first(T &&t, size_t N) -> iterator_range { + auto it = t.begin(); + std::advance(it, N); + return make_range(it, t.end()); +} + +template +iterator_range get_reverse(const T &t) { + return llvm::make_range(t.rbegin(), t.rend()); +} + +// Returns a pointer to the entry of map \p m at the key \p k, +// if there is one, and nullptr otherwise. +template +decltype(auto) valueOrNullptr(M &&m, K const &k) { + auto it = m.find(k); + return (it == m.end()) ? nullptr : &it->second; +} + +template auto to_vector(R &&r) { + std::vector v; + v.assign(r.begin(), r.end()); + return v; +} + +/// RAII utility class whose destructor calls a given function. +class ScopeGuard { +public: + explicit ScopeGuard(std::function Func) : Func(std::move(Func)) {} + ~ScopeGuard() { Func(); } + +private: + std::function Func; +}; + +template void emplace_back_unique(T &t, V &&v) { + if (t.end() == std::find(t.begin(), t.end(), v)) + t.emplace_back(v); +} + +/// Generic breadth first search. +/// +/// Note that V needs to be a comparable value type or a pointer. +/// +/// Example: Gather all (recursive) children in a graph starting at (and +/// including) ``root``: +/// +/// Node const* root = ...; +/// std::set allNodes = BreadthFirstSearch{{root}}.run([](Node const* _node, auto&& _addChild) { +/// // Potentially process ``_node``. +/// for (Node const& _child: _node->children()) +/// // Potentially filter the children to be visited. +/// _addChild(&_child); +/// }).visited; +/// +template struct BreadthFirstSearch { + /// Runs the breadth first search. The verticesToTraverse member of the struct + /// needs to be initialized. + /// @param _forEachChild is a callable of the form [...](V const& _node, + /// auto&& _addChild) { ... } that is called for each visited node and is + /// supposed to call _addChild(childNode) for every child node of _node. + template + BreadthFirstSearch &run(ForEachChild &&forEachChild) { + while (!verticesToTraverse.empty()) { + V v = std::move(verticesToTraverse.front()); + verticesToTraverse.pop_front(); + + if (!visited.insert(v).second) + continue; + + forEachChild(v, [this](V vertex) { + verticesToTraverse.emplace_back(std::move(vertex)); + }); + } + return *this; + } + + void abort() { verticesToTraverse.clear(); } + + std::list verticesToTraverse; + std::set visited{}; +}; + +} // namespace EVMUtils +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp new file mode 100644 index 000000000000..26b5bd5a967c --- /dev/null +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp @@ -0,0 +1,456 @@ +//===--- EVMOptimizedCodeTransform.h - Create stackified MIR ---*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file transforms MIR to the 'stackified' MIR using CFG, StackLayout +// and EVMAssembly classes. +// +//===----------------------------------------------------------------------===// + +#include "EVMOptimizedCodeTransform.h" +#include "EVMControlFlowGraphBuilder.h" +#include "EVMHelperUtilities.h" +#include "EVMStackDebug.h" +#include "EVMStackLayoutGenerator.h" +#include "EVMStackShuffler.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/Support/ErrorHandling.h" + +#include + +using namespace llvm; + +#define DEBUG_TYPE "evm-optimized-code-transform" + +void EVMOptimizedCodeTransform::run(EVMAssembly &Assembly, MachineFunction &MF, + const LiveIntervals &LIS, + MachineLoopInfo *MLI) { + std::unique_ptr Cfg = ControlFlowGraphBuilder::build(MF, LIS, MLI); + StackLayout Layout = StackLayoutGenerator::run(*Cfg); + EVMOptimizedCodeTransform optimizedCodeTransform(Assembly, *Cfg, Layout, MF); + optimizedCodeTransform(); +} + +void EVMOptimizedCodeTransform::operator()(CFG::FunctionCall const &Call) { +#ifndef NDEBUG + // Validate stack. + assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(CurrentStack.size() >= Call.NumArguments + (Call.CanContinue ? 1 : 0)); + + // Assert that we got the correct return label on stack. + if (Call.CanContinue) { + auto const *returnLabelSlot = std::get_if( + &CurrentStack.at(CurrentStack.size() - Call.NumArguments - 1)); + assert(returnLabelSlot && returnLabelSlot->Call == Call.Call); + } +#endif // NDEBUG + + // Emit code. + const MachineOperand *CalleeOp = Call.Call->explicit_uses().begin(); + assert(CalleeOp->isGlobal()); + Assembly.appendFuncCall(Call.Call, CalleeOp->getGlobal(), + Call.Call->getNumExplicitDefs() - Call.NumArguments - + (Call.CanContinue ? 1 : 0), + Call.CanContinue ? CallToReturnMCSymbol.at(Call.Call) + : nullptr); + if (Call.CanContinue) + Assembly.appendLabel(); + + // Update stack, remove arguments and return label from CurrentStack. + for (size_t I = 0; I < Call.NumArguments + (Call.CanContinue ? 1 : 0); ++I) + CurrentStack.pop_back(); + + // Push return values to CurrentStack. + unsigned Idx = 0; + for (const auto &MO : Call.Call->defs()) { + assert(MO.isReg()); + CurrentStack.emplace_back(TemporarySlot{Call.Call, MO.getReg(), Idx++}); + } + assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); +} + +void EVMOptimizedCodeTransform::operator()(CFG::BuiltinCall const &Call) { + size_t NumArgs = Call.Builtin->getNumExplicitOperands() - + Call.Builtin->getNumExplicitDefs(); + // Validate stack. + assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(CurrentStack.size() >= NumArgs); + // TODO: assert that we got a correct stack for the call. + + // Emit code. + Assembly.appendInstruction(Call.Builtin); + + // Update stack and remove arguments from CurrentStack. + for (size_t i = 0; i < NumArgs; ++i) + CurrentStack.pop_back(); + + // Push return values to CurrentStack. + unsigned Idx = 0; + for (const auto &MO : Call.Builtin->defs()) { + assert(MO.isReg()); + CurrentStack.emplace_back(TemporarySlot{Call.Builtin, MO.getReg(), Idx++}); + } + assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); +} + +void EVMOptimizedCodeTransform::operator()(CFG::Assignment const &Assignment) { + assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + + // Invalidate occurrences of the assigned variables. + for (auto &CurrentSlot : CurrentStack) + if (VariableSlot const *VarSlot = std::get_if(&CurrentSlot)) + if (EVMUtils::contains(Assignment.Variables, *VarSlot)) + CurrentSlot = JunkSlot{}; + + // Assign variables to current stack top. + assert(CurrentStack.size() >= Assignment.Variables.size()); + auto StackRange = + EVMUtils::take_last(CurrentStack, Assignment.Variables.size()); + auto RangeIt = StackRange.begin(), RangeE = StackRange.end(); + auto VarsIt = Assignment.Variables.begin(), + VarsE = Assignment.Variables.end(); + for (; (RangeIt != RangeE) && (VarsIt != VarsE); ++RangeIt, ++VarsIt) + *RangeIt = *VarsIt; +} + +EVMOptimizedCodeTransform::EVMOptimizedCodeTransform(EVMAssembly &Assembly, + CFG const &Cfg, + StackLayout const &Layout, + MachineFunction &MF) + : Assembly(Assembly), Layout(Layout), FuncInfo(&Cfg.FuncInfo), MF(MF) {} + +bool EVMOptimizedCodeTransform::AreLayoutsCompatible(Stack const &SourceStack, + Stack const &TargetStack) { + if (SourceStack.size() != TargetStack.size()) + return false; + + for (auto [Src, Tgt] : zip_equal(SourceStack, TargetStack)) { + if (!std::holds_alternative(Tgt) && !(Src == Tgt)) + return false; + } + + return true; +} + +void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { + auto SlotVariableName = [](StackSlot const &Slot) { + return std::visit( + Overload{ + [&](VariableSlot const &Var) { + SmallString<1024> StrBuf; + llvm::raw_svector_ostream OS(StrBuf); + OS << printReg(Var.VirtualReg, nullptr, 0, nullptr); + return std::string(StrBuf.c_str()); + }, + [&](const FunctionCallReturnLabelSlot &) { return std::string(); }, + [&](const FunctionReturnLabelSlot &) { return std::string(); }, + [&](const LiteralSlot &) { return std::string(); }, + [&](const SymbolSlot &) { return std::string(); }, + [&](const TemporarySlot &) { return std::string(); }, + [&](const JunkSlot &) { return std::string(); }}, + Slot); + }; + + assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + // ::createStackLayout asserts that it has successfully achieved the target + // layout. + ::createStackLayout( + CurrentStack, TargetStack, + // Swap callback. + [&](unsigned I) { + assert(static_cast(CurrentStack.size()) == + Assembly.getStackHeight()); + assert(I > 0 && I < CurrentStack.size()); + if (I <= 16) { + Assembly.appendSWAPInstruction(I); + } else { + int Deficit = static_cast(I) - 16; + StackSlot const &DeepSlot = + CurrentStack.at(CurrentStack.size() - I - 1); + std::string VarNameDeep = SlotVariableName(DeepSlot); + std::string VarNameTop = SlotVariableName(CurrentStack.back()); + std::string Msg = + (Twine("cannot swap ") + + (VarNameDeep.empty() ? ("slot " + stackSlotToString(DeepSlot)) + : (Twine("variable ") + VarNameDeep)) + + " with " + + (VarNameTop.empty() + ? ("slot " + stackSlotToString(CurrentStack.back())) + : (Twine("variable ") + VarNameTop)) + + ": too deep in the stack by " + std::to_string(Deficit) + + " slots in " + stackToString(CurrentStack)) + .str(); + + report_fatal_error(FuncInfo->MF->getName() + Twine(": ") + Msg); + } + }, + // Push or dup callback. + [&](StackSlot const &Slot) { + assert(static_cast(CurrentStack.size()) == + Assembly.getStackHeight()); + // Dup the slot, if already on stack and reachable. + if (auto Depth = EVMUtils::findOffset( + EVMUtils::get_reverse(CurrentStack), Slot)) { + if (*Depth < 16) { + Assembly.appendDUPInstruction(static_cast(*Depth + 1)); + return; + } else if (!canBeFreelyGenerated(Slot)) { + std::string VarName = SlotVariableName(Slot); + std::string Msg = + ((VarName.empty() ? "slot " + stackSlotToString(Slot) + : Twine("variable ") + VarName) + + " is " + std::to_string(*Depth - 15) + + " too deep in the stack " + stackToString(CurrentStack)) + .str(); + + report_fatal_error(MF.getName() + ": " + Msg); + return; + } + // else: the slot is too deep in stack, but can be freely generated, + // we fall through to push it again. + } + + // The slot can be freely generated or is an unassigned return variable. + // Push it. + std::visit( + Overload{[&](LiteralSlot const &Literal) { + Assembly.appendConstant(Literal.Value); + }, + [&](SymbolSlot const &Symbol) { + Assembly.appendSymbol(Symbol.Symbol, + Symbol.MI->getOpcode()); + }, + [&](FunctionReturnLabelSlot const &) { + llvm_unreachable("Cannot produce function return label"); + }, + [&](const FunctionCallReturnLabelSlot &ReturnLabel) { + if (!CallToReturnMCSymbol.count(ReturnLabel.Call)) + CallToReturnMCSymbol[ReturnLabel.Call] = + Assembly.createFuncRetSymbol(); + + Assembly.appendLabelReference( + CallToReturnMCSymbol[ReturnLabel.Call]); + }, + [&](const VariableSlot &Variable) { + llvm_unreachable("Variable not found on stack"); + }, + [&](const TemporarySlot &) { + llvm_unreachable("Function call result requested, but " + "not found on stack."); + }, + [&](const JunkSlot &) { + // Note: this will always be popped, so we can push + // anything. + Assembly.appendConstant(0); + }}, + Slot); + }, + // Pop callback. + [&]() { Assembly.appendPOPInstruction(); }); + + assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); +} + +void EVMOptimizedCodeTransform::createOperationEntryLayout( + const CFG::Operation &Op) { + // Create required layout for entering the Operation. + createStackLayout(Layout.operationEntryLayout.at(&Op)); + +#ifndef NDEBUG + // Assert that we have the inputs of the Operation on stack top. + assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + assert(CurrentStack.size() >= Op.Input.size()); + const Stack StackInput = + EVMUtils::to_vector(EVMUtils::take_last(CurrentStack, Op.Input.size())); + assert(AreLayoutsCompatible(StackInput, Op.Input)); +#endif // NDEBUG +} + +void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { + // Current location for the entry BB was set up in operator()(). + if (&Block != FuncInfo->Entry) + Assembly.setCurrentLocation(Block.MBB); + + // Assert that this is the first visit of the block and mark as generated. + [[maybe_unused]] auto It = GeneratedBlocks.insert(&Block); + assert(It.second); + + auto const &BlockInfo = Layout.blockInfos.at(&Block); + + // Assert that the stack is valid for entering the block. + assert(AreLayoutsCompatible(CurrentStack, BlockInfo.entryLayout)); + + // Might set some slots to junk, if not required by the block. + CurrentStack = BlockInfo.entryLayout; + + assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + + // Emit jumpdest, if required. + if (EVMUtils::valueOrNullptr(BlockLabels, &Block)) + Assembly.appendLabel(); + + for (const auto &Operation : Block.Operations) { + createOperationEntryLayout(Operation); + +#ifndef NDEBUG + size_t BaseHeight = CurrentStack.size() - Operation.Input.size(); +#endif // NDEBUG + + // Perform the Operation. + std::visit(*this, Operation.Operation); + + // Assert that the Operation produced its proclaimed output. + assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + assert(CurrentStack.size() == BaseHeight + Operation.Output.size()); + assert(CurrentStack.size() >= Operation.Output.size()); + assert(AreLayoutsCompatible(EVMUtils::to_vector(EVMUtils::take_last( + CurrentStack, Operation.Output.size())), + Operation.Output)); + } + + // Exit the block. + std::visit( + Overload{ + [&](const CFG::BasicBlock::InvalidExit &) { + llvm_unreachable("Unexpected BB terminator"); + }, + [&](const CFG::BasicBlock::Jump &Jump) { + // Create the stack expected at the jump target. + createStackLayout(Layout.blockInfos.at(Jump.Target).entryLayout); + + // If this is the only jump to the block which is a fallthrought + // we can directly continue with the target block. + if (Jump.Target->Entries.size() == 1 && Jump.FallThrough) + assert(!Jump.Backwards && !BlockLabels.count(Jump.Target)); + + if (!BlockLabels.count(Jump.Target)) + BlockLabels[Jump.Target] = Jump.Target->MBB->getSymbol(); + + if (Jump.UncondJump) + Assembly.appendUncondJump(Jump.UncondJump, Jump.Target->MBB); + + if (!GeneratedBlocks.count(Jump.Target)) + (*this)(*Jump.Target); + }, + [&](CFG::BasicBlock::ConditionalJump const &CondJump) { + // Create the shared entry layout of the jump targets, which is + // stored as exit layout of the current block. + createStackLayout(BlockInfo.exitLayout); + + // Create labels for the targets, if not already present. + if (!BlockLabels.count(CondJump.NonZero)) + BlockLabels[CondJump.NonZero] = + CondJump.NonZero->MBB->getSymbol(); + + if (!BlockLabels.count(CondJump.Zero)) + BlockLabels[CondJump.Zero] = CondJump.Zero->MBB->getSymbol(); + + // Assert that we have the correct condition on stack. + assert(!CurrentStack.empty()); + assert(CurrentStack.back() == CondJump.Condition); + + // Emit the conditional jump to the non-zero label and update the + // stored stack. + assert(CondJump.CondJump); + Assembly.appendCondJump(CondJump.CondJump, CondJump.NonZero->MBB); + CurrentStack.pop_back(); + + // Assert that we have a valid stack for both jump targets. + assert(AreLayoutsCompatible( + CurrentStack, + Layout.blockInfos.at(CondJump.NonZero).entryLayout)); + assert(AreLayoutsCompatible( + CurrentStack, Layout.blockInfos.at(CondJump.Zero).entryLayout)); + + { + // Restore the stack afterwards for the non-zero case below. + EVMUtils::ScopeGuard stackRestore([storedStack = CurrentStack, + this]() { + CurrentStack = std::move(storedStack); + Assembly.setStackHeight(static_cast(CurrentStack.size())); + }); + + // If we have already generated the zero case, jump to it, + // otherwise generate it in place. + if (CondJump.UncondJump) + Assembly.appendUncondJump(CondJump.UncondJump, + CondJump.Zero->MBB); + + if (!GeneratedBlocks.count(CondJump.Zero)) + (*this)(*CondJump.Zero); + } + // Note that each block visit terminates control flow, so we cannot + // fall through from the zero case. + + // Generate the non-zero block, if not done already. + if (!GeneratedBlocks.count(CondJump.NonZero)) + (*this)(*CondJump.NonZero); + }, + [&](CFG::BasicBlock::FunctionReturn const &FuncReturn) { + assert(FuncInfo->CanContinue); + + // Construct the function return layout, which is fully determined + // by the function signature. + Stack ExitStack = FuncReturn.RetValues; + + ExitStack.emplace_back( + FunctionReturnLabelSlot{FuncReturn.Info->MF}); + + // Create the function return layout and jump. + createStackLayout(ExitStack); + Assembly.appendJump(0); + }, + [&](CFG::BasicBlock::Unreachable const &) { + assert(Block.Operations.empty()); + }, + [&](CFG::BasicBlock::Terminated const &) { + assert(!Block.Operations.empty()); + if (const CFG::BuiltinCall *BuiltinCall = + std::get_if( + &Block.Operations.back().Operation)) + assert(BuiltinCall->TerminatesOrReverts); + else if (CFG::FunctionCall const *FunctionCall = + std::get_if( + &Block.Operations.back().Operation)) + assert(!FunctionCall->CanContinue); + else + llvm_unreachable("Unexpected BB terminator"); + }}, + Block.Exit); + + // TODO: + // We could assert that the last emitted assembly item terminated or was + // an (unconditional) jump. + CurrentStack.clear(); + Assembly.setStackHeight(0); +} + +void EVMOptimizedCodeTransform::operator()() { + assert(CurrentStack.empty() && Assembly.getStackHeight() == 0); + Assembly.setCurrentLocation(FuncInfo->Entry->MBB); + + assert(!BlockLabels.count(FuncInfo->Entry)); + + // Create function entry layout in CurrentStack. + if (FuncInfo->CanContinue) + CurrentStack.emplace_back(FunctionReturnLabelSlot{FuncInfo->MF}); + + // Calling convention: input arguments are passed in stack such that the + // first one specified in the function declaration is passed on the stack TOP. + for (auto const &Param : reverse(FuncInfo->Parameters)) + CurrentStack.emplace_back(Param); + + Assembly.setStackHeight(static_cast(CurrentStack.size())); + Assembly.appendLabel(); + + // Create the entry layout of the function body block and visit. + createStackLayout(Layout.blockInfos.at(FuncInfo->Entry).entryLayout); + + (*this)(*FuncInfo->Entry); + + Assembly.finalize(); +} diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h new file mode 100644 index 000000000000..2382c68a80b3 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h @@ -0,0 +1,90 @@ +//===--- EVMOptimizedCodeTransform.h - Create stackified MIR ---*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file transforms MIR to the 'stackified' MIR using CFG, StackLayout +// and EVMAssembly classes. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMOPTIMIZEDCODETRANSFORM_H +#define LLVM_LIB_TARGET_EVM_EVMOPTIMIZEDCODETRANSFORM_H + +#include "EVMAssembly.h" +#include "EVMControlFlowGraph.h" +#include "EVMStackLayoutGenerator.h" +#include "llvm/CodeGen/LiveIntervals.h" +#include "llvm/CodeGen/MachineLoopInfo.h" + +#include + +namespace llvm { + +class MachineInstr; +class MCSymbol; + +class EVMOptimizedCodeTransform { +public: + static void run(EVMAssembly &Assembly, MachineFunction &MF, + const LiveIntervals &LIS, MachineLoopInfo *MLI); + + /// Generate code for the function call \p Call. Only public for using with + /// std::visit. + void operator()(CFG::FunctionCall const &Call); + /// Generate code for the builtin call \p Call. Only public for using with + /// std::visit. + void operator()(CFG::BuiltinCall const &Call); + /// Generate code for the assignment \p Assignment. Only public for using + /// with std::visit. + void operator()(CFG::Assignment const &Assignment); + +private: + EVMOptimizedCodeTransform(EVMAssembly &Assembly, const CFG &Cfg, + const StackLayout &Layout, MachineFunction &MF); + + /// Checks if it's valid to transition from \p SourceStack to \p + /// TargetStack, that is \p SourceStack matches each slot in \p + /// TargetStack that is not a JunkSlot exactly. + static bool AreLayoutsCompatible(Stack const &SourceStack, + Stack const &TargetStack); + + /// Shuffles CurrentStack to the desired \p TargetStack while emitting the + /// shuffling code to Assembly. + void createStackLayout(Stack TargetStack); + + /// Creates the Op.Input stack layout from the 'CurrentStack' taking into + /// account commutative property of the operation. + void createOperationEntryLayout(const CFG::Operation &Op); + + /// Generate code for the given block \p Block. + /// Expects the current stack layout 'CurrentStack' to be a stack layout that + /// is compatible with the entry layout expected by the block. Recursively + /// generates code for blocks that are jumped to. The last emitted assembly + /// instruction is always an unconditional jump or terminating. Always exits + /// with an empty stack layout. + void operator()(CFG::BasicBlock const &Block); + + /// Generate code for the given function. + /// Resets CurrentStack. + void operator()(); + + EVMAssembly &Assembly; + StackLayout const &Layout; + CFG::FunctionInfo const *FuncInfo = nullptr; + MachineFunction &MF; + + Stack CurrentStack; + DenseMap CallToReturnMCSymbol; + DenseMap BlockLabels; + /// Set of blocks already generated. If any of the contained blocks is ever + /// jumped to, BlockLabels should contain a jump label for it. + std::set GeneratedBlocks; +}; + +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMOPTIMIZEDCODETRANSFORM_H diff --git a/llvm/lib/Target/EVM/EVMStackDebug.cpp b/llvm/lib/Target/EVM/EVMStackDebug.cpp new file mode 100644 index 000000000000..e3792e0151a5 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackDebug.cpp @@ -0,0 +1,311 @@ +//===-- EVMStackDebug.cpp - Debugging of the stackification -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares classes for dumping a state of stackifcation related data +// structures and algorithms. +// +//===----------------------------------------------------------------------===// + +#include "EVMStackDebug.h" +#include "EVMHelperUtilities.h" +#include "EVMStackLayoutGenerator.h" +#include "EVMSubtarget.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include + +using namespace llvm; + +static std::string getInstName(const MachineInstr *MI) { + const MachineFunction *MF = MI->getParent()->getParent(); + const TargetInstrInfo *TII = MF->getSubtarget().getInstrInfo(); + return TII->getName(MI->getOpcode()).str(); +} + +const Function *llvm::getCalledFunction(const MachineInstr &MI) { + for (const MachineOperand &MO : MI.operands()) { + if (!MO.isGlobal()) + continue; + const Function *Func = dyn_cast(MO.getGlobal()); + if (Func != nullptr) + return Func; + } + return nullptr; +} + +std::string llvm::stackToString(const Stack &S) { + std::string Result("[ "); + for (auto const &Slot : S) + Result += stackSlotToString(Slot) + ' '; + Result += ']'; + return Result; +} + +std::string llvm::stackSlotToString(const StackSlot &Slot) { + return std::visit( + Overload{ + [](const FunctionCallReturnLabelSlot &Ret) -> std::string { + return "RET[" + + std::string(getCalledFunction(*Ret.Call)->getName()) + "]"; + }, + [](const FunctionReturnLabelSlot &) -> std::string { return "RET"; }, + [](const VariableSlot &Var) -> std::string { + SmallString<64> S; + raw_svector_ostream OS(S); + OS << printReg(Var.VirtualReg, nullptr, 0, nullptr); + return std::string(S); + ; + }, + [](const LiteralSlot &Literal) -> std::string { + SmallString<64> S; + Literal.Value.toStringSigned(S); + return std::string(S); + }, + [](const SymbolSlot &Symbol) -> std::string { + return getInstName(Symbol.MI) + ":" + + std::string(Symbol.Symbol->getName()); + }, + [](const TemporarySlot &Tmp) -> std::string { + SmallString<128> S; + raw_svector_ostream OS(S); + OS << "TMP[" << getInstName(Tmp.MI) << ", "; + OS << std::to_string(Tmp.Index) + "]"; + return std::string(S); + }, + [](const JunkSlot &Junk) -> std::string { return "JUNK"; }}, + Slot); + ; +} + +#ifndef NDEBUG +void ControlFlowGraphPrinter::operator()(const CFG &Cfg) { + (*this)(Cfg.FuncInfo); + for (const auto &Block : Cfg.Blocks) + printBlock(Block); +} + +void ControlFlowGraphPrinter::operator()(CFG::FunctionInfo const &Info) { + OS << "Function: " << Info.MF->getName() << '\n'; + OS << "Entry block: " << getBlockId(*Info.Entry) << ";\n"; +} + +std::string ControlFlowGraphPrinter::getBlockId(CFG::BasicBlock const &Block) { + return std::to_string(Block.MBB->getNumber()) + "." + + std::string(Block.MBB->getName()); +} + +void ControlFlowGraphPrinter::printBlock(CFG::BasicBlock const &Block) { + OS << "Block" << getBlockId(Block) << " [\n"; + + // Verify that the entries of this block exit into this block. + for (auto const &Entry : Block.Entries) { + std::visit( + Overload{ + [&](CFG::BasicBlock::Jump &Jump) { + assert((Jump.Target == &Block) && "Invalid control flow graph"); + }, + [&](CFG::BasicBlock::ConditionalJump &CondJump) { + assert((CondJump.Zero == &Block || CondJump.NonZero == &Block) && + "Invalid control flow graph"); + }, + [&](const auto &) { assert(0 && "Invalid control flow graph."); }}, + Entry->Exit); + } + + for (auto const &Op : Block.Operations) { + std::visit(Overload{[&](const CFG::FunctionCall &FuncCall) { + const MachineOperand *Callee = + FuncCall.Call->explicit_uses().begin(); + OS << Callee->getGlobal()->getName() << ": "; + }, + [&](const CFG::BuiltinCall &BuiltinCall) { + OS << getInstName(BuiltinCall.Builtin) << ": "; + }, + [&](const CFG::Assignment &Assignment) { + OS << "Assignment("; + for (const auto &Var : Assignment.Variables) + OS << printReg(Var.VirtualReg, nullptr, 0, nullptr) + << ", "; + OS << "): "; + }}, + Op.Operation); + OS << stackToString(Op.Input) << " => " << stackToString(Op.Output) << '\n'; + } + OS << "];\n"; + + std::visit( + Overload{[&](const CFG::BasicBlock::Jump &Jump) { + OS << "Block" << getBlockId(Block) << "Exit [label=\""; + OS << "Jump\" FallThrough:" << Jump.FallThrough << "];\n"; + if (Jump.Backwards) + OS << "Backwards"; + OS << "Block" << getBlockId(Block) << "Exit -> Block" + << getBlockId(*Jump.Target) << ";\n"; + }, + [&](const CFG::BasicBlock::ConditionalJump &CondJump) { + OS << "Block" << getBlockId(Block) << "Exit [label=\"{ "; + OS << stackSlotToString(CondJump.Condition); + OS << "| { <0> Zero | <1> NonZero }}\" FallThrough:"; + OS << CondJump.FallThrough << "];\n"; + OS << "Block" << getBlockId(Block); + OS << "Exit:0 -> Block" << getBlockId(*CondJump.Zero) << ";\n"; + OS << "Block" << getBlockId(Block); + OS << "Exit:1 -> Block" << getBlockId(*CondJump.NonZero) + << ";\n"; + }, + [&](const CFG::BasicBlock::FunctionReturn &Return) { + OS << "Block" << getBlockId(Block) + << "Exit [label=\"FunctionReturn[" + << Return.Info->MF->getName() << "]\"];\n"; + OS << "Return values: " << stackToString(Return.RetValues); + }, + [&](const CFG::BasicBlock::Terminated &) { + OS << "Block" << getBlockId(Block) + << "Exit [label=\"Terminated\"];\n"; + }, + [&](const CFG::BasicBlock::Unreachable &) { + OS << "Block" << getBlockId(Block) + << "Exit [label=\"Unreachable\"];\n"; + }, + [&](const CFG::BasicBlock::InvalidExit &) { + assert(0 && "Invalid basic block exit"); + }}, + Block.Exit); + OS << "\n"; +} + +void StackLayoutPrinter::operator()(CFG::BasicBlock const &Block, + bool IsMainEntry) { + if (IsMainEntry) { + OS << "Entry [label=\"Entry\"];\n"; + OS << "Entry -> Block" << getBlockId(Block) << ";\n"; + } + while (!BlocksToPrint.empty()) { + CFG::BasicBlock const *block = *BlocksToPrint.begin(); + BlocksToPrint.erase(BlocksToPrint.begin()); + printBlock(*block); + } +} + +void StackLayoutPrinter::operator()(CFG::FunctionInfo const &Info) { + OS << "Function: " << Info.MF->getName() << "("; + for (const StackSlot &ParamSlot : Info.Parameters) { + if (const auto *Slot = std::get_if(&ParamSlot)) + OS << printReg(Slot->VirtualReg, nullptr, 0, nullptr) << ' '; + else if (std::holds_alternative(ParamSlot)) + OS << "[unused param] "; + else + llvm_unreachable("Unexpected stack slot"); + } + OS << ");\n"; + OS << "FunctionEntry " << " -> Block" << getBlockId(*Info.Entry) << ";\n"; + (*this)(*Info.Entry, false); +} + +void StackLayoutPrinter::printBlock(CFG::BasicBlock const &Block) { + OS << "Block" << getBlockId(Block) << " [\n"; + // Verify that the entries of this block exit into this block. + for (auto const &entry : Block.Entries) { + std::visit( + Overload{[&](CFG::BasicBlock::Jump const &Jump) { + assert(Jump.Target == &Block); + }, + [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { + assert(ConditionalJump.Zero == &Block || + ConditionalJump.NonZero == &Block); + }, + [&](auto const &) { + llvm_unreachable("Invalid control flow graph"); + }}, + entry->Exit); + } + + auto const &BlockInfo = Layout.blockInfos.at(&Block); + OS << stackToString(BlockInfo.entryLayout) << "\n"; + for (auto const &Operation : Block.Operations) { + OS << "\n"; + auto EntryLayout = Layout.operationEntryLayout.at(&Operation); + OS << stackToString(EntryLayout) << "\n"; + std::visit(Overload{[&](CFG::FunctionCall const &Call) { + const MachineOperand *Callee = + Call.Call->explicit_uses().begin(); + OS << Callee->getGlobal()->getName(); + }, + [&](CFG::BuiltinCall const &Call) { + OS << getInstName(Call.Builtin); + }, + [&](CFG::Assignment const &Assignment) { + OS << "Assignment("; + for (const auto &Var : Assignment.Variables) + OS << printReg(Var.VirtualReg, nullptr, 0, nullptr) + << ", "; + OS << ")"; + }}, + Operation.Operation); + OS << "\n"; + + assert(Operation.Input.size() <= EntryLayout.size()); + for (size_t i = 0; i < Operation.Input.size(); ++i) + EntryLayout.pop_back(); + EntryLayout += Operation.Output; + OS << stackToString(EntryLayout) << "\n"; + } + OS << "\n"; + OS << stackToString(BlockInfo.exitLayout) << "\n"; + OS << "];\n"; + + std::visit( + Overload{[&](CFG::BasicBlock::InvalidExit const &) { + assert(0 && "Invalid basic block exit"); + }, + [&](CFG::BasicBlock::Jump const &Jump) { + OS << "Block" << getBlockId(Block) << "Exit [label=\""; + if (Jump.Backwards) + OS << "Backwards"; + OS << "Jump\"];\n"; + OS << "Block" << getBlockId(Block) << "Exit -> Block" + << getBlockId(*Jump.Target) << ";\n"; + }, + [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { + OS << "Block" << getBlockId(Block) << "Exit [label=\"{ "; + OS << stackSlotToString(ConditionalJump.Condition); + OS << "| { <0> Zero | <1> NonZero }}\"];\n"; + OS << "Block" << getBlockId(Block); + OS << "Exit:0 -> Block" << getBlockId(*ConditionalJump.Zero) + << ";\n"; + OS << "Block" << getBlockId(Block); + OS << "Exit:1 -> Block" << getBlockId(*ConditionalJump.NonZero) + << ";\n"; + }, + [&](CFG::BasicBlock::FunctionReturn const &Return) { + OS << "Block" << getBlockId(Block) + << "Exit [label=\"FunctionReturn[" + << Return.Info->MF->getName() << "]\"];\n"; + }, + [&](CFG::BasicBlock::Terminated const &) { + OS << "Block" << getBlockId(Block) + << "Exit [label=\"Terminated\"];\n"; + }, + [&](CFG::BasicBlock::Unreachable const &) { + OS << "Block" << getBlockId(Block) + << "Exit [label=\"Unreachable\"];\n"; + }}, + Block.Exit); + OS << "\n"; +} + +std::string StackLayoutPrinter::getBlockId(CFG::BasicBlock const &Block) { + std::string Name = std::to_string(Block.MBB->getNumber()) + "." + + std::string(Block.MBB->getName()); + if (auto It = BlockIds.find(&Block); It != BlockIds.end()) + return std::to_string(It->second) + "(" + Name + ")"; + + size_t Id = BlockIds[&Block] = BlockCount++; + BlocksToPrint.emplace_back(&Block); + return std::to_string(Id) + "(" + Name + ")"; +} +#endif // NDEBUG diff --git a/llvm/lib/Target/EVM/EVMStackDebug.h b/llvm/lib/Target/EVM/EVMStackDebug.h new file mode 100644 index 000000000000..efe39f20f084 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackDebug.h @@ -0,0 +1,69 @@ +//===---- EVMStackDebug.h - Debugging of the stackification -----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares classes for dumping a state of stackifcation related data +// structures and algorithms. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMSTACKDEBUG_H +#define LLVM_LIB_TARGET_EVM_EVMSTACKDEBUG_H + +#include "EVMControlFlowGraph.h" +#include "llvm/CodeGen/MachineFunction.h" +#include +#include +#include +#include +#include + +namespace llvm { + +struct StackLayout; + +const Function *getCalledFunction(const MachineInstr &MI); +std::string stackSlotToString(const StackSlot &Slot); +std::string stackToString(Stack const &S); + +#ifndef NDEBUG +class ControlFlowGraphPrinter { +public: + ControlFlowGraphPrinter(raw_ostream &OS) : OS(OS) {} + void operator()(const CFG &Cfg); + +private: + void operator()(const CFG::FunctionInfo &Info); + std::string getBlockId(const CFG::BasicBlock &Block); + void printBlock(const CFG::BasicBlock &Block); + + raw_ostream &OS; +}; + +class StackLayoutPrinter { +public: + StackLayoutPrinter(raw_ostream &OS, const StackLayout &StackLayout) + : OS(OS), Layout(StackLayout) {} + + void operator()(CFG::BasicBlock const &Block, bool IsMainEntry = true); + void operator()(CFG::FunctionInfo const &Info); + +private: + void printBlock(CFG::BasicBlock const &Block); + std::string getBlockId(CFG::BasicBlock const &Block); + + raw_ostream &OS; + const StackLayout &Layout; + std::map BlockIds; + size_t BlockCount = 0; + std::list BlocksToPrint; +}; +#endif // NDEBUG + +} // end namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMSTACKDEBUG_H diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp new file mode 100644 index 000000000000..7e739a746379 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -0,0 +1,843 @@ +//===---- EVMStackLayoutGenerator.h - Stack layout generator ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the stack layout generator which for each operation +// finds complete stack layout that: +// - has the slots required for the operation at the stack top. +// - will have the operation result in a layout that makes it easy to achieve +// the next desired layout. +// It also finds an entering/exiting stack layout for each block. +// +//===----------------------------------------------------------------------===// + +#include "EVMStackLayoutGenerator.h" +#include "EVMHelperUtilities.h" +#include "EVMRegisterInfo.h" +#include "EVMStackDebug.h" +#include "EVMStackShuffler.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/IR/DebugLoc.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" + +using namespace llvm; + +#define DEBUG_TYPE "evm-stack-layout-generator" + +StackLayout StackLayoutGenerator::run(const CFG &Cfg) { + StackLayout Layout; + StackLayoutGenerator LayoutGenerator{Layout, &Cfg.FuncInfo}; + LayoutGenerator.processEntryPoint(*Cfg.FuncInfo.Entry, &Cfg.FuncInfo); + + LLVM_DEBUG({ + dbgs() << "************* Stack Layout *************\n"; + StackLayoutPrinter P(dbgs(), Layout); + P(Cfg.FuncInfo); + }); + + return Layout; +} + +StackLayoutGenerator::StackLayoutGenerator( + StackLayout &Layout, CFG::FunctionInfo const *FunctionInfo) + : Layout(Layout), CurrentFunctionInfo(FunctionInfo) {} + +namespace { + +/// Returns all stack too deep errors that would occur when shuffling \p Source +/// to \p Target. +std::vector +findStackTooDeep(Stack const &Source, Stack const &Target) { + Stack CurrentStack = Source; + std::vector Errors; + + auto getVariableChoices = [](auto &&SlotRange) { + std::vector Result; + for (auto const &Slot : SlotRange) + if (auto const *VarSlot = std::get_if(&Slot)) + if (!EVMUtils::contains(Result, VarSlot->VirtualReg)) + Result.push_back(VarSlot->VirtualReg); + return Result; + }; + + ::createStackLayout( + CurrentStack, Target, + [&](unsigned I) { + if (I > 16) + Errors.emplace_back(StackLayoutGenerator::StackTooDeep{ + I - 16, + getVariableChoices(EVMUtils::take_last(CurrentStack, I + 1))}); + }, + [&](StackSlot const &Slot) { + if (canBeFreelyGenerated(Slot)) + return; + + if (auto depth = + EVMUtils::findOffset(EVMUtils::get_reverse(CurrentStack), Slot); + depth && *depth >= 16) + Errors.emplace_back(StackLayoutGenerator::StackTooDeep{ + *depth - 15, getVariableChoices( + EVMUtils::take_last(CurrentStack, *depth + 1))}); + }, + [&]() {}); + return Errors; +} + +/// Returns the ideal stack to have before executing an operation that outputs +/// \p OperationOutput, s.t. shuffling to \p Post is cheap (excluding the +/// input of the operation itself). If \p GenerateSlotOnTheFly returns true for +/// a slot, this slot should not occur in the ideal stack, but rather be +/// generated on the fly during shuffling. +template +Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, + Callable GenerateSlotOnTheFly) { + struct PreviousSlot { + size_t slot; + }; + using LayoutT = std::vector>; + + // Determine the number of slots that have to be on stack before executing the + // operation (excluding the inputs of the operation itself). That is slots + // that should not be generated on the fly and are not outputs of the + // operation. + size_t PreOperationLayoutSize = Post.size(); + for (auto const &Slot : Post) + if (EVMUtils::contains(OperationOutput, Slot) || GenerateSlotOnTheFly(Slot)) + --PreOperationLayoutSize; + + // The symbolic layout directly after the operation has the form + // PreviousSlot{0}, ..., PreviousSlot{n}, [output<0>], ..., [output] + LayoutT Layout; + for (size_t Index = 0; Index < PreOperationLayoutSize; ++Index) + Layout.emplace_back(PreviousSlot{Index}); + Layout += OperationOutput; + + // Shortcut for trivial case. + if (Layout.empty()) + return Stack{}; + + // Next we will shuffle the layout to the Post stack using ShuffleOperations + // that are aware of PreviousSlot's. + struct ShuffleOperations { + LayoutT &Layout; + const Stack &Post; + std::set Outputs; + Multiplicity Mult; + Callable GenerateSlotOnTheFly; + ShuffleOperations(LayoutT &Layout, Stack const &Post, + Callable GenerateSlotOnTheFly) + : Layout(Layout), Post(Post), + GenerateSlotOnTheFly(GenerateSlotOnTheFly) { + for (auto const &LayoutSlot : Layout) + if (const StackSlot *Slot = std::get_if(&LayoutSlot)) + Outputs.insert(*Slot); + + for (auto const &LayoutSlot : Layout) + if (const StackSlot *Slot = std::get_if(&LayoutSlot)) + --Mult[*Slot]; + + for (auto &&Slot : Post) + if (Outputs.count(Slot) || GenerateSlotOnTheFly(Slot)) + ++Mult[Slot]; + } + + bool isCompatible(size_t Source, size_t Target) { + return Source < Layout.size() && Target < Post.size() && + (std::holds_alternative(Post.at(Target)) || + std::visit(Overload{[&](const PreviousSlot &) { + return !Outputs.count(Post.at(Target)) && + !GenerateSlotOnTheFly( + Post.at(Target)); + }, + [&](const StackSlot &S) { + return S == Post.at(Target); + }}, + Layout.at(Source))); + } + + bool sourceIsSame(size_t Lhs, size_t Rhs) { + return std::visit( + Overload{ + [&](PreviousSlot const &, PreviousSlot const &) { return true; }, + [&](StackSlot const &Lhs, StackSlot const &Rhs) { + return Lhs == Rhs; + }, + [&](auto const &, auto const &) { return false; }}, + Layout.at(Lhs), Layout.at(Rhs)); + } + + int sourceMultiplicity(size_t Offset) { + return std::visit( + Overload{[&](PreviousSlot const &) { return 0; }, + [&](StackSlot const &S) { return Mult.at(S); }}, + Layout.at(Offset)); + } + + int targetMultiplicity(size_t Offset) { + if (!Outputs.count(Post.at(Offset)) && + !GenerateSlotOnTheFly(Post.at(Offset))) + return 0; + return Mult.at(Post.at(Offset)); + } + + bool targetIsArbitrary(size_t Offset) { + return Offset < Post.size() && + std::holds_alternative(Post.at(Offset)); + } + + void swap(size_t I) { + assert(!std::holds_alternative( + Layout.at(Layout.size() - I - 1)) || + !std::holds_alternative(Layout.back())); + std::swap(Layout.at(Layout.size() - I - 1), Layout.back()); + } + + size_t sourceSize() { return Layout.size(); } + + size_t targetSize() { return Post.size(); } + + void pop() { Layout.pop_back(); } + + void pushOrDupTarget(size_t Offset) { Layout.push_back(Post.at(Offset)); } + }; + + Shuffler::shuffle(Layout, Post, GenerateSlotOnTheFly); + + // Now we can construct the ideal layout before the operation. + // "layout" has shuffled the PreviousSlot{x} to new places using minimal + // operations to move the operation output in place. The resulting permutation + // of the PreviousSlot yields the ideal positions of slots before the + // operation, i.e. if PreviousSlot{2} is at a position at which Post contains + // VariableSlot{"tmp"}, then we want the variable tmp in the slot at offset 2 + // in the layout before the operation. + assert(Layout.size() == Post.size()); + std::vector> IdealLayout(Post.size(), std::nullopt); + for (unsigned Idx = 0; Idx < std::min(Layout.size(), Post.size()); ++Idx) { + auto &Slot = Post[Idx]; + auto &IdealPosition = Layout[Idx]; + if (PreviousSlot *PrevSlot = std::get_if(&IdealPosition)) + IdealLayout.at(PrevSlot->slot) = Slot; + } + + // The tail of layout must have contained the operation outputs and will not + // have been assigned slots in the last loop. + while (!IdealLayout.empty() && !IdealLayout.back()) + IdealLayout.pop_back(); + + assert(IdealLayout.size() == PreOperationLayoutSize); + + Stack Result; + for (const auto &Item : IdealLayout) { + assert(Item); + Result.emplace_back(*Item); + } + + return Result; +} + +} // end anonymous namespace + +Stack StackLayoutGenerator::propagateStackThroughOperation( + Stack ExitStack, const CFG::Operation &Operation, + bool AggressiveStackCompression) { + // Enable aggressive stack compression for recursive calls. + if (std::holds_alternative(Operation.Operation)) + // TODO: compress stack for recursive functions. + AggressiveStackCompression = false; + + // This is a huge tradeoff between code size, gas cost and stack size. + auto generateSlotOnTheFly = [&](StackSlot const &Slot) { + return AggressiveStackCompression && canBeFreelyGenerated(Slot); + }; + + // Determine the ideal permutation of the slots in ExitLayout that are not + // operation outputs (and not to be generated on the fly), s.t. shuffling the + // 'IdealStack + Operation.output' to ExitLayout is cheap. + Stack IdealStack = + createIdealLayout(Operation.Output, ExitStack, generateSlotOnTheFly); + + // Make sure the resulting previous slots do not overlap with any assignmed + // variables. + if (auto const *Assignment = + std::get_if(&Operation.Operation)) + for (auto &StackSlot : IdealStack) + if (auto const *VarSlot = std::get_if(&StackSlot)) + assert(!EVMUtils::contains(Assignment->Variables, *VarSlot)); + + // Since stack+Operation.output can be easily shuffled to ExitLayout, the + // desired layout before the operation is stack+Operation.input; + IdealStack += Operation.Input; + + // Store the exact desired operation entry layout. The stored layout will be + // recreated by the code transform before executing the operation. However, + // this recreation can produce slots that can be freely generated or are + // duplicated, i.e. we can compress the stack afterwards without causing + // problems for code generation later. + Layout.operationEntryLayout[&Operation] = IdealStack; + + // Remove anything from the stack top that can be freely generated or dupped + // from deeper on the stack. + while (!IdealStack.empty()) { + if (canBeFreelyGenerated(IdealStack.back())) + IdealStack.pop_back(); + else if (auto Offset = EVMUtils::findOffset( + EVMUtils::drop_first(EVMUtils::get_reverse(IdealStack), 1), + IdealStack.back())) { + if (*Offset + 2 < 16) + IdealStack.pop_back(); + else + break; + } else + break; + } + + return IdealStack; +} + +Stack StackLayoutGenerator::propagateStackThroughBlock( + Stack ExitStack, CFG::BasicBlock const &Block, + bool AggressiveStackCompression) { + Stack CurrentStack = ExitStack; + for (auto &Operation : EVMUtils::get_reverse(Block.Operations)) { + Stack NewStack = propagateStackThroughOperation(CurrentStack, Operation, + AggressiveStackCompression); + if (!AggressiveStackCompression && + !findStackTooDeep(NewStack, CurrentStack).empty()) + // If we had stack errors, run again with aggressive stack compression. + return propagateStackThroughBlock(std::move(ExitStack), Block, true); + CurrentStack = std::move(NewStack); + } + return CurrentStack; +} + +void StackLayoutGenerator::processEntryPoint( + CFG::BasicBlock const &Entry, CFG::FunctionInfo const *FunctionInfo) { + std::list ToVisit{&Entry}; + std::set Visited; + + // TODO: check whether visiting only a subset of these in the outer iteration + // below is enough. + std::list> + BackwardsJumps = collectBackwardsJumps(Entry); + while (!ToVisit.empty()) { + // First calculate stack layouts without walking backwards jumps, i.e. + // assuming the current preliminary entry layout of the backwards jump + // target as the initial exit layout of the backwards-jumping block. + while (!ToVisit.empty()) { + CFG::BasicBlock const *Block = *ToVisit.begin(); + ToVisit.pop_front(); + + if (Visited.count(Block)) + continue; + + if (std::optional ExitLayout = + getExitLayoutOrStageDependencies(*Block, Visited, ToVisit)) { + Visited.emplace(Block); + auto &Info = Layout.blockInfos[Block]; + Info.exitLayout = *ExitLayout; + Info.entryLayout = propagateStackThroughBlock(Info.exitLayout, *Block); + for (auto Entry : Block->Entries) + ToVisit.emplace_back(Entry); + } + } + + // Determine which backwards jumps still require fixing and stage revisits + // of appropriate nodes. + for (auto [JumpingBlock, Target] : BackwardsJumps) { + // This block jumps backwards, but does not provide all slots required by + // the jump target on exit. Therefore we need to visit the subgraph + // between 'Target' and 'JumpingBlock' again. + auto StartIt = std::begin(Layout.blockInfos[Target].entryLayout); + auto EndIt = std::end(Layout.blockInfos[Target].entryLayout); + if (std::any_of(StartIt, EndIt, + [exitLayout = Layout.blockInfos[JumpingBlock].exitLayout]( + StackSlot const &Slot) { + return !EVMUtils::contains(exitLayout, Slot); + })) { + // In particular we can visit backwards starting from 'JumpingBlock' + // and mark all entries to-be-visited again until we hit 'Target'. + ToVisit.emplace_front(JumpingBlock); + // Since we are likely to permute the entry layout of 'Target', we + // also visit its entries again. This is not required for correctness, + // since the set of stack slots will match, but it may move some + // required stack shuffling from the loop condition to outside the loop. + for (CFG::BasicBlock const *Entry : Target->Entries) + Visited.erase(Entry); + + EVMUtils::BreadthFirstSearch{{JumpingBlock}} + .run([&Visited, Target = Target](const CFG::BasicBlock *Block, + auto AddChild) { + Visited.erase(Block); + if (Block == Target) + return; + for (auto const *Entry : Block->Entries) + AddChild(Entry); + }); + // While the shuffled layout for 'Target' will be compatible, it can + // be worthwhile propagating it further up once more. This would mean + // not stopping at Block == Target above, resp. even doing + // Visited.clear() here, revisiting the entire graph. This is a tradeoff + // between the runtime of this process and the optimality of the result. + // Also note that while visiting the entire graph again *can* be + // helpful, it can also be detrimental. + } + } + } + + stitchConditionalJumps(Entry); + fillInJunk(Entry, FunctionInfo); +} + +std::optional StackLayoutGenerator::getExitLayoutOrStageDependencies( + const CFG::BasicBlock &Block, + const std::set &Visited, + std::list &ToVisit) const { + return std::visit( + Overload{ + [&](CFG::BasicBlock::Jump const &Jump) -> std::optional { + if (Jump.Backwards) { + // Choose the best currently known entry layout of the jump target + // as initial exit. Note that this may not yet be the final + // layout. + auto It = Layout.blockInfos.find(Jump.Target); + if (It == Layout.blockInfos.end()) + return Stack{}; + + return It->second.entryLayout; + } + // If the current iteration has already visited the jump target, + // start from its entry layout. + if (Visited.count(Jump.Target)) + return Layout.blockInfos.at(Jump.Target).entryLayout; + // Otherwise stage the jump target for visit and defer the current + // block. + ToVisit.emplace_front(Jump.Target); + return std::nullopt; + }, + [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) + -> std::optional { + bool ZeroVisited = Visited.count(ConditionalJump.Zero); + bool NonZeroVisited = Visited.count(ConditionalJump.NonZero); + + if (ZeroVisited && NonZeroVisited) { + // If the current iteration has already Visited both jump targets, + // start from its entry layout. + Stack CombonedStack = combineStack( + Layout.blockInfos.at(ConditionalJump.Zero).entryLayout, + Layout.blockInfos.at(ConditionalJump.NonZero).entryLayout); + // Additionally, the jump condition has to be at the stack top at + // exit. + CombonedStack.emplace_back(ConditionalJump.Condition); + return CombonedStack; + } + + // If one of the jump targets has not been visited, stage it for + // visit and defer the current block. + if (!ZeroVisited) + ToVisit.emplace_front(ConditionalJump.Zero); + + if (!NonZeroVisited) + ToVisit.emplace_front(ConditionalJump.NonZero); + + return std::nullopt; + }, + [&](CFG::BasicBlock::FunctionReturn const &FunctionReturn) + -> std::optional { + // A function return needs the return variables and the function + // return label slot on stack. + assert(FunctionReturn.Info); + Stack ReturnStack = FunctionReturn.RetValues; + ReturnStack.emplace_back( + FunctionReturnLabelSlot{FunctionReturn.Info->MF}); + return ReturnStack; + }, + [&](CFG::BasicBlock::Terminated const &) -> std::optional { + // A terminating block can have an empty stack on exit. + return Stack{}; + }, + [&](CFG::BasicBlock::Unreachable const &) -> std::optional { + // A unreachable block can have an empty stack on exit. + return Stack{}; + }, + [](CFG::BasicBlock::InvalidExit const &) -> std::optional { + llvm_unreachable("Unexpected BB terminator"); + }}, + Block.Exit); +} + +std::list> +StackLayoutGenerator::collectBackwardsJumps( + CFG::BasicBlock const &Entry) const { + std::list> + BackwardsJumps; + EVMUtils::BreadthFirstSearch{{&Entry}}.run( + [&](CFG::BasicBlock const *Block, auto AddChild) { + std::visit( + Overload{ + [&](CFG::BasicBlock::InvalidExit const &) { + llvm_unreachable("Unexpected BB terminator"); + }, + [&](CFG::BasicBlock::Jump const &Jump) { + if (Jump.Backwards) + BackwardsJumps.emplace_back(Block, Jump.Target); + AddChild(Jump.Target); + }, + [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { + AddChild(ConditionalJump.Zero); + AddChild(ConditionalJump.NonZero); + }, + [&](CFG::BasicBlock::FunctionReturn const &) {}, + [&](CFG::BasicBlock::Terminated const &) {}, + [&](CFG::BasicBlock::Unreachable const &) {}, + }, + Block->Exit); + }); + return BackwardsJumps; +} + +void StackLayoutGenerator::stitchConditionalJumps( + CFG::BasicBlock const &Block) { + EVMUtils::BreadthFirstSearch BFS{{&Block}}; + BFS.run([&](CFG::BasicBlock const *Block, auto AddChild) { + auto &Info = Layout.blockInfos.at(Block); + std::visit( + Overload{ + [&](CFG::BasicBlock::InvalidExit const &) { + llvm_unreachable("Unexpected BB terminator"); + }, + [&](CFG::BasicBlock::Jump const &Jump) { + if (!Jump.Backwards) + AddChild(Jump.Target); + }, + [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { + auto &ZeroTargetInfo = Layout.blockInfos.at(ConditionalJump.Zero); + auto &NonZeroTargetInfo = + Layout.blockInfos.at(ConditionalJump.NonZero); + Stack ExitLayout = Info.exitLayout; + + // The last block must have produced the condition at the stack + // top. + assert(!ExitLayout.empty()); + assert(ExitLayout.back() == ConditionalJump.Condition); + // The condition is consumed by the jump. + ExitLayout.pop_back(); + + auto FixJumpTargetEntry = + [&](Stack const &OriginalEntryLayout) -> Stack { + Stack NewEntryLayout = ExitLayout; + // Whatever the block being jumped to does not actually require, + // can be marked as junk. + for (auto &Slot : NewEntryLayout) + if (!EVMUtils::contains(OriginalEntryLayout, Slot)) + Slot = JunkSlot{}; +#ifndef NDEBUG + // Make sure everything the block being jumped to requires is + // actually present or can be generated. + for (auto const &Slot : OriginalEntryLayout) + assert(canBeFreelyGenerated(Slot) || + EVMUtils::contains(NewEntryLayout, Slot)); +#endif // NDEBUG + return NewEntryLayout; + }; + + ZeroTargetInfo.entryLayout = + FixJumpTargetEntry(ZeroTargetInfo.entryLayout); + NonZeroTargetInfo.entryLayout = + FixJumpTargetEntry(NonZeroTargetInfo.entryLayout); + AddChild(ConditionalJump.Zero); + AddChild(ConditionalJump.NonZero); + }, + [&](CFG::BasicBlock::FunctionReturn const &) {}, + [&](CFG::BasicBlock::Terminated const &) {}, + [&](CFG::BasicBlock::Unreachable const &) {}, + }, + Block->Exit); + }); +} + +Stack StackLayoutGenerator::combineStack(Stack const &Stack1, + Stack const &Stack2) { + // TODO: it would be nicer to replace this by a constructive algorithm. + // Currently it uses a reduced version of the Heap Algorithm to partly + // brute-force, which seems to work decently well. + + Stack CommonPrefix; + for (unsigned Idx = 0; Idx < std::min(Stack1.size(), Stack2.size()); ++Idx) { + const StackSlot &Slot1 = Stack1[Idx]; + const StackSlot &Slot2 = Stack2[Idx]; + if (!(Slot1 == Slot2)) + break; + CommonPrefix.emplace_back(Slot1); + } + + Stack Stack1Tail, Stack2Tail; + for (auto Slot : EVMUtils::drop_first(Stack1, CommonPrefix.size())) + Stack1Tail.emplace_back(Slot); + + for (auto Slot : EVMUtils::drop_first(Stack2, CommonPrefix.size())) + Stack2Tail.emplace_back(Slot); + + if (Stack1Tail.empty()) + return CommonPrefix + compressStack(Stack2Tail); + + if (Stack2Tail.empty()) + return CommonPrefix + compressStack(Stack1Tail); + + Stack Candidate; + for (auto Slot : Stack1Tail) + if (!EVMUtils::contains(Candidate, Slot)) + Candidate.emplace_back(Slot); + + for (auto Slot : Stack2Tail) + if (!EVMUtils::contains(Candidate, Slot)) + Candidate.emplace_back(Slot); + + { + auto RemIt = std::remove_if( + Candidate.begin(), Candidate.end(), [](StackSlot const &Slot) { + return std::holds_alternative(Slot) || + std::holds_alternative(Slot) || + std::holds_alternative(Slot); + }); + Candidate.erase(RemIt, Candidate.end()); + } + + auto evaluate = [&](Stack const &Candidate) -> size_t { + size_t NumOps = 0; + Stack TestStack = Candidate; + auto Swap = [&](unsigned SwapDepth) { + ++NumOps; + if (SwapDepth > 16) + NumOps += 1000; + }; + + auto DupOrPush = [&](StackSlot const &Slot) { + if (canBeFreelyGenerated(Slot)) + return; + + Stack Tmp = CommonPrefix; + Tmp += TestStack; + + auto Depth = EVMUtils::findOffset(EVMUtils::get_reverse(Tmp), Slot); + if (Depth && *Depth >= 16) + NumOps += 1000; + }; + createStackLayout(TestStack, Stack1Tail, Swap, DupOrPush, [&]() {}); + TestStack = Candidate; + createStackLayout(TestStack, Stack2Tail, Swap, DupOrPush, [&]() {}); + return NumOps; + }; + + // See https://en.wikipedia.org/wiki/Heap's_algorithm + size_t N = Candidate.size(); + Stack BestCandidate = Candidate; + size_t BestCost = evaluate(Candidate); + std::vector C(N, 0); + size_t I = 1; + while (I < N) { + if (C[I] < I) { + if (I & 1) + std::swap(Candidate.front(), Candidate[I]); + else + std::swap(Candidate[C[I]], Candidate[I]); + + size_t Cost = evaluate(Candidate); + if (Cost < BestCost) { + BestCost = Cost; + BestCandidate = Candidate; + } + ++C[I]; + // Note that for a proper implementation of the Heap algorithm this would + // need to revert back to 'I = 1'. However, the incorrect implementation + // produces decent result and the proper version would have N! complexity + // and is thereby not feasible. + ++I; + } else { + C[I] = 0; + ++I; + } + } + + return CommonPrefix + BestCandidate; +} + +Stack StackLayoutGenerator::compressStack(Stack CurStack) { + std::optional FirstDupOffset; + do { + if (FirstDupOffset) { + if (*FirstDupOffset != (CurStack.size() - 1)) + std::swap(CurStack.at(*FirstDupOffset), CurStack.back()); + CurStack.pop_back(); + FirstDupOffset.reset(); + } + + auto I = CurStack.rbegin(), E = CurStack.rend(); + for (size_t Depth = 0; I < E; ++I, ++Depth) { + StackSlot &Slot = *I; + if (canBeFreelyGenerated(Slot)) { + FirstDupOffset = CurStack.size() - Depth - 1; + break; + } + + if (auto DupDepth = EVMUtils::findOffset( + EVMUtils::drop_first(EVMUtils::get_reverse(CurStack), Depth + 1), + Slot)) { + if (Depth + *DupDepth <= 16) { + FirstDupOffset = CurStack.size() - Depth - 1; + break; + } + } + } + } while (FirstDupOffset); + return CurStack; +} + +/// Returns the number of operations required to transform stack \p Source to +/// \p Target. +size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { + size_t OpGas = 0; + auto Swap = [&](unsigned SwapDepth) { + if (SwapDepth > 16) + OpGas += 1000; + else + OpGas += 3; // SWAP* gas price; + }; + + auto DupOrPush = [&](StackSlot const &Slot) { + if (canBeFreelyGenerated(Slot)) + OpGas += 3; + else { + auto Depth = EVMUtils::findOffset(EVMUtils::get_reverse(Source), Slot); + if (!Depth) + llvm_unreachable("No slot in the stack"); + + if (*Depth < 16) + OpGas += 3; // DUP* gas price + else + OpGas += 1000; + } + }; + auto Pop = [&]() { OpGas += 2; }; + + createStackLayout(Source, Target, Swap, DupOrPush, Pop); + return OpGas; +} + +void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const &Block, + CFG::FunctionInfo const *FunctionInfo) { + /// Recursively adds junk to the subgraph starting on \p Entry. + /// Since it is only called on cut-vertices, the full subgraph retains proper + /// stack balance. + auto AddJunkRecursive = [&](CFG::BasicBlock const *Entry, size_t NumJunk) { + EVMUtils::BreadthFirstSearch BFS{{Entry}}; + + BFS.run([&](CFG::BasicBlock const *Block, auto AddChild) { + auto &BlockInfo = Layout.blockInfos.at(Block); + BlockInfo.entryLayout = + Stack{NumJunk, JunkSlot{}} + std::move(BlockInfo.entryLayout); + + for (auto const &Operation : Block->Operations) { + auto &OpEntryLayout = Layout.operationEntryLayout.at(&Operation); + OpEntryLayout = Stack{NumJunk, JunkSlot{}} + std::move(OpEntryLayout); + } + + BlockInfo.exitLayout = + Stack{NumJunk, JunkSlot{}} + std::move(BlockInfo.exitLayout); + + std::visit( + Overload{ + [&](CFG::BasicBlock::InvalidExit const &) { + llvm_unreachable("Unexpected BB terminator"); + }, + [&](CFG::BasicBlock::Jump const &Jump) { AddChild(Jump.Target); }, + [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { + AddChild(ConditionalJump.Zero); + AddChild(ConditionalJump.NonZero); + }, + [&](CFG::BasicBlock::FunctionReturn const &) { + llvm_unreachable("FunctionReturn : unexpected BB terminator"); + }, + [&](CFG::BasicBlock::Terminated const &) {}, + [&](CFG::BasicBlock::Unreachable const &) {}, + }, + Block->Exit); + }); + }; + + /// Returns the number of junk slots to be prepended to \p TargetLayout for + /// an optimal transition from \p EntryLayout to \p TargetLayout. + auto GetBestNumJunk = [&](Stack const &EntryLayout, + Stack const &TargetLayout) -> size_t { + size_t BestCost = EvaluateStackTransform(EntryLayout, TargetLayout); + size_t BestNumJunk = 0; + size_t MaxJunk = EntryLayout.size(); + for (size_t NumJunk = 1; NumJunk <= MaxJunk; ++NumJunk) { + size_t Cost = EvaluateStackTransform( + EntryLayout, Stack{NumJunk, JunkSlot{}} + TargetLayout); + if (Cost < BestCost) { + BestCost = Cost; + BestNumJunk = NumJunk; + } + } + return BestNumJunk; + }; + + if (FunctionInfo && !FunctionInfo->CanContinue && Block.AllowsJunk()) { + Stack Params; + for (const auto &Param : FunctionInfo->Parameters) + Params.emplace_back(Param); + std::reverse(Params.begin(), Params.end()); + size_t BestNumJunk = + GetBestNumJunk(Params, Layout.blockInfos.at(&Block).entryLayout); + if (BestNumJunk > 0) + AddJunkRecursive(&Block, BestNumJunk); + } + + /// Traverses the CFG and at each block that allows junk, i.e. that is a + /// cut-vertex that never leads to a function return, checks if adding junk + /// reduces the shuffling cost upon entering and if so recursively adds junk + /// to the spanned subgraph. + EVMUtils::BreadthFirstSearch{{&Block}}.run( + [&](CFG::BasicBlock const *Block, auto AddChild) { + if (Block->AllowsJunk()) { + auto &BlockInfo = Layout.blockInfos.at(Block); + Stack EntryLayout = BlockInfo.entryLayout; + Stack const &NextLayout = + Block->Operations.empty() + ? BlockInfo.exitLayout + : Layout.operationEntryLayout.at(&Block->Operations.front()); + if (EntryLayout != NextLayout) { + size_t BestNumJunk = GetBestNumJunk(EntryLayout, NextLayout); + if (BestNumJunk > 0) { + AddJunkRecursive(Block, BestNumJunk); + BlockInfo.entryLayout = EntryLayout; + } + } + } + + std::visit( + Overload{ + [&](CFG::BasicBlock::InvalidExit const &) { + llvm_unreachable("Invalid BB terminator"); + }, + [&](CFG::BasicBlock::Jump const &Jump) { + AddChild(Jump.Target); + }, + [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { + AddChild(ConditionalJump.Zero); + AddChild(ConditionalJump.NonZero); + }, + [&](CFG::BasicBlock::FunctionReturn const &) {}, + [&](CFG::BasicBlock::Terminated const &) {}, + [&](CFG::BasicBlock::Unreachable const &) {}, + }, + Block->Exit); + }); +} diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h new file mode 100644 index 000000000000..ff675b88f30d --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h @@ -0,0 +1,130 @@ +//===---- EVMStackLayoutGenerator.h - Stack layout generator ----*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file implements the stack layout generator which for each operation +// finds complete stack layout that: +// - has the slots required for the operation at the stack top. +// - will have the operation result in a layout that makes it easy to achieve +// the next desired layout. +// It also finds an entering/exiting stack layout for each block. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMSTACKLAYOUTGENERATOR_H +#define LLVM_LIB_TARGET_EVM_EVMSTACKLAYOUTGENERATOR_H + +#include "EVMControlFlowGraph.h" +#include +#include + +namespace llvm { + +/// Returns the number of operations required to transform stack \p Source to +/// \p Target. +size_t EvaluateStackTransform(Stack Source, Stack const &Target); + +struct StackLayout { + struct BlockInfo { + /// Complete stack layout that is required for entering a block. + Stack entryLayout; + /// The resulting stack layout after executing the block. + Stack exitLayout; + }; + std::map blockInfos; + /// For each operation the complete stack layout that: + /// - has the slots required for the operation at the stack top. + /// - will have the operation result in a layout that makes it easy to achieve + /// the next desired layout. + std::map operationEntryLayout; +}; + +class StackLayoutGenerator { +public: + struct StackTooDeep { + /// Number of slots that need to be saved. + size_t deficit = 0; + /// Set of variables, eliminating which would decrease the stack deficit. + std::vector variableChoices; + }; + + static StackLayout run(CFG const &Cfg); + /// Returns all stack too deep errors for the given CFG. + static std::vector reportStackTooDeep(CFG const &Cfg); + +private: + StackLayoutGenerator(StackLayout &Context, + CFG::FunctionInfo const *FunctionInfo); + + /// Returns the optimal entry stack layout, s.t. \p Operation can be applied + /// to it and the result can be transformed to \p ExitStack with minimal stack + /// shuffling. Simultaneously stores the entry layout required for executing + /// the operation in Layout. + Stack propagateStackThroughOperation(Stack ExitStack, + CFG::Operation const &Operation, + bool AggressiveStackCompression = false); + + /// Returns the desired stack layout at the entry of \p Block, assuming the + /// layout after executing the block should be \p ExitStack. + Stack propagateStackThroughBlock(Stack ExitStack, + CFG::BasicBlock const &block, + bool AggressiveStackCompression = false); + + /// Main algorithm walking the graph from entry to exit and propagating back + /// the stack layouts to the entries. Iteratively reruns itself along + /// backwards jumps until the layout is stabilized. + void processEntryPoint(CFG::BasicBlock const &Entry, + CFG::FunctionInfo const *FunctionInfo = nullptr); + + /// Returns the best known exit layout of \p Block, if all dependencies are + /// already \p Visited. If not, adds the dependencies to \p DependencyList and + /// returns std::nullopt. + std::optional getExitLayoutOrStageDependencies( + CFG::BasicBlock const &Block, + std::set const &Visited, + std::list &DependencyList) const; + + /// Returns a pair of '{jumpingBlock, targetBlock}' for each backwards jump + /// in the graph starting at \p Eentry. + std::list> + collectBackwardsJumps(CFG::BasicBlock const &Entry) const; + + /// After the main algorithms, layouts at conditional jumps are merely + /// compatible, i.e. the exit layout of the jumping block is a superset of the + /// entry layout of the target block. This function modifies the entry layouts + /// of conditional jump targets, s.t., the entry layout of target blocks match + /// the exit layout of the jumping block exactly, except that slots not + /// required after the jump are marked as 'JunkSlot's. + void stitchConditionalJumps(CFG::BasicBlock const &Block); + + /// Calculates the ideal stack layout, s.t., both \p Stack1 and \p Stack2 can + /// be achieved with minimal stack shuffling when starting from the returned + /// layout. + static Stack combineStack(Stack const &Stack1, Stack const &Stack2); + + /// Walks through the CFG and reports any stack too deep errors that would + /// occur when generating code for it without countermeasures. + std::vector + reportStackTooDeep(CFG::BasicBlock const &Entry) const; + + /// Returns a copy of \p Stack stripped of all duplicates and slots that can + /// be freely generated. Attempts to create a layout that requires a minimal + /// amount of operations to reconstruct the original stack \p Stack. + static Stack compressStack(Stack Stack); + + /// Fills in junk when entering branches that do not need a clean stack in + /// case the result is cheaper. + void fillInJunk(CFG::BasicBlock const &Block, + CFG::FunctionInfo const *FunctionInfo = nullptr); + + StackLayout &Layout; + CFG::FunctionInfo const *CurrentFunctionInfo = nullptr; +}; + +} // end namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMSTACKLAYOUTGENERATOR_H diff --git a/llvm/lib/Target/EVM/EVMStackShuffler.h b/llvm/lib/Target/EVM/EVMStackShuffler.h new file mode 100644 index 000000000000..52432682a32d --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackShuffler.h @@ -0,0 +1,555 @@ +//===-- EVMStackShuffler.h - Implementation of stack shuffling ---*- C++ +//-*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file declares template algorithms to find optimal (cheapest) transition +// between two stack layouts using three shuffling primitives: `swap`, `dup`, +// and `pop`. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMSTACKSHUFFLER_H +#define LLVM_LIB_TARGET_EVM_EVMSTACKSHUFFLER_H + +#include "EVMControlFlowGraph.h" +#include "EVMHelperUtilities.h" +#include "llvm/CodeGen/MachineFunction.h" +#include +#include +#include +#include + +namespace llvm { + +/* +template struct Overload : Ts... { +using Ts::operator()...; +}; +template Overload(Ts...) -> Overload; + +template static inline SmallVector iota17(T begin, T end) { +SmallVector R(end - begin); +std::iota(R.begin(), R.end(), begin); +return R; +} +*/ +// Abstraction of stack shuffling operations. Can be defined as actual concept +// once we switch to C++20. Used as an interface for the stack shuffler below. +// The shuffle operation class is expected to internally keep track of a current +// stack layout (the "source layout") that the shuffler is supposed to shuffle +// to a fixed target stack layout. The shuffler works iteratively. At each +// iteration it instantiates an instance of the shuffle operations and queries +// it for various information about the current source stack layout and the +// target layout, as described in the interface below. Based on that information +// the shuffler decides which is the next optimal operation to perform on the +// stack and calls the corresponding entry point in the shuffling operations +// (swap, pushOrDupTarget or pop). +/* +template +concept ShuffleOperationConcept = + requires(ShuffleOperations ops, size_t sourceOffset, + size_t targetOffset, size_t depth) { + + // Returns true, iff the current slot at sourceOffset in source layout + // is a suitable slot at targetOffset. + { ops.isCompatible(sourceOffset, targetOffset) } + -> std::convertible_to; + + // Returns true, iff the slots at the two given source offsets are identical. + { ops.sourceIsSame(sourceOffset, sourceOffset) } -> + std::convertible_to; + + // Returns a positive integer n, if the slot at the given source offset + // needs n more copies. Returns a negative integer -n, if the slot at the + // given source offsets occurs n times too many. Returns zero if the amount + // of occurrences, in the current source layout, of the slot at the given + // source offset matches the desired amount of occurrences in the target. + { ops.sourceMultiplicity(sourceOffset) } -> std::convertible_to; + + // Returns a positive integer n, if the slot at the given target offset + // needs n more copies. Returns a negative integer -n, if the slot at the + // given target offsets occurs n times too many. Returns zero if the amount + // of occurrences, in the current source layout, of the slot at the given + // target offset matches the desired amount of occurrences in the target. + { ops.targetMultiplicity(targetOffset) } -> std::convertible_to; + + // Returns true, iff any slot is compatible with the given target offset. + { ops.targetIsArbitrary(targetOffset) } -> std::convertible_to; + + // Returns the number of slots in the source layout. + { ops.sourceSize() } -> std::convertible_to; + + // Returns the number of slots in the target layout. + { ops.targetSize() } -> std::convertible_to; + + // Swaps the top most slot in the source with the slot `depth` slots below + // the top. In terms of EVM opcodes this is supposed to be a `SWAP`. + // In terms of vectors this is supposed to be + //`std::swap(source.at(source.size() - depth - 1, source.top))`. + { ops.swap(depth) }; + + // Pops the top most slot in the source, i.e. the slot at offset + // ops.sourceSize() - 1. In terms of EVM opcodes this is `POP`. + // In terms of vectors this is `source.pop();`. + { ops.pop() }; + + // Dups or pushes the slot that is supposed to end up at the given + // target offset. + { ops.pushOrDupTarget(targetOffset) }; +}; +*/ + +/// Helper class that can perform shuffling of a source stack layout to a target +/// stack layout via abstracted shuffle operations. +template +class Shuffler { +public: + /// Executes the stack shuffling operations. Instantiates an instance of + /// ShuffleOperations in each iteration. Each iteration performs exactly one + /// operation that modifies the stack. After `shuffle`, source and target have + /// the same size and all slots in the source layout are compatible with the + /// slots at the same target offset. + template static void shuffle(Args &&...args) { + bool NeedsMoreShuffling = true; + // The shuffling algorithm should always terminate in polynomial time, but + // we provide a limit in case it does not terminate due to a bug. + size_t IterationCount = 0; + while (IterationCount < 1000 && + (NeedsMoreShuffling = shuffleStep(std::forward(args)...))) + ++IterationCount; + + if (NeedsMoreShuffling) + llvm_unreachable("Could not create stack layout after 1000 iterations."); + } + +private: + // If dupping an ideal slot causes a slot that will still be required to + // become unreachable, then dup the latter slot first. + // Returns true, if it performed a dup. + static bool dupDeepSlotIfRequired(ShuffleOperations &Ops) { + // Check if the stack is large enough for anything to potentially become + // unreachable. + if (Ops.sourceSize() < 15) + return false; + // Check whether any deep slot might still be needed later (i.e. we still + // need to reach it with a DUP or SWAP). + for (size_t SourceOffset = 0; SourceOffset < (Ops.sourceSize() - 15); + ++SourceOffset) { + // This slot needs to be moved. + if (!Ops.isCompatible(SourceOffset, SourceOffset)) { + // If the current top fixes the slot, swap it down now. + if (Ops.isCompatible(Ops.sourceSize() - 1, SourceOffset)) { + Ops.swap(Ops.sourceSize() - SourceOffset - 1); + return true; + } + // Bring up a slot to fix this now, if possible. + if (bringUpTargetSlot(Ops, SourceOffset)) + return true; + // Otherwise swap up the slot that will fix the offending slot. + for (auto offset = SourceOffset + 1; offset < Ops.sourceSize(); + ++offset) + if (Ops.isCompatible(offset, SourceOffset)) { + Ops.swap(Ops.sourceSize() - offset - 1); + return true; + } + // Otherwise give up - we will need stack compression or stack limit + // evasion. + } + // We need another copy of this slot. + else if (Ops.sourceMultiplicity(SourceOffset) > 0) { + // If this slot occurs again later, we skip this occurrence. + // TODO: use C++ 20 ranges::views::iota + if (const auto &R = iota17(SourceOffset + 1, Ops.sourceSize()); + std::any_of(R.begin(), R.end(), [&](size_t Offset) { + return Ops.sourceIsSame(SourceOffset, Offset); + })) + continue; + + // Bring up the target slot that would otherwise become unreachable. + for (size_t TargetOffset = 0; TargetOffset < Ops.targetSize(); + ++TargetOffset) + if (!Ops.targetIsArbitrary(TargetOffset) && + Ops.isCompatible(SourceOffset, TargetOffset)) { + Ops.pushOrDupTarget(TargetOffset); + return true; + } + } + } + return false; + } + + /// Finds a slot to dup or push with the aim of eventually fixing \p + /// TargetOffset in the target. In the simplest case, the slot at \p + /// TargetOffset has a multiplicity > 0, i.e. it can directly be dupped or + /// pushed and the next iteration will fix \p TargetOffset. But, in general, + /// there may already be enough copies of the slot that is supposed to end up + /// at \p TargetOffset on stack, s.t. it cannot be dupped again. In that case + /// there has to be a copy of the desired slot on stack already elsewhere that + /// is not yet in place (`nextOffset` below). The fact that ``nextOffset`` is + /// not in place means that we can (recursively) try bringing up the slot that + /// is supposed to end up at ``nextOffset`` in the *target*. When the target + /// slot at ``nextOffset`` is fixed, the current source slot at ``nextOffset`` + /// will be at the stack top, which is the slot required at \p TargetOffset. + static bool bringUpTargetSlot(ShuffleOperations &Ops, size_t TargetOffset) { + std::list ToVisit{TargetOffset}; + std::set Visited; + + while (!ToVisit.empty()) { + size_t Offset = *ToVisit.begin(); + ToVisit.erase(ToVisit.begin()); + Visited.emplace(Offset); + if (Ops.targetMultiplicity(Offset) > 0) { + Ops.pushOrDupTarget(Offset); + return true; + } + // There must be another slot we can dup/push that will lead to the target + // slot at ``offset`` to be fixed. + for (size_t NextOffset = 0; + NextOffset < std::min(Ops.sourceSize(), Ops.targetSize()); + ++NextOffset) + if (!Ops.isCompatible(NextOffset, NextOffset) && + Ops.isCompatible(NextOffset, Offset)) + if (!Visited.count(NextOffset)) + ToVisit.emplace_back(NextOffset); + } + return false; + } + + /// Performs a single stack operation, transforming the source layout closer + /// to the target layout. + template static bool shuffleStep(Args &&...args) { + ShuffleOperations Ops{std::forward(args)...}; + + // All source slots are final. + if (const auto &R = iota17(0u, Ops.sourceSize()); + std::all_of(R.begin(), R.end(), [&](size_t Index) { + return Ops.isCompatible(Index, Index); + })) { + // Bring up all remaining target slots, if any, or terminate otherwise. + if (Ops.sourceSize() < Ops.targetSize()) { + if (!dupDeepSlotIfRequired(Ops)) { + [[maybe_unused]] bool Res = bringUpTargetSlot(Ops, Ops.sourceSize()); + assert(Res); + } + return true; + } + return false; + } + + size_t SourceTop = Ops.sourceSize() - 1; + // If we no longer need the current stack top, we pop it, unless we need an + // arbitrary slot at this position in the target. + if (Ops.sourceMultiplicity(SourceTop) < 0 && + !Ops.targetIsArbitrary(SourceTop)) { + Ops.pop(); + return true; + } + + assert(Ops.targetSize() > 0); + + // If the top is not supposed to be exactly what is on top right now, try to + // find a lower position to swap it to. + if (!Ops.isCompatible(SourceTop, SourceTop) || + Ops.targetIsArbitrary(SourceTop)) + for (size_t Offset = 0; + Offset < std::min(Ops.sourceSize(), Ops.targetSize()); ++Offset) + // It makes sense to swap to a lower position, if + if (!Ops.isCompatible( + Offset, Offset) && // The lower slot is not already in position. + !Ops.sourceIsSame( + Offset, SourceTop) && // We would not just swap identical slots. + Ops.isCompatible( + SourceTop, + Offset)) { // The lower position wants to have this slot. + // We cannot swap that deep. + if (Ops.sourceSize() - Offset - 1 > 16) { + // If there is a reachable slot to be removed, park the current top + // there. + for (size_t SwapDepth = 16; SwapDepth > 0; --SwapDepth) + if (Ops.sourceMultiplicity(Ops.sourceSize() - 1 - SwapDepth) < + 0) { + Ops.swap(SwapDepth); + if (Ops.targetIsArbitrary(SourceTop)) + // Usually we keep a slot that is to-be-removed, if the + // current top is arbitrary. However, since we are in a + // stack-too-deep situation, pop it immediately to compress + // the stack (we can always push back junk in the end). + Ops.pop(); + return true; + } + // TODO: otherwise we rely on stack compression or stack-to-memory. + } + Ops.swap(Ops.sourceSize() - Offset - 1); + return true; + } + + // Ops.sourceSize() > Ops.targetSize() cannot be true anymore, since if the + // source top is no longer required, we already popped it, and if it is + // required, we already swapped it down to a suitable target position. + assert(Ops.sourceSize() <= Ops.targetSize()); + + // If a lower slot should be removed, try to bring up the slot that should + // end up there and bring it up. Note that after the cases above, there will + // always be a target slot to duplicate in this case. + for (size_t Offset = 0; Offset < Ops.sourceSize(); ++Offset) + if (!Ops.isCompatible( + Offset, Offset) && // The lower slot is not already in position. + Ops.sourceMultiplicity(Offset) < + 0 && // We have too many copies of this slot. + Offset <= + Ops.targetSize() && // There is a target slot at this position. + !Ops.targetIsArbitrary( + Offset)) { // And that target slot is not arbitrary. + if (!dupDeepSlotIfRequired(Ops)) { + [[maybe_unused]] bool Res = bringUpTargetSlot(Ops, Offset); + assert(Res); + } + return true; + } + + // At this point we want to keep all slots. + for (size_t i = 0; i < Ops.sourceSize(); ++i) + assert(Ops.sourceMultiplicity(i) >= 0); + assert(Ops.sourceSize() <= Ops.targetSize()); + + // If the top is not in position, try to find a slot that wants to be at the + // top and swap it up. + if (!Ops.isCompatible(SourceTop, SourceTop)) + for (size_t sourceOffset = 0; sourceOffset < Ops.sourceSize(); + ++sourceOffset) + if (!Ops.isCompatible(sourceOffset, sourceOffset) && + Ops.isCompatible(sourceOffset, SourceTop)) { + Ops.swap(Ops.sourceSize() - sourceOffset - 1); + return true; + } + + // If we still need more slots, produce a suitable one. + if (Ops.sourceSize() < Ops.targetSize()) { + if (!dupDeepSlotIfRequired(Ops)) { + [[maybe_unused]] bool Res = bringUpTargetSlot(Ops, Ops.sourceSize()); + assert(Res); + } + return true; + } + + // The stack has the correct size, each slot has the correct number of + // copies and the top is in position. + assert(Ops.sourceSize() == Ops.targetSize()); + size_t Size = Ops.sourceSize(); + for (size_t I = 0; I < Ops.sourceSize(); ++I) + assert(Ops.sourceMultiplicity(I) == 0 && + (Ops.targetIsArbitrary(I) || Ops.targetMultiplicity(I) == 0)); + assert(Ops.isCompatible(SourceTop, SourceTop)); + + const auto &SwappableOffsets = iota17(Size > 17 ? Size - 17 : 0u, Size); + + // If we find a lower slot that is out of position, but also compatible with + // the top, swap that up. + for (size_t Offset : SwappableOffsets) + if (!Ops.isCompatible(Offset, Offset) && + Ops.isCompatible(SourceTop, Offset)) { + Ops.swap(Size - Offset - 1); + return true; + } + + // Swap up any reachable slot that is still out of position. + for (size_t Offset : SwappableOffsets) + if (!Ops.isCompatible(Offset, Offset) && + !Ops.sourceIsSame(Offset, SourceTop)) { + Ops.swap(Size - Offset - 1); + return true; + } + + // We are in a stack-too-deep situation and try to reduce the stack size. + // If the current top is merely kept since the target slot is arbitrary, pop + // it. + if (Ops.targetIsArbitrary(SourceTop) && + Ops.sourceMultiplicity(SourceTop) <= 0) { + Ops.pop(); + return true; + } + + // If any reachable slot is merely kept, since the target slot is arbitrary, + // swap it up and pop it. + for (size_t Offset : SwappableOffsets) + if (Ops.targetIsArbitrary(Offset) && + Ops.sourceMultiplicity(Offset) <= 0) { + Ops.swap(Size - Offset - 1); + Ops.pop(); + return true; + } + + // We cannot avoid a stack-too-deep error. Repeat the above without + // restricting to reachable slots. + for (size_t Offset = 0; Offset < Size; ++Offset) + if (!Ops.isCompatible(Offset, Offset) && + Ops.isCompatible(SourceTop, Offset)) { + Ops.swap(Size - Offset - 1); + return true; + } + + for (size_t Offset = 0; Offset < Size; ++Offset) + if (!Ops.isCompatible(Offset, Offset) && + !Ops.sourceIsSame(Offset, SourceTop)) { + Ops.swap(Size - Offset - 1); + return true; + } + + llvm_unreachable("Unexpected state"); + } +}; + +/// A simple optimized map for mapping StackSlot to ints. +class Multiplicity { +public: + int &operator[](StackSlot const &Slot) { + if (auto *p = std::get_if(&Slot)) + return FunctionCallReturnLabelSlotMultiplicity[*p]; + if (std::holds_alternative(Slot)) + return FunctionReturnLabelSlotMultiplicity; + if (auto *p = std::get_if(&Slot)) + return VariableSlotMultiplicity[*p]; + if (auto *p = std::get_if(&Slot)) + return LiteralSlotMultiplicity[*p]; + if (auto *p = std::get_if(&Slot)) + return SymbolSlotMultiplicity[*p]; + if (auto *p = std::get_if(&Slot)) + return TemporarySlotMultiplicity[*p]; + + assert(std::holds_alternative(Slot)); + return JunkSlotMultiplicity; + } + + int at(StackSlot const &Slot) const { + if (auto *p = std::get_if(&Slot)) + return FunctionCallReturnLabelSlotMultiplicity.at(*p); + if (std::holds_alternative(Slot)) + return FunctionReturnLabelSlotMultiplicity; + if (auto *p = std::get_if(&Slot)) + return VariableSlotMultiplicity.at(*p); + if (auto *p = std::get_if(&Slot)) + return LiteralSlotMultiplicity.at(*p); + if (auto *p = std::get_if(&Slot)) + return SymbolSlotMultiplicity.at(*p); + if (auto *p = std::get_if(&Slot)) + return TemporarySlotMultiplicity.at(*p); + + assert(std::holds_alternative(Slot)); + return JunkSlotMultiplicity; + } + +private: + std::map + FunctionCallReturnLabelSlotMultiplicity; + int FunctionReturnLabelSlotMultiplicity = 0; + std::map VariableSlotMultiplicity; + std::map LiteralSlotMultiplicity; + std::map SymbolSlotMultiplicity; + std::map TemporarySlotMultiplicity; + int JunkSlotMultiplicity = 0; +}; + +/// Transforms \p CurrentStack to \p TargetStack, invoking the provided +/// shuffling operations. Modifies `CurrentStack` itself after each invocation +/// of the shuffling operations. +/// \p Swap is a function with signature void(unsigned) that is called when the +/// top most slot is swapped with the slot `depth` slots below the top. In terms +/// of EVM opcodes this is supposed to be a `SWAP`. +/// \p PushOrDup is a function with signature void(StackSlot const&) that is +/// called to push or dup the slot given as its argument to the stack top. +/// \p Pop is a function with signature void() that is called when the top most +/// slot is popped. +template +void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, + SwapT Swap, PushOrDupT PushOrDup, PopT Pop) { + struct ShuffleOperations { + Stack ¤tStack; + Stack const &targetStack; + SwapT swapCallback; + PushOrDupT pushOrDupCallback; + PopT popCallback; + Multiplicity multiplicity; + + ShuffleOperations(Stack &CurrentStack, Stack const &TargetStack, SwapT Swap, + PushOrDupT PushOrDup, PopT Pop) + : currentStack(CurrentStack), targetStack(TargetStack), + swapCallback(Swap), pushOrDupCallback(PushOrDup), popCallback(Pop) { + for (auto const &slot : currentStack) + --multiplicity[slot]; + + for (unsigned Offset = 0; Offset < targetStack.size(); ++Offset) { + auto &Slot = targetStack[Offset]; + if (std::holds_alternative(Slot) && + Offset < currentStack.size()) + ++multiplicity[currentStack.at(Offset)]; + else + ++multiplicity[Slot]; + } + } + + bool isCompatible(size_t Source, size_t Target) { + return Source < currentStack.size() && Target < targetStack.size() && + (std::holds_alternative(targetStack.at(Target)) || + currentStack.at(Source) == targetStack.at(Target)); + } + + bool sourceIsSame(size_t Lhs, size_t Rhs) { + return currentStack.at(Lhs) == currentStack.at(Rhs); + } + + int sourceMultiplicity(size_t Offset) { + return multiplicity.at(currentStack.at(Offset)); + } + + int targetMultiplicity(size_t Offset) { + return multiplicity.at(targetStack.at(Offset)); + } + + bool targetIsArbitrary(size_t Offset) { + return Offset < targetStack.size() && + std::holds_alternative(targetStack.at(Offset)); + } + + void swap(size_t I) { + swapCallback(static_cast(I)); + std::swap(currentStack.at(currentStack.size() - I - 1), + currentStack.back()); + } + + size_t sourceSize() { return currentStack.size(); } + + size_t targetSize() { return targetStack.size(); } + + void pop() { + popCallback(); + currentStack.pop_back(); + } + + void pushOrDupTarget(size_t Offset) { + auto const &targetSlot = targetStack.at(Offset); + pushOrDupCallback(targetSlot); + currentStack.push_back(targetSlot); + } + }; + + Shuffler::shuffle(CurrentStack, TargetStack, Swap, + PushOrDup, Pop); + + assert(CurrentStack.size() == TargetStack.size()); + for (unsigned I = 0; I < CurrentStack.size(); ++I) { + auto &Current = CurrentStack[I]; + auto &Target = TargetStack[I]; + if (std::holds_alternative(Target)) + Current = JunkSlot{}; + else + assert(Current == Target); + } +} + +} // end namespace llvm +#endif // LLVM_LIB_TARGET_EVM_EVMSTACKSHUFFLER_H diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.cpp b/llvm/lib/Target/EVM/EVMTargetMachine.cpp index 76111b648a8a..e1991ddfb969 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.cpp +++ b/llvm/lib/Target/EVM/EVMTargetMachine.cpp @@ -39,6 +39,10 @@ cl::opt cl::desc("EVM: output stack registers in" " instruction output for test purposes only."), cl::init(false)); +cl::opt + EVMUseLocalStakify("evm-use-local-stackify", cl::Hidden, + cl::desc("EVM: use the local stackification algorithm"), + cl::init(false)); extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeEVMTarget() { // Register the target. @@ -53,6 +57,7 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeEVMTarget() { initializeEVMSingleUseExpressionPass(PR); initializeEVMSplitCriticalEdgesPass(PR); initializeEVMStackifyPass(PR); + initializeEVMBPStackificationPass(PR); } static std::string computeDataLayout() { @@ -204,9 +209,13 @@ void EVMPassConfig::addPreEmitPass() { addPass(&MachineBlockPlacementID); addPass(createEVMOptimizeLiveIntervals()); addPass(createEVMSingleUseExpression()); - // Run the register coloring pass to reduce the total number of registers. - addPass(createEVMRegColoring()); - addPass(createEVMStackify()); + if (EVMUseLocalStakify) { + // Run the register coloring pass to reduce the total number of registers. + addPass(createEVMRegColoring()); + addPass(createEVMStackify()); + } else { + addPass(createEVMBPStackification()); + } } } diff --git a/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.cpp b/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.cpp index d9f73e7a1de7..cb4ae74f2672 100644 --- a/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.cpp +++ b/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.cpp @@ -122,3 +122,81 @@ unsigned llvm::EVM::getPUSHOpcode(const APInt &Imm) { llvm_unreachable("Unexpected stack depth"); } } + +unsigned llvm::EVM::getDUPOpcode(unsigned Depth) { + switch (Depth) { + case 1: + return EVM::DUP1; + case 2: + return EVM::DUP2; + case 3: + return EVM::DUP3; + case 4: + return EVM::DUP4; + case 5: + return EVM::DUP5; + case 6: + return EVM::DUP6; + case 7: + return EVM::DUP7; + case 8: + return EVM::DUP8; + case 9: + return EVM::DUP9; + case 10: + return EVM::DUP10; + case 11: + return EVM::DUP11; + case 12: + return EVM::DUP12; + case 13: + return EVM::DUP13; + case 14: + return EVM::DUP14; + case 15: + return EVM::DUP15; + case 16: + return EVM::DUP16; + default: + llvm_unreachable("Unexpected stack depth"); + } +} + +unsigned llvm::EVM::getSWAPOpcode(unsigned Depth) { + switch (Depth) { + case 1: + return EVM::SWAP1; + case 2: + return EVM::SWAP2; + case 3: + return EVM::SWAP3; + case 4: + return EVM::SWAP4; + case 5: + return EVM::SWAP5; + case 6: + return EVM::SWAP6; + case 7: + return EVM::SWAP7; + case 8: + return EVM::SWAP8; + case 9: + return EVM::SWAP9; + case 10: + return EVM::SWAP10; + case 11: + return EVM::SWAP11; + case 12: + return EVM::SWAP12; + case 13: + return EVM::SWAP13; + case 14: + return EVM::SWAP14; + case 15: + return EVM::SWAP15; + case 16: + return EVM::SWAP16; + default: + llvm_unreachable("Unexpected stack depth"); + } +} diff --git a/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.h b/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.h index 5b4abc84d6cc..e0c6998bbb5a 100644 --- a/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.h +++ b/llvm/lib/Target/EVM/TargetInfo/EVMTargetInfo.h @@ -25,6 +25,8 @@ namespace EVM { unsigned getStackOpcode(unsigned Opcode); unsigned getRegisterOpcode(unsigned Opcode); unsigned getPUSHOpcode(const APInt &Imm); +unsigned getDUPOpcode(unsigned Depth); +unsigned getSWAPOpcode(unsigned Depth); } // namespace EVM diff --git a/llvm/test/CodeGen/EVM/stack-ops-commutable.ll b/llvm/test/CodeGen/EVM/stack-ops-commutable.ll new file mode 100644 index 000000000000..9a14eb4c59cb --- /dev/null +++ b/llvm/test/CodeGen/EVM/stack-ops-commutable.ll @@ -0,0 +1,258 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +define void @no_manipulations_needed_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: no_manipulations_needed_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = add i256 %a1, %a2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define i256 @no_manipulations_needed_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: no_manipulations_needed_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a1, %a2 + ret i256 %x1 +} + +define void @reorder_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: reorder_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = add i256 %a2, %a1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define i256 @reorder_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: reorder_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a2, %a1 + ret i256 %x1 +} + +define void @swap_first_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: swap_first_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = add i256 %a3, %a2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define void @swap_second_with_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) noreturn { +; CHECK-LABEL: swap_second_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP4 +; CHECK-NEXT: ADD +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = add i256 %a1, %a4 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define i256 @swap_first_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwind { +; CHECK-LABEL: swap_first_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: POP +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a1, %a4 + ret i256 %x1 +} + +define i256 @swap_second_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwind { +; CHECK-LABEL: swap_second_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: POP +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a4, %a1 + ret i256 %x1 +} + +define void @first_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: first_arg_alive_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DUP4 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = add i256 %a1, %a2 + %x2 = sub i256 %a1, 4 + %x3 = udiv i256 %x2, %x1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x3) + unreachable +} + +define i256 @first_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: first_arg_alive_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a1, %a2 + %x2 = sub i256 %a1, 4 + %x3 = udiv i256 %x2, %x1 + ret i256 %x3 +} + +define void @second_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: second_arg_alive_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP2 +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = add i256 %a1, %a2 + %x2 = sub i256 %a2, 4 + %x3 = udiv i256 %x2, %x1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x3) + unreachable +} + +define i256 @second_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: second_arg_alive_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP2 +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a1, %a2 + %x2 = sub i256 %a2, 4 + %x3 = udiv i256 %x2, %x1 + ret i256 %x3 +} + +define void @both_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: both_arg_alive_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: ADD +; CHECK-NEXT: ADD +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = add i256 %a1, %a2 + %x2 = udiv i256 %a2, %a1 + %x3 = add i256 %x1, %x2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x3) + unreachable +} + +define i256 @both_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: both_arg_alive_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: ADD +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a1, %a2 + %x2 = udiv i256 %a2, %a1 + %x3 = add i256 %x1, %x2 + ret i256 %x3 +} + +define i256 @same_arg_dead_with_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: same_arg_dead_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT +; CHECK-NEXT: JUMP + %x1 = add i256 %a2, %a2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + ret i256 %x1 +} + +declare void @llvm.evm.revert(ptr addrspace(1), i256) diff --git a/llvm/test/CodeGen/EVM/stack-ops.ll b/llvm/test/CodeGen/EVM/stack-ops.ll new file mode 100644 index 000000000000..92dfaf24887f --- /dev/null +++ b/llvm/test/CodeGen/EVM/stack-ops.ll @@ -0,0 +1,340 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +define void @no_manipulations_needed_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: no_manipulations_needed_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SUB +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a1, %a2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define i256 @no_manipulations_needed_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: no_manipulations_needed_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a1, %a2 + ret i256 %x1 +} + +define void @reorder_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: reorder_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: SUB +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a2, %a1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define i256 @reorder_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: reorder_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a2, %a1 + ret i256 %x1 +} + +define void @swap_first_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: swap_first_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: SUB +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a3, %a2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define void @swap_second_with_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) noreturn { +; CHECK-LABEL: swap_second_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP4 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: SUB +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a1, %a4 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define i256 @swap_first_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwind { +; CHECK-LABEL: swap_first_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a3, %a2 + ret i256 %x1 +} + +define i256 @swap_second_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwind { +; CHECK-LABEL: swap_second_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: POP +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a1, %a4 + ret i256 %x1 +} + +define void @swap_both_with_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) noreturn { +; CHECK-LABEL: swap_both_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP4 +; CHECK-NEXT: SUB +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a4, %a1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable +} + +define i256 @swap_both_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwind { +; CHECK-LABEL: swap_both_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: POP +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a4, %a1 + ret i256 %x1 +} + +define void @first_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: first_arg_alive_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a1, %a2 + %x2 = sub i256 %a1, 4 + %x3 = udiv i256 %x2, %x1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x3) + unreachable +} + +define i256 @first_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: first_arg_alive_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a1, %a2 + %x2 = sub i256 %a1, 4 + %x3 = udiv i256 %x2, %x1 + ret i256 %x3 +} + +define void @second_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: second_arg_alive_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP2 +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a1, %a2 + %x2 = sub i256 %a2, 4 + %x3 = udiv i256 %x2, %x1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x3) + unreachable +} + +define i256 @second_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: second_arg_alive_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DUP2 +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH1 4 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a1, %a2 + %x2 = sub i256 %a2, 4 + %x3 = udiv i256 %x2, %x1 + ret i256 %x3 +} + +define void @both_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { +; CHECK-LABEL: both_arg_alive_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: ADD +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT + %x1 = sub i256 %a1, %a2 + %x2 = udiv i256 %a2, %a1 + %x3 = add i256 %x1, %x2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x3) + unreachable +} + +define i256 @both_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: both_arg_alive_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: DIV +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SUB +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = sub i256 %a1, %a2 + %x2 = udiv i256 %a2, %a1 + %x3 = add i256 %x1, %x2 + ret i256 %x3 +} + +define i256 @same_arg_dead_with_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: same_arg_dead_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT +; CHECK-NEXT: JUMP + %x1 = add i256 %a2, %a2 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + ret i256 %x1 +} + +define i256 @same_arg_dead_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: same_arg_dead_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a2, %a2 + ret i256 %x1 +} + +define i256 @same_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: same_arg_alive_with_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP1 +; CHECK-NEXT: DUP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: DUP2 +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT +; CHECK-NEXT: JUMP + %x1 = add i256 %a2, %a2 + %x2 = add i256 %a2, %x1 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x2) + ret i256 %x2 +} + +define i256 @same_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: same_arg_alive_no_junk: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP1 +; CHECK-NEXT: DUP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = add i256 %a2, %a2 + %x2 = add i256 %a2, %x1 + ret i256 %x2 +} + +declare void @llvm.evm.revert(ptr addrspace(1), i256) diff --git a/llvm/test/CodeGen/EVM/unused_function_arguments.ll b/llvm/test/CodeGen/EVM/unused_function_arguments.ll new file mode 100644 index 000000000000..0aa142b88b10 --- /dev/null +++ b/llvm/test/CodeGen/EVM/unused_function_arguments.ll @@ -0,0 +1,52 @@ +; RUN: llc < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +define i256 @foo(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: @foo +; CHECK: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP +; CHECK-NEXT: POP +; CHECK-NEXT: DUP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + + %x1 = add i256 %a1, %a1 + ret i256 %x1 +} + +define i256 @wat(i256 %a1, i256 %a2, i256 %a3) nounwind { +; CHECK-LABEL: @wat +; CHECK: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: DUP1 +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + + %x1 = add i256 %a2, %a2 + ret i256 %x1 +} + +define i256 @bar() nounwind { +; CHECK-LABEL: @bar +; CHECK: JUMPDEST +; CHECK-NEXT: PUSH4 @.FUNC_RET0 +; CHECK-NEXT: PUSH1 3 +; CHECK-NEXT: PUSH1 2 +; CHECK-NEXT: PUSH1 1 +; CHECK-NEXT: PUSH4 @foo +; CHECK-NEXT: JUMP +; CHECK-LABEL: .FUNC_RET0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + + %res = call i256 @foo(i256 1, i256 2, i256 3) + ret i256 %res +} diff --git a/llvm/test/CodeGen/Generic/2007-01-15-LoadSelectCycle.ll b/llvm/test/CodeGen/Generic/2007-01-15-LoadSelectCycle.ll index 0bd23db7c62c..1d667d736a70 100644 --- a/llvm/test/CodeGen/Generic/2007-01-15-LoadSelectCycle.ll +++ b/llvm/test/CodeGen/Generic/2007-01-15-LoadSelectCycle.ll @@ -1,5 +1,6 @@ ; RUN: llc < %s ; PR1114 +; UNSUPPORTED: target=evm{{.*}} declare i1 @foo() diff --git a/llvm/test/CodeGen/Generic/2009-04-28-i128-cmp-crash.ll b/llvm/test/CodeGen/Generic/2009-04-28-i128-cmp-crash.ll index 605fe346c9d3..bed186acb538 100644 --- a/llvm/test/CodeGen/Generic/2009-04-28-i128-cmp-crash.ll +++ b/llvm/test/CodeGen/Generic/2009-04-28-i128-cmp-crash.ll @@ -2,6 +2,7 @@ ; rdar://6836460 ; rdar://7516906 ; PR5963 +; UNSUPPORTED: target=evm{{.*}} define i32 @test(ptr %P) nounwind { entry: diff --git a/llvm/test/CodeGen/Generic/2011-07-07-ScheduleDAGCrash.ll b/llvm/test/CodeGen/Generic/2011-07-07-ScheduleDAGCrash.ll index f5f2276ac820..f72452fc0cbf 100644 --- a/llvm/test/CodeGen/Generic/2011-07-07-ScheduleDAGCrash.ll +++ b/llvm/test/CodeGen/Generic/2011-07-07-ScheduleDAGCrash.ll @@ -1,4 +1,5 @@ ; RUN: llc < %s +; UNSUPPORTED: target=evm{{.*}} ; This caused ScheduleDAG to crash in EmitPhysRegCopy when searching ; the uses of a copy to a physical register without ignoring non-data diff --git a/llvm/test/CodeGen/Generic/2012-06-08-APIntCrash.ll b/llvm/test/CodeGen/Generic/2012-06-08-APIntCrash.ll index f08923669ece..ce3c4ec680df 100644 --- a/llvm/test/CodeGen/Generic/2012-06-08-APIntCrash.ll +++ b/llvm/test/CodeGen/Generic/2012-06-08-APIntCrash.ll @@ -1,4 +1,5 @@ ; RUN: llc < %s +; UNSUPPORTED: target=evm{{.*}} define void @test1(ptr %ptr) { diff --git a/llvm/test/CodeGen/Generic/i128-addsub.ll b/llvm/test/CodeGen/Generic/i128-addsub.ll index e61658ed2430..7a5e3e6f1e1f 100644 --- a/llvm/test/CodeGen/Generic/i128-addsub.ll +++ b/llvm/test/CodeGen/Generic/i128-addsub.ll @@ -1,4 +1,5 @@ ; RUN: llc < %s +; UNSUPPORTED: target=evm{{.*}} define void @test_add(i64 %AL, i64 %AH, i64 %BL, i64 %BH, ptr %RL, ptr %RH) { entry: diff --git a/llvm/test/CodeGen/Generic/multiple-return-values-cross-block-with-invoke.ll b/llvm/test/CodeGen/Generic/multiple-return-values-cross-block-with-invoke.ll index 6cc2b4040d18..e9a4fe5a1955 100644 --- a/llvm/test/CodeGen/Generic/multiple-return-values-cross-block-with-invoke.ll +++ b/llvm/test/CodeGen/Generic/multiple-return-values-cross-block-with-invoke.ll @@ -1,4 +1,5 @@ ; RUN: llc < %s +; UNSUPPORTED: target=evm{{.*}} declare { i64, double } @wild() define void @foo(ptr %p, ptr %q) nounwind personality ptr @__gxx_personality_v0 { diff --git a/llvm/test/CodeGen/Generic/undef-phi.ll b/llvm/test/CodeGen/Generic/undef-phi.ll index 0e221fe612ab..89d73901436d 100644 --- a/llvm/test/CodeGen/Generic/undef-phi.ll +++ b/llvm/test/CodeGen/Generic/undef-phi.ll @@ -1,4 +1,5 @@ ; RUN: llc < %s -verify-machineinstrs -verify-coalescing +; UNSUPPORTED: target=evm{{.*}} ; ; This function has a PHI with one undefined input. Verify that PHIElimination ; inserts an IMPLICIT_DEF instruction in the predecessor so all paths to the use diff --git a/llvm/test/Transforms/BranchFolding/2007-10-19-InlineAsmDirectives.ll b/llvm/test/Transforms/BranchFolding/2007-10-19-InlineAsmDirectives.ll index 3a601d0c3f4a..865c60038f8f 100644 --- a/llvm/test/Transforms/BranchFolding/2007-10-19-InlineAsmDirectives.ll +++ b/llvm/test/Transforms/BranchFolding/2007-10-19-InlineAsmDirectives.ll @@ -1,6 +1,7 @@ ; RUN: opt < %s -O3 | llc -no-integrated-as | FileCheck %s ; REQUIRES: default_triple ; XFAIL: target=eravm{{.*}} +; UNSUPPORTED: target=evm{{.*}} ;; We don't want branch folding to fold asm directives. diff --git a/llvm/test/tools/UpdateTestChecks/update_llc_test_checks/Inputs/evm-basic.ll.expected b/llvm/test/tools/UpdateTestChecks/update_llc_test_checks/Inputs/evm-basic.ll.expected index 4b6359646f18..4a4f1e007b5d 100644 --- a/llvm/test/tools/UpdateTestChecks/update_llc_test_checks/Inputs/evm-basic.ll.expected +++ b/llvm/test/tools/UpdateTestChecks/update_llc_test_checks/Inputs/evm-basic.ll.expected @@ -5,17 +5,12 @@ define i256 @swap_second_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwin ; CHECK-LABEL: swap_second_no_junk: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: DUP2 -; CHECK-NEXT: DUP6 -; CHECK-NEXT: SUB -; CHECK-NEXT: SWAP5 -; CHECK-NEXT: POP -; CHECK-NEXT: DUP1 -; CHECK-NEXT: SWAP4 -; CHECK-NEXT: POP -; CHECK-NEXT: POP +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: SWAP2 ; CHECK-NEXT: POP ; CHECK-NEXT: POP +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %x1 = sub i256 %a4, %a1 ret i256 %x1 diff --git a/llvm/unittests/Target/EVM/CMakeLists.txt b/llvm/unittests/Target/EVM/CMakeLists.txt new file mode 100644 index 000000000000..e259d70e745d --- /dev/null +++ b/llvm/unittests/Target/EVM/CMakeLists.txt @@ -0,0 +1,23 @@ +include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/EVM + ${LLVM_BINARY_DIR}/lib/Target/EVM + ) + +set(LLVM_LINK_COMPONENTS + EVM + EVMDesc + EVMInfo + CodeGen + Core + MC + MIRParser + SelectionDAG + Support + TargetParser +) + +add_llvm_target_unittest(EVMTests + StackShuffler.cpp + ) + +set_property(TARGET EVMTests PROPERTY FOLDER "Tests/UnitTests/TargetTests") diff --git a/llvm/unittests/Target/EVM/StackShuffler.cpp b/llvm/unittests/Target/EVM/StackShuffler.cpp new file mode 100644 index 000000000000..82bfde1b5e6a --- /dev/null +++ b/llvm/unittests/Target/EVM/StackShuffler.cpp @@ -0,0 +1,196 @@ +//===---------- llvm/unittests/MC/AssemblerTest.cpp -----------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "EVMControlFlowGraph.h" +#include "EVMRegisterInfo.h" +#include "EVMStackDebug.h" +#include "EVMStackShuffler.h" +#include "EVMTargetMachine.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm-c/Core.h" +#include "llvm-c/IRReader.h" +#include "llvm-c/TargetMachine.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Target/CodeGenCWrappers.h" +#include "llvm/Target/TargetMachine.h" +#include "gtest/gtest.h" +#include +#include + +using namespace llvm; + +#include +static TargetMachine *unwrap(LLVMTargetMachineRef P) { + return reinterpret_cast(P); +} + +class LLDCTest : public testing::Test { + void SetUp() override { + LLVMInitializeEVMTargetInfo(); + LLVMInitializeEVMTarget(); + LLVMInitializeEVMTargetMC(); + + LLVMTargetRef Target = 0; + const char *Triple = "evm"; + char *ErrMsg = 0; + if (LLVMGetTargetFromTriple(Triple, &Target, &ErrMsg)) { + FAIL() << "Failed to create target from the triple (" << Triple + << "): " << ErrMsg; + return; + } + ASSERT_TRUE(Target); + + // Construct a TargetMachine. + TM = + LLVMCreateTargetMachine(Target, Triple, "", "", LLVMCodeGenLevelDefault, + LLVMRelocDefault, LLVMCodeModelDefault); + Context = std::make_unique(); + Mod = std::make_unique("TestModule", *Context); + Mod->setDataLayout(unwrap(TM)->createDataLayout()); + const LLVMTargetMachine &LLVMTM = + static_cast(*unwrap(TM)); + MMIWP = std::make_unique(&LLVMTM); + + Type *const ReturnType = Type::getVoidTy(Mod->getContext()); + FunctionType *FunctionType = FunctionType::get(ReturnType, false); + Function *const F = Function::Create( + FunctionType, GlobalValue::InternalLinkage, "TestFunction", Mod.get()); + MF = &MMIWP->getMMI().getOrCreateMachineFunction(*F); + } + + void TearDown() override { LLVMDisposeTargetMachine(TM); } + +public: + LLVMTargetMachineRef TM; + MachineFunction *MF = nullptr; + std::unique_ptr Context; + std::unique_ptr Mod; + std::unique_ptr MMIWP; +}; + +TEST_F(LLDCTest, Basic) { + Stack SourceStack; + Stack TargetStack; + + MachineBasicBlock *MBB = MF->CreateMachineBasicBlock(nullptr); + MF->push_back(MBB); + + const TargetInstrInfo *TII = MF->getSubtarget().getInstrInfo(); + const MCInstrDesc &MCID = TII->get(EVM::SELFBALANCE); + MachineRegisterInfo &MRI = MF->getRegInfo(); + + auto CreateInstr = [&]() { + Register Reg = MRI.createVirtualRegister(&EVM::GPRRegClass); + MachineInstr *MI = BuildMI(MBB, DebugLoc(), MCID, Reg); + return std::pair(MI, Reg); + }; + SmallVector> Instrs; + for (unsigned I = 0; I < 17; ++I) + Instrs.emplace_back(CreateInstr()); + + // Create the source stack layout: + // [ %0 %1 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET RET %5 ] + SourceStack.emplace_back(VariableSlot{Instrs[0].second}); + SourceStack.emplace_back(VariableSlot{Instrs[1].second}); + SourceStack.emplace_back(VariableSlot{Instrs[2].second}); + SourceStack.emplace_back(VariableSlot{Instrs[3].second}); + SourceStack.emplace_back(VariableSlot{Instrs[4].second}); + SourceStack.emplace_back(VariableSlot{Instrs[5].second}); + SourceStack.emplace_back(VariableSlot{Instrs[6].second}); + SourceStack.emplace_back(VariableSlot{Instrs[7].second}); + SourceStack.emplace_back(VariableSlot{Instrs[9].second}); + SourceStack.emplace_back(VariableSlot{Instrs[10].second}); + SourceStack.emplace_back(VariableSlot{Instrs[11].second}); + SourceStack.emplace_back(VariableSlot{Instrs[12].second}); + SourceStack.emplace_back(VariableSlot{Instrs[13].second}); + SourceStack.emplace_back(VariableSlot{Instrs[14].second}); + SourceStack.emplace_back(VariableSlot{Instrs[15].second}); + SourceStack.emplace_back(VariableSlot{Instrs[16].second}); + SourceStack.emplace_back(FunctionReturnLabelSlot{MBB->getParent()}); + SourceStack.emplace_back(FunctionReturnLabelSlot{MBB->getParent()}); + SourceStack.emplace_back(VariableSlot{Instrs[5].second}); + + // [ %1 %0 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET JUNK JUNK ] + TargetStack.emplace_back(VariableSlot{Instrs[1].second}); + TargetStack.emplace_back(VariableSlot{Instrs[0].second}); + TargetStack.emplace_back(VariableSlot{Instrs[2].second}); + TargetStack.emplace_back(VariableSlot{Instrs[3].second}); + TargetStack.emplace_back(VariableSlot{Instrs[4].second}); + TargetStack.emplace_back(VariableSlot{Instrs[5].second}); + TargetStack.emplace_back(VariableSlot{Instrs[6].second}); + TargetStack.emplace_back(VariableSlot{Instrs[7].second}); + TargetStack.emplace_back(VariableSlot{Instrs[9].second}); + TargetStack.emplace_back(VariableSlot{Instrs[10].second}); + TargetStack.emplace_back(VariableSlot{Instrs[11].second}); + TargetStack.emplace_back(VariableSlot{Instrs[12].second}); + TargetStack.emplace_back(VariableSlot{Instrs[13].second}); + TargetStack.emplace_back(VariableSlot{Instrs[14].second}); + TargetStack.emplace_back(VariableSlot{Instrs[15].second}); + TargetStack.emplace_back(VariableSlot{Instrs[16].second}); + TargetStack.emplace_back(FunctionReturnLabelSlot{MBB->getParent()}); + TargetStack.emplace_back(JunkSlot{}); + TargetStack.emplace_back(JunkSlot{}); + + StringRef Reference("\ +[ %0 %1 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET RET %5 ]\n\ +POP\n\ +[ %0 %1 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET RET ]\n\ +SWAP16\n\ +[ %0 RET %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET %1 ]\n\ +SWAP16\n\ +[ %0 %1 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET RET ]\n\ +POP\n\ +[ %0 %1 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET ]\n\ +SWAP15\n\ +[ %0 RET %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 %1 ]\n\ +SWAP16\n\ +[ %1 RET %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 %0 ]\n\ +SWAP15\n\ +[ %1 %0 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET ]\n\ +PUSH JUNK\n\ +[ %1 %0 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET JUNK ]\n\ +PUSH JUNK\n\ +[ %1 %0 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET JUNK JUNK ]\n"); + + std::ostringstream Output; + createStackLayout( + SourceStack, TargetStack, + [&](unsigned SwapDepth) { // swap + Output << stackToString(SourceStack) << '\n'; + Output << "SWAP" << SwapDepth << '\n'; + }, + [&](StackSlot const &Slot) { // dupOrPush + Output << stackToString(SourceStack) << '\n'; + if (canBeFreelyGenerated(Slot)) + Output << "PUSH " << stackSlotToString(Slot) << '\n'; + else { + Stack TmpStack = SourceStack; + std::reverse(TmpStack.begin(), TmpStack.end()); + auto It = std::find(TmpStack.begin(), TmpStack.end(), Slot); + if (It == TmpStack.end()) + FAIL() << "Invalid DUP operation."; + + auto Depth = std::distance(TmpStack.begin(), It); + Output << "DUP" << Depth + 1 << '\n'; + } + }, + [&]() { // pop + Output << stackToString(SourceStack) << '\n'; + Output << "POP" << '\n'; + }); + + Output << stackToString(SourceStack) << '\n'; + std::cerr << Output.str(); + EXPECT_TRUE(Reference == Output.str()); +} From 96ed76f6dc21012f86763490a9fa14a5ccce8346 Mon Sep 17 00:00:00 2001 From: Dmitry Borisenkov Date: Mon, 27 Jan 2025 20:39:30 +0200 Subject: [PATCH 06/42] Handle EVM::LINKERSYMBOL after rebasing on '[EVM] Add libraries support' --- llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp index dc359cc51965..d8971fe672a0 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp +++ b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp @@ -235,6 +235,7 @@ void ControlFlowGraphBuilder::handleMachineInstr(MachineInstr &MI) { case EVM::COPY_I256: case EVM::DATASIZE: case EVM::DATAOFFSET: + case EVM::LINKERSYMBOL: // The copy/data instructions just represent an assignment. This case is // handled below. break; @@ -276,7 +277,8 @@ void ControlFlowGraphBuilder::handleMachineInstr(MachineInstr &MI) { Variables.push_back(VariableSlot{DefReg}); } break; case EVM::DATASIZE: - case EVM::DATAOFFSET: { + case EVM::DATAOFFSET: + case EVM::LINKERSYMBOL: { const Register DefReg = MI.getOperand(0).getReg(); MCSymbol *Sym = MI.getOperand(1).getMCSymbol(); Input.push_back(SymbolSlot{Sym, &MI}); From 7dcb3a496d5bb8a244b47699f4fd23d2c87f9cbc Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 15 Nov 2024 14:42:51 +0100 Subject: [PATCH 07/42] [EVM] Support commutable operations in BP stackification algorithm --- llvm/lib/Target/EVM/EVMControlFlowGraph.h | 3 + .../Target/EVM/EVMControlFlowGraphBuilder.cpp | 6 +- llvm/lib/Target/EVM/EVMInstrInfo.td | 24 +-- .../Target/EVM/EVMOptimizedCodeTransform.cpp | 70 +++++++-- llvm/test/CodeGen/EVM/stack-ops-commutable.ll | 137 ++++++++++++++++-- llvm/test/CodeGen/EVM/stack-ops.ll | 20 +-- .../CodeGen/EVM/unused_function_arguments.ll | 4 +- 7 files changed, 210 insertions(+), 54 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraph.h b/llvm/lib/Target/EVM/EVMControlFlowGraph.h index 904abf4f05c7..86b7e2720560 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraph.h +++ b/llvm/lib/Target/EVM/EVMControlFlowGraph.h @@ -176,6 +176,9 @@ struct CFG { struct BuiltinCall { MachineInstr *Builtin = nullptr; + // True if this instruction has commutable operands. In EVM ISA + // commutable operands always take top two stack slots. + bool IsCommutable = false; bool TerminatesOrReverts = false; }; diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp index d8971fe672a0..b23ee8a866df 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp +++ b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp @@ -259,9 +259,9 @@ void ControlFlowGraphBuilder::handleMachineInstr(MachineInstr &MI) { default: { Stack Input, Output; collectInstrOperands(MI, &Input, &Output); - CurrentBlock->Operations.emplace_back( - CFG::Operation{std::move(Input), std::move(Output), - CFG::BuiltinCall{&MI, TerminatesOrReverts}}); + CurrentBlock->Operations.emplace_back(CFG::Operation{ + std::move(Input), std::move(Output), + CFG::BuiltinCall{&MI, MI.isCommutable(), TerminatesOrReverts}}); } break; } diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index 90aa86bcbd1c..f7fb7c5cc4e9 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -270,17 +270,19 @@ defm SDIV : BinaryInst; defm MOD : BinaryInst; defm SMOD : BinaryInst; -defm ADDMOD - : I<(outs GPR:$dst), (ins GPR:$add_op1, GPR:$add_op2, GPR:$denom), - [(set GPR:$dst, - (int_evm_addmod GPR:$add_op1, GPR:$add_op2, GPR:$denom))], - "ADDMOD", " $dst, $add_op1, $add_op2, $denom", 0x08, 8>; - -defm MULMOD - : I<(outs GPR:$dst), (ins GPR:$mul_op1, GPR:$mul_op2, GPR:$denom), - [(set GPR:$dst, - (int_evm_mulmod GPR:$mul_op1, GPR:$mul_op2, GPR:$denom))], - "MULMOD", " $dst, $mul_op1, $mul_op2, $denom", 0x09, 8>; +let isCommutable = 1 in { + defm ADDMOD + : I<(outs GPR:$dst), (ins GPR:$add_op1, GPR:$add_op2, GPR:$denom), + [(set GPR:$dst, + (int_evm_addmod GPR:$add_op1, GPR:$add_op2, GPR:$denom))], + "ADDMOD", " $dst, $add_op1, $add_op2, $denom", 0x08, 8>; + + defm MULMOD + : I<(outs GPR:$dst), (ins GPR:$mul_op1, GPR:$mul_op2, GPR:$denom), + [(set GPR:$dst, + (int_evm_mulmod GPR:$mul_op1, GPR:$mul_op2, GPR:$denom))], + "MULMOD", " $dst, $mul_op1, $mul_op2, $denom", 0x09, 8>; +} defm EXP : I<(outs GPR:$dst), (ins GPR:$base, GPR:$exp), diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp index 26b5bd5a967c..3025675257f2 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp @@ -258,16 +258,54 @@ void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { void EVMOptimizedCodeTransform::createOperationEntryLayout( const CFG::Operation &Op) { // Create required layout for entering the Operation. - createStackLayout(Layout.operationEntryLayout.at(&Op)); + // Check if we can choose cheaper stack shuffling if the Operation is an + // instruction with commutable arguments. + if (const auto *Inst = std::get_if(&Op.Operation); + Inst && Inst->IsCommutable) { + // Get the stack layout before the instruction. + const Stack &DefaultTargetStack = Layout.operationEntryLayout.at(&Op); + size_t DefaultCost = + EvaluateStackTransform(CurrentStack, DefaultTargetStack); + + // Commutable operands always take top two stack slots. + const unsigned OpIdx1 = 0, OpIdx2 = 1; + assert(DefaultTargetStack.size() > 1); + + // Swap the commutable stack items and measure the stack shuffling cost + // again. + Stack CommutedTargetStack = DefaultTargetStack; + std::swap(CommutedTargetStack[CommutedTargetStack.size() - OpIdx1 - 1], + CommutedTargetStack[CommutedTargetStack.size() - OpIdx2 - 1]); + size_t CommutedCost = + EvaluateStackTransform(CurrentStack, CommutedTargetStack); + // Choose the cheapest transformation. + createStackLayout(CommutedCost < DefaultCost ? CommutedTargetStack + : DefaultTargetStack); +#ifndef NDEBUG + // Assert that we have the inputs of the Operation on stack top. + assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + assert(CurrentStack.size() >= Op.Input.size()); + Stack StackInput = + EVMUtils::to_vector(EVMUtils::take_last(CurrentStack, Op.Input.size())); + // Adjust the StackInput to match the commuted stack. + if (CommutedCost < DefaultCost) { + std::swap(StackInput[StackInput.size() - OpIdx1 - 1], + StackInput[StackInput.size() - OpIdx2 - 1]); + } + assert(AreLayoutsCompatible(StackInput, Op.Input)); +#endif // NDEBUG + } else { + createStackLayout(Layout.operationEntryLayout.at(&Op)); #ifndef NDEBUG - // Assert that we have the inputs of the Operation on stack top. - assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); - assert(CurrentStack.size() >= Op.Input.size()); - const Stack StackInput = - EVMUtils::to_vector(EVMUtils::take_last(CurrentStack, Op.Input.size())); - assert(AreLayoutsCompatible(StackInput, Op.Input)); + // Assert that we have the inputs of the Operation on stack top. + assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + assert(CurrentStack.size() >= Op.Input.size()); + const Stack StackInput = + EVMUtils::to_vector(EVMUtils::take_last(CurrentStack, Op.Input.size())); + assert(AreLayoutsCompatible(StackInput, Op.Input)); #endif // NDEBUG + } } void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { @@ -281,12 +319,14 @@ void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { auto const &BlockInfo = Layout.blockInfos.at(&Block); - // Assert that the stack is valid for entering the block. - assert(AreLayoutsCompatible(CurrentStack, BlockInfo.entryLayout)); - - // Might set some slots to junk, if not required by the block. - CurrentStack = BlockInfo.entryLayout; - + // Assert that the stack is valid for entering the block. The entry layout + // of the function entry block should is fully determined by the first + // instruction, so we can ignore 'BlockInfo.entryLayout'. + if (&Block != FuncInfo->Entry) { + assert(AreLayoutsCompatible(CurrentStack, BlockInfo.entryLayout)); + // Might set some slots to junk, if not required by the block. + CurrentStack = BlockInfo.entryLayout; + } assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); // Emit jumpdest, if required. @@ -447,9 +487,7 @@ void EVMOptimizedCodeTransform::operator()() { Assembly.setStackHeight(static_cast(CurrentStack.size())); Assembly.appendLabel(); - // Create the entry layout of the function body block and visit. - createStackLayout(Layout.blockInfos.at(FuncInfo->Entry).entryLayout); - + // Visit the function entry block. (*this)(*FuncInfo->Entry); Assembly.finalize(); diff --git a/llvm/test/CodeGen/EVM/stack-ops-commutable.ll b/llvm/test/CodeGen/EVM/stack-ops-commutable.ll index 9a14eb4c59cb..93f9e1c34938 100644 --- a/llvm/test/CodeGen/EVM/stack-ops-commutable.ll +++ b/llvm/test/CodeGen/EVM/stack-ops-commutable.ll @@ -8,7 +8,6 @@ define void @no_manipulations_needed_with_junk(i256 %a1, i256 %a2, i256 %a3) nor ; CHECK-LABEL: no_manipulations_needed_with_junk: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: ADD ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: REVERT @@ -17,6 +16,75 @@ define void @no_manipulations_needed_with_junk(i256 %a1, i256 %a2, i256 %a3) nor unreachable } +define void @no_manipulations_needed_with_junk_eq(i256 %a1, i256 %a2, i256 %a3) noreturn { + %cmp = icmp eq i256 %a1, %a2 + %x1 = zext i1 %cmp to i256 + call void @llvm.evm.revert(ptr addrspace(1) null, i256 %x1) + unreachable + +; CHECK-LABEL: no_manipulations_needed_with_junk_eq: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: EQ +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: REVERT +} + +define i256 @no_manipulations_needed_no_junk_addmod(i256 %a1, i256 %a2, i256 %a3) { +; CHECK-LABEL: no_manipulations_needed_no_junk_addmod: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ADDMOD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = call i256 @llvm.evm.addmod(i256 %a2, i256 %a1, i256 %a3) + ret i256 %x1 +} + +define i256 @no_manipulations_needed_no_junk_mulmod(i256 %a1, i256 %a2, i256 %a3) { +; CHECK-LABEL: no_manipulations_needed_no_junk_mulmod: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: MULMOD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = call i256 @llvm.evm.mulmod(i256 %a2, i256 %a1, i256 %a3) + ret i256 %x1 +} + +define i256 @no_manipulations_needed_no_junk_and(i256 %a1, i256 %a2) { +; CHECK-LABEL: no_manipulations_needed_no_junk_and: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: AND +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = and i256 %a2, %a1 + ret i256 %x1 +} + +define i256 @no_manipulations_needed_no_junk_or(i256 %a1, i256 %a2) { +; CHECK-LABEL: no_manipulations_needed_no_junk_or: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: OR +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = or i256 %a2, %a1 + ret i256 %x1 +} + +define i256 @no_manipulations_needed_no_junk_xor(i256 %a1, i256 %a2) { +; CHECK-LABEL: no_manipulations_needed_no_junk_xor: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: XOR +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP + %x1 = xor i256 %a2, %a1 + ret i256 %x1 +} + define i256 @no_manipulations_needed_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK-LABEL: no_manipulations_needed_no_junk: ; CHECK: ; %bb.0: @@ -34,7 +102,6 @@ define void @reorder_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { ; CHECK-LABEL: reorder_with_junk: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: ADD ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: REVERT @@ -61,7 +128,6 @@ define void @swap_first_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: ADD ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: REVERT @@ -70,6 +136,20 @@ define void @swap_first_with_junk(i256 %a1, i256 %a2, i256 %a3) noreturn { unreachable } +define i256 @two_commutable(i256 %a1, i256 %a2, i256 %a3) { + %x1 = add i256 %a3, %a2 + %x2 = add i256 %a1, %x1 + ret i256 %x2 +; CHECK-LABEL: two_commutable: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: ADD +; CHECK-NEXT: ADD +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP +} + define void @swap_second_with_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) noreturn { ; CHECK-LABEL: swap_second_with_junk: ; CHECK: ; %bb.0: @@ -87,7 +167,6 @@ define i256 @swap_first_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwind ; CHECK-LABEL: swap_first_no_junk: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP3 ; CHECK-NEXT: SWAP2 ; CHECK-NEXT: POP ; CHECK-NEXT: POP @@ -102,7 +181,6 @@ define i256 @swap_second_no_junk(i256 %a1, i256 %a2, i256 %a3, i256 %a4) nounwin ; CHECK-LABEL: swap_second_no_junk: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP3 ; CHECK-NEXT: SWAP2 ; CHECK-NEXT: POP ; CHECK-NEXT: POP @@ -179,11 +257,10 @@ define i256 @second_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: DUP2 +; CHECK-NEXT: PUSH1 4 ; CHECK-NEXT: SWAP3 +; CHECK-NEXT: SWAP4 ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH1 4 -; CHECK-NEXT: SWAP2 ; CHECK-NEXT: ADD ; CHECK-NEXT: SWAP2 ; CHECK-NEXT: SUB @@ -220,10 +297,10 @@ define i256 @both_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK-LABEL: both_arg_alive_no_junk: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP2 +; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP3 ; CHECK-NEXT: POP ; CHECK-NEXT: DUP2 -; CHECK-NEXT: DUP2 ; CHECK-NEXT: DIV ; CHECK-NEXT: SWAP2 ; CHECK-NEXT: ADD @@ -241,9 +318,9 @@ define i256 @same_arg_dead_with_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: POP ; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP ; CHECK-NEXT: ADD ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: DUP2 @@ -255,4 +332,40 @@ define i256 @same_arg_dead_with_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ret i256 %x1 } +define void @commutable_not_in_function_entry() noreturn { + +; CHECK-LABEL: .BB{{[0-9]+}}_3: +; CHECK: JUMPDEST +; CHECK-NEXT: PUSH4 4294967295 +; CHECK-NEXT: AND +; CHECK-NEXT: PUSH0 + +enter: + %offset = inttoptr i256 0 to ptr addrspace(2) + %load = call i256 @llvm.evm.calldataload(ptr addrspace(2) %offset) + %calldata = trunc i256 %load to i32 + br label %header + +header: + %phi = phi i32 [ %calldata, %enter ], [ %inc, %do ] + %phi2 = phi i32 [ 1, %enter ], [ %mul, %do ] + %cmp = icmp sgt i32 %phi, 0 + br i1 %cmp, label %do, label %exit + +do: + %mul = mul nsw i32 %phi2, %phi + %inc = add nsw i32 %phi, -1 + br label %header + +exit: + %res = zext i32 %phi2 to i256 + store i256 %res, ptr addrspace(1) null, align 4 + call void @llvm.evm.return(ptr addrspace(1) null, i256 32) + unreachable +} + +declare i256 @llvm.evm.addmod(i256, i256, i256) +declare i256 @llvm.evm.mulmod(i256, i256, i256) +declare i256 @llvm.evm.calldataload(ptr addrspace(2)) +declare void @llvm.evm.return(ptr addrspace(1), i256) declare void @llvm.evm.revert(ptr addrspace(1), i256) diff --git a/llvm/test/CodeGen/EVM/stack-ops.ll b/llvm/test/CodeGen/EVM/stack-ops.ll index 92dfaf24887f..40fe299cf9f8 100644 --- a/llvm/test/CodeGen/EVM/stack-ops.ll +++ b/llvm/test/CodeGen/EVM/stack-ops.ll @@ -247,10 +247,10 @@ define i256 @both_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK-LABEL: both_arg_alive_no_junk: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP2 +; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP3 ; CHECK-NEXT: POP ; CHECK-NEXT: DUP2 -; CHECK-NEXT: DUP2 ; CHECK-NEXT: DIV ; CHECK-NEXT: SWAP2 ; CHECK-NEXT: SUB @@ -268,9 +268,9 @@ define i256 @same_arg_dead_with_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: POP ; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP ; CHECK-NEXT: ADD ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: DUP2 @@ -287,9 +287,9 @@ define i256 @same_arg_dead_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: POP ; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP ; CHECK-NEXT: ADD ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP @@ -302,10 +302,10 @@ define i256 @same_arg_alive_with_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: POP ; CHECK-NEXT: DUP1 ; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: POP ; CHECK-NEXT: ADD ; CHECK-NEXT: ADD ; CHECK-NEXT: SWAP1 @@ -324,10 +324,10 @@ define i256 @same_arg_alive_no_junk(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: POP ; CHECK-NEXT: DUP1 ; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: POP ; CHECK-NEXT: ADD ; CHECK-NEXT: ADD ; CHECK-NEXT: SWAP1 diff --git a/llvm/test/CodeGen/EVM/unused_function_arguments.ll b/llvm/test/CodeGen/EVM/unused_function_arguments.ll index 0aa142b88b10..50e68f31e91c 100644 --- a/llvm/test/CodeGen/EVM/unused_function_arguments.ll +++ b/llvm/test/CodeGen/EVM/unused_function_arguments.ll @@ -22,9 +22,9 @@ define i256 @wat(i256 %a1, i256 %a2, i256 %a3) nounwind { ; CHECK-LABEL: @wat ; CHECK: JUMPDEST ; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: POP ; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: POP ; CHECK-NEXT: ADD ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP From b8a89765e2eb4fd4437d4959f1b66a94c7edacb5 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic <129192835+vladimirradosavljevic@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:55:32 +0100 Subject: [PATCH 08/42] [EVM] Remove FunctionInfo struct (#742) Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMControlFlowGraph.h | 18 +++------- .../Target/EVM/EVMControlFlowGraphBuilder.cpp | 35 +++++++------------ .../Target/EVM/EVMControlFlowGraphBuilder.h | 1 + .../Target/EVM/EVMOptimizedCodeTransform.cpp | 26 +++++++------- .../Target/EVM/EVMOptimizedCodeTransform.h | 5 +-- llvm/lib/Target/EVM/EVMStackDebug.cpp | 35 +++++++++++-------- llvm/lib/Target/EVM/EVMStackDebug.h | 16 ++++++--- .../Target/EVM/EVMStackLayoutGenerator.cpp | 32 ++++++++--------- llvm/lib/Target/EVM/EVMStackLayoutGenerator.h | 13 ++++--- 9 files changed, 86 insertions(+), 95 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraph.h b/llvm/lib/Target/EVM/EVMControlFlowGraph.h index 86b7e2720560..dacff6f8f87e 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraph.h +++ b/llvm/lib/Target/EVM/EVMControlFlowGraph.h @@ -167,7 +167,7 @@ inline bool canBeFreelyGenerated(StackSlot const &Slot) { /// Control flow graph consisting of 'CFG::BasicBlock`s' connected by control /// flow. struct CFG { - explicit CFG() {} + explicit CFG(const MachineFunction &MF) : MF(MF) {} CFG(CFG const &) = delete; CFG(CFG &&) = delete; CFG &operator=(CFG const &) = delete; @@ -204,7 +204,6 @@ struct CFG { std::variant Operation; }; - struct FunctionInfo; /// A basic control flow block containing 'Operation`s' acting on the stack. /// Maintains a list of entry blocks and a typed exit. struct BasicBlock { @@ -230,7 +229,6 @@ struct CFG { struct FunctionReturn { Stack RetValues; - CFG::FunctionInfo *Info = nullptr; }; struct Terminated {}; @@ -254,21 +252,13 @@ struct CFG { Exit = InvalidExit{}; }; - struct FunctionInfo { - MachineFunction *MF = nullptr; - BasicBlock *Entry = nullptr; - std::vector Parameters; - std::vector Exits; - bool CanContinue = true; - }; - - FunctionInfo FuncInfo; - /// Container for blocks for explicit ownership. std::list Blocks; DenseMap MachineBBToBB; + std::vector Parameters; + const MachineFunction &MF; - BasicBlock &getBlock(const MachineBasicBlock *MBB) { + BasicBlock &getBlock(const MachineBasicBlock *MBB) const { auto It = MachineBBToBB.find(MBB); assert(It != MachineBBToBB.end()); return *It->second; diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp index b23ee8a866df..929dcd4d9ed3 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp +++ b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp @@ -30,13 +30,12 @@ using namespace llvm; /// Marks each block that needs to maintain a clean stack. That is each block /// that has an outgoing path to a function return. -static void markNeedsCleanStack(CFG &Cfg) { - for (CFG::BasicBlock *Exit : Cfg.FuncInfo.Exits) +static void markNeedsCleanStack(std::vector &Exits) { + for (CFG::BasicBlock *Exit : Exits) EVMUtils::BreadthFirstSearch{{Exit}}.run( [&](CFG::BasicBlock *Block, auto AddChild) { Block->NeedsCleanStack = true; - // TODO: it seems this is not needed, as the return block has - // no childs. + // Traverse over predecessors to mark them as well. for (CFG::BasicBlock *Entry : Block->Entries) AddChild(Entry); }); @@ -45,8 +44,7 @@ static void markNeedsCleanStack(CFG &Cfg) { /// Marks each cut-vertex in the CFG, i.e. each block that begins a disconnected /// sub-graph of the CFG. Entering such a block means that control flow will /// never return to a previously visited block. -static void markStartsOfSubGraphs(CFG &Cfg) { - CFG::BasicBlock *Entry = Cfg.FuncInfo.Entry; +static void markStartsOfSubGraphs(CFG::BasicBlock *Entry) { /** * Detect bridges following Algorithm 1 in * https://arxiv.org/pdf/2108.07346.pdf and mark the bridge targets as starts @@ -112,27 +110,19 @@ static void markStartsOfSubGraphs(CFG &Cfg) { std::unique_ptr ControlFlowGraphBuilder::build(MachineFunction &MF, const LiveIntervals &LIS, MachineLoopInfo *MLI) { - auto Result = std::make_unique(); + auto Result = std::make_unique(MF); ControlFlowGraphBuilder Builder(*Result, LIS, MLI); for (MachineBasicBlock &MBB : MF) Result->createBlock(&MBB); - Result->FuncInfo.MF = &MF; - Result->FuncInfo.Entry = &Result->getBlock(&MF.front()); - const Function &F = MF.getFunction(); - if (F.hasFnAttribute(Attribute::NoReturn)) - Result->FuncInfo.CanContinue = false; - // Handle function parameters auto *MFI = MF.getInfo(); - Result->FuncInfo.Parameters = - std::vector(MFI->getNumParams(), JunkSlot{}); + Result->Parameters = std::vector(MFI->getNumParams(), JunkSlot{}); for (const MachineInstr &MI : MF.front()) { if (MI.getOpcode() == EVM::ARGUMENT) { int64_t ArgIdx = MI.getOperand(1).getImm(); - Result->FuncInfo.Parameters[ArgIdx] = - VariableSlot{MI.getOperand(0).getReg()}; + Result->Parameters[ArgIdx] = VariableSlot{MI.getOperand(0).getReg()}; } } @@ -142,12 +132,12 @@ std::unique_ptr ControlFlowGraphBuilder::build(MachineFunction &MF, for (MachineBasicBlock &MBB : MF) Builder.handleBasicBlockSuccessors(MBB); - markStartsOfSubGraphs(*Result); - markNeedsCleanStack(*Result); + markStartsOfSubGraphs(&Result->getBlock(&MF.front())); + markNeedsCleanStack(Builder.Exits); LLVM_DEBUG({ dbgs() << "************* CFG *************\n"; - ControlFlowGraphPrinter P(dbgs()); + ControlFlowGraphPrinter P(dbgs(), MF); P(*Result); }); @@ -330,7 +320,7 @@ void ControlFlowGraphBuilder::handleFunctionCall(const MachineInstr &MI) { } void ControlFlowGraphBuilder::handleReturn(const MachineInstr &MI) { - Cfg.FuncInfo.Exits.emplace_back(CurrentBlock); + Exits.emplace_back(CurrentBlock); Stack Input; collectInstrOperands(MI, &Input, nullptr); // We need to reverse input operands to restore original ordering, @@ -338,8 +328,7 @@ void ControlFlowGraphBuilder::handleReturn(const MachineInstr &MI) { // Calling convention: return values are passed in stack such that the // last one specified in the RET instruction is passed on the stack TOP. std::reverse(Input.begin(), Input.end()); - CurrentBlock->Exit = - CFG::BasicBlock::FunctionReturn{std::move(Input), &Cfg.FuncInfo}; + CurrentBlock->Exit = CFG::BasicBlock::FunctionReturn{std::move(Input)}; } static std::pair diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h index efe9b97313d0..df56def2ccee 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h +++ b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h @@ -49,6 +49,7 @@ class ControlFlowGraphBuilder { const LiveIntervals &LIS; MachineLoopInfo *MLI = nullptr; CFG::BasicBlock *CurrentBlock = nullptr; + std::vector Exits; }; } // namespace llvm diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp index 3025675257f2..9b79e094b971 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp @@ -121,7 +121,8 @@ EVMOptimizedCodeTransform::EVMOptimizedCodeTransform(EVMAssembly &Assembly, CFG const &Cfg, StackLayout const &Layout, MachineFunction &MF) - : Assembly(Assembly), Layout(Layout), FuncInfo(&Cfg.FuncInfo), MF(MF) {} + : Assembly(Assembly), Layout(Layout), MF(MF), + EntryBB(Cfg.getBlock(&MF.front())), Parameters(Cfg.Parameters) {} bool EVMOptimizedCodeTransform::AreLayoutsCompatible(Stack const &SourceStack, Stack const &TargetStack) { @@ -185,7 +186,7 @@ void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { " slots in " + stackToString(CurrentStack)) .str(); - report_fatal_error(FuncInfo->MF->getName() + Twine(": ") + Msg); + report_fatal_error(MF.getName() + Twine(": ") + Msg); } }, // Push or dup callback. @@ -310,7 +311,7 @@ void EVMOptimizedCodeTransform::createOperationEntryLayout( void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { // Current location for the entry BB was set up in operator()(). - if (&Block != FuncInfo->Entry) + if (&Block != &EntryBB) Assembly.setCurrentLocation(Block.MBB); // Assert that this is the first visit of the block and mark as generated. @@ -322,7 +323,7 @@ void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { // Assert that the stack is valid for entering the block. The entry layout // of the function entry block should is fully determined by the first // instruction, so we can ignore 'BlockInfo.entryLayout'. - if (&Block != FuncInfo->Entry) { + if (&Block != &EntryBB) { assert(AreLayoutsCompatible(CurrentStack, BlockInfo.entryLayout)); // Might set some slots to junk, if not required by the block. CurrentStack = BlockInfo.entryLayout; @@ -431,14 +432,13 @@ void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { (*this)(*CondJump.NonZero); }, [&](CFG::BasicBlock::FunctionReturn const &FuncReturn) { - assert(FuncInfo->CanContinue); + assert(!MF.getFunction().hasFnAttribute(Attribute::NoReturn)); // Construct the function return layout, which is fully determined // by the function signature. Stack ExitStack = FuncReturn.RetValues; - ExitStack.emplace_back( - FunctionReturnLabelSlot{FuncReturn.Info->MF}); + ExitStack.emplace_back(FunctionReturnLabelSlot{&MF}); // Create the function return layout and jump. createStackLayout(ExitStack); @@ -471,24 +471,24 @@ void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { void EVMOptimizedCodeTransform::operator()() { assert(CurrentStack.empty() && Assembly.getStackHeight() == 0); - Assembly.setCurrentLocation(FuncInfo->Entry->MBB); + Assembly.setCurrentLocation(EntryBB.MBB); - assert(!BlockLabels.count(FuncInfo->Entry)); + assert(!BlockLabels.count(&EntryBB)); // Create function entry layout in CurrentStack. - if (FuncInfo->CanContinue) - CurrentStack.emplace_back(FunctionReturnLabelSlot{FuncInfo->MF}); + if (!MF.getFunction().hasFnAttribute(Attribute::NoReturn)) + CurrentStack.emplace_back(FunctionReturnLabelSlot{&MF}); // Calling convention: input arguments are passed in stack such that the // first one specified in the function declaration is passed on the stack TOP. - for (auto const &Param : reverse(FuncInfo->Parameters)) + for (auto const &Param : reverse(Parameters)) CurrentStack.emplace_back(Param); Assembly.setStackHeight(static_cast(CurrentStack.size())); Assembly.appendLabel(); // Visit the function entry block. - (*this)(*FuncInfo->Entry); + (*this)(EntryBB); Assembly.finalize(); } diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h index 2382c68a80b3..aa4f08ee74af 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h @@ -74,8 +74,9 @@ class EVMOptimizedCodeTransform { EVMAssembly &Assembly; StackLayout const &Layout; - CFG::FunctionInfo const *FuncInfo = nullptr; - MachineFunction &MF; + const MachineFunction &MF; + const CFG::BasicBlock &EntryBB; + const std::vector &Parameters; Stack CurrentStack; DenseMap CallToReturnMCSymbol; diff --git a/llvm/lib/Target/EVM/EVMStackDebug.cpp b/llvm/lib/Target/EVM/EVMStackDebug.cpp index e3792e0151a5..e9bb24e6bb38 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.cpp +++ b/llvm/lib/Target/EVM/EVMStackDebug.cpp @@ -83,19 +83,22 @@ std::string llvm::stackSlotToString(const StackSlot &Slot) { #ifndef NDEBUG void ControlFlowGraphPrinter::operator()(const CFG &Cfg) { - (*this)(Cfg.FuncInfo); + (*this)(); for (const auto &Block : Cfg.Blocks) printBlock(Block); } -void ControlFlowGraphPrinter::operator()(CFG::FunctionInfo const &Info) { - OS << "Function: " << Info.MF->getName() << '\n'; - OS << "Entry block: " << getBlockId(*Info.Entry) << ";\n"; +void ControlFlowGraphPrinter::operator()() { + OS << "Function: " << MF.getName() << '\n'; + OS << "Entry block: " << getBlockId(MF.front()) << ";\n"; } std::string ControlFlowGraphPrinter::getBlockId(CFG::BasicBlock const &Block) { - return std::to_string(Block.MBB->getNumber()) + "." + - std::string(Block.MBB->getName()); + return getBlockId(*Block.MBB); +} + +std::string ControlFlowGraphPrinter::getBlockId(const MachineBasicBlock &MBB) { + return std::to_string(MBB.getNumber()) + "." + std::string(MBB.getName()); } void ControlFlowGraphPrinter::printBlock(CFG::BasicBlock const &Block) { @@ -159,8 +162,8 @@ void ControlFlowGraphPrinter::printBlock(CFG::BasicBlock const &Block) { }, [&](const CFG::BasicBlock::FunctionReturn &Return) { OS << "Block" << getBlockId(Block) - << "Exit [label=\"FunctionReturn[" - << Return.Info->MF->getName() << "]\"];\n"; + << "Exit [label=\"FunctionReturn[" << MF.getName() + << "]\"];\n"; OS << "Return values: " << stackToString(Return.RetValues); }, [&](const CFG::BasicBlock::Terminated &) { @@ -191,9 +194,10 @@ void StackLayoutPrinter::operator()(CFG::BasicBlock const &Block, } } -void StackLayoutPrinter::operator()(CFG::FunctionInfo const &Info) { - OS << "Function: " << Info.MF->getName() << "("; - for (const StackSlot &ParamSlot : Info.Parameters) { +void StackLayoutPrinter::operator()(CFG::BasicBlock const &EntryBB, + const std::vector &Parameters) { + OS << "Function: " << MF.getName() << "("; + for (const StackSlot &ParamSlot : Parameters) { if (const auto *Slot = std::get_if(&ParamSlot)) OS << printReg(Slot->VirtualReg, nullptr, 0, nullptr) << ' '; else if (std::holds_alternative(ParamSlot)) @@ -202,8 +206,9 @@ void StackLayoutPrinter::operator()(CFG::FunctionInfo const &Info) { llvm_unreachable("Unexpected stack slot"); } OS << ");\n"; - OS << "FunctionEntry " << " -> Block" << getBlockId(*Info.Entry) << ";\n"; - (*this)(*Info.Entry, false); + OS << "FunctionEntry " + << " -> Block" << getBlockId(EntryBB) << ";\n"; + (*this)(EntryBB, false); } void StackLayoutPrinter::printBlock(CFG::BasicBlock const &Block) { @@ -283,8 +288,8 @@ void StackLayoutPrinter::printBlock(CFG::BasicBlock const &Block) { }, [&](CFG::BasicBlock::FunctionReturn const &Return) { OS << "Block" << getBlockId(Block) - << "Exit [label=\"FunctionReturn[" - << Return.Info->MF->getName() << "]\"];\n"; + << "Exit [label=\"FunctionReturn[" << MF.getName() + << "]\"];\n"; }, [&](CFG::BasicBlock::Terminated const &) { OS << "Block" << getBlockId(Block) diff --git a/llvm/lib/Target/EVM/EVMStackDebug.h b/llvm/lib/Target/EVM/EVMStackDebug.h index efe39f20f084..7978fe79c729 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.h +++ b/llvm/lib/Target/EVM/EVMStackDebug.h @@ -33,24 +33,29 @@ std::string stackToString(Stack const &S); #ifndef NDEBUG class ControlFlowGraphPrinter { public: - ControlFlowGraphPrinter(raw_ostream &OS) : OS(OS) {} + ControlFlowGraphPrinter(raw_ostream &OS, const MachineFunction &MF) + : OS(OS), MF(MF) {} void operator()(const CFG &Cfg); private: - void operator()(const CFG::FunctionInfo &Info); + void operator()(); std::string getBlockId(const CFG::BasicBlock &Block); + std::string getBlockId(const MachineBasicBlock &MBB); void printBlock(const CFG::BasicBlock &Block); raw_ostream &OS; + const MachineFunction &MF; }; class StackLayoutPrinter { public: - StackLayoutPrinter(raw_ostream &OS, const StackLayout &StackLayout) - : OS(OS), Layout(StackLayout) {} + StackLayoutPrinter(raw_ostream &OS, const StackLayout &StackLayout, + const MachineFunction &MF) + : OS(OS), Layout(StackLayout), MF(MF) {} void operator()(CFG::BasicBlock const &Block, bool IsMainEntry = true); - void operator()(CFG::FunctionInfo const &Info); + void operator()(CFG::BasicBlock const &EntryBB, + const std::vector &Parameters); private: void printBlock(CFG::BasicBlock const &Block); @@ -58,6 +63,7 @@ class StackLayoutPrinter { raw_ostream &OS; const StackLayout &Layout; + const MachineFunction &MF; std::map BlockIds; size_t BlockCount = 0; std::list BlocksToPrint; diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp index 7e739a746379..d596c61e44a2 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -33,21 +33,24 @@ using namespace llvm; StackLayout StackLayoutGenerator::run(const CFG &Cfg) { StackLayout Layout; - StackLayoutGenerator LayoutGenerator{Layout, &Cfg.FuncInfo}; - LayoutGenerator.processEntryPoint(*Cfg.FuncInfo.Entry, &Cfg.FuncInfo); + StackLayoutGenerator LayoutGenerator{Layout, Cfg.MF, Cfg.Parameters}; + + auto &EntryBB = Cfg.getBlock(&Cfg.MF.front()); + LayoutGenerator.processEntryPoint(EntryBB); LLVM_DEBUG({ dbgs() << "************* Stack Layout *************\n"; - StackLayoutPrinter P(dbgs(), Layout); - P(Cfg.FuncInfo); + StackLayoutPrinter P(dbgs(), Layout, Cfg.MF); + P(EntryBB, Cfg.Parameters); }); return Layout; } StackLayoutGenerator::StackLayoutGenerator( - StackLayout &Layout, CFG::FunctionInfo const *FunctionInfo) - : Layout(Layout), CurrentFunctionInfo(FunctionInfo) {} + StackLayout &Layout, const MachineFunction &MF, + const std::vector &Parameters) + : Layout(Layout), Parameters(Parameters), MF(MF) {} namespace { @@ -317,8 +320,7 @@ Stack StackLayoutGenerator::propagateStackThroughBlock( return CurrentStack; } -void StackLayoutGenerator::processEntryPoint( - CFG::BasicBlock const &Entry, CFG::FunctionInfo const *FunctionInfo) { +void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const &Entry) { std::list ToVisit{&Entry}; std::set Visited; @@ -392,7 +394,7 @@ void StackLayoutGenerator::processEntryPoint( } stitchConditionalJumps(Entry); - fillInJunk(Entry, FunctionInfo); + fillInJunk(Entry); } std::optional StackLayoutGenerator::getExitLayoutOrStageDependencies( @@ -452,10 +454,8 @@ std::optional StackLayoutGenerator::getExitLayoutOrStageDependencies( -> std::optional { // A function return needs the return variables and the function // return label slot on stack. - assert(FunctionReturn.Info); Stack ReturnStack = FunctionReturn.RetValues; - ReturnStack.emplace_back( - FunctionReturnLabelSlot{FunctionReturn.Info->MF}); + ReturnStack.emplace_back(FunctionReturnLabelSlot{&MF}); return ReturnStack; }, [&](CFG::BasicBlock::Terminated const &) -> std::optional { @@ -730,8 +730,7 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { return OpGas; } -void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const &Block, - CFG::FunctionInfo const *FunctionInfo) { +void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const &Block) { /// Recursively adds junk to the subgraph starting on \p Entry. /// Since it is only called on cut-vertices, the full subgraph retains proper /// stack balance. @@ -789,9 +788,10 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const &Block, return BestNumJunk; }; - if (FunctionInfo && !FunctionInfo->CanContinue && Block.AllowsJunk()) { + if (MF.getFunction().hasFnAttribute(Attribute::NoReturn) && + Block.AllowsJunk()) { Stack Params; - for (const auto &Param : FunctionInfo->Parameters) + for (const auto &Param : Parameters) Params.emplace_back(Param); std::reverse(Params.begin(), Params.end()); size_t BestNumJunk = diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h index ff675b88f30d..aed7a5b3d7ba 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h @@ -57,8 +57,8 @@ class StackLayoutGenerator { static std::vector reportStackTooDeep(CFG const &Cfg); private: - StackLayoutGenerator(StackLayout &Context, - CFG::FunctionInfo const *FunctionInfo); + StackLayoutGenerator(StackLayout &Layout, const MachineFunction &MF, + const std::vector &Parameters); /// Returns the optimal entry stack layout, s.t. \p Operation can be applied /// to it and the result can be transformed to \p ExitStack with minimal stack @@ -77,8 +77,7 @@ class StackLayoutGenerator { /// Main algorithm walking the graph from entry to exit and propagating back /// the stack layouts to the entries. Iteratively reruns itself along /// backwards jumps until the layout is stabilized. - void processEntryPoint(CFG::BasicBlock const &Entry, - CFG::FunctionInfo const *FunctionInfo = nullptr); + void processEntryPoint(CFG::BasicBlock const &Entry); /// Returns the best known exit layout of \p Block, if all dependencies are /// already \p Visited. If not, adds the dependencies to \p DependencyList and @@ -118,11 +117,11 @@ class StackLayoutGenerator { /// Fills in junk when entering branches that do not need a clean stack in /// case the result is cheaper. - void fillInJunk(CFG::BasicBlock const &Block, - CFG::FunctionInfo const *FunctionInfo = nullptr); + void fillInJunk(CFG::BasicBlock const &Block); StackLayout &Layout; - CFG::FunctionInfo const *CurrentFunctionInfo = nullptr; + const std::vector &Parameters; + const MachineFunction &MF; }; } // end namespace llvm From 3c63c6fe5b6b756c889bb786a543d2ad8315d1ef Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Wed, 4 Dec 2024 14:49:21 +0100 Subject: [PATCH 09/42] [EVM] Introduce pseudo jumps, call and ret instructions This patch adds pseudo jumps, call and ret instructions to fix machine verifier after stackification and to reduce complexity added with bundles. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMAsmPrinter.cpp | 67 ++++++++------- llvm/lib/Target/EVM/EVMAssembly.cpp | 81 ++++++------------- llvm/lib/Target/EVM/EVMAssembly.h | 4 +- llvm/lib/Target/EVM/EVMInstrInfo.td | 14 +++- .../Target/EVM/EVMOptimizedCodeTransform.cpp | 2 +- llvm/lib/Target/EVM/EVMStackify.cpp | 47 +++-------- llvm/test/CodeGen/EVM/fallthrough.mir | 45 ----------- 7 files changed, 84 insertions(+), 176 deletions(-) delete mode 100644 llvm/test/CodeGen/EVM/fallthrough.mir diff --git a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp index df5257c8a4bb..104cc8472d49 100644 --- a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp +++ b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp @@ -50,18 +50,10 @@ class EVMAsmPrinter : public AsmPrinter { StringRef getPassName() const override { return "EVM Assembly "; } - void SetupMachineFunction(MachineFunction &MF) override; - void emitInstruction(const MachineInstr *MI) override; void emitFunctionEntryLabel() override; - /// Return true if the basic block has exactly one predecessor and the control - /// transfer mechanism between the predecessor and this block is a - /// fall-through. - bool isBlockOnlyReachableByFallthrough( - const MachineBasicBlock *MBB) const override; - void emitEndOfAsmFile(Module &) override; private: @@ -70,22 +62,6 @@ class EVMAsmPrinter : public AsmPrinter { }; } // end of anonymous namespace -void EVMAsmPrinter::SetupMachineFunction(MachineFunction &MF) { - // Unbundle bundles. - for (MachineBasicBlock &MBB : MF) { - MachineBasicBlock::instr_iterator I = MBB.instr_begin(), - E = MBB.instr_end(); - for (; I != E; ++I) { - if (I->isBundledWithPred()) { - assert(I->isConditionalBranch() || I->isUnconditionalBranch()); - I->unbundleFromPred(); - } - } - } - - AsmPrinter::SetupMachineFunction(MF); -} - void EVMAsmPrinter::emitFunctionEntryLabel() { AsmPrinter::emitFunctionEntryLabel(); @@ -111,14 +87,43 @@ void EVMAsmPrinter::emitFunctionEntryLabel() { void EVMAsmPrinter::emitInstruction(const MachineInstr *MI) { EVMMCInstLower MCInstLowering(OutContext, *this, VRegMapping, MF->getRegInfo()); - unsigned Opc = MI->getOpcode(); - if (Opc == EVM::DATASIZE_S || Opc == EVM::DATAOFFSET_S) { - emitAssemblySymbol(MI); + + switch (MI->getOpcode()) { + default: + break; + case EVM::PseudoCALL: + case EVM::PseudoRET: { + // TODO: #746: Use PseudoInstExpansion and do this expansion in tblgen. + MCInst Jump; + Jump.setOpcode(EVM::JUMP_S); + EmitToStreamer(*OutStreamer, Jump); + return; + } + case EVM::PseudoJUMP: + case EVM::PseudoJUMPI: { + MCInst Push; + Push.setOpcode(EVM::PUSH4_S); + + // TODO: #745: Refactor EVMMCInstLower::Lower so we could use lowerOperand + // instead of creating a MCOperand directly. + MCOperand MCOp = MCOperand::createExpr(MCSymbolRefExpr::create( + MI->getOperand(0).getMBB()->getSymbol(), OutContext)); + Push.addOperand(MCOp); + EmitToStreamer(*OutStreamer, Push); + + MCInst Jump; + Jump.setOpcode(MI->getOpcode() == EVM::PseudoJUMP ? EVM::JUMP_S + : EVM::JUMPI_S); + EmitToStreamer(*OutStreamer, Jump); return; } - if (Opc == EVM::LINKERSYMBOL_S) { + case EVM::LINKERSYMBOL_S: emitWideRelocatableSymbol(MI); return; + case EVM::DATASIZE_S: + case EVM::DATAOFFSET_S: + emitAssemblySymbol(MI); + return; } MCInst TmpInst; @@ -126,12 +131,6 @@ void EVMAsmPrinter::emitInstruction(const MachineInstr *MI) { EmitToStreamer(*OutStreamer, TmpInst); } -bool EVMAsmPrinter::isBlockOnlyReachableByFallthrough( - const MachineBasicBlock *MBB) const { - // For simplicity, always emit BB labels. - return false; -} - void EVMAsmPrinter::emitAssemblySymbol(const MachineInstr *MI) { MCSymbol *LinkerSymbol = MI->getOperand(0).getMCSymbol(); StringRef LinkerSymbolName = LinkerSymbol->getName(); diff --git a/llvm/lib/Target/EVM/EVMAssembly.cpp b/llvm/lib/Target/EVM/EVMAssembly.cpp index 21a7020e284c..775af0097186 100644 --- a/llvm/lib/Target/EVM/EVMAssembly.cpp +++ b/llvm/lib/Target/EVM/EVMAssembly.cpp @@ -136,15 +136,6 @@ void EVMAssembly::appendLabelReference(MCSymbol *Label) { CurMIIt = std::next(CurMIIt); } -void EVMAssembly::appendMBBReference(MachineBasicBlock *MBB) { - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::PUSH_LABEL)) - .addMBB(MBB); - StackHeight += 1; - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); -} - MCSymbol *EVMAssembly::createFuncRetSymbol() { return MF->getContext().createTempSymbol("FUNC_RET", true); } @@ -165,9 +156,9 @@ void EVMAssembly::appendFuncCall(const MachineInstr *MI, LLVM_DEBUG(dumpInst(&*CurMIIt)); CurMIIt = std::next(CurMIIt); - // Create jump to the callee. Note, we don't add the 'target' operand to JUMP. - // This should be fine, unless we run MachineVerifier after this step - CurMIIt = BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::JUMP)); + // Create jump to the callee. + CurMIIt = + BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoCALL)); if (RetSym) CurMIIt->setPostInstrSymbol(*MF, RetSym); AssemblyInstrs.insert(&*CurMIIt); @@ -175,9 +166,8 @@ void EVMAssembly::appendFuncCall(const MachineInstr *MI, CurMIIt = std::next(CurMIIt); } -void EVMAssembly::appendJump(int StackAdj) { - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::JUMP)); - StackHeight += StackAdj; +void EVMAssembly::appendRet() { + CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::PseudoRET)); AssemblyInstrs.insert(&*CurMIIt); LLVM_DEBUG(dumpInst(&*CurMIIt)); CurMIIt = std::next(CurMIIt); @@ -186,22 +176,25 @@ void EVMAssembly::appendJump(int StackAdj) { void EVMAssembly::appendUncondJump(MachineInstr *MI, MachineBasicBlock *Target) { assert(MI->getOpcode() == EVM::JUMP); - appendMBBReference(Target); - [[maybe_unused]] auto It = AssemblyInstrs.insert(MI); - assert(It.second && StackHeight > 0); - StackHeight -= 1; - LLVM_DEBUG(dumpInst(MI)); - CurMIIt = std::next(MIIter(MI)); + CurMIIt = + BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoJUMP)) + .addMBB(Target); + assert(StackHeight >= 0); + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); } void EVMAssembly::appendCondJump(MachineInstr *MI, MachineBasicBlock *Target) { assert(MI->getOpcode() == EVM::JUMPI); - appendMBBReference(Target); - [[maybe_unused]] auto It = AssemblyInstrs.insert(MI); - assert(It.second && StackHeight > 1); - StackHeight -= 2; - LLVM_DEBUG(dumpInst(MI)); - CurMIIt = std::next(MIIter(MI)); + CurMIIt = + BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoJUMPI)) + .addMBB(Target); + StackHeight -= 1; + assert(StackHeight >= 0); + AssemblyInstrs.insert(&*CurMIIt); + LLVM_DEBUG(dumpInst(&*CurMIIt)); + CurMIIt = std::next(CurMIIt); } // Remove all registers operands of the \p MI and repaces the opcode with @@ -211,7 +204,9 @@ void EVMAssembly::stackifyInstruction(MachineInstr *MI) { return; unsigned RegOpcode = MI->getOpcode(); - if (RegOpcode == EVM::PUSH_LABEL) + if (RegOpcode == EVM::PUSH_LABEL || RegOpcode == EVM::PseudoJUMP || + RegOpcode == EVM::PseudoJUMPI || RegOpcode == EVM::PseudoCALL || + RegOpcode == EVM::PseudoRET) return; // Remove register operands. @@ -233,8 +228,7 @@ void EVMAssembly::finalize() { SmallVector ToRemove; for (MachineBasicBlock &MBB : *MF) { for (MachineInstr &MI : MBB) { - if (!AssemblyInstrs.count(&MI) && MI.getOpcode() != EVM::JUMP && - MI.getOpcode() != EVM::JUMPI) + if (!AssemblyInstrs.count(&MI)) ToRemove.emplace_back(&MI); } } @@ -253,31 +247,4 @@ void EVMAssembly::finalize() { // In a stackified code register liveness has no meaning. MachineRegisterInfo &MRI = MF->getRegInfo(); MRI.invalidateLiveness(); - - // In EVM architecture jump target is set up using one of PUSH* instructions - // that come right before the jump instruction. - // For example: - - // PUSH_LABEL %bb.10 - // JUMPI_S - // PUSH_LABEL %bb.9 - // JUMP_S - // - // The problem here is that such MIR is not valid. There should not be - // non-terminator (PUSH) instructions between terminator (JUMP) ones. - // To overcome this issue, we bundle adjacent instructions - // together and unbundle them in the AsmPrinter. - for (MachineBasicBlock &MBB : *MF) { - MachineBasicBlock::instr_iterator I = MBB.instr_begin(), - E = MBB.instr_end(); - // Skip the first instruction, as it's not interested anyway. - ++I; - for (; I != E; ++I) { - if (I->isBranch()) { - auto P = std::prev(I); - if (P->getOpcode() == EVM::PUSH_LABEL) - I->bundleWithPred(); - } - } - } } diff --git a/llvm/lib/Target/EVM/EVMAssembly.h b/llvm/lib/Target/EVM/EVMAssembly.h index 8f40acfebe9e..58c0e2ed36de 100644 --- a/llvm/lib/Target/EVM/EVMAssembly.h +++ b/llvm/lib/Target/EVM/EVMAssembly.h @@ -71,7 +71,7 @@ class EVMAssembly { void appendFuncCall(const MachineInstr *MI, const llvm::GlobalValue *Func, int stackAdj, MCSymbol *RetSym = nullptr); - void appendJump(int stackAdj); + void appendRet(); void appendCondJump(MachineInstr *MI, MachineBasicBlock *Target); @@ -79,8 +79,6 @@ class EVMAssembly { void appendLabelReference(MCSymbol *Label); - void appendMBBReference(MachineBasicBlock *MBB); - MCSymbol *createFuncRetSymbol(); // Erases unused codegen-only instructions and removes register operands diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index f7fb7c5cc4e9..68c89120dff6 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -248,6 +248,8 @@ let variadicOpsAreDefs = 1 in def FCALL : NRI<(outs), (ins jmptarget:$callee, variable_ops), [], "FCALL\t$callee">; +let isPseudo = 1 in +def PseudoCALL : EVMPseudo<(outs), (ins), []>; } // Uses = [SP], isCall = 1 @@ -405,18 +407,26 @@ let isBranch = 1, isTerminator = 1 in { defm JUMPI : I<(outs), (ins jmptarget:$dst, GPR:$cond), [(brcond GPR:$cond, bb:$dst)], "JUMPI", " $dst, $cond", 0x57, 10>; +let isPseudo = 1 in +def PseudoJUMPI : EVMPseudo<(outs), (ins jmptarget:$dst), []>; -let isBarrier = 1 in +let isBarrier = 1 in { defm JUMP : I<(outs), (ins jmptarget:$dst), [(br bb:$dst)], "JUMP", " $dst", 0x56, 8>; +let isPseudo = 1 in +def PseudoJUMP : EVMPseudo<(outs), (ins jmptarget:$dst), []>; +} // isBarrier = 1 } // isBranch = 1, isTerminator = 1 // This isn't really a control flow instruction, but it should be used to mark // destination of jump instructions. defm JUMPDEST : I<(outs), (ins), [], "JUMPDEST", "", 0x5B, 1>; -let isBarrier = 1, isTerminator = 1, isReturn = 1 in +let isBarrier = 1, isTerminator = 1, isReturn = 1 in { def RET : NRI<(outs), (ins variable_ops), [(EVMret)], "RET">; +let isPseudo = 1 in +def PseudoRET : EVMPseudo<(outs), (ins), []>; +} //===----------------------------------------------------------------------===// diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp index 9b79e094b971..046067ee640f 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp @@ -442,7 +442,7 @@ void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { // Create the function return layout and jump. createStackLayout(ExitStack); - Assembly.appendJump(0); + Assembly.appendRet(); }, [&](CFG::BasicBlock::Unreachable const &) { assert(Block.Operations.empty()); diff --git a/llvm/lib/Target/EVM/EVMStackify.cpp b/llvm/lib/Target/EVM/EVMStackify.cpp index 722078251f8f..145ff2d7d211 100644 --- a/llvm/lib/Target/EVM/EVMStackify.cpp +++ b/llvm/lib/Target/EVM/EVMStackify.cpp @@ -720,16 +720,20 @@ void StackModel::handleArgument(MachineInstr *MI) { void StackModel::handleLStackAtJump(MachineBasicBlock *MBB, MachineInstr *MI, const Register &Reg) { + assert(MI->getOpcode() == EVM::JUMP || MI->getOpcode() == EVM::JUMPI); + // If the condition register is in the L-stack, we need to move it to // the bottom of the L-stack. After that we should clean clean the L-stack. // In case of an unconditional jump, the Reg value should be // EVM::NoRegister. clearPhysStackAtInst(StackType::L, MI, Reg); - // Insert "PUSH_LABEL %bb" instruction that should be be replaced with - // the actual PUSH* one in the MC layer to contain actual jump target - // offset. - BuildMI(*MI->getParent(), MI, DebugLoc(), TII->get(EVM::PUSH_LABEL)) + // Insert pseudo jump instruciton that will be replaced with PUSH and JUMP + // instructions in AsmPrinter. + ToErase.push_back(MI); + unsigned PseudoJumpOpc = + MI->getOpcode() == EVM::JUMP ? EVM::PseudoJUMP : EVM::PseudoJUMPI; + BuildMI(*MI->getParent(), MI, DebugLoc(), TII->get(PseudoJumpOpc)) .addMBB(MBB); // Add JUMPDEST at the beginning of the target MBB. @@ -752,7 +756,7 @@ void StackModel::handleJump(MachineInstr *MI) { void StackModel::handleReturn(MachineInstr *MI) { ToErase.push_back(MI); BuildMI(*MI->getParent(), std::next(MIIter(MI)), DebugLoc(), - TII->get(EVM::JUMP)); + TII->get(EVM::PseudoRET)); // Collect the use registers of the RET instruction. SmallVector ReturnRegs; @@ -872,7 +876,7 @@ void StackModel::handleCall(MachineInstr *MI) { MCSymbol *RetSym = MF->getContext().createTempSymbol("FUNC_RET", true); // Create jump to the callee. - It = BuildMI(MBB, It, MI->getDebugLoc(), TII->get(EVM::JUMP)); + It = BuildMI(MBB, It, MI->getDebugLoc(), TII->get(EVM::PseudoCALL)); It->setPostInstrSymbol(*MF, RetSym); // Create push of the return address. @@ -995,7 +999,9 @@ void StackModel::stackifyInstruction(MachineInstr *MI) { return; unsigned RegOpcode = MI->getOpcode(); - if (RegOpcode == EVM::PUSH_LABEL) + if (RegOpcode == EVM::PUSH_LABEL || RegOpcode == EVM::PseudoJUMP || + RegOpcode == EVM::PseudoJUMPI || RegOpcode == EVM::PseudoCALL || + RegOpcode == EVM::PseudoRET) return; // Remove register operands. @@ -1048,33 +1054,6 @@ void StackModel::postProcess() { // In a stackified code register liveness has no meaning. MachineRegisterInfo &MRI = MF->getRegInfo(); MRI.invalidateLiveness(); - - // In EVM architecture jump target is set up using one of PUSH* instructions - // that come right before the jump instruction. - // For example: - - // PUSH_LABEL %bb.10 - // JUMPI_S - // PUSH_LABEL %bb.9 - // JUMP_S - // - // The problem here is that such MIR is not valid. There should not be - // non-terminator (PUSH) instructions between terminator (JUMP) ones. - // To overcome this issue, we bundle adjacent instructions - // together and unbundle them in the AsmPrinter. - for (MachineBasicBlock &MBB : *MF) { - MachineBasicBlock::instr_iterator I = MBB.instr_begin(), - E = MBB.instr_end(); - // Skip the first instruction, as it's not interested anyway. - ++I; - for (; I != E; ++I) { - if (I->isBranch()) { - auto P = std::prev(I); - if (P->getOpcode() == EVM::PUSH_LABEL) - I->bundleWithPred(); - } - } - } } void StackModel::dumpState() const { diff --git a/llvm/test/CodeGen/EVM/fallthrough.mir b/llvm/test/CodeGen/EVM/fallthrough.mir deleted file mode 100644 index 1e578fb13513..000000000000 --- a/llvm/test/CodeGen/EVM/fallthrough.mir +++ /dev/null @@ -1,45 +0,0 @@ -# RUN: llc -x mir --start-after=evm-stackify < %s | FileCheck %s - - ---- | - - target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" - target triple = "evm" - define void @test_fallthrough() { ret void } - -... ---- -# CHECK: PUSH4 @.BB0_1 -# CHECK-NEXT: JUMPI -# CHECK-NEXT: PUSH4 @.BB0_2 -# CHECK-NEXT: JUMP -# CHECK-NEXT: .BB0_1: - -name: test_fallthrough -tracksRegLiveness: false -machineFunctionInfo: - isStackified: true -body: | - bb.0: - JUMPDEST_S - PUSH_LABEL %bb.10, implicit-def $arguments { - JUMPI_S - } - PUSH_LABEL %bb.13, implicit-def $arguments { - JUMP_S - } - - bb.10: - liveins: $value_stack - JUMPDEST_S - PUSH0_S - PUSH0_S - REVERT_S - - bb.13: - liveins: $value_stack - JUMPDEST_S - PUSH0_S - PUSH0_S - REVERT_S -... From 4fa5e91768a0572f488530dc9f3b896d4c8764a9 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Thu, 5 Dec 2024 11:58:55 +0100 Subject: [PATCH 10/42] [EVM] Move generation of JUMPDEST to the AsmPrinter phase Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMAsmPrinter.cpp | 54 ++++++++++++++++++- llvm/lib/Target/EVM/EVMAssembly.cpp | 30 +++++------ llvm/lib/Target/EVM/EVMAssembly.h | 2 - llvm/lib/Target/EVM/EVMInstrInfo.td | 2 +- .../Target/EVM/EVMOptimizedCodeTransform.cpp | 7 --- llvm/lib/Target/EVM/EVMStackify.cpp | 26 +++------ 6 files changed, 74 insertions(+), 47 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp index 104cc8472d49..5aac092fbb64 100644 --- a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp +++ b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp @@ -30,6 +30,8 @@ using namespace llvm; +extern cl::opt EVMKeepRegisters; + #define DEBUG_TYPE "asm-printer" namespace { @@ -52,6 +54,8 @@ class EVMAsmPrinter : public AsmPrinter { void emitInstruction(const MachineInstr *MI) override; + void emitBasicBlockStart(const MachineBasicBlock &MBB) override; + void emitFunctionEntryLabel() override; void emitEndOfAsmFile(Module &) override; @@ -59,6 +63,7 @@ class EVMAsmPrinter : public AsmPrinter { private: void emitAssemblySymbol(const MachineInstr *MI); void emitWideRelocatableSymbol(const MachineInstr *MI); + void emitJumpDest(); }; } // end of anonymous namespace @@ -84,6 +89,15 @@ void EVMAsmPrinter::emitFunctionEntryLabel() { } } +void EVMAsmPrinter::emitBasicBlockStart(const MachineBasicBlock &MBB) { + AsmPrinter::emitBasicBlockStart(MBB); + + // Emit JUMPDEST instruction at the beginning of the basic block, if + // this is not a block that is only reachable by fallthrough. + if (!EVMKeepRegisters && !AsmPrinter::isBlockOnlyReachableByFallthrough(&MBB)) + emitJumpDest(); +} + void EVMAsmPrinter::emitInstruction(const MachineInstr *MI) { EVMMCInstLower MCInstLowering(OutContext, *this, VRegMapping, MF->getRegInfo()); @@ -91,7 +105,39 @@ void EVMAsmPrinter::emitInstruction(const MachineInstr *MI) { switch (MI->getOpcode()) { default: break; - case EVM::PseudoCALL: + case EVM::PseudoCALL: { + // Generate push instruction with the address of a function. + MCInst Push; + Push.setOpcode(EVM::PUSH4_S); + assert(MI->getOperand(0).isGlobal() && + "The first operand of PseudoCALL should be a GlobalValue."); + + // TODO: #745: Refactor EVMMCInstLower::Lower so we could use lowerOperand + // instead of creating a MCOperand directly. + MCOperand MCOp = MCOperand::createExpr(MCSymbolRefExpr::create( + getSymbol(MI->getOperand(0).getGlobal()), OutContext)); + Push.addOperand(MCOp); + EmitToStreamer(*OutStreamer, Push); + + // Jump to a function. + MCInst Jump; + Jump.setOpcode(EVM::JUMP_S); + EmitToStreamer(*OutStreamer, Jump); + + // In case a function has a return label, emit it, and also + // emit a JUMPDEST instruction. + if (MI->getNumExplicitOperands() > 1) { + // We need to emit ret label after JUMP instruction, so we couldn't + // use setPostInstrSymbol since label would be created after + // JUMPDEST instruction. To overcome this, we added MCSymbol operand + // and we are emitting label manually here. + assert(MI->getOperand(1).isMCSymbol() && + "The second operand of PseudoCALL should be a MCSymbol."); + OutStreamer->emitLabel(MI->getOperand(1).getMCSymbol()); + emitJumpDest(); + } + return; + } case EVM::PseudoRET: { // TODO: #746: Use PseudoInstExpansion and do this expansion in tblgen. MCInst Jump; @@ -206,6 +252,12 @@ void EVMAsmPrinter::emitWideRelocatableSymbol(const MachineInstr *MI) { void EVMAsmPrinter::emitEndOfAsmFile(Module &) { WideRelocSymbolsSet.clear(); } +void EVMAsmPrinter::emitJumpDest() { + MCInst JumpDest; + JumpDest.setOpcode(EVM::JUMPDEST_S); + EmitToStreamer(*OutStreamer, JumpDest); +} + extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeEVMAsmPrinter() { const RegisterAsmPrinter X(getTheEVMTarget()); } diff --git a/llvm/lib/Target/EVM/EVMAssembly.cpp b/llvm/lib/Target/EVM/EVMAssembly.cpp index 775af0097186..7335679f09c6 100644 --- a/llvm/lib/Target/EVM/EVMAssembly.cpp +++ b/llvm/lib/Target/EVM/EVMAssembly.cpp @@ -120,13 +120,6 @@ void EVMAssembly::appendConstant(uint64_t Val) { appendConstant(APInt(256, Val)); } -void EVMAssembly::appendLabel() { - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::JUMPDEST)); - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); -} - void EVMAssembly::appendLabelReference(MCSymbol *Label) { CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::PUSH_LABEL)) .addSym(Label); @@ -145,22 +138,25 @@ void EVMAssembly::appendFuncCall(const MachineInstr *MI, MCSymbol *RetSym) { // Push the function label assert(CurMBB == MI->getParent()); + + // Create pseudo jump to the callee, that will be expanded into PUSH and JUMP + // instructions in the AsmPrinter. CurMIIt = - BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PUSH_LABEL)) + BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoCALL)) .addGlobalAddress(Func); + + // If this function returns, we need to create a label after JUMP instruction + // that is followed by JUMPDEST and this is taken care in the AsmPrinter. + // In case we use setPostInstrSymbol here, the label will be created + // after the JUMPDEST instruction, which is not what we want. + if (RetSym) + MachineInstrBuilder(*CurMIIt->getParent()->getParent(), CurMIIt) + .addSym(RetSym); + // PUSH_LABEL technically increases the stack height on 1, but we don't // increase it explicitly here, as the label will be consumed by the following // JUMP. - AssemblyInstrs.insert(&*CurMIIt); StackHeight += StackAdj; - LLVM_DEBUG(dumpInst(&*CurMIIt)); - - CurMIIt = std::next(CurMIIt); - // Create jump to the callee. - CurMIIt = - BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoCALL)); - if (RetSym) - CurMIIt->setPostInstrSymbol(*MF, RetSym); AssemblyInstrs.insert(&*CurMIIt); LLVM_DEBUG(dumpInst(&*CurMIIt)); CurMIIt = std::next(CurMIIt); diff --git a/llvm/lib/Target/EVM/EVMAssembly.h b/llvm/lib/Target/EVM/EVMAssembly.h index 58c0e2ed36de..d6303ae136df 100644 --- a/llvm/lib/Target/EVM/EVMAssembly.h +++ b/llvm/lib/Target/EVM/EVMAssembly.h @@ -66,8 +66,6 @@ class EVMAssembly { void appendConstant(uint64_t Val); - void appendLabel(); - void appendFuncCall(const MachineInstr *MI, const llvm::GlobalValue *Func, int stackAdj, MCSymbol *RetSym = nullptr); diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index 68c89120dff6..4bc7d71a9f8f 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -249,7 +249,7 @@ def FCALL : NRI<(outs), (ins jmptarget:$callee, variable_ops), [], "FCALL\t$callee">; let isPseudo = 1 in -def PseudoCALL : EVMPseudo<(outs), (ins), []>; +def PseudoCALL : EVMPseudo<(outs), (ins jmptarget:$callee, variable_ops), []>; } // Uses = [SP], isCall = 1 diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp index 046067ee640f..b90758353298 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp @@ -57,8 +57,6 @@ void EVMOptimizedCodeTransform::operator()(CFG::FunctionCall const &Call) { (Call.CanContinue ? 1 : 0), Call.CanContinue ? CallToReturnMCSymbol.at(Call.Call) : nullptr); - if (Call.CanContinue) - Assembly.appendLabel(); // Update stack, remove arguments and return label from CurrentStack. for (size_t I = 0; I < Call.NumArguments + (Call.CanContinue ? 1 : 0); ++I) @@ -330,10 +328,6 @@ void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { } assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); - // Emit jumpdest, if required. - if (EVMUtils::valueOrNullptr(BlockLabels, &Block)) - Assembly.appendLabel(); - for (const auto &Operation : Block.Operations) { createOperationEntryLayout(Operation); @@ -485,7 +479,6 @@ void EVMOptimizedCodeTransform::operator()() { CurrentStack.emplace_back(Param); Assembly.setStackHeight(static_cast(CurrentStack.size())); - Assembly.appendLabel(); // Visit the function entry block. (*this)(EntryBB); diff --git a/llvm/lib/Target/EVM/EVMStackify.cpp b/llvm/lib/Target/EVM/EVMStackify.cpp index 145ff2d7d211..d5ff664dc712 100644 --- a/llvm/lib/Target/EVM/EVMStackify.cpp +++ b/llvm/lib/Target/EVM/EVMStackify.cpp @@ -735,10 +735,6 @@ void StackModel::handleLStackAtJump(MachineBasicBlock *MBB, MachineInstr *MI, MI->getOpcode() == EVM::JUMP ? EVM::PseudoJUMP : EVM::PseudoJUMPI; BuildMI(*MI->getParent(), MI, DebugLoc(), TII->get(PseudoJumpOpc)) .addMBB(MBB); - - // Add JUMPDEST at the beginning of the target MBB. - if (MBB->empty() || MBB->begin()->getOpcode() != EVM::JUMPDEST) - BuildMI(*MBB, MBB->begin(), DebugLoc(), TII->get(EVM::JUMPDEST)); } void StackModel::handleCondJump(MachineInstr *MI) { @@ -868,25 +864,21 @@ void StackModel::handleCall(MachineInstr *MI) { // Callee removes them form the stack and pushes return values. MachineBasicBlock &MBB = *MI->getParent(); - // Create return destination. - MIIter It = BuildMI(MBB, MI, MI->getDebugLoc(), TII->get(EVM::JUMPDEST)); // Add symbol just after the jump that will be used as the return // address from the function. MCSymbol *RetSym = MF->getContext().createTempSymbol("FUNC_RET", true); - // Create jump to the callee. - It = BuildMI(MBB, It, MI->getDebugLoc(), TII->get(EVM::PseudoCALL)); - It->setPostInstrSymbol(*MF, RetSym); + // Create pseudo jump to the callee, that will be expanded into PUSH, JUMP + // return label and JUMPDEST instructions in the AsmPrinter. + const MachineOperand *CalleeOp = MI->explicit_uses().begin(); + assert(CalleeOp->isGlobal()); + MIIter It = BuildMI(MBB, MI, MI->getDebugLoc(), TII->get(EVM::PseudoCALL)) + .addGlobalAddress(CalleeOp->getGlobal()) + .addSym(RetSym); // Create push of the return address. BuildMI(MBB, It, MI->getDebugLoc(), TII->get(EVM::PUSH_LABEL)).addSym(RetSym); - - // Create push of the callee's address. - const MachineOperand *CalleeOp = MI->explicit_uses().begin(); - assert(CalleeOp->isGlobal()); - BuildMI(MBB, It, MI->getDebugLoc(), TII->get(EVM::PUSH_LABEL)) - .addGlobalAddress(CalleeOp->getGlobal()); } void StackModel::clearFrameObjsAtInst(MachineInstr *MI) { @@ -986,10 +978,6 @@ void StackModel::preProcess() { assert(!MF->empty()); allocateFrameObjects(); allocateXStack(); - // Add JUMPDEST at the beginning of the first MBB, - // so this function can be jumped to. - MachineBasicBlock &MBB = MF->front(); - BuildMI(MBB, MBB.begin(), DebugLoc(), TII->get(EVM::JUMPDEST)); } // Remove all registers operands of the \p MI and repaces the opcode with From e39a610e47c5d181386858910259bb649a7bb889 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 13 Dec 2024 15:46:01 +0100 Subject: [PATCH 11/42] [EVM] Refactor EVMOptimizedCodeTransform Signed-off-by: Vladimir Radosavljevic --- .../EVMBackwardPropagationStackification.cpp | 9 +- llvm/lib/Target/EVM/EVMHelperUtilities.h | 18 - .../Target/EVM/EVMOptimizedCodeTransform.cpp | 405 +++++++----------- .../Target/EVM/EVMOptimizedCodeTransform.h | 70 +-- .../Target/EVM/EVMStackLayoutGenerator.cpp | 11 + 5 files changed, 197 insertions(+), 316 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp index 7a571204c899..a2e60b31c2da 100644 --- a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp +++ b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp @@ -25,9 +25,9 @@ #include "EVM.h" #include "EVMAssembly.h" +#include "EVMControlFlowGraphBuilder.h" #include "EVMOptimizedCodeTransform.h" #include "EVMSubtarget.h" -#include "llvm/ADT/SetVector.h" #include "llvm/CodeGen/LiveIntervals.h" #include "llvm/CodeGen/MachineLoopInfo.h" #include "llvm/CodeGen/Passes.h" @@ -87,7 +87,7 @@ bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { MachineRegisterInfo &MRI = MF.getRegInfo(); const EVMInstrInfo *TII = MF.getSubtarget().getInstrInfo(); - LiveIntervals &LIS = getAnalysis(); + auto &LIS = getAnalysis(); MachineLoopInfo *MLI = &getAnalysis(); // We don't preserve SSA form. @@ -96,6 +96,9 @@ bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { assert(MRI.tracksLiveness() && "Stackification expects liveness"); EVMAssembly Assembly(&MF, TII); - EVMOptimizedCodeTransform::run(Assembly, MF, LIS, MLI); + std::unique_ptr Cfg = ControlFlowGraphBuilder::build(MF, LIS, MLI); + StackLayout Layout = StackLayoutGenerator::run(*Cfg); + EVMOptimizedCodeTransform(Assembly, Layout, MF) + .run(Cfg->getBlock(&Cfg->MF.front())); return true; } diff --git a/llvm/lib/Target/EVM/EVMHelperUtilities.h b/llvm/lib/Target/EVM/EVMHelperUtilities.h index 6f137535cbcd..8aea5b49f5dd 100644 --- a/llvm/lib/Target/EVM/EVMHelperUtilities.h +++ b/llvm/lib/Target/EVM/EVMHelperUtilities.h @@ -103,30 +103,12 @@ iterator_range get_reverse(const T &t) { return llvm::make_range(t.rbegin(), t.rend()); } -// Returns a pointer to the entry of map \p m at the key \p k, -// if there is one, and nullptr otherwise. -template -decltype(auto) valueOrNullptr(M &&m, K const &k) { - auto it = m.find(k); - return (it == m.end()) ? nullptr : &it->second; -} - template auto to_vector(R &&r) { std::vector v; v.assign(r.begin(), r.end()); return v; } -/// RAII utility class whose destructor calls a given function. -class ScopeGuard { -public: - explicit ScopeGuard(std::function Func) : Func(std::move(Func)) {} - ~ScopeGuard() { Func(); } - -private: - std::function Func; -}; - template void emplace_back_unique(T &t, V &&v) { if (t.end() == std::find(t.begin(), t.end(), v)) t.emplace_back(v); diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp index b90758353298..efa3b7b2cc12 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp @@ -12,42 +12,25 @@ //===----------------------------------------------------------------------===// #include "EVMOptimizedCodeTransform.h" -#include "EVMControlFlowGraphBuilder.h" -#include "EVMHelperUtilities.h" #include "EVMStackDebug.h" -#include "EVMStackLayoutGenerator.h" #include "EVMStackShuffler.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/Support/ErrorHandling.h" - -#include using namespace llvm; #define DEBUG_TYPE "evm-optimized-code-transform" -void EVMOptimizedCodeTransform::run(EVMAssembly &Assembly, MachineFunction &MF, - const LiveIntervals &LIS, - MachineLoopInfo *MLI) { - std::unique_ptr Cfg = ControlFlowGraphBuilder::build(MF, LIS, MLI); - StackLayout Layout = StackLayoutGenerator::run(*Cfg); - EVMOptimizedCodeTransform optimizedCodeTransform(Assembly, *Cfg, Layout, MF); - optimizedCodeTransform(); -} - -void EVMOptimizedCodeTransform::operator()(CFG::FunctionCall const &Call) { -#ifndef NDEBUG +void EVMOptimizedCodeTransform::visitCall(const CFG::FunctionCall &Call) { // Validate stack. assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); assert(CurrentStack.size() >= Call.NumArguments + (Call.CanContinue ? 1 : 0)); // Assert that we got the correct return label on stack. if (Call.CanContinue) { - auto const *returnLabelSlot = std::get_if( - &CurrentStack.at(CurrentStack.size() - Call.NumArguments - 1)); + [[maybe_unused]] const auto *returnLabelSlot = + std::get_if( + &CurrentStack.at(CurrentStack.size() - Call.NumArguments - 1)); assert(returnLabelSlot && returnLabelSlot->Call == Call.Call); } -#endif // NDEBUG // Emit code. const MachineOperand *CalleeOp = Call.Call->explicit_uses().begin(); @@ -71,7 +54,7 @@ void EVMOptimizedCodeTransform::operator()(CFG::FunctionCall const &Call) { assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); } -void EVMOptimizedCodeTransform::operator()(CFG::BuiltinCall const &Call) { +void EVMOptimizedCodeTransform::visitInst(const CFG::BuiltinCall &Call) { size_t NumArgs = Call.Builtin->getNumExplicitOperands() - Call.Builtin->getNumExplicitDefs(); // Validate stack. @@ -95,19 +78,19 @@ void EVMOptimizedCodeTransform::operator()(CFG::BuiltinCall const &Call) { assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); } -void EVMOptimizedCodeTransform::operator()(CFG::Assignment const &Assignment) { +void EVMOptimizedCodeTransform::visitAssign(const CFG::Assignment &Assignment) { assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); // Invalidate occurrences of the assigned variables. for (auto &CurrentSlot : CurrentStack) - if (VariableSlot const *VarSlot = std::get_if(&CurrentSlot)) - if (EVMUtils::contains(Assignment.Variables, *VarSlot)) + if (const VariableSlot *VarSlot = std::get_if(&CurrentSlot)) + if (is_contained(Assignment.Variables, *VarSlot)) CurrentSlot = JunkSlot{}; // Assign variables to current stack top. assert(CurrentStack.size() >= Assignment.Variables.size()); - auto StackRange = - EVMUtils::take_last(CurrentStack, Assignment.Variables.size()); + auto StackRange = make_range(CurrentStack.end() - Assignment.Variables.size(), + CurrentStack.end()); auto RangeIt = StackRange.begin(), RangeE = StackRange.end(); auto VarsIt = Assignment.Variables.begin(), VarsE = Assignment.Variables.end(); @@ -115,33 +98,22 @@ void EVMOptimizedCodeTransform::operator()(CFG::Assignment const &Assignment) { *RangeIt = *VarsIt; } -EVMOptimizedCodeTransform::EVMOptimizedCodeTransform(EVMAssembly &Assembly, - CFG const &Cfg, - StackLayout const &Layout, - MachineFunction &MF) - : Assembly(Assembly), Layout(Layout), MF(MF), - EntryBB(Cfg.getBlock(&MF.front())), Parameters(Cfg.Parameters) {} - -bool EVMOptimizedCodeTransform::AreLayoutsCompatible(Stack const &SourceStack, - Stack const &TargetStack) { - if (SourceStack.size() != TargetStack.size()) - return false; - - for (auto [Src, Tgt] : zip_equal(SourceStack, TargetStack)) { - if (!std::holds_alternative(Tgt) && !(Src == Tgt)) - return false; - } - - return true; +bool EVMOptimizedCodeTransform::areLayoutsCompatible(const Stack &SourceStack, + const Stack &TargetStack) { + return SourceStack.size() == TargetStack.size() && + all_of(zip_equal(SourceStack, TargetStack), [](const auto &Pair) { + const auto &[Src, Tgt] = Pair; + return std::holds_alternative(Tgt) || (Src == Tgt); + }); } -void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { - auto SlotVariableName = [](StackSlot const &Slot) { +void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { + auto SlotVariableName = [](const StackSlot &Slot) { return std::visit( Overload{ - [&](VariableSlot const &Var) { + [&](const VariableSlot &Var) { SmallString<1024> StrBuf; - llvm::raw_svector_ostream OS(StrBuf); + raw_svector_ostream OS(StrBuf); OS << printReg(Var.VirtualReg, nullptr, 0, nullptr); return std::string(StrBuf.c_str()); }, @@ -168,7 +140,7 @@ void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { Assembly.appendSWAPInstruction(I); } else { int Deficit = static_cast(I) - 16; - StackSlot const &DeepSlot = + const StackSlot &DeepSlot = CurrentStack.at(CurrentStack.size() - I - 1); std::string VarNameDeep = SlotVariableName(DeepSlot); std::string VarNameTop = SlotVariableName(CurrentStack.back()); @@ -188,21 +160,24 @@ void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { } }, // Push or dup callback. - [&](StackSlot const &Slot) { + [&](const StackSlot &Slot) { assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + // Dup the slot, if already on stack and reachable. - if (auto Depth = EVMUtils::findOffset( - EVMUtils::get_reverse(CurrentStack), Slot)) { - if (*Depth < 16) { - Assembly.appendDUPInstruction(static_cast(*Depth + 1)); + auto SlotIt = llvm::find(llvm::reverse(CurrentStack), Slot); + if (SlotIt != CurrentStack.rend()) { + unsigned Depth = std::distance(CurrentStack.rbegin(), SlotIt); + if (Depth < 16) { + Assembly.appendDUPInstruction(static_cast(Depth + 1)); return; - } else if (!canBeFreelyGenerated(Slot)) { + } + if (!canBeFreelyGenerated(Slot)) { std::string VarName = SlotVariableName(Slot); std::string Msg = ((VarName.empty() ? "slot " + stackSlotToString(Slot) : Twine("variable ") + VarName) + - " is " + std::to_string(*Depth - 15) + + " is " + std::to_string(Depth - 15) + " too deep in the stack " + stackToString(CurrentStack)) .str(); @@ -216,14 +191,14 @@ void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { // The slot can be freely generated or is an unassigned return variable. // Push it. std::visit( - Overload{[&](LiteralSlot const &Literal) { + Overload{[&](const LiteralSlot &Literal) { Assembly.appendConstant(Literal.Value); }, - [&](SymbolSlot const &Symbol) { + [&](const SymbolSlot &Symbol) { Assembly.appendSymbol(Symbol.Symbol, Symbol.MI->getOpcode()); }, - [&](FunctionReturnLabelSlot const &) { + [&](const FunctionReturnLabelSlot &) { llvm_unreachable("Cannot produce function return label"); }, [&](const FunctionCallReturnLabelSlot &ReturnLabel) { @@ -254,13 +229,14 @@ void EVMOptimizedCodeTransform::createStackLayout(Stack TargetStack) { assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); } -void EVMOptimizedCodeTransform::createOperationEntryLayout( +void EVMOptimizedCodeTransform::createOperationLayout( const CFG::Operation &Op) { // Create required layout for entering the Operation. // Check if we can choose cheaper stack shuffling if the Operation is an // instruction with commutable arguments. + bool SwapCommutable = false; if (const auto *Inst = std::get_if(&Op.Operation); - Inst && Inst->IsCommutable) { + Inst && Inst->Builtin->isCommutable()) { // Get the stack layout before the instruction. const Stack &DefaultTargetStack = Layout.operationEntryLayout.at(&Op); size_t DefaultCost = @@ -278,210 +254,147 @@ void EVMOptimizedCodeTransform::createOperationEntryLayout( size_t CommutedCost = EvaluateStackTransform(CurrentStack, CommutedTargetStack); // Choose the cheapest transformation. - createStackLayout(CommutedCost < DefaultCost ? CommutedTargetStack - : DefaultTargetStack); -#ifndef NDEBUG - // Assert that we have the inputs of the Operation on stack top. - assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); - assert(CurrentStack.size() >= Op.Input.size()); - Stack StackInput = - EVMUtils::to_vector(EVMUtils::take_last(CurrentStack, Op.Input.size())); - // Adjust the StackInput to match the commuted stack. - if (CommutedCost < DefaultCost) { - std::swap(StackInput[StackInput.size() - OpIdx1 - 1], - StackInput[StackInput.size() - OpIdx2 - 1]); - } - assert(AreLayoutsCompatible(StackInput, Op.Input)); -#endif // NDEBUG + SwapCommutable = CommutedCost < DefaultCost; + createStackLayout(SwapCommutable ? CommutedTargetStack + : DefaultTargetStack); } else { createStackLayout(Layout.operationEntryLayout.at(&Op)); + } -#ifndef NDEBUG - // Assert that we have the inputs of the Operation on stack top. - assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); - assert(CurrentStack.size() >= Op.Input.size()); - const Stack StackInput = - EVMUtils::to_vector(EVMUtils::take_last(CurrentStack, Op.Input.size())); - assert(AreLayoutsCompatible(StackInput, Op.Input)); -#endif // NDEBUG + // Assert that we have the inputs of the Operation on stack top. + assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + assert(CurrentStack.size() >= Op.Input.size()); + Stack StackInput(CurrentStack.end() - Op.Input.size(), CurrentStack.end()); + // Adjust the StackInput if needed. + if (SwapCommutable) { + std::swap(StackInput[StackInput.size() - 1], + StackInput[StackInput.size() - 2]); } + assert(areLayoutsCompatible(StackInput, Op.Input)); } -void EVMOptimizedCodeTransform::operator()(const CFG::BasicBlock &Block) { - // Current location for the entry BB was set up in operator()(). - if (&Block != &EntryBB) - Assembly.setCurrentLocation(Block.MBB); +void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { + assert(CurrentStack.empty() && Assembly.getStackHeight() == 0); - // Assert that this is the first visit of the block and mark as generated. - [[maybe_unused]] auto It = GeneratedBlocks.insert(&Block); - assert(It.second); + SmallPtrSet Visited; + SmallVector WorkList{&EntryBB}; + while (!WorkList.empty()) { + auto *Block = WorkList.pop_back_val(); + if (!Visited.insert(Block).second) + continue; - auto const &BlockInfo = Layout.blockInfos.at(&Block); + const auto &BlockInfo = Layout.blockInfos.at(Block); - // Assert that the stack is valid for entering the block. The entry layout - // of the function entry block should is fully determined by the first - // instruction, so we can ignore 'BlockInfo.entryLayout'. - if (&Block != &EntryBB) { - assert(AreLayoutsCompatible(CurrentStack, BlockInfo.entryLayout)); // Might set some slots to junk, if not required by the block. CurrentStack = BlockInfo.entryLayout; - } - assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); - - for (const auto &Operation : Block.Operations) { - createOperationEntryLayout(Operation); - -#ifndef NDEBUG - size_t BaseHeight = CurrentStack.size() - Operation.Input.size(); -#endif // NDEBUG + Assembly.setStackHeight(static_cast(CurrentStack.size())); + Assembly.setCurrentLocation(Block->MBB); + + for (const auto &Operation : Block->Operations) { + createOperationLayout(Operation); + + [[maybe_unused]] size_t BaseHeight = + CurrentStack.size() - Operation.Input.size(); + + // Perform the Operation. + std::visit( + Overload{[this](const CFG::FunctionCall &Call) { visitCall(Call); }, + [this](const CFG::BuiltinCall &Call) { visitInst(Call); }, + [this](const CFG::Assignment &Assignment) { + visitAssign(Assignment); + }}, + Operation.Operation); + + // Assert that the Operation produced its proclaimed output. + assert(static_cast(CurrentStack.size()) == + Assembly.getStackHeight()); + assert(CurrentStack.size() == BaseHeight + Operation.Output.size()); + assert(CurrentStack.size() >= Operation.Output.size()); + assert(areLayoutsCompatible( + Stack(CurrentStack.end() - Operation.Output.size(), + CurrentStack.end()), + Operation.Output)); + } - // Perform the Operation. - std::visit(*this, Operation.Operation); + // Exit the block. + std::visit( + Overload{ + [&](const CFG::BasicBlock::InvalidExit &) { + llvm_unreachable("Unexpected BB terminator"); + }, + [&](const CFG::BasicBlock::Jump &Jump) { + // Create the stack expected at the jump target. + createStackLayout(Layout.blockInfos.at(Jump.Target).entryLayout); - // Assert that the Operation produced its proclaimed output. - assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); - assert(CurrentStack.size() == BaseHeight + Operation.Output.size()); - assert(CurrentStack.size() >= Operation.Output.size()); - assert(AreLayoutsCompatible(EVMUtils::to_vector(EVMUtils::take_last( - CurrentStack, Operation.Output.size())), - Operation.Output)); - } + // Assert that we have a valid stack for the target. + assert(areLayoutsCompatible( + CurrentStack, Layout.blockInfos.at(Jump.Target).entryLayout)); - // Exit the block. - std::visit( - Overload{ - [&](const CFG::BasicBlock::InvalidExit &) { - llvm_unreachable("Unexpected BB terminator"); - }, - [&](const CFG::BasicBlock::Jump &Jump) { - // Create the stack expected at the jump target. - createStackLayout(Layout.blockInfos.at(Jump.Target).entryLayout); - - // If this is the only jump to the block which is a fallthrought - // we can directly continue with the target block. - if (Jump.Target->Entries.size() == 1 && Jump.FallThrough) - assert(!Jump.Backwards && !BlockLabels.count(Jump.Target)); - - if (!BlockLabels.count(Jump.Target)) - BlockLabels[Jump.Target] = Jump.Target->MBB->getSymbol(); - - if (Jump.UncondJump) - Assembly.appendUncondJump(Jump.UncondJump, Jump.Target->MBB); - - if (!GeneratedBlocks.count(Jump.Target)) - (*this)(*Jump.Target); - }, - [&](CFG::BasicBlock::ConditionalJump const &CondJump) { - // Create the shared entry layout of the jump targets, which is - // stored as exit layout of the current block. - createStackLayout(BlockInfo.exitLayout); - - // Create labels for the targets, if not already present. - if (!BlockLabels.count(CondJump.NonZero)) - BlockLabels[CondJump.NonZero] = - CondJump.NonZero->MBB->getSymbol(); - - if (!BlockLabels.count(CondJump.Zero)) - BlockLabels[CondJump.Zero] = CondJump.Zero->MBB->getSymbol(); - - // Assert that we have the correct condition on stack. - assert(!CurrentStack.empty()); - assert(CurrentStack.back() == CondJump.Condition); - - // Emit the conditional jump to the non-zero label and update the - // stored stack. - assert(CondJump.CondJump); - Assembly.appendCondJump(CondJump.CondJump, CondJump.NonZero->MBB); - CurrentStack.pop_back(); - - // Assert that we have a valid stack for both jump targets. - assert(AreLayoutsCompatible( - CurrentStack, - Layout.blockInfos.at(CondJump.NonZero).entryLayout)); - assert(AreLayoutsCompatible( - CurrentStack, Layout.blockInfos.at(CondJump.Zero).entryLayout)); - - { - // Restore the stack afterwards for the non-zero case below. - EVMUtils::ScopeGuard stackRestore([storedStack = CurrentStack, - this]() { - CurrentStack = std::move(storedStack); - Assembly.setStackHeight(static_cast(CurrentStack.size())); - }); - - // If we have already generated the zero case, jump to it, - // otherwise generate it in place. + if (Jump.UncondJump) + Assembly.appendUncondJump(Jump.UncondJump, Jump.Target->MBB); + WorkList.emplace_back(Jump.Target); + }, + [&](CFG::BasicBlock::ConditionalJump const &CondJump) { + // Create the shared entry layout of the jump targets, which is + // stored as exit layout of the current block. + createStackLayout(BlockInfo.exitLayout); + + // Assert that we have the correct condition on stack. + assert(!CurrentStack.empty()); + assert(CurrentStack.back() == CondJump.Condition); + + // Emit the conditional jump to the non-zero label and update the + // stored stack. + assert(CondJump.CondJump); + Assembly.appendCondJump(CondJump.CondJump, CondJump.NonZero->MBB); + CurrentStack.pop_back(); + + // Assert that we have a valid stack for both jump targets. + assert(areLayoutsCompatible( + CurrentStack, + Layout.blockInfos.at(CondJump.NonZero).entryLayout)); + assert(areLayoutsCompatible( + CurrentStack, + Layout.blockInfos.at(CondJump.Zero).entryLayout)); + + // Generate unconditional jump if needed. if (CondJump.UncondJump) Assembly.appendUncondJump(CondJump.UncondJump, CondJump.Zero->MBB); + WorkList.emplace_back(CondJump.NonZero); + WorkList.emplace_back(CondJump.Zero); + }, + [&](CFG::BasicBlock::FunctionReturn const &FuncReturn) { + assert(!MF.getFunction().hasFnAttribute(Attribute::NoReturn)); - if (!GeneratedBlocks.count(CondJump.Zero)) - (*this)(*CondJump.Zero); - } - // Note that each block visit terminates control flow, so we cannot - // fall through from the zero case. - - // Generate the non-zero block, if not done already. - if (!GeneratedBlocks.count(CondJump.NonZero)) - (*this)(*CondJump.NonZero); - }, - [&](CFG::BasicBlock::FunctionReturn const &FuncReturn) { - assert(!MF.getFunction().hasFnAttribute(Attribute::NoReturn)); - - // Construct the function return layout, which is fully determined - // by the function signature. - Stack ExitStack = FuncReturn.RetValues; - - ExitStack.emplace_back(FunctionReturnLabelSlot{&MF}); - - // Create the function return layout and jump. - createStackLayout(ExitStack); - Assembly.appendRet(); - }, - [&](CFG::BasicBlock::Unreachable const &) { - assert(Block.Operations.empty()); - }, - [&](CFG::BasicBlock::Terminated const &) { - assert(!Block.Operations.empty()); - if (const CFG::BuiltinCall *BuiltinCall = - std::get_if( - &Block.Operations.back().Operation)) - assert(BuiltinCall->TerminatesOrReverts); - else if (CFG::FunctionCall const *FunctionCall = - std::get_if( - &Block.Operations.back().Operation)) - assert(!FunctionCall->CanContinue); - else - llvm_unreachable("Unexpected BB terminator"); - }}, - Block.Exit); - - // TODO: - // We could assert that the last emitted assembly item terminated or was - // an (unconditional) jump. - CurrentStack.clear(); - Assembly.setStackHeight(0); -} - -void EVMOptimizedCodeTransform::operator()() { - assert(CurrentStack.empty() && Assembly.getStackHeight() == 0); - Assembly.setCurrentLocation(EntryBB.MBB); - - assert(!BlockLabels.count(&EntryBB)); - - // Create function entry layout in CurrentStack. - if (!MF.getFunction().hasFnAttribute(Attribute::NoReturn)) - CurrentStack.emplace_back(FunctionReturnLabelSlot{&MF}); - - // Calling convention: input arguments are passed in stack such that the - // first one specified in the function declaration is passed on the stack TOP. - for (auto const &Param : reverse(Parameters)) - CurrentStack.emplace_back(Param); + // Construct the function return layout, which is fully determined + // by the function signature. + Stack ExitStack = FuncReturn.RetValues; - Assembly.setStackHeight(static_cast(CurrentStack.size())); + ExitStack.emplace_back(FunctionReturnLabelSlot{&MF}); - // Visit the function entry block. - (*this)(EntryBB); + // Create the function return layout and jump. + createStackLayout(ExitStack); + Assembly.appendRet(); + }, + [&](CFG::BasicBlock::Unreachable const &) { + assert(Block->Operations.empty()); + }, + [&](CFG::BasicBlock::Terminated const &) { + assert(!Block->Operations.empty()); + if (const CFG::BuiltinCall *BuiltinCall = + std::get_if( + &Block->Operations.back().Operation)) + assert(BuiltinCall->TerminatesOrReverts); + else if (CFG::FunctionCall const *FunctionCall = + std::get_if( + &Block->Operations.back().Operation)) + assert(!FunctionCall->CanContinue); + else + llvm_unreachable("Unexpected BB terminator"); + }}, + Block->Exit); + } Assembly.finalize(); } diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h index aa4f08ee74af..3dc04144be84 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h @@ -15,12 +15,7 @@ #define LLVM_LIB_TARGET_EVM_EVMOPTIMIZEDCODETRANSFORM_H #include "EVMAssembly.h" -#include "EVMControlFlowGraph.h" #include "EVMStackLayoutGenerator.h" -#include "llvm/CodeGen/LiveIntervals.h" -#include "llvm/CodeGen/MachineLoopInfo.h" - -#include namespace llvm { @@ -29,61 +24,38 @@ class MCSymbol; class EVMOptimizedCodeTransform { public: - static void run(EVMAssembly &Assembly, MachineFunction &MF, - const LiveIntervals &LIS, MachineLoopInfo *MLI); + EVMOptimizedCodeTransform(EVMAssembly &Assembly, const StackLayout &Layout, + MachineFunction &MF) + : Assembly(Assembly), Layout(Layout), MF(MF) {} - /// Generate code for the function call \p Call. Only public for using with - /// std::visit. - void operator()(CFG::FunctionCall const &Call); - /// Generate code for the builtin call \p Call. Only public for using with - /// std::visit. - void operator()(CFG::BuiltinCall const &Call); - /// Generate code for the assignment \p Assignment. Only public for using - /// with std::visit. - void operator()(CFG::Assignment const &Assignment); + /// Stackify instructions, starting from the \p EntryBB. + void run(CFG::BasicBlock &EntryBB); private: - EVMOptimizedCodeTransform(EVMAssembly &Assembly, const CFG &Cfg, - const StackLayout &Layout, MachineFunction &MF); + EVMAssembly &Assembly; + const StackLayout &Layout; + const MachineFunction &MF; + Stack CurrentStack; + DenseMap CallToReturnMCSymbol; /// Checks if it's valid to transition from \p SourceStack to \p /// TargetStack, that is \p SourceStack matches each slot in \p /// TargetStack that is not a JunkSlot exactly. - static bool AreLayoutsCompatible(Stack const &SourceStack, - Stack const &TargetStack); + bool areLayoutsCompatible(const Stack &SourceStack, const Stack &TargetStack); - /// Shuffles CurrentStack to the desired \p TargetStack while emitting the - /// shuffling code to Assembly. - void createStackLayout(Stack TargetStack); + /// Shuffles CurrentStack to the desired \p TargetStack. + void createStackLayout(const Stack &TargetStack); /// Creates the Op.Input stack layout from the 'CurrentStack' taking into /// account commutative property of the operation. - void createOperationEntryLayout(const CFG::Operation &Op); - - /// Generate code for the given block \p Block. - /// Expects the current stack layout 'CurrentStack' to be a stack layout that - /// is compatible with the entry layout expected by the block. Recursively - /// generates code for blocks that are jumped to. The last emitted assembly - /// instruction is always an unconditional jump or terminating. Always exits - /// with an empty stack layout. - void operator()(CFG::BasicBlock const &Block); - - /// Generate code for the given function. - /// Resets CurrentStack. - void operator()(); - - EVMAssembly &Assembly; - StackLayout const &Layout; - const MachineFunction &MF; - const CFG::BasicBlock &EntryBB; - const std::vector &Parameters; - - Stack CurrentStack; - DenseMap CallToReturnMCSymbol; - DenseMap BlockLabels; - /// Set of blocks already generated. If any of the contained blocks is ever - /// jumped to, BlockLabels should contain a jump label for it. - std::set GeneratedBlocks; + void createOperationLayout(const CFG::Operation &Op); + + /// Generate code for the function call \p Call. + void visitCall(const CFG::FunctionCall &Call); + /// Generate code for the builtin call \p Call. + void visitInst(const CFG::BuiltinCall &Call); + /// Generate code for the assignment \p Assignment. + void visitAssign(const CFG::Assignment &Assignment); }; } // namespace llvm diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp index d596c61e44a2..c1b58d3f420e 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -395,6 +395,17 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const &Entry) { stitchConditionalJumps(Entry); fillInJunk(Entry); + + // Create function entry layout. + Stack EntryStack; + if (!MF.getFunction().hasFnAttribute(Attribute::NoReturn)) + EntryStack.emplace_back(FunctionReturnLabelSlot{&MF}); + + // Calling convention: input arguments are passed in stack such that the + // first one specified in the function declaration is passed on the stack TOP. + for (auto const &Param : reverse(Parameters)) + EntryStack.emplace_back(Param); + Layout.blockInfos[&Entry].entryLayout = EntryStack; } std::optional StackLayoutGenerator::getExitLayoutOrStageDependencies( From d2cd9a685156b2f2cc426ffbe1ba770e896add1b Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 13 Dec 2024 15:47:08 +0100 Subject: [PATCH 12/42] [EVM] Refactor EVMAssembly Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMAssembly.cpp | 271 +++++++----------- llvm/lib/Target/EVM/EVMAssembly.h | 58 ++-- .../EVMBackwardPropagationStackification.cpp | 6 +- llvm/lib/Target/EVM/EVMControlFlowGraph.h | 1 + .../Target/EVM/EVMControlFlowGraphBuilder.cpp | 2 +- llvm/lib/Target/EVM/EVMInstrFormats.td | 6 +- llvm/lib/Target/EVM/EVMInstrInfo.td | 15 +- .../Target/EVM/EVMOptimizedCodeTransform.cpp | 44 ++- .../Target/EVM/EVMOptimizedCodeTransform.h | 8 +- 9 files changed, 165 insertions(+), 246 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMAssembly.cpp b/llvm/lib/Target/EVM/EVMAssembly.cpp index 7335679f09c6..aca2f7269762 100644 --- a/llvm/lib/Target/EVM/EVMAssembly.cpp +++ b/llvm/lib/Target/EVM/EVMAssembly.cpp @@ -14,233 +14,184 @@ //===----------------------------------------------------------------------===// #include "EVMAssembly.h" -#include "EVM.h" #include "EVMMachineFunctionInfo.h" -#include "EVMSubtarget.h" -#include "MCTargetDesc/EVMMCTargetDesc.h" #include "TargetInfo/EVMTargetInfo.h" -#include "llvm/CodeGen/MachineFunction.h" -#include "llvm/CodeGen/MachineInstrBuilder.h" #include "llvm/MC/MCContext.h" using namespace llvm; #define DEBUG_TYPE "evm-assembly" -#ifndef NDEBUG -void EVMAssembly::dumpInst(const MachineInstr *MI) const { - LLVM_DEBUG(dbgs() << "Adding: " << *MI << "stack height: " << StackHeight - << "\n"); -} -#endif // NDEBUG - int EVMAssembly::getStackHeight() const { return StackHeight; } -void EVMAssembly::setStackHeight(int Height) { +void EVMAssembly::init(MachineBasicBlock *MBB, int Height) { StackHeight = Height; - LLVM_DEBUG(dbgs() << "Set stack height: " << StackHeight << "\n"); -} - -void EVMAssembly::setCurrentLocation(MachineBasicBlock *MBB) { CurMBB = MBB; - CurMIIt = MBB->begin(); + LLVM_DEBUG(dbgs() << "\n" + << "Set stack height: " << StackHeight << "\n"); LLVM_DEBUG(dbgs() << "Setting current location to: " << MBB->getNumber() << "." << MBB->getName() << "\n"); } -void EVMAssembly::appendInstruction(MachineInstr *MI) { -#ifndef NDEBUG +void EVMAssembly::emitInst(const MachineInstr *MI) { unsigned Opc = MI->getOpcode(); assert(Opc != EVM::JUMP && Opc != EVM::JUMPI && Opc != EVM::ARGUMENT && Opc != EVM::RET && Opc != EVM::CONST_I256 && Opc != EVM::COPY_I256 && - Opc != EVM::FCALL); -#endif // NDEBUG + Opc != EVM::FCALL && "Unexpected instruction"); - [[maybe_unused]] auto Ret = AssemblyInstrs.insert(MI); - assert(Ret.second); + // The stack height is increased by the number of defs and decreased + // by the number of inputs. To get the number of inputs, we subtract + // the total number of operands from the number of defs, so the + // calculation is as follows: + // Defs - (Ops - Defs) = 2 * Defs - Ops int StackAdj = (2 * static_cast(MI->getNumExplicitDefs())) - static_cast(MI->getNumExplicitOperands()); StackHeight += StackAdj; - LLVM_DEBUG(dumpInst(MI)); - CurMIIt = std::next(MIIter(MI)); + + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + verify(NewMI); } -void EVMAssembly::appendSWAPInstruction(unsigned Depth) { +void EVMAssembly::emitSWAP(unsigned Depth) { unsigned Opc = EVM::getSWAPOpcode(Depth); - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opc)); - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + verify(NewMI); } -void EVMAssembly::appendDUPInstruction(unsigned Depth) { - unsigned Opc = EVM::getDUPOpcode(Depth); - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opc)); +void EVMAssembly::emitDUP(unsigned Depth) { StackHeight += 1; - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + unsigned Opc = EVM::getDUPOpcode(Depth); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + verify(NewMI); } -void EVMAssembly::appendPOPInstruction() { - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::POP)); - assert(StackHeight > 0); +void EVMAssembly::emitPOP() { StackHeight -= 1; - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + assert(StackHeight >= 0); + auto NewMI = + BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), TII->get(EVM::POP_S)); + verify(NewMI); } -void EVMAssembly::appendConstant(const APInt &Val) { - unsigned Opc = EVM::getPUSHOpcode(Val); - MachineInstrBuilder Builder = - BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opc)); - if (Opc != EVM::PUSH0) { - LLVMContext &Ctx = MF->getFunction().getContext(); - Builder.addCImm(ConstantInt::get(Ctx, Val)); - } +void EVMAssembly::emitConstant(const APInt &Val) { StackHeight += 1; - CurMIIt = Builder; - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + unsigned Opc = EVM::getPUSHOpcode(Val); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + if (Opc != EVM::PUSH0) + NewMI.addCImm(ConstantInt::get(MF.getFunction().getContext(), Val)); + verify(NewMI); } -void EVMAssembly::appendSymbol(MCSymbol *Symbol, unsigned Opcode) { - // This is codegen-only instruction, that will be converted into PUSH4. - CurMIIt = - BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(Opcode)).addSym(Symbol); +void EVMAssembly::emitSymbol(const MachineInstr *MI, MCSymbol *Symbol) { + unsigned Opc = MI->getOpcode(); + assert(Opc == EVM::DATASIZE || + Opc == EVM::DATAOFFSET && "Unexpected symbol instruction"); StackHeight += 1; - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + // This is codegen-only instruction, that will be converted into PUSH4. + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::getStackOpcode(Opc))) + .addSym(Symbol); + verify(NewMI); } -void EVMAssembly::appendConstant(uint64_t Val) { - appendConstant(APInt(256, Val)); -} +void EVMAssembly::emitConstant(uint64_t Val) { emitConstant(APInt(256, Val)); } -void EVMAssembly::appendLabelReference(MCSymbol *Label) { - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::PUSH_LABEL)) - .addSym(Label); +void EVMAssembly::emitLabelReference(const MachineInstr *Call) { + assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); StackHeight += 1; - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); -} - -MCSymbol *EVMAssembly::createFuncRetSymbol() { - return MF->getContext().createTempSymbol("FUNC_RET", true); -} - -void EVMAssembly::appendFuncCall(const MachineInstr *MI, - const llvm::GlobalValue *Func, int StackAdj, - MCSymbol *RetSym) { - // Push the function label + auto [It, Inserted] = CallReturnSyms.try_emplace(Call); + if (Inserted) + It->second = MF.getContext().createTempSymbol("FUNC_RET", true); + auto NewMI = + BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), TII->get(EVM::PUSH_LABEL)) + .addSym(It->second); + verify(NewMI); +} + +void EVMAssembly::emitFuncCall(const MachineInstr *MI, const GlobalValue *Func, + int StackAdj, bool WillReturn) { + assert(MI->getOpcode() == EVM::FCALL && "Unexpected call instruction"); assert(CurMBB == MI->getParent()); + // PUSH_LABEL technically increases the stack height on 1, but we don't + // increase it explicitly here, as the label will be consumed by the following + // JUMP. + StackHeight += StackAdj; + // Create pseudo jump to the callee, that will be expanded into PUSH and JUMP // instructions in the AsmPrinter. - CurMIIt = - BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoCALL)) - .addGlobalAddress(Func); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoCALL)) + .addGlobalAddress(Func); // If this function returns, we need to create a label after JUMP instruction // that is followed by JUMPDEST and this is taken care in the AsmPrinter. // In case we use setPostInstrSymbol here, the label will be created // after the JUMPDEST instruction, which is not what we want. - if (RetSym) - MachineInstrBuilder(*CurMIIt->getParent()->getParent(), CurMIIt) - .addSym(RetSym); - - // PUSH_LABEL technically increases the stack height on 1, but we don't - // increase it explicitly here, as the label will be consumed by the following - // JUMP. - StackHeight += StackAdj; - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + if (WillReturn) + NewMI.addSym(CallReturnSyms.at(MI)); + verify(NewMI); } -void EVMAssembly::appendRet() { - CurMIIt = BuildMI(*CurMBB, CurMIIt, DebugLoc(), TII->get(EVM::PseudoRET)); - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); +void EVMAssembly::emitRet(const MachineInstr *MI) { + assert(MI->getOpcode() == EVM::RET && "Unexpected ret instruction"); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoRET)); + verify(NewMI); } -void EVMAssembly::appendUncondJump(MachineInstr *MI, - MachineBasicBlock *Target) { - assert(MI->getOpcode() == EVM::JUMP); - CurMIIt = - BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoJUMP)) - .addMBB(Target); +void EVMAssembly::emitUncondJump(const MachineInstr *MI, + MachineBasicBlock *Target) { + assert(MI->getOpcode() == EVM::JUMP && + "Unexpected unconditional jump instruction"); assert(StackHeight >= 0); - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoJUMP)) + .addMBB(Target); + verify(NewMI); } -void EVMAssembly::appendCondJump(MachineInstr *MI, MachineBasicBlock *Target) { - assert(MI->getOpcode() == EVM::JUMPI); - CurMIIt = - BuildMI(*CurMBB, CurMIIt, MI->getDebugLoc(), TII->get(EVM::PseudoJUMPI)) - .addMBB(Target); +void EVMAssembly::emitCondJump(const MachineInstr *MI, + MachineBasicBlock *Target) { + assert(MI->getOpcode() == EVM::JUMPI && + "Unexpected conditional jump instruction"); StackHeight -= 1; assert(StackHeight >= 0); - AssemblyInstrs.insert(&*CurMIIt); - LLVM_DEBUG(dumpInst(&*CurMIIt)); - CurMIIt = std::next(CurMIIt); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoJUMPI)) + .addMBB(Target); + verify(NewMI); } -// Remove all registers operands of the \p MI and repaces the opcode with -// the stack variant variant. -void EVMAssembly::stackifyInstruction(MachineInstr *MI) { - if (MI->isDebugInstr() || MI->isLabel() || MI->isInlineAsm()) - return; - - unsigned RegOpcode = MI->getOpcode(); - if (RegOpcode == EVM::PUSH_LABEL || RegOpcode == EVM::PseudoJUMP || - RegOpcode == EVM::PseudoJUMPI || RegOpcode == EVM::PseudoCALL || - RegOpcode == EVM::PseudoRET) - return; - - // Remove register operands. - for (unsigned I = MI->getNumOperands(); I > 0; --I) { - auto &MO = MI->getOperand(I - 1); - if (MO.isReg()) { - MI->removeOperand(I - 1); - } - } - - // Transform 'register' instruction to the 'stack' one. - unsigned StackOpcode = EVM::getStackOpcode(RegOpcode); - MI->setDesc(TII->get(StackOpcode)); +// Verify that a stackified instruction doesn't have registers and dump it. +void EVMAssembly::verify(const MachineInstr *MI) const { + assert(EVMInstrInfo::isStack(MI) && + "Only stackified instructions are allowed"); + assert(all_of(MI->operands(), + [](const MachineOperand &MO) { return !MO.isReg(); }) && + "Registers are not allowed in stackified instructions"); + + LLVM_DEBUG(dbgs() << "Adding: " << *MI << "stack height: " << StackHeight + << "\n"); } void EVMAssembly::finalize() { - // Collect and erase instructions that are not required in - // a stackified code. These are auxiliary codegn-only instructions. - SmallVector ToRemove; - for (MachineBasicBlock &MBB : *MF) { - for (MachineInstr &MI : MBB) { - if (!AssemblyInstrs.count(&MI)) - ToRemove.emplace_back(&MI); - } - } - - for (MachineInstr *MI : ToRemove) - MI->eraseFromParent(); - - // Remove register operands and replace instruction opcode with 'stack' one. - for (MachineBasicBlock &MBB : *MF) - for (MachineInstr &MI : MBB) - stackifyInstruction(&MI); - - auto *MFI = MF->getInfo(); + for (MachineBasicBlock &MBB : MF) + for (MachineInstr &MI : make_early_inc_range(MBB)) + // Remove all the instructions that are not stackified. + // FIXME: Fix debug info for stackified instructions and don't + // remove debug instructions. + if (!EVMInstrInfo::isStack(&MI)) + MI.eraseFromParent(); + + auto *MFI = MF.getInfo(); MFI->setIsStackified(); // In a stackified code register liveness has no meaning. - MachineRegisterInfo &MRI = MF->getRegInfo(); + MachineRegisterInfo &MRI = MF.getRegInfo(); MRI.invalidateLiveness(); } diff --git a/llvm/lib/Target/EVM/EVMAssembly.h b/llvm/lib/Target/EVM/EVMAssembly.h index d6303ae136df..0eb07f128595 100644 --- a/llvm/lib/Target/EVM/EVMAssembly.h +++ b/llvm/lib/Target/EVM/EVMAssembly.h @@ -16,10 +16,7 @@ #ifndef LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H #define LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H -#include "EVM.h" - #include "EVMSubtarget.h" -#include "MCTargetDesc/EVMMCTargetDesc.h" #include "llvm/CodeGen/MachineFunction.h" namespace llvm { @@ -29,66 +26,53 @@ class MCSymbol; class EVMAssembly { private: - using MIIter = MachineBasicBlock::iterator; - - MachineFunction *MF; + MachineFunction &MF; const EVMInstrInfo *TII; - int StackHeight = 0; - MIIter CurMIIt; - MachineBasicBlock *CurMBB; - DenseSet AssemblyInstrs; + MachineBasicBlock *CurMBB{}; + DenseMap CallReturnSyms; public: - EVMAssembly(MachineFunction *MF, const EVMInstrInfo *TII) - : MF(MF), TII(TII) {} + explicit EVMAssembly(MachineFunction &MF) + : MF(MF), TII(MF.getSubtarget().getInstrInfo()) {} // Retrieve the current height of the stack. // This does not have to be zero at the MF beginning because of // possible arguments. int getStackHeight() const; - void setStackHeight(int Height); + void init(MachineBasicBlock *MBB, int Height); - void setCurrentLocation(MachineBasicBlock *MBB); + void emitInst(const MachineInstr *MI); - void appendInstruction(MachineInstr *MI); + void emitSWAP(unsigned Depth); - void appendSWAPInstruction(unsigned Depth); + void emitDUP(unsigned Depth); - void appendDUPInstruction(unsigned Depth); + void emitPOP(); - void appendPOPInstruction(); + void emitConstant(const APInt &Val); - void appendConstant(const APInt &Val); + void emitSymbol(const MachineInstr *MI, MCSymbol *Symbol); - void appendSymbol(MCSymbol *Symbol, unsigned Opcode); + void emitConstant(uint64_t Val); - void appendConstant(uint64_t Val); + void emitFuncCall(const MachineInstr *MI, const GlobalValue *Func, + int stackAdj, bool WillReturn); - void appendFuncCall(const MachineInstr *MI, const llvm::GlobalValue *Func, - int stackAdj, MCSymbol *RetSym = nullptr); + void emitRet(const MachineInstr *MI); - void appendRet(); + void emitCondJump(const MachineInstr *MI, MachineBasicBlock *Target); - void appendCondJump(MachineInstr *MI, MachineBasicBlock *Target); + void emitUncondJump(const MachineInstr *MI, MachineBasicBlock *Target); - void appendUncondJump(MachineInstr *MI, MachineBasicBlock *Target); + void emitLabelReference(const MachineInstr *Call); - void appendLabelReference(MCSymbol *Label); - - MCSymbol *createFuncRetSymbol(); - - // Erases unused codegen-only instructions and removes register operands - // of the remaining ones. + // Erases unused codegen-only instructions. void finalize(); private: - void stackifyInstruction(MachineInstr *MI); - -#ifndef NDEBUG - void dumpInst(const MachineInstr *MI) const; -#endif // NDEBUG + void verify(const MachineInstr *MI) const; }; } // namespace llvm diff --git a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp index a2e60b31c2da..fd250349b35f 100644 --- a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp +++ b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp @@ -24,7 +24,6 @@ //===----------------------------------------------------------------------===// #include "EVM.h" -#include "EVMAssembly.h" #include "EVMControlFlowGraphBuilder.h" #include "EVMOptimizedCodeTransform.h" #include "EVMSubtarget.h" @@ -86,7 +85,6 @@ bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { }); MachineRegisterInfo &MRI = MF.getRegInfo(); - const EVMInstrInfo *TII = MF.getSubtarget().getInstrInfo(); auto &LIS = getAnalysis(); MachineLoopInfo *MLI = &getAnalysis(); @@ -95,10 +93,8 @@ bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { assert(MRI.tracksLiveness() && "Stackification expects liveness"); - EVMAssembly Assembly(&MF, TII); std::unique_ptr Cfg = ControlFlowGraphBuilder::build(MF, LIS, MLI); StackLayout Layout = StackLayoutGenerator::run(*Cfg); - EVMOptimizedCodeTransform(Assembly, Layout, MF) - .run(Cfg->getBlock(&Cfg->MF.front())); + EVMOptimizedCodeTransform(Layout, MF).run(Cfg->getBlock(&Cfg->MF.front())); return true; } diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraph.h b/llvm/lib/Target/EVM/EVMControlFlowGraph.h index dacff6f8f87e..97f9059ac8af 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraph.h +++ b/llvm/lib/Target/EVM/EVMControlFlowGraph.h @@ -229,6 +229,7 @@ struct CFG { struct FunctionReturn { Stack RetValues; + const MachineInstr *Ret = nullptr; }; struct Terminated {}; diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp index 929dcd4d9ed3..423199a4d1ce 100644 --- a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp +++ b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp @@ -328,7 +328,7 @@ void ControlFlowGraphBuilder::handleReturn(const MachineInstr &MI) { // Calling convention: return values are passed in stack such that the // last one specified in the RET instruction is passed on the stack TOP. std::reverse(Input.begin(), Input.end()); - CurrentBlock->Exit = CFG::BasicBlock::FunctionReturn{std::move(Input)}; + CurrentBlock->Exit = CFG::BasicBlock::FunctionReturn{std::move(Input), &MI}; } static std::pair diff --git a/llvm/lib/Target/EVM/EVMInstrFormats.td b/llvm/lib/Target/EVM/EVMInstrFormats.td index 1c90c91c433c..c8068ebe0352 100644 --- a/llvm/lib/Target/EVM/EVMInstrFormats.td +++ b/llvm/lib/Target/EVM/EVMInstrFormats.td @@ -53,7 +53,7 @@ class NI pattern, bit stack, let Opc = inst; let Inst{7-0} = Opc; let GasCost = cost; - let Defs = [ARGUMENTS]; + let Defs = !if(stack, [], [ARGUMENTS]); } // Generates both register and stack based versions of one actual instruction. @@ -74,8 +74,8 @@ class NRI pattern, string asmstr> : NI { } -class EVMPseudo pattern> - : NI { +class EVMPseudo pattern, bit stack = 0> + : NI { let isPseudo = 1; let isCodeGenOnly = 1; } diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index 4bc7d71a9f8f..3698cf1a054a 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -248,10 +248,10 @@ let variadicOpsAreDefs = 1 in def FCALL : NRI<(outs), (ins jmptarget:$callee, variable_ops), [], "FCALL\t$callee">; -let isPseudo = 1 in -def PseudoCALL : EVMPseudo<(outs), (ins jmptarget:$callee, variable_ops), []>; } // Uses = [SP], isCall = 1 +let isCall = 1 in +def PseudoCALL : EVMPseudo<(outs), (ins jmptarget:$callee, variable_ops), [], true>; //===----------------------------------------------------------------------===// // EVM arithmetic instructions. @@ -407,14 +407,12 @@ let isBranch = 1, isTerminator = 1 in { defm JUMPI : I<(outs), (ins jmptarget:$dst, GPR:$cond), [(brcond GPR:$cond, bb:$dst)], "JUMPI", " $dst, $cond", 0x57, 10>; -let isPseudo = 1 in -def PseudoJUMPI : EVMPseudo<(outs), (ins jmptarget:$dst), []>; +def PseudoJUMPI : EVMPseudo<(outs), (ins jmptarget:$dst), [], true>; let isBarrier = 1 in { defm JUMP : I<(outs), (ins jmptarget:$dst), [(br bb:$dst)], "JUMP", " $dst", 0x56, 8>; -let isPseudo = 1 in -def PseudoJUMP : EVMPseudo<(outs), (ins jmptarget:$dst), []>; +def PseudoJUMP : EVMPseudo<(outs), (ins jmptarget:$dst), [], true>; } // isBarrier = 1 } // isBranch = 1, isTerminator = 1 @@ -424,8 +422,7 @@ defm JUMPDEST : I<(outs), (ins), [], "JUMPDEST", "", 0x5B, 1>; let isBarrier = 1, isTerminator = 1, isReturn = 1 in { def RET : NRI<(outs), (ins variable_ops), [(EVMret)], "RET">; -let isPseudo = 1 in -def PseudoRET : EVMPseudo<(outs), (ins), []>; +def PseudoRET : EVMPseudo<(outs), (ins), [], true>; } @@ -804,7 +801,7 @@ foreach I = {1-16} in { defm PUSH0 : I<(outs), (ins), [], "PUSH0", "", 0x5F, 2>; -def PUSH_LABEL : NI<(outs), (ins jmptarget:$dst), [], false, "", 0, 0> { +def PUSH_LABEL : NI<(outs), (ins jmptarget:$dst), [], true, "", 0, 0> { let isCodeGenOnly = 1; } diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp index efa3b7b2cc12..e572e735efdd 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp @@ -35,11 +35,10 @@ void EVMOptimizedCodeTransform::visitCall(const CFG::FunctionCall &Call) { // Emit code. const MachineOperand *CalleeOp = Call.Call->explicit_uses().begin(); assert(CalleeOp->isGlobal()); - Assembly.appendFuncCall(Call.Call, CalleeOp->getGlobal(), - Call.Call->getNumExplicitDefs() - Call.NumArguments - - (Call.CanContinue ? 1 : 0), - Call.CanContinue ? CallToReturnMCSymbol.at(Call.Call) - : nullptr); + Assembly.emitFuncCall(Call.Call, CalleeOp->getGlobal(), + Call.Call->getNumExplicitDefs() - Call.NumArguments - + (Call.CanContinue ? 1 : 0), + Call.CanContinue); // Update stack, remove arguments and return label from CurrentStack. for (size_t I = 0; I < Call.NumArguments + (Call.CanContinue ? 1 : 0); ++I) @@ -63,7 +62,7 @@ void EVMOptimizedCodeTransform::visitInst(const CFG::BuiltinCall &Call) { // TODO: assert that we got a correct stack for the call. // Emit code. - Assembly.appendInstruction(Call.Builtin); + Assembly.emitInst(Call.Builtin); // Update stack and remove arguments from CurrentStack. for (size_t i = 0; i < NumArgs; ++i) @@ -137,7 +136,7 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { Assembly.getStackHeight()); assert(I > 0 && I < CurrentStack.size()); if (I <= 16) { - Assembly.appendSWAPInstruction(I); + Assembly.emitSWAP(I); } else { int Deficit = static_cast(I) - 16; const StackSlot &DeepSlot = @@ -169,7 +168,7 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { if (SlotIt != CurrentStack.rend()) { unsigned Depth = std::distance(CurrentStack.rbegin(), SlotIt); if (Depth < 16) { - Assembly.appendDUPInstruction(static_cast(Depth + 1)); + Assembly.emitDUP(static_cast(Depth + 1)); return; } if (!canBeFreelyGenerated(Slot)) { @@ -192,22 +191,16 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { // Push it. std::visit( Overload{[&](const LiteralSlot &Literal) { - Assembly.appendConstant(Literal.Value); + Assembly.emitConstant(Literal.Value); }, [&](const SymbolSlot &Symbol) { - Assembly.appendSymbol(Symbol.Symbol, - Symbol.MI->getOpcode()); + Assembly.emitSymbol(Symbol.MI, Symbol.Symbol); }, [&](const FunctionReturnLabelSlot &) { llvm_unreachable("Cannot produce function return label"); }, [&](const FunctionCallReturnLabelSlot &ReturnLabel) { - if (!CallToReturnMCSymbol.count(ReturnLabel.Call)) - CallToReturnMCSymbol[ReturnLabel.Call] = - Assembly.createFuncRetSymbol(); - - Assembly.appendLabelReference( - CallToReturnMCSymbol[ReturnLabel.Call]); + Assembly.emitLabelReference(ReturnLabel.Call); }, [&](const VariableSlot &Variable) { llvm_unreachable("Variable not found on stack"); @@ -219,12 +212,12 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { [&](const JunkSlot &) { // Note: this will always be popped, so we can push // anything. - Assembly.appendConstant(0); + Assembly.emitConstant(0); }}, Slot); }, // Pop callback. - [&]() { Assembly.appendPOPInstruction(); }); + [&]() { Assembly.emitPOP(); }); assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); } @@ -287,8 +280,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Might set some slots to junk, if not required by the block. CurrentStack = BlockInfo.entryLayout; - Assembly.setStackHeight(static_cast(CurrentStack.size())); - Assembly.setCurrentLocation(Block->MBB); + Assembly.init(Block->MBB, static_cast(CurrentStack.size())); for (const auto &Operation : Block->Operations) { createOperationLayout(Operation); @@ -331,7 +323,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { CurrentStack, Layout.blockInfos.at(Jump.Target).entryLayout)); if (Jump.UncondJump) - Assembly.appendUncondJump(Jump.UncondJump, Jump.Target->MBB); + Assembly.emitUncondJump(Jump.UncondJump, Jump.Target->MBB); WorkList.emplace_back(Jump.Target); }, [&](CFG::BasicBlock::ConditionalJump const &CondJump) { @@ -346,7 +338,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Emit the conditional jump to the non-zero label and update the // stored stack. assert(CondJump.CondJump); - Assembly.appendCondJump(CondJump.CondJump, CondJump.NonZero->MBB); + Assembly.emitCondJump(CondJump.CondJump, CondJump.NonZero->MBB); CurrentStack.pop_back(); // Assert that we have a valid stack for both jump targets. @@ -359,8 +351,8 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Generate unconditional jump if needed. if (CondJump.UncondJump) - Assembly.appendUncondJump(CondJump.UncondJump, - CondJump.Zero->MBB); + Assembly.emitUncondJump(CondJump.UncondJump, + CondJump.Zero->MBB); WorkList.emplace_back(CondJump.NonZero); WorkList.emplace_back(CondJump.Zero); }, @@ -375,7 +367,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Create the function return layout and jump. createStackLayout(ExitStack); - Assembly.appendRet(); + Assembly.emitRet(FuncReturn.Ret); }, [&](CFG::BasicBlock::Unreachable const &) { assert(Block->Operations.empty()); diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h index 3dc04144be84..60482027cdf9 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h +++ b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h @@ -24,19 +24,17 @@ class MCSymbol; class EVMOptimizedCodeTransform { public: - EVMOptimizedCodeTransform(EVMAssembly &Assembly, const StackLayout &Layout, - MachineFunction &MF) - : Assembly(Assembly), Layout(Layout), MF(MF) {} + EVMOptimizedCodeTransform(const StackLayout &Layout, MachineFunction &MF) + : Assembly(MF), Layout(Layout), MF(MF) {} /// Stackify instructions, starting from the \p EntryBB. void run(CFG::BasicBlock &EntryBB); private: - EVMAssembly &Assembly; + EVMAssembly Assembly; const StackLayout &Layout; const MachineFunction &MF; Stack CurrentStack; - DenseMap CallToReturnMCSymbol; /// Checks if it's valid to transition from \p SourceStack to \p /// TargetStack, that is \p SourceStack matches each slot in \p From 21d3983bd084a36ee284ee2aea7953daa214bead Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Thu, 12 Dec 2024 17:07:37 +0100 Subject: [PATCH 13/42] [EVM] Merge EVMOptimizedCodeTransform and EVMAssembly into one file Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/CMakeLists.txt | 3 +- llvm/lib/Target/EVM/EVMAssembly.cpp | 197 ------------- llvm/lib/Target/EVM/EVMAssembly.h | 80 ----- .../EVMBackwardPropagationStackification.cpp | 4 +- .../Target/EVM/EVMOptimizedCodeTransform.h | 61 ---- ...ansform.cpp => EVMStackifyCodeEmitter.cpp} | 274 +++++++++++++++--- llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h | 91 ++++++ 7 files changed, 322 insertions(+), 388 deletions(-) delete mode 100644 llvm/lib/Target/EVM/EVMAssembly.cpp delete mode 100644 llvm/lib/Target/EVM/EVMAssembly.h delete mode 100644 llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h rename llvm/lib/Target/EVM/{EVMOptimizedCodeTransform.cpp => EVMStackifyCodeEmitter.cpp} (58%) create mode 100644 llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index 5f9d00e568c4..7f22724a1f8e 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -20,7 +20,6 @@ add_llvm_target(EVMCodeGen EVMAllocaHoisting.cpp EVMArgumentMove.cpp EVMAsmPrinter.cpp - EVMAssembly.cpp EVMBackwardPropagationStackification.cpp EVMCodegenPrepare.cpp EVMControlFlowGraphBuilder.cpp @@ -33,7 +32,6 @@ add_llvm_target(EVMCodeGen EVMMachineFunctionInfo.cpp EVMMCInstLower.cpp EVMOptimizeLiveIntervals.cpp - EVMOptimizedCodeTransform.cpp EVMRegColoring.cpp EVMRegisterInfo.cpp EVMSingleUseExpression.cpp @@ -41,6 +39,7 @@ add_llvm_target(EVMCodeGen EVMStackDebug.cpp EVMStackLayoutGenerator.cpp EVMStackify.cpp + EVMStackifyCodeEmitter.cpp EVMSubtarget.cpp EVMTargetMachine.cpp EVMTargetTransformInfo.cpp diff --git a/llvm/lib/Target/EVM/EVMAssembly.cpp b/llvm/lib/Target/EVM/EVMAssembly.cpp deleted file mode 100644 index aca2f7269762..000000000000 --- a/llvm/lib/Target/EVM/EVMAssembly.cpp +++ /dev/null @@ -1,197 +0,0 @@ -//===----------- EVMAssembly.cpp - EVM Assembly generator -------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file creates Machine IR in stackified form. It provides different -// callbacks when the EVMOptimizedCodeTransform needs to emit operation, -// stack manipulation instruction, and so on. It the end, it walks over MIR -// instructions removing register operands. -// -//===----------------------------------------------------------------------===// - -#include "EVMAssembly.h" -#include "EVMMachineFunctionInfo.h" -#include "TargetInfo/EVMTargetInfo.h" -#include "llvm/MC/MCContext.h" - -using namespace llvm; - -#define DEBUG_TYPE "evm-assembly" - -int EVMAssembly::getStackHeight() const { return StackHeight; } - -void EVMAssembly::init(MachineBasicBlock *MBB, int Height) { - StackHeight = Height; - CurMBB = MBB; - LLVM_DEBUG(dbgs() << "\n" - << "Set stack height: " << StackHeight << "\n"); - LLVM_DEBUG(dbgs() << "Setting current location to: " << MBB->getNumber() - << "." << MBB->getName() << "\n"); -} - -void EVMAssembly::emitInst(const MachineInstr *MI) { - unsigned Opc = MI->getOpcode(); - assert(Opc != EVM::JUMP && Opc != EVM::JUMPI && Opc != EVM::ARGUMENT && - Opc != EVM::RET && Opc != EVM::CONST_I256 && Opc != EVM::COPY_I256 && - Opc != EVM::FCALL && "Unexpected instruction"); - - // The stack height is increased by the number of defs and decreased - // by the number of inputs. To get the number of inputs, we subtract - // the total number of operands from the number of defs, so the - // calculation is as follows: - // Defs - (Ops - Defs) = 2 * Defs - Ops - int StackAdj = (2 * static_cast(MI->getNumExplicitDefs())) - - static_cast(MI->getNumExplicitOperands()); - StackHeight += StackAdj; - - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), - TII->get(EVM::getStackOpcode(Opc))); - verify(NewMI); -} - -void EVMAssembly::emitSWAP(unsigned Depth) { - unsigned Opc = EVM::getSWAPOpcode(Depth); - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), - TII->get(EVM::getStackOpcode(Opc))); - verify(NewMI); -} - -void EVMAssembly::emitDUP(unsigned Depth) { - StackHeight += 1; - unsigned Opc = EVM::getDUPOpcode(Depth); - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), - TII->get(EVM::getStackOpcode(Opc))); - verify(NewMI); -} - -void EVMAssembly::emitPOP() { - StackHeight -= 1; - assert(StackHeight >= 0); - auto NewMI = - BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), TII->get(EVM::POP_S)); - verify(NewMI); -} - -void EVMAssembly::emitConstant(const APInt &Val) { - StackHeight += 1; - unsigned Opc = EVM::getPUSHOpcode(Val); - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), - TII->get(EVM::getStackOpcode(Opc))); - if (Opc != EVM::PUSH0) - NewMI.addCImm(ConstantInt::get(MF.getFunction().getContext(), Val)); - verify(NewMI); -} - -void EVMAssembly::emitSymbol(const MachineInstr *MI, MCSymbol *Symbol) { - unsigned Opc = MI->getOpcode(); - assert(Opc == EVM::DATASIZE || - Opc == EVM::DATAOFFSET && "Unexpected symbol instruction"); - StackHeight += 1; - // This is codegen-only instruction, that will be converted into PUSH4. - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), - TII->get(EVM::getStackOpcode(Opc))) - .addSym(Symbol); - verify(NewMI); -} - -void EVMAssembly::emitConstant(uint64_t Val) { emitConstant(APInt(256, Val)); } - -void EVMAssembly::emitLabelReference(const MachineInstr *Call) { - assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); - StackHeight += 1; - auto [It, Inserted] = CallReturnSyms.try_emplace(Call); - if (Inserted) - It->second = MF.getContext().createTempSymbol("FUNC_RET", true); - auto NewMI = - BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), TII->get(EVM::PUSH_LABEL)) - .addSym(It->second); - verify(NewMI); -} - -void EVMAssembly::emitFuncCall(const MachineInstr *MI, const GlobalValue *Func, - int StackAdj, bool WillReturn) { - assert(MI->getOpcode() == EVM::FCALL && "Unexpected call instruction"); - assert(CurMBB == MI->getParent()); - - // PUSH_LABEL technically increases the stack height on 1, but we don't - // increase it explicitly here, as the label will be consumed by the following - // JUMP. - StackHeight += StackAdj; - - // Create pseudo jump to the callee, that will be expanded into PUSH and JUMP - // instructions in the AsmPrinter. - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), - TII->get(EVM::PseudoCALL)) - .addGlobalAddress(Func); - - // If this function returns, we need to create a label after JUMP instruction - // that is followed by JUMPDEST and this is taken care in the AsmPrinter. - // In case we use setPostInstrSymbol here, the label will be created - // after the JUMPDEST instruction, which is not what we want. - if (WillReturn) - NewMI.addSym(CallReturnSyms.at(MI)); - verify(NewMI); -} - -void EVMAssembly::emitRet(const MachineInstr *MI) { - assert(MI->getOpcode() == EVM::RET && "Unexpected ret instruction"); - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), - TII->get(EVM::PseudoRET)); - verify(NewMI); -} - -void EVMAssembly::emitUncondJump(const MachineInstr *MI, - MachineBasicBlock *Target) { - assert(MI->getOpcode() == EVM::JUMP && - "Unexpected unconditional jump instruction"); - assert(StackHeight >= 0); - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), - TII->get(EVM::PseudoJUMP)) - .addMBB(Target); - verify(NewMI); -} - -void EVMAssembly::emitCondJump(const MachineInstr *MI, - MachineBasicBlock *Target) { - assert(MI->getOpcode() == EVM::JUMPI && - "Unexpected conditional jump instruction"); - StackHeight -= 1; - assert(StackHeight >= 0); - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), - TII->get(EVM::PseudoJUMPI)) - .addMBB(Target); - verify(NewMI); -} - -// Verify that a stackified instruction doesn't have registers and dump it. -void EVMAssembly::verify(const MachineInstr *MI) const { - assert(EVMInstrInfo::isStack(MI) && - "Only stackified instructions are allowed"); - assert(all_of(MI->operands(), - [](const MachineOperand &MO) { return !MO.isReg(); }) && - "Registers are not allowed in stackified instructions"); - - LLVM_DEBUG(dbgs() << "Adding: " << *MI << "stack height: " << StackHeight - << "\n"); -} - -void EVMAssembly::finalize() { - for (MachineBasicBlock &MBB : MF) - for (MachineInstr &MI : make_early_inc_range(MBB)) - // Remove all the instructions that are not stackified. - // FIXME: Fix debug info for stackified instructions and don't - // remove debug instructions. - if (!EVMInstrInfo::isStack(&MI)) - MI.eraseFromParent(); - - auto *MFI = MF.getInfo(); - MFI->setIsStackified(); - - // In a stackified code register liveness has no meaning. - MachineRegisterInfo &MRI = MF.getRegInfo(); - MRI.invalidateLiveness(); -} diff --git a/llvm/lib/Target/EVM/EVMAssembly.h b/llvm/lib/Target/EVM/EVMAssembly.h deleted file mode 100644 index 0eb07f128595..000000000000 --- a/llvm/lib/Target/EVM/EVMAssembly.h +++ /dev/null @@ -1,80 +0,0 @@ -//===------------- EVMAssembly.h - EVM Assembly generator -------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file creates Machine IR in stackified form. It provides different -// callbacks when the EVMOptimizedCodeTransform needs to emit operation, -// stack manipulation instruction, and so on. It the end, it walks over MIR -// instructions removing register operands. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H -#define LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H - -#include "EVMSubtarget.h" -#include "llvm/CodeGen/MachineFunction.h" - -namespace llvm { - -class MachineInstr; -class MCSymbol; - -class EVMAssembly { -private: - MachineFunction &MF; - const EVMInstrInfo *TII; - int StackHeight = 0; - MachineBasicBlock *CurMBB{}; - DenseMap CallReturnSyms; - -public: - explicit EVMAssembly(MachineFunction &MF) - : MF(MF), TII(MF.getSubtarget().getInstrInfo()) {} - - // Retrieve the current height of the stack. - // This does not have to be zero at the MF beginning because of - // possible arguments. - int getStackHeight() const; - - void init(MachineBasicBlock *MBB, int Height); - - void emitInst(const MachineInstr *MI); - - void emitSWAP(unsigned Depth); - - void emitDUP(unsigned Depth); - - void emitPOP(); - - void emitConstant(const APInt &Val); - - void emitSymbol(const MachineInstr *MI, MCSymbol *Symbol); - - void emitConstant(uint64_t Val); - - void emitFuncCall(const MachineInstr *MI, const GlobalValue *Func, - int stackAdj, bool WillReturn); - - void emitRet(const MachineInstr *MI); - - void emitCondJump(const MachineInstr *MI, MachineBasicBlock *Target); - - void emitUncondJump(const MachineInstr *MI, MachineBasicBlock *Target); - - void emitLabelReference(const MachineInstr *Call); - - // Erases unused codegen-only instructions. - void finalize(); - -private: - void verify(const MachineInstr *MI) const; -}; - -} // namespace llvm - -#endif // LLVM_LIB_TARGET_EVM_EVMASSEMBLY_H diff --git a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp index fd250349b35f..a1d79eb213a2 100644 --- a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp +++ b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp @@ -25,7 +25,7 @@ #include "EVM.h" #include "EVMControlFlowGraphBuilder.h" -#include "EVMOptimizedCodeTransform.h" +#include "EVMStackifyCodeEmitter.h" #include "EVMSubtarget.h" #include "llvm/CodeGen/LiveIntervals.h" #include "llvm/CodeGen/MachineLoopInfo.h" @@ -95,6 +95,6 @@ bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { std::unique_ptr Cfg = ControlFlowGraphBuilder::build(MF, LIS, MLI); StackLayout Layout = StackLayoutGenerator::run(*Cfg); - EVMOptimizedCodeTransform(Layout, MF).run(Cfg->getBlock(&Cfg->MF.front())); + EVMStackifyCodeEmitter(Layout, MF).run(Cfg->getBlock(&Cfg->MF.front())); return true; } diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h b/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h deleted file mode 100644 index 60482027cdf9..000000000000 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.h +++ /dev/null @@ -1,61 +0,0 @@ -//===--- EVMOptimizedCodeTransform.h - Create stackified MIR ---*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file transforms MIR to the 'stackified' MIR using CFG, StackLayout -// and EVMAssembly classes. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIB_TARGET_EVM_EVMOPTIMIZEDCODETRANSFORM_H -#define LLVM_LIB_TARGET_EVM_EVMOPTIMIZEDCODETRANSFORM_H - -#include "EVMAssembly.h" -#include "EVMStackLayoutGenerator.h" - -namespace llvm { - -class MachineInstr; -class MCSymbol; - -class EVMOptimizedCodeTransform { -public: - EVMOptimizedCodeTransform(const StackLayout &Layout, MachineFunction &MF) - : Assembly(MF), Layout(Layout), MF(MF) {} - - /// Stackify instructions, starting from the \p EntryBB. - void run(CFG::BasicBlock &EntryBB); - -private: - EVMAssembly Assembly; - const StackLayout &Layout; - const MachineFunction &MF; - Stack CurrentStack; - - /// Checks if it's valid to transition from \p SourceStack to \p - /// TargetStack, that is \p SourceStack matches each slot in \p - /// TargetStack that is not a JunkSlot exactly. - bool areLayoutsCompatible(const Stack &SourceStack, const Stack &TargetStack); - - /// Shuffles CurrentStack to the desired \p TargetStack. - void createStackLayout(const Stack &TargetStack); - - /// Creates the Op.Input stack layout from the 'CurrentStack' taking into - /// account commutative property of the operation. - void createOperationLayout(const CFG::Operation &Op); - - /// Generate code for the function call \p Call. - void visitCall(const CFG::FunctionCall &Call); - /// Generate code for the builtin call \p Call. - void visitInst(const CFG::BuiltinCall &Call); - /// Generate code for the assignment \p Assignment. - void visitAssign(const CFG::Assignment &Assignment); -}; - -} // namespace llvm - -#endif // LLVM_LIB_TARGET_EVM_EVMOPTIMIZEDCODETRANSFORM_H diff --git a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp similarity index 58% rename from llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp rename to llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index e572e735efdd..3a17a6e7575c 100644 --- a/llvm/lib/Target/EVM/EVMOptimizedCodeTransform.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -1,4 +1,4 @@ -//===--- EVMOptimizedCodeTransform.h - Create stackified MIR ---*- C++ -*-===// +//===--- EVMStackifyCodeEmitter.h - Create stackified MIR -------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -6,22 +6,207 @@ // //===----------------------------------------------------------------------===// // -// This file transforms MIR to the 'stackified' MIR using CFG, StackLayout -// and EVMAssembly classes. +// This file transforms MIR to the 'stackified' MIR. // //===----------------------------------------------------------------------===// -#include "EVMOptimizedCodeTransform.h" +#include "EVMStackifyCodeEmitter.h" +#include "EVMMachineFunctionInfo.h" #include "EVMStackDebug.h" #include "EVMStackShuffler.h" +#include "TargetInfo/EVMTargetInfo.h" +#include "llvm/MC/MCContext.h" using namespace llvm; -#define DEBUG_TYPE "evm-optimized-code-transform" +#define DEBUG_TYPE "evm-stackify-code-emitter" -void EVMOptimizedCodeTransform::visitCall(const CFG::FunctionCall &Call) { +int EVMStackifyCodeEmitter::CodeEmitter::getStackHeight() const { + return StackHeight; +} + +void EVMStackifyCodeEmitter::CodeEmitter::init(MachineBasicBlock *MBB, + int Height) { + StackHeight = Height; + CurMBB = MBB; + LLVM_DEBUG(dbgs() << "\n" + << "Set stack height: " << StackHeight << "\n"); + LLVM_DEBUG(dbgs() << "Setting current location to: " << MBB->getNumber() + << "." << MBB->getName() << "\n"); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitInst(const MachineInstr *MI) { + unsigned Opc = MI->getOpcode(); + assert(Opc != EVM::JUMP && Opc != EVM::JUMPI && Opc != EVM::ARGUMENT && + Opc != EVM::RET && Opc != EVM::CONST_I256 && Opc != EVM::COPY_I256 && + Opc != EVM::FCALL && "Unexpected instruction"); + + // The stack height is increased by the number of defs and decreased + // by the number of inputs. To get the number of inputs, we subtract + // the total number of operands from the number of defs, so the + // calculation is as follows: + // Defs - (Ops - Defs) = 2 * Defs - Ops + int StackAdj = (2 * static_cast(MI->getNumExplicitDefs())) - + static_cast(MI->getNumExplicitOperands()); + StackHeight += StackAdj; + + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitSWAP(unsigned Depth) { + unsigned Opc = EVM::getSWAPOpcode(Depth); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitDUP(unsigned Depth) { + StackHeight += 1; + unsigned Opc = EVM::getDUPOpcode(Depth); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitPOP() { + StackHeight -= 1; + assert(StackHeight >= 0); + auto NewMI = + BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), TII->get(EVM::POP_S)); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitConstant(const APInt &Val) { + StackHeight += 1; + unsigned Opc = EVM::getPUSHOpcode(Val); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), + TII->get(EVM::getStackOpcode(Opc))); + if (Opc != EVM::PUSH0) + NewMI.addCImm(ConstantInt::get(MF.getFunction().getContext(), Val)); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitSymbol(const MachineInstr *MI, + MCSymbol *Symbol) { + unsigned Opc = MI->getOpcode(); + assert(Opc == EVM::DATASIZE || + Opc == EVM::DATAOFFSET && "Unexpected symbol instruction"); + StackHeight += 1; + // This is codegen-only instruction, that will be converted into PUSH4. + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::getStackOpcode(Opc))) + .addSym(Symbol); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitConstant(uint64_t Val) { + emitConstant(APInt(256, Val)); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitLabelReference( + const MachineInstr *Call) { + assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); + StackHeight += 1; + auto [It, Inserted] = CallReturnSyms.try_emplace(Call); + if (Inserted) + It->second = MF.getContext().createTempSymbol("FUNC_RET", true); + auto NewMI = + BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), TII->get(EVM::PUSH_LABEL)) + .addSym(It->second); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitFuncCall(const MachineInstr *MI, + const GlobalValue *Func, + int StackAdj, + bool WillReturn) { + assert(MI->getOpcode() == EVM::FCALL && "Unexpected call instruction"); + assert(CurMBB == MI->getParent()); + + // PUSH_LABEL technically increases the stack height on 1, but we don't + // increase it explicitly here, as the label will be consumed by the following + // JUMP. + StackHeight += StackAdj; + + // Create pseudo jump to the callee, that will be expanded into PUSH and JUMP + // instructions in the AsmPrinter. + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoCALL)) + .addGlobalAddress(Func); + + // If this function returns, we need to create a label after JUMP instruction + // that is followed by JUMPDEST and this is taken care in the AsmPrinter. + // In case we use setPostInstrSymbol here, the label will be created + // after the JUMPDEST instruction, which is not what we want. + if (WillReturn) + NewMI.addSym(CallReturnSyms.at(MI)); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitRet(const MachineInstr *MI) { + assert(MI->getOpcode() == EVM::RET && "Unexpected ret instruction"); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoRET)); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitUncondJump( + const MachineInstr *MI, MachineBasicBlock *Target) { + assert(MI->getOpcode() == EVM::JUMP && + "Unexpected unconditional jump instruction"); + assert(StackHeight >= 0); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoJUMP)) + .addMBB(Target); + verify(NewMI); +} + +void EVMStackifyCodeEmitter::CodeEmitter::emitCondJump( + const MachineInstr *MI, MachineBasicBlock *Target) { + assert(MI->getOpcode() == EVM::JUMPI && + "Unexpected conditional jump instruction"); + StackHeight -= 1; + assert(StackHeight >= 0); + auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(EVM::PseudoJUMPI)) + .addMBB(Target); + verify(NewMI); +} + +// Verify that a stackified instruction doesn't have registers and dump it. +void EVMStackifyCodeEmitter::CodeEmitter::verify(const MachineInstr *MI) const { + assert(EVMInstrInfo::isStack(MI) && + "Only stackified instructions are allowed"); + assert(all_of(MI->operands(), + [](const MachineOperand &MO) { return !MO.isReg(); }) && + "Registers are not allowed in stackified instructions"); + + LLVM_DEBUG(dbgs() << "Adding: " << *MI << "stack height: " << StackHeight + << "\n"); +} + +void EVMStackifyCodeEmitter::CodeEmitter::finalize() { + for (MachineBasicBlock &MBB : MF) + for (MachineInstr &MI : make_early_inc_range(MBB)) + // Remove all the instructions that are not stackified. + // FIXME: Fix debug info for stackified instructions and don't + // remove debug instructions. + if (!EVMInstrInfo::isStack(&MI)) + MI.eraseFromParent(); + + auto *MFI = MF.getInfo(); + MFI->setIsStackified(); + + // In a stackified code register liveness has no meaning. + MachineRegisterInfo &MRI = MF.getRegInfo(); + MRI.invalidateLiveness(); +} + +void EVMStackifyCodeEmitter::visitCall(const CFG::FunctionCall &Call) { // Validate stack. - assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); assert(CurrentStack.size() >= Call.NumArguments + (Call.CanContinue ? 1 : 0)); // Assert that we got the correct return label on stack. @@ -35,10 +220,10 @@ void EVMOptimizedCodeTransform::visitCall(const CFG::FunctionCall &Call) { // Emit code. const MachineOperand *CalleeOp = Call.Call->explicit_uses().begin(); assert(CalleeOp->isGlobal()); - Assembly.emitFuncCall(Call.Call, CalleeOp->getGlobal(), - Call.Call->getNumExplicitDefs() - Call.NumArguments - - (Call.CanContinue ? 1 : 0), - Call.CanContinue); + Emitter.emitFuncCall(Call.Call, CalleeOp->getGlobal(), + Call.Call->getNumExplicitDefs() - Call.NumArguments - + (Call.CanContinue ? 1 : 0), + Call.CanContinue); // Update stack, remove arguments and return label from CurrentStack. for (size_t I = 0; I < Call.NumArguments + (Call.CanContinue ? 1 : 0); ++I) @@ -50,19 +235,19 @@ void EVMOptimizedCodeTransform::visitCall(const CFG::FunctionCall &Call) { assert(MO.isReg()); CurrentStack.emplace_back(TemporarySlot{Call.Call, MO.getReg(), Idx++}); } - assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); } -void EVMOptimizedCodeTransform::visitInst(const CFG::BuiltinCall &Call) { +void EVMStackifyCodeEmitter::visitInst(const CFG::BuiltinCall &Call) { size_t NumArgs = Call.Builtin->getNumExplicitOperands() - Call.Builtin->getNumExplicitDefs(); // Validate stack. - assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); assert(CurrentStack.size() >= NumArgs); // TODO: assert that we got a correct stack for the call. // Emit code. - Assembly.emitInst(Call.Builtin); + Emitter.emitInst(Call.Builtin); // Update stack and remove arguments from CurrentStack. for (size_t i = 0; i < NumArgs; ++i) @@ -74,11 +259,11 @@ void EVMOptimizedCodeTransform::visitInst(const CFG::BuiltinCall &Call) { assert(MO.isReg()); CurrentStack.emplace_back(TemporarySlot{Call.Builtin, MO.getReg(), Idx++}); } - assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); } -void EVMOptimizedCodeTransform::visitAssign(const CFG::Assignment &Assignment) { - assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); +void EVMStackifyCodeEmitter::visitAssign(const CFG::Assignment &Assignment) { + assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); // Invalidate occurrences of the assigned variables. for (auto &CurrentSlot : CurrentStack) @@ -97,8 +282,8 @@ void EVMOptimizedCodeTransform::visitAssign(const CFG::Assignment &Assignment) { *RangeIt = *VarsIt; } -bool EVMOptimizedCodeTransform::areLayoutsCompatible(const Stack &SourceStack, - const Stack &TargetStack) { +bool EVMStackifyCodeEmitter::areLayoutsCompatible(const Stack &SourceStack, + const Stack &TargetStack) { return SourceStack.size() == TargetStack.size() && all_of(zip_equal(SourceStack, TargetStack), [](const auto &Pair) { const auto &[Src, Tgt] = Pair; @@ -106,7 +291,7 @@ bool EVMOptimizedCodeTransform::areLayoutsCompatible(const Stack &SourceStack, }); } -void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { +void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { auto SlotVariableName = [](const StackSlot &Slot) { return std::visit( Overload{ @@ -125,7 +310,7 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { Slot); }; - assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); // ::createStackLayout asserts that it has successfully achieved the target // layout. ::createStackLayout( @@ -133,10 +318,10 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { // Swap callback. [&](unsigned I) { assert(static_cast(CurrentStack.size()) == - Assembly.getStackHeight()); + Emitter.getStackHeight()); assert(I > 0 && I < CurrentStack.size()); if (I <= 16) { - Assembly.emitSWAP(I); + Emitter.emitSWAP(I); } else { int Deficit = static_cast(I) - 16; const StackSlot &DeepSlot = @@ -161,14 +346,14 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { // Push or dup callback. [&](const StackSlot &Slot) { assert(static_cast(CurrentStack.size()) == - Assembly.getStackHeight()); + Emitter.getStackHeight()); // Dup the slot, if already on stack and reachable. auto SlotIt = llvm::find(llvm::reverse(CurrentStack), Slot); if (SlotIt != CurrentStack.rend()) { unsigned Depth = std::distance(CurrentStack.rbegin(), SlotIt); if (Depth < 16) { - Assembly.emitDUP(static_cast(Depth + 1)); + Emitter.emitDUP(static_cast(Depth + 1)); return; } if (!canBeFreelyGenerated(Slot)) { @@ -191,16 +376,16 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { // Push it. std::visit( Overload{[&](const LiteralSlot &Literal) { - Assembly.emitConstant(Literal.Value); + Emitter.emitConstant(Literal.Value); }, [&](const SymbolSlot &Symbol) { - Assembly.emitSymbol(Symbol.MI, Symbol.Symbol); + Emitter.emitSymbol(Symbol.MI, Symbol.Symbol); }, [&](const FunctionReturnLabelSlot &) { llvm_unreachable("Cannot produce function return label"); }, [&](const FunctionCallReturnLabelSlot &ReturnLabel) { - Assembly.emitLabelReference(ReturnLabel.Call); + Emitter.emitLabelReference(ReturnLabel.Call); }, [&](const VariableSlot &Variable) { llvm_unreachable("Variable not found on stack"); @@ -212,18 +397,17 @@ void EVMOptimizedCodeTransform::createStackLayout(const Stack &TargetStack) { [&](const JunkSlot &) { // Note: this will always be popped, so we can push // anything. - Assembly.emitConstant(0); + Emitter.emitConstant(0); }}, Slot); }, // Pop callback. - [&]() { Assembly.emitPOP(); }); + [&]() { Emitter.emitPOP(); }); - assert(Assembly.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); } -void EVMOptimizedCodeTransform::createOperationLayout( - const CFG::Operation &Op) { +void EVMStackifyCodeEmitter::createOperationLayout(const CFG::Operation &Op) { // Create required layout for entering the Operation. // Check if we can choose cheaper stack shuffling if the Operation is an // instruction with commutable arguments. @@ -255,7 +439,7 @@ void EVMOptimizedCodeTransform::createOperationLayout( } // Assert that we have the inputs of the Operation on stack top. - assert(static_cast(CurrentStack.size()) == Assembly.getStackHeight()); + assert(static_cast(CurrentStack.size()) == Emitter.getStackHeight()); assert(CurrentStack.size() >= Op.Input.size()); Stack StackInput(CurrentStack.end() - Op.Input.size(), CurrentStack.end()); // Adjust the StackInput if needed. @@ -266,8 +450,8 @@ void EVMOptimizedCodeTransform::createOperationLayout( assert(areLayoutsCompatible(StackInput, Op.Input)); } -void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { - assert(CurrentStack.empty() && Assembly.getStackHeight() == 0); +void EVMStackifyCodeEmitter::run(CFG::BasicBlock &EntryBB) { + assert(CurrentStack.empty() && Emitter.getStackHeight() == 0); SmallPtrSet Visited; SmallVector WorkList{&EntryBB}; @@ -280,7 +464,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Might set some slots to junk, if not required by the block. CurrentStack = BlockInfo.entryLayout; - Assembly.init(Block->MBB, static_cast(CurrentStack.size())); + Emitter.init(Block->MBB, static_cast(CurrentStack.size())); for (const auto &Operation : Block->Operations) { createOperationLayout(Operation); @@ -298,8 +482,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { Operation.Operation); // Assert that the Operation produced its proclaimed output. - assert(static_cast(CurrentStack.size()) == - Assembly.getStackHeight()); + assert(static_cast(CurrentStack.size()) == Emitter.getStackHeight()); assert(CurrentStack.size() == BaseHeight + Operation.Output.size()); assert(CurrentStack.size() >= Operation.Output.size()); assert(areLayoutsCompatible( @@ -323,7 +506,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { CurrentStack, Layout.blockInfos.at(Jump.Target).entryLayout)); if (Jump.UncondJump) - Assembly.emitUncondJump(Jump.UncondJump, Jump.Target->MBB); + Emitter.emitUncondJump(Jump.UncondJump, Jump.Target->MBB); WorkList.emplace_back(Jump.Target); }, [&](CFG::BasicBlock::ConditionalJump const &CondJump) { @@ -338,7 +521,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Emit the conditional jump to the non-zero label and update the // stored stack. assert(CondJump.CondJump); - Assembly.emitCondJump(CondJump.CondJump, CondJump.NonZero->MBB); + Emitter.emitCondJump(CondJump.CondJump, CondJump.NonZero->MBB); CurrentStack.pop_back(); // Assert that we have a valid stack for both jump targets. @@ -351,8 +534,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Generate unconditional jump if needed. if (CondJump.UncondJump) - Assembly.emitUncondJump(CondJump.UncondJump, - CondJump.Zero->MBB); + Emitter.emitUncondJump(CondJump.UncondJump, CondJump.Zero->MBB); WorkList.emplace_back(CondJump.NonZero); WorkList.emplace_back(CondJump.Zero); }, @@ -367,7 +549,7 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { // Create the function return layout and jump. createStackLayout(ExitStack); - Assembly.emitRet(FuncReturn.Ret); + Emitter.emitRet(FuncReturn.Ret); }, [&](CFG::BasicBlock::Unreachable const &) { assert(Block->Operations.empty()); @@ -388,5 +570,5 @@ void EVMOptimizedCodeTransform::run(CFG::BasicBlock &EntryBB) { Block->Exit); } - Assembly.finalize(); + Emitter.finalize(); } diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h new file mode 100644 index 000000000000..a2697ef0ca4e --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h @@ -0,0 +1,91 @@ +//===--- EVMStackifyCodeEmitter.h - Create stackified MIR ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file transforms MIR to the 'stackified' MIR. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMSTACKIFYCODEEMITTER_H +#define LLVM_LIB_TARGET_EVM_EVMSTACKIFYCODEEMITTER_H + +#include "EVMStackLayoutGenerator.h" +#include "EVMSubtarget.h" + +namespace llvm { + +class MachineInstr; +class MCSymbol; + +class EVMStackifyCodeEmitter { +public: + EVMStackifyCodeEmitter(const StackLayout &Layout, MachineFunction &MF) + : Emitter(MF), Layout(Layout), MF(MF) {} + + /// Stackify instructions, starting from the \p EntryBB. + void run(CFG::BasicBlock &EntryBB); + +private: + class CodeEmitter { + public: + explicit CodeEmitter(MachineFunction &MF) + : MF(MF), TII(MF.getSubtarget().getInstrInfo()) {} + int getStackHeight() const; + void init(MachineBasicBlock *MBB, int Height); + void emitInst(const MachineInstr *MI); + void emitSWAP(unsigned Depth); + void emitDUP(unsigned Depth); + void emitPOP(); + void emitConstant(const APInt &Val); + void emitSymbol(const MachineInstr *MI, MCSymbol *Symbol); + void emitConstant(uint64_t Val); + void emitFuncCall(const MachineInstr *MI, const GlobalValue *Func, + int StackAdj, bool WillReturn); + void emitRet(const MachineInstr *MI); + void emitCondJump(const MachineInstr *MI, MachineBasicBlock *Target); + void emitUncondJump(const MachineInstr *MI, MachineBasicBlock *Target); + void emitLabelReference(const MachineInstr *Call); + void finalize(); + + private: + MachineFunction &MF; + const EVMInstrInfo *TII; + int StackHeight = 0; + MachineBasicBlock *CurMBB{}; + DenseMap CallReturnSyms; + + void verify(const MachineInstr *MI) const; + }; + + CodeEmitter Emitter; + const StackLayout &Layout; + const MachineFunction &MF; + Stack CurrentStack; + + /// Checks if it's valid to transition from \p SourceStack to \p + /// TargetStack, that is \p SourceStack matches each slot in \p + /// TargetStack that is not a JunkSlot exactly. + bool areLayoutsCompatible(const Stack &SourceStack, const Stack &TargetStack); + + /// Shuffles CurrentStack to the desired \p TargetStack. + void createStackLayout(const Stack &TargetStack); + + /// Creates the Op.Input stack layout from the 'CurrentStack' taking into + /// account commutative property of the operation. + void createOperationLayout(const CFG::Operation &Op); + + /// Generate code for the function call \p Call. + void visitCall(const CFG::FunctionCall &Call); + /// Generate code for the builtin call \p Call. + void visitInst(const CFG::BuiltinCall &Call); + /// Generate code for the assignment \p Assignment. + void visitAssign(const CFG::Assignment &Assignment); +}; + +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMSTACKIFYCODEEMITTER_H From 650fe8d75e17b26715614881fe556e83deeb0422 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Wed, 18 Dec 2024 17:51:52 +0100 Subject: [PATCH 14/42] [EVM] Address comments Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMAsmPrinter.cpp | 4 - .../lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 181 +++++++++--------- llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h | 17 +- 3 files changed, 100 insertions(+), 102 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp index 5aac092fbb64..e1a42f97a0e6 100644 --- a/llvm/lib/Target/EVM/EVMAsmPrinter.cpp +++ b/llvm/lib/Target/EVM/EVMAsmPrinter.cpp @@ -127,10 +127,6 @@ void EVMAsmPrinter::emitInstruction(const MachineInstr *MI) { // In case a function has a return label, emit it, and also // emit a JUMPDEST instruction. if (MI->getNumExplicitOperands() > 1) { - // We need to emit ret label after JUMP instruction, so we couldn't - // use setPostInstrSymbol since label would be created after - // JUMPDEST instruction. To overcome this, we added MCSymbol operand - // and we are emitting label manually here. assert(MI->getOperand(1).isMCSymbol() && "The second operand of PseudoCALL should be a MCSymbol."); OutStreamer->emitLabel(MI->getOperand(1).getMCSymbol()); diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index 3a17a6e7575c..9c2a06d32f60 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -21,12 +21,36 @@ using namespace llvm; #define DEBUG_TYPE "evm-stackify-code-emitter" -int EVMStackifyCodeEmitter::CodeEmitter::getStackHeight() const { +// Return whether the function of the call instruction will return. +static bool callWillReturn(const MachineInstr *Call) { + assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); + const MachineOperand *FuncOp = Call->explicit_uses().begin(); + assert(FuncOp->isGlobal() && "Expected a global value"); + const auto *Func = dyn_cast(FuncOp->getGlobal()); + assert(Func && "Expected a function"); + return !Func->hasFnAttribute(Attribute::NoReturn); +} + +// Return the number of input arguments of the call instruction. +static size_t getCallArgCount(const MachineInstr *Call) { + assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); + assert(Call->explicit_uses().begin()->isGlobal() && + "First operand must be a function"); + size_t NumExplicitInputs = + Call->getNumExplicitOperands() - Call->getNumExplicitDefs(); + + // The first operand is a function, so don't count it. If function + // will return, we need to account for the return label. + constexpr size_t NumFuncOp = 1; + return NumExplicitInputs - NumFuncOp + callWillReturn(Call); +} + +size_t EVMStackifyCodeEmitter::CodeEmitter::stackHeight() const { return StackHeight; } -void EVMStackifyCodeEmitter::CodeEmitter::init(MachineBasicBlock *MBB, - int Height) { +void EVMStackifyCodeEmitter::CodeEmitter::enterMBB(MachineBasicBlock *MBB, + int Height) { StackHeight = Height; CurMBB = MBB; LLVM_DEBUG(dbgs() << "\n" @@ -41,14 +65,10 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitInst(const MachineInstr *MI) { Opc != EVM::RET && Opc != EVM::CONST_I256 && Opc != EVM::COPY_I256 && Opc != EVM::FCALL && "Unexpected instruction"); - // The stack height is increased by the number of defs and decreased - // by the number of inputs. To get the number of inputs, we subtract - // the total number of operands from the number of defs, so the - // calculation is as follows: - // Defs - (Ops - Defs) = 2 * Defs - Ops - int StackAdj = (2 * static_cast(MI->getNumExplicitDefs())) - - static_cast(MI->getNumExplicitOperands()); - StackHeight += StackAdj; + size_t NumInputs = MI->getNumExplicitOperands() - MI->getNumExplicitDefs(); + assert(StackHeight >= NumInputs && "Not enough operands on the stack"); + StackHeight -= NumInputs; + StackHeight += MI->getNumExplicitDefs(); auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), TII->get(EVM::getStackOpcode(Opc))); @@ -71,8 +91,8 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitDUP(unsigned Depth) { } void EVMStackifyCodeEmitter::CodeEmitter::emitPOP() { + assert(StackHeight > 0 && "Expected at least one operand on the stack"); StackHeight -= 1; - assert(StackHeight >= 0); auto NewMI = BuildMI(*CurMBB, CurMBB->end(), DebugLoc(), TII->get(EVM::POP_S)); verify(NewMI); @@ -88,6 +108,10 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitConstant(const APInt &Val) { verify(NewMI); } +void EVMStackifyCodeEmitter::CodeEmitter::emitConstant(uint64_t Val) { + emitConstant(APInt(256, Val)); +} + void EVMStackifyCodeEmitter::CodeEmitter::emitSymbol(const MachineInstr *MI, MCSymbol *Symbol) { unsigned Opc = MI->getOpcode(); @@ -101,10 +125,6 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitSymbol(const MachineInstr *MI, verify(NewMI); } -void EVMStackifyCodeEmitter::CodeEmitter::emitConstant(uint64_t Val) { - emitConstant(APInt(256, Val)); -} - void EVMStackifyCodeEmitter::CodeEmitter::emitLabelReference( const MachineInstr *Call) { assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); @@ -118,29 +138,27 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitLabelReference( verify(NewMI); } -void EVMStackifyCodeEmitter::CodeEmitter::emitFuncCall(const MachineInstr *MI, - const GlobalValue *Func, - int StackAdj, - bool WillReturn) { +void EVMStackifyCodeEmitter::CodeEmitter::emitFuncCall(const MachineInstr *MI) { assert(MI->getOpcode() == EVM::FCALL && "Unexpected call instruction"); assert(CurMBB == MI->getParent()); - // PUSH_LABEL technically increases the stack height on 1, but we don't - // increase it explicitly here, as the label will be consumed by the following - // JUMP. - StackHeight += StackAdj; + size_t NumInputs = getCallArgCount(MI); + assert(StackHeight >= NumInputs && "Not enough operands on the stack"); + StackHeight -= NumInputs; - // Create pseudo jump to the callee, that will be expanded into PUSH and JUMP - // instructions in the AsmPrinter. + // PUSH_LABEL increases the stack height on 1, but we don't increase it + // explicitly here, as the label will be consumed by the following JUMP. + StackHeight += MI->getNumExplicitDefs(); + + // Create pseudo jump to the function, that will be expanded into PUSH and + // JUMP instructions in the AsmPrinter. auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), TII->get(EVM::PseudoCALL)) - .addGlobalAddress(Func); + .addGlobalAddress(MI->explicit_uses().begin()->getGlobal()); - // If this function returns, we need to create a label after JUMP instruction - // that is followed by JUMPDEST and this is taken care in the AsmPrinter. - // In case we use setPostInstrSymbol here, the label will be created - // after the JUMPDEST instruction, which is not what we want. - if (WillReturn) + // If this function returns, add a return label so we can emit it together + // with JUMPDEST. This is taken care in the AsmPrinter. + if (callWillReturn(MI)) NewMI.addSym(CallReturnSyms.at(MI)); verify(NewMI); } @@ -156,7 +174,6 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitUncondJump( const MachineInstr *MI, MachineBasicBlock *Target) { assert(MI->getOpcode() == EVM::JUMP && "Unexpected unconditional jump instruction"); - assert(StackHeight >= 0); auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), TII->get(EVM::PseudoJUMP)) .addMBB(Target); @@ -167,8 +184,8 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitCondJump( const MachineInstr *MI, MachineBasicBlock *Target) { assert(MI->getOpcode() == EVM::JUMPI && "Unexpected conditional jump instruction"); + assert(StackHeight > 0 && "Expected at least one operand on the stack"); StackHeight -= 1; - assert(StackHeight >= 0); auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), TII->get(EVM::PseudoJUMPI)) .addMBB(Target); @@ -186,12 +203,11 @@ void EVMStackifyCodeEmitter::CodeEmitter::verify(const MachineInstr *MI) const { LLVM_DEBUG(dbgs() << "Adding: " << *MI << "stack height: " << StackHeight << "\n"); } - void EVMStackifyCodeEmitter::CodeEmitter::finalize() { for (MachineBasicBlock &MBB : MF) for (MachineInstr &MI : make_early_inc_range(MBB)) // Remove all the instructions that are not stackified. - // FIXME: Fix debug info for stackified instructions and don't + // TODO: #749: Fix debug info for stackified instructions and don't // remove debug instructions. if (!EVMInstrInfo::isStack(&MI)) MI.eraseFromParent(); @@ -204,66 +220,54 @@ void EVMStackifyCodeEmitter::CodeEmitter::finalize() { MRI.invalidateLiveness(); } +void EVMStackifyCodeEmitter::adjustStackForInst(const MachineInstr *MI, + size_t NumArgs) { + // Remove arguments from CurrentStack. + CurrentStack.erase(CurrentStack.end() - NumArgs, CurrentStack.end()); + + // Push return values to CurrentStack. + unsigned Idx = 0; + for (const auto &MO : MI->defs()) { + assert(MO.isReg()); + CurrentStack.emplace_back(TemporarySlot{MI, MO.getReg(), Idx++}); + } + assert(Emitter.stackHeight() == CurrentStack.size()); +} + void EVMStackifyCodeEmitter::visitCall(const CFG::FunctionCall &Call) { + size_t NumArgs = getCallArgCount(Call.Call); // Validate stack. - assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); - assert(CurrentStack.size() >= Call.NumArguments + (Call.CanContinue ? 1 : 0)); + assert(Emitter.stackHeight() == CurrentStack.size()); + assert(CurrentStack.size() >= NumArgs); // Assert that we got the correct return label on stack. - if (Call.CanContinue) { + if (callWillReturn(Call.Call)) { [[maybe_unused]] const auto *returnLabelSlot = std::get_if( - &CurrentStack.at(CurrentStack.size() - Call.NumArguments - 1)); + &CurrentStack.at(CurrentStack.size() - NumArgs)); assert(returnLabelSlot && returnLabelSlot->Call == Call.Call); } - // Emit code. - const MachineOperand *CalleeOp = Call.Call->explicit_uses().begin(); - assert(CalleeOp->isGlobal()); - Emitter.emitFuncCall(Call.Call, CalleeOp->getGlobal(), - Call.Call->getNumExplicitDefs() - Call.NumArguments - - (Call.CanContinue ? 1 : 0), - Call.CanContinue); - - // Update stack, remove arguments and return label from CurrentStack. - for (size_t I = 0; I < Call.NumArguments + (Call.CanContinue ? 1 : 0); ++I) - CurrentStack.pop_back(); - - // Push return values to CurrentStack. - unsigned Idx = 0; - for (const auto &MO : Call.Call->defs()) { - assert(MO.isReg()); - CurrentStack.emplace_back(TemporarySlot{Call.Call, MO.getReg(), Idx++}); - } - assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); + // Emit call. + Emitter.emitFuncCall(Call.Call); + adjustStackForInst(Call.Call, NumArgs); } void EVMStackifyCodeEmitter::visitInst(const CFG::BuiltinCall &Call) { size_t NumArgs = Call.Builtin->getNumExplicitOperands() - Call.Builtin->getNumExplicitDefs(); // Validate stack. - assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.stackHeight() == CurrentStack.size()); assert(CurrentStack.size() >= NumArgs); // TODO: assert that we got a correct stack for the call. - // Emit code. + // Emit instruction. Emitter.emitInst(Call.Builtin); - - // Update stack and remove arguments from CurrentStack. - for (size_t i = 0; i < NumArgs; ++i) - CurrentStack.pop_back(); - - // Push return values to CurrentStack. - unsigned Idx = 0; - for (const auto &MO : Call.Builtin->defs()) { - assert(MO.isReg()); - CurrentStack.emplace_back(TemporarySlot{Call.Builtin, MO.getReg(), Idx++}); - } - assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); + adjustStackForInst(Call.Builtin, NumArgs); } void EVMStackifyCodeEmitter::visitAssign(const CFG::Assignment &Assignment) { - assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.stackHeight() == CurrentStack.size()); // Invalidate occurrences of the assigned variables. for (auto &CurrentSlot : CurrentStack) @@ -273,13 +277,8 @@ void EVMStackifyCodeEmitter::visitAssign(const CFG::Assignment &Assignment) { // Assign variables to current stack top. assert(CurrentStack.size() >= Assignment.Variables.size()); - auto StackRange = make_range(CurrentStack.end() - Assignment.Variables.size(), - CurrentStack.end()); - auto RangeIt = StackRange.begin(), RangeE = StackRange.end(); - auto VarsIt = Assignment.Variables.begin(), - VarsE = Assignment.Variables.end(); - for (; (RangeIt != RangeE) && (VarsIt != VarsE); ++RangeIt, ++VarsIt) - *RangeIt = *VarsIt; + llvm::copy(Assignment.Variables, + CurrentStack.end() - Assignment.Variables.size()); } bool EVMStackifyCodeEmitter::areLayoutsCompatible(const Stack &SourceStack, @@ -310,15 +309,14 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { Slot); }; - assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.stackHeight() == CurrentStack.size()); // ::createStackLayout asserts that it has successfully achieved the target // layout. ::createStackLayout( CurrentStack, TargetStack, // Swap callback. [&](unsigned I) { - assert(static_cast(CurrentStack.size()) == - Emitter.getStackHeight()); + assert(CurrentStack.size() == Emitter.stackHeight()); assert(I > 0 && I < CurrentStack.size()); if (I <= 16) { Emitter.emitSWAP(I); @@ -345,8 +343,7 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { }, // Push or dup callback. [&](const StackSlot &Slot) { - assert(static_cast(CurrentStack.size()) == - Emitter.getStackHeight()); + assert(CurrentStack.size() == Emitter.stackHeight()); // Dup the slot, if already on stack and reachable. auto SlotIt = llvm::find(llvm::reverse(CurrentStack), Slot); @@ -404,7 +401,7 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { // Pop callback. [&]() { Emitter.emitPOP(); }); - assert(Emitter.getStackHeight() == static_cast(CurrentStack.size())); + assert(Emitter.stackHeight() == CurrentStack.size()); } void EVMStackifyCodeEmitter::createOperationLayout(const CFG::Operation &Op) { @@ -439,7 +436,7 @@ void EVMStackifyCodeEmitter::createOperationLayout(const CFG::Operation &Op) { } // Assert that we have the inputs of the Operation on stack top. - assert(static_cast(CurrentStack.size()) == Emitter.getStackHeight()); + assert(CurrentStack.size() == Emitter.stackHeight()); assert(CurrentStack.size() >= Op.Input.size()); Stack StackInput(CurrentStack.end() - Op.Input.size(), CurrentStack.end()); // Adjust the StackInput if needed. @@ -451,7 +448,7 @@ void EVMStackifyCodeEmitter::createOperationLayout(const CFG::Operation &Op) { } void EVMStackifyCodeEmitter::run(CFG::BasicBlock &EntryBB) { - assert(CurrentStack.empty() && Emitter.getStackHeight() == 0); + assert(CurrentStack.empty() && Emitter.stackHeight() == 0); SmallPtrSet Visited; SmallVector WorkList{&EntryBB}; @@ -464,7 +461,7 @@ void EVMStackifyCodeEmitter::run(CFG::BasicBlock &EntryBB) { // Might set some slots to junk, if not required by the block. CurrentStack = BlockInfo.entryLayout; - Emitter.init(Block->MBB, static_cast(CurrentStack.size())); + Emitter.enterMBB(Block->MBB, CurrentStack.size()); for (const auto &Operation : Block->Operations) { createOperationLayout(Operation); @@ -482,7 +479,7 @@ void EVMStackifyCodeEmitter::run(CFG::BasicBlock &EntryBB) { Operation.Operation); // Assert that the Operation produced its proclaimed output. - assert(static_cast(CurrentStack.size()) == Emitter.getStackHeight()); + assert(CurrentStack.size() == Emitter.stackHeight()); assert(CurrentStack.size() == BaseHeight + Operation.Output.size()); assert(CurrentStack.size() >= Operation.Output.size()); assert(areLayoutsCompatible( @@ -563,7 +560,7 @@ void EVMStackifyCodeEmitter::run(CFG::BasicBlock &EntryBB) { else if (CFG::FunctionCall const *FunctionCall = std::get_if( &Block->Operations.back().Operation)) - assert(!FunctionCall->CanContinue); + assert(!callWillReturn(FunctionCall->Call)); else llvm_unreachable("Unexpected BB terminator"); }}, diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h index a2697ef0ca4e..15223de52637 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h @@ -34,27 +34,29 @@ class EVMStackifyCodeEmitter { public: explicit CodeEmitter(MachineFunction &MF) : MF(MF), TII(MF.getSubtarget().getInstrInfo()) {} - int getStackHeight() const; - void init(MachineBasicBlock *MBB, int Height); + size_t stackHeight() const; + void enterMBB(MachineBasicBlock *MBB, int Height); void emitInst(const MachineInstr *MI); void emitSWAP(unsigned Depth); void emitDUP(unsigned Depth); void emitPOP(); void emitConstant(const APInt &Val); - void emitSymbol(const MachineInstr *MI, MCSymbol *Symbol); void emitConstant(uint64_t Val); - void emitFuncCall(const MachineInstr *MI, const GlobalValue *Func, - int StackAdj, bool WillReturn); + void emitSymbol(const MachineInstr *MI, MCSymbol *Symbol); + void emitFuncCall(const MachineInstr *MI); void emitRet(const MachineInstr *MI); void emitCondJump(const MachineInstr *MI, MachineBasicBlock *Target); void emitUncondJump(const MachineInstr *MI, MachineBasicBlock *Target); void emitLabelReference(const MachineInstr *Call); + /// Remove all the instructions that are not stackified and set that all + /// instructions are stackified in a function from now on. Also, invalidate + /// the register liveness, as it has no meaning in a stackified code. void finalize(); private: MachineFunction &MF; const EVMInstrInfo *TII; - int StackHeight = 0; + size_t StackHeight = 0; MachineBasicBlock *CurMBB{}; DenseMap CallReturnSyms; @@ -78,6 +80,9 @@ class EVMStackifyCodeEmitter { /// account commutative property of the operation. void createOperationLayout(const CFG::Operation &Op); + /// Remove the arguments from the stack and push the return values. + void adjustStackForInst(const MachineInstr *MI, size_t NumArgs); + /// Generate code for the function call \p Call. void visitCall(const CFG::FunctionCall &Call); /// Generate code for the builtin call \p Call. From ae9699dde5566bba8787879a2fc603dbc14215cb Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 20 Dec 2024 01:35:15 +0100 Subject: [PATCH 15/42] [EVM] Refactor EVMControlFlowGraph/EVMControlFlowGraphBuilder - Removed EVMControlFlowGraph/EVMControlFlowGraphBuilder classes - Functionality that analyzes machine CFG and provides query methods was moved to EVMMachineCFGInfo class - Information about MBB terminators is represented in EVMMBBTerminatorsInfo class - StackSlot, Stack and Operation definitions were moved to EVMStackModel - Replaced almost all the std::map/std::set with llvm counterparts in EVMStackLayoutGenerator --- llvm/lib/Target/EVM/CMakeLists.txt | 4 +- .../EVMBackwardPropagationStackification.cpp | 10 +- llvm/lib/Target/EVM/EVMControlFlowGraph.h | 279 ---------- .../Target/EVM/EVMControlFlowGraphBuilder.cpp | 426 -------------- .../Target/EVM/EVMControlFlowGraphBuilder.h | 57 -- llvm/lib/Target/EVM/EVMHelperUtilities.cpp | 27 + llvm/lib/Target/EVM/EVMHelperUtilities.h | 7 +- llvm/lib/Target/EVM/EVMInstrInfo.td | 4 +- llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp | 196 +++++++ llvm/lib/Target/EVM/EVMMachineCFGInfo.h | 122 ++++ llvm/lib/Target/EVM/EVMStackDebug.cpp | 233 ++------ llvm/lib/Target/EVM/EVMStackDebug.h | 46 +- .../Target/EVM/EVMStackLayoutGenerator.cpp | 524 ++++++++---------- llvm/lib/Target/EVM/EVMStackLayoutGenerator.h | 98 ++-- llvm/lib/Target/EVM/EVMStackModel.cpp | 205 +++++++ llvm/lib/Target/EVM/EVMStackModel.h | 218 ++++++++ llvm/lib/Target/EVM/EVMStackShuffler.h | 2 +- .../lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 206 +++---- llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h | 25 +- llvm/unittests/Target/EVM/StackShuffler.cpp | 4 +- 20 files changed, 1239 insertions(+), 1454 deletions(-) delete mode 100644 llvm/lib/Target/EVM/EVMControlFlowGraph.h delete mode 100644 llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp delete mode 100644 llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h create mode 100644 llvm/lib/Target/EVM/EVMHelperUtilities.cpp create mode 100644 llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp create mode 100644 llvm/lib/Target/EVM/EVMMachineCFGInfo.h create mode 100644 llvm/lib/Target/EVM/EVMStackModel.cpp create mode 100644 llvm/lib/Target/EVM/EVMStackModel.h diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index 7f22724a1f8e..7925a6fd9385 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -22,13 +22,14 @@ add_llvm_target(EVMCodeGen EVMAsmPrinter.cpp EVMBackwardPropagationStackification.cpp EVMCodegenPrepare.cpp - EVMControlFlowGraphBuilder.cpp EVMFrameLowering.cpp + EVMHelperUtilities.cpp EVMISelDAGToDAG.cpp EVMISelLowering.cpp EVMInstrInfo.cpp EVMLinkRuntime.cpp EVMLowerIntrinsics.cpp + EVMMachineCFGInfo.cpp EVMMachineFunctionInfo.cpp EVMMCInstLower.cpp EVMOptimizeLiveIntervals.cpp @@ -38,6 +39,7 @@ add_llvm_target(EVMCodeGen EVMSplitCriticalEdges.cpp EVMStackDebug.cpp EVMStackLayoutGenerator.cpp + EVMStackModel.cpp EVMStackify.cpp EVMStackifyCodeEmitter.cpp EVMSubtarget.cpp diff --git a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp index a1d79eb213a2..3c26c080b2db 100644 --- a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp +++ b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp @@ -24,7 +24,6 @@ //===----------------------------------------------------------------------===// #include "EVM.h" -#include "EVMControlFlowGraphBuilder.h" #include "EVMStackifyCodeEmitter.h" #include "EVMSubtarget.h" #include "llvm/CodeGen/LiveIntervals.h" @@ -92,9 +91,10 @@ bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { MRI.leaveSSA(); assert(MRI.tracksLiveness() && "Stackification expects liveness"); - - std::unique_ptr Cfg = ControlFlowGraphBuilder::build(MF, LIS, MLI); - StackLayout Layout = StackLayoutGenerator::run(*Cfg); - EVMStackifyCodeEmitter(Layout, MF).run(Cfg->getBlock(&Cfg->MF.front())); + EVMMachineCFGInfo CFGInfo(MF, MLI); + EVMStackModel StackModel(MF, LIS); + std::unique_ptr Layout = + EVMStackLayoutGenerator(MF, StackModel, CFGInfo).run(); + EVMStackifyCodeEmitter(*Layout, StackModel, CFGInfo, MF).run(); return true; } diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraph.h b/llvm/lib/Target/EVM/EVMControlFlowGraph.h deleted file mode 100644 index 97f9059ac8af..000000000000 --- a/llvm/lib/Target/EVM/EVMControlFlowGraph.h +++ /dev/null @@ -1,279 +0,0 @@ -//===----- EVMControlFlowGraph.h - CFG for BP stackification ----*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines Control Flow Graph used for the backward propagation -// stackification algorithm. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H -#define LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H - -#include "llvm/ADT/APInt.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/CodeGen/MachineInstr.h" -#include "llvm/CodeGen/Register.h" -#include "llvm/MC/MCSymbol.h" - -#include -#include -#include - -namespace llvm { - -class MachineFunction; -class MachineBasicBlock; -class MachineInstr; -/// The following structs describe different kinds of stack slots. -/// Each stack slot is equality- and less-than-comparable and -/// specifies an attribute 'canBeFreelyGenerated' that is true, -/// if a slot of this kind always has a known value at compile time and -/// therefore can safely be removed from the stack at any time and then -/// regenerated later. - -/// The label pushed as return label before a function call, i.e. the label the -/// call is supposed to return to. -struct FunctionCallReturnLabelSlot { - const MachineInstr *Call = nullptr; - static constexpr bool canBeFreelyGenerated = true; - - bool operator==(FunctionCallReturnLabelSlot const &Rhs) const { - return Call == Rhs.Call; - } - - bool operator<(FunctionCallReturnLabelSlot const &Rhs) const { - return Call < Rhs.Call; - } -}; - -/// The return jump target of a function while generating the code of the -/// function body. I.e. the caller of a function pushes a -/// 'FunctionCallReturnLabelSlot' (see above) before jumping to the function -/// and this very slot is viewed as 'FunctionReturnLabelSlot' inside the -/// function body and jumped to when returning from the function. -struct FunctionReturnLabelSlot { - const MachineFunction *MF = nullptr; - static constexpr bool canBeFreelyGenerated = false; - - bool operator==(FunctionReturnLabelSlot const &Rhs) const { - // There can never be return label slots of different functions on stack - // simultaneously. - assert(MF == Rhs.MF); - return true; - } - - bool operator<(FunctionReturnLabelSlot const &Rhs) const { - // There can never be return label slots of different functions on stack - // simultaneously. - assert(MF == Rhs.MF); - return false; - } -}; - -/// A slot containing the current value of a particular variable. -struct VariableSlot { - Register VirtualReg; - static constexpr bool canBeFreelyGenerated = false; - - bool operator==(VariableSlot const &Rhs) const { - return VirtualReg == Rhs.VirtualReg; - } - - bool operator<(VariableSlot const &Rhs) const { - return VirtualReg < Rhs.VirtualReg; - } -}; - -/// A slot containing a literal value. -struct LiteralSlot { - APInt Value; - static constexpr bool canBeFreelyGenerated = true; - - bool operator==(LiteralSlot const &Rhs) const { return Value == Rhs.Value; } - - bool operator<(LiteralSlot const &Rhs) const { return Value.ult(Rhs.Value); } -}; - -/// A slot containing a MCSymbol. -struct SymbolSlot { - MCSymbol *Symbol; - const MachineInstr *MI = nullptr; - static constexpr bool canBeFreelyGenerated = true; - - bool operator==(SymbolSlot const &Rhs) const { - return Symbol == Rhs.Symbol && MI->getOpcode() == Rhs.MI->getOpcode(); - } - - bool operator<(SymbolSlot const &Rhs) const { - return std::make_pair(Symbol, MI->getOpcode()) < - std::make_pair(Rhs.Symbol, Rhs.MI->getOpcode()); - } -}; - -/// A slot containing the index-th return value of a previous call. -struct TemporarySlot { - /// The call that returned this slot. - const MachineInstr *MI = nullptr; - - Register VirtualReg; - /// Specifies to which of the values returned by the call this slot refers. - /// index == 0 refers to the slot deepest in the stack after the call. - size_t Index = 0; - static constexpr bool canBeFreelyGenerated = false; - - bool operator==(TemporarySlot const &Rhs) const { - return MI == Rhs.MI && Index == Rhs.Index; - } - - bool operator<(TemporarySlot const &Rhs) const { - return std::make_pair(MI, Index) < std::make_pair(Rhs.MI, Rhs.Index); - } -}; - -/// A slot containing an arbitrary value that is always eventually popped and -/// never used. Used to maintain stack balance on control flow joins. -struct JunkSlot { - static constexpr bool canBeFreelyGenerated = true; - - bool operator==(JunkSlot const &) const { return true; } - - bool operator<(JunkSlot const &) const { return false; } -}; - -using StackSlot = - std::variant; - -/// The stack top is usually the last element of the vector. -using Stack = std::vector; - -/// Returns true if Slot can be generated on the stack at any time. -inline bool canBeFreelyGenerated(StackSlot const &Slot) { - return std::visit( - [](auto const &TypedSlot) { - return std::decay_t::canBeFreelyGenerated; - }, - Slot); -} - -/// Control flow graph consisting of 'CFG::BasicBlock`s' connected by control -/// flow. -struct CFG { - explicit CFG(const MachineFunction &MF) : MF(MF) {} - CFG(CFG const &) = delete; - CFG(CFG &&) = delete; - CFG &operator=(CFG const &) = delete; - CFG &operator=(CFG &&) = delete; - ~CFG() = default; - - struct BuiltinCall { - MachineInstr *Builtin = nullptr; - // True if this instruction has commutable operands. In EVM ISA - // commutable operands always take top two stack slots. - bool IsCommutable = false; - bool TerminatesOrReverts = false; - }; - - struct FunctionCall { - const MachineInstr *Call; - /// True, if the call can return. - bool CanContinue = true; - size_t NumArguments = 0; - }; - - struct Assignment { - /// The variables being assigned to also occur as 'Output' in the - /// 'Operation' containing the assignment, but are also stored here for - /// convenience. - std::vector Variables; - }; - - struct Operation { - /// Stack slots this operation expects at the top of the stack and consumes. - Stack Input; - /// Stack slots this operation leaves on the stack as output. - Stack Output; - std::variant Operation; - }; - - /// A basic control flow block containing 'Operation`s' acting on the stack. - /// Maintains a list of entry blocks and a typed exit. - struct BasicBlock { - struct InvalidExit {}; - - struct Unreachable {}; - - struct ConditionalJump { - StackSlot Condition; - BasicBlock *NonZero = nullptr; - BasicBlock *Zero = nullptr; - bool FallThrough = false; - MachineInstr *CondJump = nullptr; - MachineInstr *UncondJump = nullptr; - }; - - struct Jump { - BasicBlock *Target = nullptr; - bool FallThrough = false; - bool Backwards = false; - MachineInstr *UncondJump = nullptr; - }; - - struct FunctionReturn { - Stack RetValues; - const MachineInstr *Ret = nullptr; - }; - - struct Terminated {}; - - MachineBasicBlock *MBB; - std::vector Entries; - std::vector Operations; - /// True, if the block is the beginning of a disconnected subgraph. That is, - /// if no block that is reachable from this block is an ancestor of this - /// block. In other words, this is true, if this block is the target of a - /// cut-edge/bridge in the CFG or if the block itself terminates. - bool IsStartOfSubGraph = false; - /// True, if there is a path from this block to a function return. - bool NeedsCleanStack = false; - /// If the block starts a sub-graph and does not lead to a function return, - /// we are free to add junk to it. - bool AllowsJunk() const { return IsStartOfSubGraph && !NeedsCleanStack; } - - std::variant - Exit = InvalidExit{}; - }; - - /// Container for blocks for explicit ownership. - std::list Blocks; - DenseMap MachineBBToBB; - std::vector Parameters; - const MachineFunction &MF; - - BasicBlock &getBlock(const MachineBasicBlock *MBB) const { - auto It = MachineBBToBB.find(MBB); - assert(It != MachineBBToBB.end()); - return *It->second; - } - - void createBlock(MachineBasicBlock *MBB) { - auto It = MachineBBToBB.find(MBB); - if (It == MachineBBToBB.end()) { - BasicBlock &Block = Blocks.emplace_back(BasicBlock{MBB, {}, {}}); - MachineBBToBB[MBB] = &Block; - } - } -}; - -} // namespace llvm - -#endif // LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp deleted file mode 100644 index 423199a4d1ce..000000000000 --- a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.cpp +++ /dev/null @@ -1,426 +0,0 @@ -//===----- EVMControlFlowGraphBuilder.CPP - CFG builder ---------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file builds the Control Flow Graph used for the backward propagation -// stackification algorithm. -// -//===----------------------------------------------------------------------===// - -#include "EVM.h" - -#include "EVMControlFlowGraphBuilder.h" -#include "EVMHelperUtilities.h" -#include "EVMMachineFunctionInfo.h" -#include "EVMStackDebug.h" -#include "EVMSubtarget.h" -#include "MCTargetDesc/EVMMCTargetDesc.h" -#include "llvm/CodeGen/MachineFunction.h" - -#include -#include - -using namespace llvm; - -#define DEBUG_TYPE "evm-control-flow-graph-builder" - -/// Marks each block that needs to maintain a clean stack. That is each block -/// that has an outgoing path to a function return. -static void markNeedsCleanStack(std::vector &Exits) { - for (CFG::BasicBlock *Exit : Exits) - EVMUtils::BreadthFirstSearch{{Exit}}.run( - [&](CFG::BasicBlock *Block, auto AddChild) { - Block->NeedsCleanStack = true; - // Traverse over predecessors to mark them as well. - for (CFG::BasicBlock *Entry : Block->Entries) - AddChild(Entry); - }); -} - -/// Marks each cut-vertex in the CFG, i.e. each block that begins a disconnected -/// sub-graph of the CFG. Entering such a block means that control flow will -/// never return to a previously visited block. -static void markStartsOfSubGraphs(CFG::BasicBlock *Entry) { - /** - * Detect bridges following Algorithm 1 in - * https://arxiv.org/pdf/2108.07346.pdf and mark the bridge targets as starts - * of sub-graphs. - */ - std::set Visited; - std::map Disc; - std::map Low; - std::map Parent; - size_t Time = 0; - auto Dfs = [&](CFG::BasicBlock *U, auto Recurse) -> void { - Visited.insert(U); - Disc[U] = Low[U] = Time; - Time++; - - std::vector Children = U->Entries; - std::visit(Overload{[&](CFG::BasicBlock::Jump const &Jump) { - Children.emplace_back(Jump.Target); - }, - [&](CFG::BasicBlock::ConditionalJump const &Jump) { - Children.emplace_back(Jump.Zero); - Children.emplace_back(Jump.NonZero); - }, - [&](CFG::BasicBlock::FunctionReturn const &) {}, - [&](CFG::BasicBlock::Terminated const &) { - U->IsStartOfSubGraph = true; - }, - [&](CFG::BasicBlock::Unreachable const &) { - U->IsStartOfSubGraph = true; - }, - [&](CFG::BasicBlock::InvalidExit const &) { - llvm_unreachable("Unexpected BB terminator"); - }}, - U->Exit); - - for (CFG::BasicBlock *V : Children) { - // Ignore the loop edge, as it cannot be the bridge. - if (V == U) - continue; - - if (!Visited.count(V)) { - Parent[V] = U; - Recurse(V, Recurse); - Low[U] = std::min(Low[U], Low[V]); - if (Low[V] > Disc[U]) { - // U <-> V is a cut edge in the undirected graph - bool EdgeVtoU = EVMUtils::contains(U->Entries, V); - bool EdgeUtoV = EVMUtils::contains(V->Entries, U); - if (EdgeVtoU && !EdgeUtoV) - // Cut edge V -> U - U->IsStartOfSubGraph = true; - else if (EdgeUtoV && !EdgeVtoU) - // Cut edge U -> v - V->IsStartOfSubGraph = true; - } - } else if (V != Parent[U]) - Low[U] = std::min(Low[U], Disc[V]); - } - }; - Dfs(Entry, Dfs); -} - -std::unique_ptr ControlFlowGraphBuilder::build(MachineFunction &MF, - const LiveIntervals &LIS, - MachineLoopInfo *MLI) { - auto Result = std::make_unique(MF); - ControlFlowGraphBuilder Builder(*Result, LIS, MLI); - - for (MachineBasicBlock &MBB : MF) - Result->createBlock(&MBB); - - // Handle function parameters - auto *MFI = MF.getInfo(); - Result->Parameters = std::vector(MFI->getNumParams(), JunkSlot{}); - for (const MachineInstr &MI : MF.front()) { - if (MI.getOpcode() == EVM::ARGUMENT) { - int64_t ArgIdx = MI.getOperand(1).getImm(); - Result->Parameters[ArgIdx] = VariableSlot{MI.getOperand(0).getReg()}; - } - } - - for (MachineBasicBlock &MBB : MF) - Builder.handleBasicBlock(MBB); - - for (MachineBasicBlock &MBB : MF) - Builder.handleBasicBlockSuccessors(MBB); - - markStartsOfSubGraphs(&Result->getBlock(&MF.front())); - markNeedsCleanStack(Builder.Exits); - - LLVM_DEBUG({ - dbgs() << "************* CFG *************\n"; - ControlFlowGraphPrinter P(dbgs(), MF); - P(*Result); - }); - - return Result; -} - -void ControlFlowGraphBuilder::handleBasicBlock(MachineBasicBlock &MBB) { - CurrentBlock = &Cfg.getBlock(&MBB); - for (MachineInstr &MI : MBB) - handleMachineInstr(MI); -} - -StackSlot ControlFlowGraphBuilder::getDefiningSlot(const MachineInstr &MI, - Register Reg) const { - StackSlot Slot = VariableSlot{Reg}; - SlotIndex Idx = LIS.getInstructionIndex(MI); - const LiveInterval *LI = &LIS.getInterval(Reg); - LiveQueryResult LRQ = LI->Query(Idx); - const VNInfo *VNI = LRQ.valueIn(); - assert(VNI && "Use of non-existing value"); - // If the virtual register defines a constant and this is the only - // definition, emit the literal slot as MI's input. - if (LI->containsOneValue()) { - assert(!VNI->isPHIDef()); - const MachineInstr *DefMI = LIS.getInstructionFromIndex(VNI->def); - assert(DefMI && "Dead valno in interval"); - if (DefMI->getOpcode() == EVM::CONST_I256) { - const APInt Imm = DefMI->getOperand(1).getCImm()->getValue(); - Slot = LiteralSlot{std::move(Imm)}; - } - } - - return Slot; -} - -void ControlFlowGraphBuilder::collectInstrOperands(const MachineInstr &MI, - Stack *Input, - Stack *Output) const { - if (Input) { - for (const auto &MO : reverse(MI.explicit_uses())) { - // All the non-register operands are handled in instruction specific - // handlers. - if (!MO.isReg()) - continue; - - const Register Reg = MO.getReg(); - // SP is not used anyhow. - if (Reg == EVM::SP) - continue; - - Input->push_back(getDefiningSlot(MI, Reg)); - } - } - - if (Output) { - unsigned ArgsNumber = 0; - for (const auto &MO : MI.defs()) - Output->push_back(TemporarySlot{&MI, MO.getReg(), ArgsNumber++}); - } -} - -void ControlFlowGraphBuilder::handleMachineInstr(MachineInstr &MI) { - bool TerminatesOrReverts = false; - unsigned Opc = MI.getOpcode(); - switch (Opc) { - case EVM::STACK_LOAD: - [[fallthrough]]; - case EVM::STACK_STORE: - llvm_unreachable("Unexpected stack memory instruction"); - return; - case EVM::ARGUMENT: - // Is handled above. - return; - case EVM::FCALL: - handleFunctionCall(MI); - break; - case EVM::RET: - handleReturn(MI); - return; - case EVM::JUMP: - [[fallthrough]]; - case EVM::JUMPI: - // Branch instructions are handled separately. - return; - case EVM::COPY_I256: - case EVM::DATASIZE: - case EVM::DATAOFFSET: - case EVM::LINKERSYMBOL: - // The copy/data instructions just represent an assignment. This case is - // handled below. - break; - case EVM::CONST_I256: { - const LiveInterval *LI = &LIS.getInterval(MI.getOperand(0).getReg()); - // If the virtual register has the only definition, ignore this instruction, - // as we create literal slots from the immediate value at the register uses. - if (LI->containsOneValue()) - return; - } break; - case EVM::REVERT: - [[fallthrough]]; - case EVM::RETURN: - [[fallthrough]]; - case EVM::STOP: - [[fallthrough]]; - case EVM::INVALID: - CurrentBlock->Exit = CFG::BasicBlock::Terminated{}; - TerminatesOrReverts = true; - [[fallthrough]]; - default: { - Stack Input, Output; - collectInstrOperands(MI, &Input, &Output); - CurrentBlock->Operations.emplace_back(CFG::Operation{ - std::move(Input), std::move(Output), - CFG::BuiltinCall{&MI, MI.isCommutable(), TerminatesOrReverts}}); - } break; - } - - // Cretae CFG::Assignment object for the MI. - Stack Input, Output; - std::vector Variables; - switch (MI.getOpcode()) { - case EVM::CONST_I256: { - const Register DefReg = MI.getOperand(0).getReg(); - const APInt Imm = MI.getOperand(1).getCImm()->getValue(); - Input.push_back(LiteralSlot{std::move(Imm)}); - Output.push_back(VariableSlot{DefReg}); - Variables.push_back(VariableSlot{DefReg}); - } break; - case EVM::DATASIZE: - case EVM::DATAOFFSET: - case EVM::LINKERSYMBOL: { - const Register DefReg = MI.getOperand(0).getReg(); - MCSymbol *Sym = MI.getOperand(1).getMCSymbol(); - Input.push_back(SymbolSlot{Sym, &MI}); - Output.push_back(VariableSlot{DefReg}); - Variables.push_back(VariableSlot{DefReg}); - } break; - case EVM::COPY_I256: { - // Copy instruction corresponds to the assignment operator, so - // we do not need to create intermediate TmpSlots. - Stack In; - collectInstrOperands(MI, &In, nullptr); - Input = In; - const Register DefReg = MI.getOperand(0).getReg(); - Output.push_back(VariableSlot{DefReg}); - Variables.push_back(VariableSlot{DefReg}); - } break; - default: { - unsigned ArgsNumber = 0; - for (const auto &MO : MI.defs()) { - assert(MO.isReg()); - const Register Reg = MO.getReg(); - Input.push_back(TemporarySlot{&MI, Reg, ArgsNumber++}); - Output.push_back(VariableSlot{Reg}); - Variables.push_back(VariableSlot{Reg}); - } - } break; - } - // We don't need an assignment part of the instructions that do not write - // results. - if (!Input.empty() || !Output.empty()) - CurrentBlock->Operations.emplace_back( - CFG::Operation{std::move(Input), std::move(Output), - CFG::Assignment{std::move(Variables)}}); -} - -void ControlFlowGraphBuilder::handleFunctionCall(const MachineInstr &MI) { - Stack Input, Output; - const Function *Called = getCalledFunction(MI); - bool IsNoReturn = Called->hasFnAttribute(Attribute::NoReturn); - if (IsNoReturn) - CurrentBlock->Exit = CFG::BasicBlock::Terminated{}; - else - Input.push_back(FunctionCallReturnLabelSlot{&MI}); - collectInstrOperands(MI, &Input, &Output); - CurrentBlock->Operations.emplace_back( - CFG::Operation{Input, Output, - CFG::FunctionCall{&MI, !IsNoReturn, - Input.size() - (IsNoReturn ? 0 : 1)}}); -} - -void ControlFlowGraphBuilder::handleReturn(const MachineInstr &MI) { - Exits.emplace_back(CurrentBlock); - Stack Input; - collectInstrOperands(MI, &Input, nullptr); - // We need to reverse input operands to restore original ordering, - // as it is in the instruction. - // Calling convention: return values are passed in stack such that the - // last one specified in the RET instruction is passed on the stack TOP. - std::reverse(Input.begin(), Input.end()); - CurrentBlock->Exit = CFG::BasicBlock::FunctionReturn{std::move(Input), &MI}; -} - -static std::pair -getMBBJumps(MachineBasicBlock &MBB) { - MachineInstr *CondJump = nullptr; - MachineInstr *UncondJump = nullptr; - MachineBasicBlock::reverse_iterator I = MBB.rbegin(), E = MBB.rend(); - while (I != E) { - if (I->isUnconditionalBranch()) - UncondJump = &*I; - else if (I->isConditionalBranch()) - CondJump = &*I; - ++I; - } - return std::make_pair(CondJump, UncondJump); -} - -void ControlFlowGraphBuilder::handleBasicBlockSuccessors( - MachineBasicBlock &MBB) { - MachineBasicBlock *TBB = nullptr, *FBB = nullptr; - SmallVector Cond; - const TargetInstrInfo *TII = MBB.getParent()->getSubtarget().getInstrInfo(); - CurrentBlock = &Cfg.getBlock(&MBB); - if (TII->analyzeBranch(MBB, TBB, FBB, Cond)) { - if (!std::holds_alternative( - CurrentBlock->Exit) && - !std::holds_alternative( - CurrentBlock->Exit)) - llvm_unreachable("Unexpected MBB termination"); - return; - } - -#ifndef NDEBUG - // This corresponds to a noreturn functions at the end of the MBB. - if (std::holds_alternative(CurrentBlock->Exit)) { - CFG::FunctionCall *Call = std::get_if( - &CurrentBlock->Operations.back().Operation); - assert(Call && !Call->CanContinue); - return; - } -#endif // NDEBUG - - // This corresponds to 'unreachable' at the BB end. - if (!TBB && !FBB && MBB.succ_empty()) { - CurrentBlock->Exit = CFG::BasicBlock::Unreachable{}; - return; - } - - CurrentBlock = &Cfg.getBlock(&MBB); - bool IsLatch = false; - MachineLoop *ML = MLI->getLoopFor(&MBB); - if (ML) { - SmallVector Latches; - ML->getLoopLatches(Latches); - IsLatch = std::any_of( - Latches.begin(), Latches.end(), - [&MBB](const MachineBasicBlock *Latch) { return &MBB == Latch; }); - } - - std::pair MBBJumps = getMBBJumps(MBB); - if (!TBB || (TBB && Cond.empty())) { - // Fall through, or unconditional jump. - bool FallThrough = !TBB; - if (!TBB) { - assert(MBB.getSingleSuccessor()); - TBB = MBB.getFallThrough(); - assert(TBB); - } - CFG::BasicBlock &Target = Cfg.getBlock(TBB); - assert(!MBBJumps.first); - assert((FallThrough && !MBBJumps.second) || !FallThrough); - CurrentBlock->Exit = - CFG::BasicBlock::Jump{&Target, FallThrough, IsLatch, MBBJumps.second}; - EVMUtils::emplace_back_unique(Target.Entries, CurrentBlock); - } else if (TBB && !Cond.empty()) { - assert(!IsLatch); - // Conditional jump + fallthrough or unconditional jump. - bool FallThrough = !FBB; - if (!FBB) { - FBB = MBB.getFallThrough(); - assert(FBB); - } - - CFG::BasicBlock &NonZeroTarget = Cfg.getBlock(TBB); - CFG::BasicBlock &ZeroTarget = Cfg.getBlock(FBB); - assert(Cond[0].isReg()); - auto CondSlot = getDefiningSlot(*Cond[0].getParent(), Cond[0].getReg()); - CurrentBlock->Exit = CFG::BasicBlock::ConditionalJump{ - std::move(CondSlot), &NonZeroTarget, &ZeroTarget, - FallThrough, MBBJumps.first, MBBJumps.second}; - - EVMUtils::emplace_back_unique(NonZeroTarget.Entries, CurrentBlock); - EVMUtils::emplace_back_unique(ZeroTarget.Entries, CurrentBlock); - } -} diff --git a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h b/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h deleted file mode 100644 index df56def2ccee..000000000000 --- a/llvm/lib/Target/EVM/EVMControlFlowGraphBuilder.h +++ /dev/null @@ -1,57 +0,0 @@ -//===----- EVMControlFlowGraphBuilder.h - CFG builder -----------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file builds the Control Flow Graph used for the backward propagation -// stackification algorithm. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPHBUILDER_H -#define LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPHBUILDER_H - -#include "EVMControlFlowGraph.h" -#include "llvm/ADT/DenseSet.h" -#include "llvm/CodeGen/LiveIntervals.h" -#include "llvm/CodeGen/MachineLoopInfo.h" - -namespace llvm { - -class MachineFunction; -class MachineBasicBlock; - -class ControlFlowGraphBuilder { -public: - ControlFlowGraphBuilder(ControlFlowGraphBuilder const &) = delete; - ControlFlowGraphBuilder &operator=(ControlFlowGraphBuilder const &) = delete; - static std::unique_ptr - build(MachineFunction &MF, const LiveIntervals &LIS, MachineLoopInfo *MLI); - -private: - ControlFlowGraphBuilder(CFG &Cfg, const LiveIntervals &LIS, - MachineLoopInfo *MLI) - : Cfg(Cfg), LIS(LIS), MLI(MLI) {} - - void handleBasicBlock(MachineBasicBlock &MBB); - void handleMachineInstr(MachineInstr &MI); - void handleFunctionCall(const MachineInstr &MI); - void handleReturn(const MachineInstr &MI); - void handleBasicBlockSuccessors(MachineBasicBlock &MBB); - StackSlot getDefiningSlot(const MachineInstr &MI, Register Reg) const; - void collectInstrOperands(const MachineInstr &MI, Stack *Input, - Stack *Output) const; - - CFG &Cfg; - const LiveIntervals &LIS; - MachineLoopInfo *MLI = nullptr; - CFG::BasicBlock *CurrentBlock = nullptr; - std::vector Exits; -}; - -} // namespace llvm - -#endif // LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H diff --git a/llvm/lib/Target/EVM/EVMHelperUtilities.cpp b/llvm/lib/Target/EVM/EVMHelperUtilities.cpp new file mode 100644 index 000000000000..46cfac9b5588 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMHelperUtilities.cpp @@ -0,0 +1,27 @@ +//===----------- EVMHelperUtilities.cpp - Helper utilities ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// +//===----------------------------------------------------------------------===// + +#include "EVMHelperUtilities.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/IR/Function.h" + +using namespace llvm; + +// Return whether the function of the call instruction will return. +bool EVMUtils::callWillReturn(const MachineInstr *Call) { + assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); + const MachineOperand *FuncOp = Call->explicit_uses().begin(); + assert(FuncOp->isGlobal() && "Expected a global value"); + const auto *Func = dyn_cast(FuncOp->getGlobal()); + assert(Func && "Expected a function"); + return !Func->hasFnAttribute(Attribute::NoReturn); +} diff --git a/llvm/lib/Target/EVM/EVMHelperUtilities.h b/llvm/lib/Target/EVM/EVMHelperUtilities.h index 8aea5b49f5dd..2ff5849f493a 100644 --- a/llvm/lib/Target/EVM/EVMHelperUtilities.h +++ b/llvm/lib/Target/EVM/EVMHelperUtilities.h @@ -1,4 +1,4 @@ -//===----- EVMHelperUtilities.h - CFG for stackification --------*- C++ -*-===// +//===----------- EVMHelperUtilities.h - Helper utilities --------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -15,6 +15,7 @@ #ifndef LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H #define LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H +#include "llvm/ADT/SmallVector.h" #include "llvm/ADT/iterator_range.h" #include #include @@ -25,6 +26,8 @@ namespace llvm { +class MachineInstr; + template struct Overload : Ts... { using Ts::operator()...; }; @@ -70,6 +73,8 @@ inline std::vector operator+(std::vector &&lhs, std::vector &&rhs) { namespace EVMUtils { +bool callWillReturn(const MachineInstr *Call); + template bool contains(const T &t, const V &v) { return std::end(t) != std::find(std::begin(t), std::end(t), v); } diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index 3698cf1a054a..2da934ab2070 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -757,7 +757,7 @@ defm CREATE2 // EVM instructions to return with error. //===----------------------------------------------------------------------===// -let isTerminator = 1, isBarrier = 1, isReturn = 1 in { +let isTerminator = 1, isBarrier = 1 in { defm REVERT : I<(outs), (ins GPR:$offset, GPR:$size), [(int_evm_revert GPR:$offset, GPR:$size)], @@ -774,7 +774,7 @@ let hasSideEffects = 1 in { : I<(outs), (ins GPR:$addr), [(int_evm_selfdestruct GPR:$addr)], "SELFDESTRUCT", " $addr", 0xFF, 5000>; - let isTerminator = 1, isBarrier = 1, isReturn = 1 in { + let isTerminator = 1, isBarrier = 1 in { defm STOP : I<(outs), (ins), [(int_evm_stop)], "STOP", "", 0x00, 0>; defm INVALID : I<(outs), (ins), [(int_evm_invalid)], "INVALID", "", 0xFE, 0>; } diff --git a/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp b/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp new file mode 100644 index 000000000000..c6f45aa65909 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp @@ -0,0 +1,196 @@ +//===--------- EVMMachineCFGInfo.cpp - Machine CFG info ---------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides some information about machine Control Flow Graph. +// +//===----------------------------------------------------------------------===// + +#include "EVMMachineCFGInfo.h" +#include "EVMHelperUtilities.h" +#include "EVMMachineFunctionInfo.h" +#include "EVMSubtarget.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/LiveIntervals.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineLoopInfo.h" + +using namespace llvm; + +static std::pair +getBranchInstructions(MachineBasicBlock &MBB) { + MachineInstr *ConditionalBranch = nullptr; + MachineInstr *UnconditionalBranch = nullptr; + MachineBasicBlock::reverse_iterator I = MBB.rbegin(), E = MBB.rend(); + while (I != E && I->isTerminator()) { + if (I->isUnconditionalBranch()) + UnconditionalBranch = &*I; + else if (I->isConditionalBranch()) + ConditionalBranch = &*I; + ++I; + } + return {ConditionalBranch, UnconditionalBranch}; +} + +static bool isTerminate(const MachineInstr *MI) { + switch (MI->getOpcode()) { + default: + return false; + case EVM::REVERT: + case EVM::RETURN: + case EVM::STOP: + case EVM::INVALID: + return true; + } +} + +EVMMachineCFGInfo::EVMMachineCFGInfo(MachineFunction &MF, + MachineLoopInfo *MLI) { + const TargetInstrInfo *TII = MF.getSubtarget().getInstrInfo(); + for (MachineBasicBlock &MBB : MF) + collectTerminatorsInfo(TII, MLI, MBB); + + SmallVector ReturnBlocks; + for (const MachineBasicBlock &MBB : MF) { + const EVMMBBTerminatorsInfo *TermInfo = getTerminatorsInfo(&MBB); + if (TermInfo->getExitType() == MBBExitType::FunctionReturn) + ReturnBlocks.emplace_back(&MBB); + } + collectBlocksLeadingToFunctionReturn(ReturnBlocks); + collectCutVertexes(&MF.front()); +} + +const EVMMBBTerminatorsInfo * +EVMMachineCFGInfo::getTerminatorsInfo(const MachineBasicBlock *MBB) const { + return MBBTerminatorsInfoMap.at(MBB).get(); +} + +void EVMMachineCFGInfo::collectTerminatorsInfo(const TargetInstrInfo *TII, + const MachineLoopInfo *MLI, + MachineBasicBlock &MBB) { + assert(MBBTerminatorsInfoMap.count(&MBB) == 0); + + MBBTerminatorsInfoMap.try_emplace(&MBB, + std::make_unique()); + EVMMBBTerminatorsInfo *Info = MBBTerminatorsInfoMap.at(&MBB).get(); + SmallVector Cond; + MachineBasicBlock *TBB = nullptr, *FBB = nullptr; + if (TII->analyzeBranch(MBB, TBB, FBB, Cond)) { + MachineInstr *LastMI = &MBB.back(); + Info->LastTerm = LastMI; + if (LastMI->isReturn()) + Info->ExitType = MBBExitType::FunctionReturn; + else if (isTerminate(LastMI)) + Info->ExitType = MBBExitType::Terminate; + else + llvm_unreachable("Unexpected MBB exit"); + return; + } + + // A non-terminator instruction is followed by 'unreachable', + // or a 'noreturn' function is at the end of MBB. + if (!TBB && !FBB && MBB.succ_empty()) { + Info->ExitType = MBBExitType::Terminate; + return; + } + + bool IsLatch = false; + if (MachineLoop *ML = MLI->getLoopFor(&MBB)) + IsLatch = ML->isLoopLatch(&MBB); + auto [CondBr, UncondBr] = getBranchInstructions(MBB); + if (!TBB || (TBB && Cond.empty())) { + // Fall through, or unconditional jump. + assert(!CondBr); + if (!TBB) { + assert(!UncondBr); + assert(MBB.getSingleSuccessor()); + TBB = MBB.getFallThrough(); + assert(TBB); + } + Info->ExitType = MBBExitType::UnconditionalBranch; + Info->BranchInfo.Unconditional = {TBB, UncondBr, IsLatch}; + } else if (TBB && !Cond.empty()) { + assert(!IsLatch); + // Conditional jump + fallthrough, or + // conditional jump followed by unconditional jump). + if (!FBB) { + FBB = MBB.getFallThrough(); + assert(FBB); + } + Info->ExitType = MBBExitType::ConditionalBranch; + assert(Cond[0].isIdenticalTo(CondBr->getOperand(1))); + Info->BranchInfo.Conditional = {&CondBr->getOperand(1), TBB, FBB, CondBr, + UncondBr}; + } +} + +// Mark basic blocks that have outgoing paths to function returns. +void EVMMachineCFGInfo::collectBlocksLeadingToFunctionReturn( + const SmallVector &Returns) { + SmallPtrSet Visited; + SmallVector WorkList = Returns; + while (!WorkList.empty()) { + const MachineBasicBlock *MBB = WorkList.pop_back_val(); + if (!Visited.insert(MBB).second) + continue; + + ToFuncReturnVertexes.insert(MBB); + WorkList.append(MBB->pred_begin(), MBB->pred_end()); + } +} + +// Collect cut-vertexes of the CFG, i.e. each blocks that begin a disconnected +// sub-graph of the CFG. Entering a cut-vertex block means that control flow +// will never return to a previously visited block. +void EVMMachineCFGInfo::collectCutVertexes(const MachineBasicBlock *Entry) { + DenseSet Visited; + DenseMap Disc; + DenseMap Low; + DenseMap Parent; + size_t Time = 0; + auto Dfs = [&](const MachineBasicBlock *U, auto Recurse) -> void { + Visited.insert(U); + Disc[U] = Low[U] = Time; + Time++; + + SmallVector Children(U->predecessors()); + const EVMMBBTerminatorsInfo *UTermInfo = getTerminatorsInfo(U); + switch (UTermInfo->getExitType()) { + case MBBExitType::Terminate: + CutVertexes.insert(U); + break; + default: + Children.append(U->succ_begin(), U->succ_end()); + break; + } + + for (const MachineBasicBlock *V : Children) { + // Ignore the loop edge, as it cannot be the bridge. + if (V == U) + continue; + + if (!Visited.count(V)) { + Parent[V] = U; + Recurse(V, Recurse); + Low[U] = std::min(Low[U], Low[V]); + if (Low[V] > Disc[U]) { + // U <-> V is a cut edge in the undirected graph + bool EdgeVtoU = std::count(U->pred_begin(), U->pred_end(), V); + bool EdgeUtoV = std::count(V->pred_begin(), V->pred_end(), U); + if (EdgeVtoU && !EdgeUtoV) + // Cut edge V -> U + CutVertexes.insert(U); + else if (EdgeUtoV && !EdgeVtoU) + // Cut edge U -> v + CutVertexes.insert(V); + } + } else if (V != Parent[U]) + Low[U] = std::min(Low[U], Disc[V]); + } + }; + Dfs(Entry, Dfs); +} diff --git a/llvm/lib/Target/EVM/EVMMachineCFGInfo.h b/llvm/lib/Target/EVM/EVMMachineCFGInfo.h new file mode 100644 index 000000000000..6792765ee8ff --- /dev/null +++ b/llvm/lib/Target/EVM/EVMMachineCFGInfo.h @@ -0,0 +1,122 @@ +//===-------- EVMMachineCFGInfo.h - Machine CFG info -----------*- C++ --*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides some information about machine Control Flow Graph. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMMACHINE_CFG_INFO_H +#define LLVM_LIB_TARGET_EVM_EVMMACHINE_CFG_INFO_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/CodeGen/MachineOperand.h" + +#include + +namespace llvm { + +class LiveIntervals; +class MachineFunction; +class MachineBasicBlock; +class MachineLoopInfo; +class TargetInstrInfo; + +enum class MBBExitType { + Invalid, + ConditionalBranch, + UnconditionalBranch, + FunctionReturn, + Terminate +}; + +class EVMMBBTerminatorsInfo { + friend class EVMMachineCFGInfo; + + union BranchInfoUnion { + BranchInfoUnion() {} + struct { + const MachineOperand *Condition; + MachineBasicBlock *TrueBB; + MachineBasicBlock *FalseBB; + MachineInstr *CondBr; + MachineInstr *UncondBr; + } Conditional; + + struct { + MachineBasicBlock *TargetBB; + MachineInstr *Br; + bool IsLatch; + } Unconditional; + } BranchInfo; + + MBBExitType ExitType = MBBExitType::Invalid; + MachineInstr *LastTerm = nullptr; + +public: + MBBExitType getExitType() const { + assert(ExitType != MBBExitType::Invalid); + return ExitType; + } + + std::tuple + getConditionalBranch() const { + assert(ExitType == MBBExitType::ConditionalBranch); + return {BranchInfo.Conditional.CondBr, BranchInfo.Conditional.UncondBr, + BranchInfo.Conditional.TrueBB, BranchInfo.Conditional.FalseBB, + BranchInfo.Conditional.Condition}; + } + + std::tuple + getUnconditionalBranch() const { + assert(ExitType == MBBExitType::UnconditionalBranch); + return {BranchInfo.Unconditional.Br, BranchInfo.Unconditional.TargetBB, + BranchInfo.Unconditional.IsLatch}; + } + + MachineInstr *getFunctionReturn() const { + assert(ExitType == MBBExitType::FunctionReturn); + return LastTerm; + } +}; + +class EVMMachineCFGInfo { +public: + EVMMachineCFGInfo(const EVMMachineCFGInfo &) = delete; + EVMMachineCFGInfo &operator=(const EVMMachineCFGInfo &) = delete; + EVMMachineCFGInfo(MachineFunction &MF, MachineLoopInfo *MLI); + + const EVMMBBTerminatorsInfo * + getTerminatorsInfo(const MachineBasicBlock *MBB) const; + + bool isCutVertex(const MachineBasicBlock *MBB) const { + return CutVertexes.count(MBB) > 0; + } + + bool isOnPathToFuncReturn(const MachineBasicBlock *MBB) const { + return ToFuncReturnVertexes.count(MBB) > 0; + } + +private: + DenseMap> + MBBTerminatorsInfoMap; + DenseSet ToFuncReturnVertexes; + DenseSet CutVertexes; + + void collectTerminatorsInfo(const TargetInstrInfo *TII, + const MachineLoopInfo *MLI, + MachineBasicBlock &MBB); + void collectBlocksLeadingToFunctionReturn( + const SmallVector &Returns); + void collectCutVertexes(const MachineBasicBlock *Entry); +}; + +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMMACHINE_CFG_INFO_H diff --git a/llvm/lib/Target/EVM/EVMStackDebug.cpp b/llvm/lib/Target/EVM/EVMStackDebug.cpp index e9bb24e6bb38..d91a04627580 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.cpp +++ b/llvm/lib/Target/EVM/EVMStackDebug.cpp @@ -82,122 +82,10 @@ std::string llvm::stackSlotToString(const StackSlot &Slot) { } #ifndef NDEBUG -void ControlFlowGraphPrinter::operator()(const CFG &Cfg) { - (*this)(); - for (const auto &Block : Cfg.Blocks) - printBlock(Block); -} - -void ControlFlowGraphPrinter::operator()() { - OS << "Function: " << MF.getName() << '\n'; - OS << "Entry block: " << getBlockId(MF.front()) << ";\n"; -} - -std::string ControlFlowGraphPrinter::getBlockId(CFG::BasicBlock const &Block) { - return getBlockId(*Block.MBB); -} - -std::string ControlFlowGraphPrinter::getBlockId(const MachineBasicBlock &MBB) { - return std::to_string(MBB.getNumber()) + "." + std::string(MBB.getName()); -} - -void ControlFlowGraphPrinter::printBlock(CFG::BasicBlock const &Block) { - OS << "Block" << getBlockId(Block) << " [\n"; - - // Verify that the entries of this block exit into this block. - for (auto const &Entry : Block.Entries) { - std::visit( - Overload{ - [&](CFG::BasicBlock::Jump &Jump) { - assert((Jump.Target == &Block) && "Invalid control flow graph"); - }, - [&](CFG::BasicBlock::ConditionalJump &CondJump) { - assert((CondJump.Zero == &Block || CondJump.NonZero == &Block) && - "Invalid control flow graph"); - }, - [&](const auto &) { assert(0 && "Invalid control flow graph."); }}, - Entry->Exit); - } - - for (auto const &Op : Block.Operations) { - std::visit(Overload{[&](const CFG::FunctionCall &FuncCall) { - const MachineOperand *Callee = - FuncCall.Call->explicit_uses().begin(); - OS << Callee->getGlobal()->getName() << ": "; - }, - [&](const CFG::BuiltinCall &BuiltinCall) { - OS << getInstName(BuiltinCall.Builtin) << ": "; - }, - [&](const CFG::Assignment &Assignment) { - OS << "Assignment("; - for (const auto &Var : Assignment.Variables) - OS << printReg(Var.VirtualReg, nullptr, 0, nullptr) - << ", "; - OS << "): "; - }}, - Op.Operation); - OS << stackToString(Op.Input) << " => " << stackToString(Op.Output) << '\n'; - } - OS << "];\n"; - std::visit( - Overload{[&](const CFG::BasicBlock::Jump &Jump) { - OS << "Block" << getBlockId(Block) << "Exit [label=\""; - OS << "Jump\" FallThrough:" << Jump.FallThrough << "];\n"; - if (Jump.Backwards) - OS << "Backwards"; - OS << "Block" << getBlockId(Block) << "Exit -> Block" - << getBlockId(*Jump.Target) << ";\n"; - }, - [&](const CFG::BasicBlock::ConditionalJump &CondJump) { - OS << "Block" << getBlockId(Block) << "Exit [label=\"{ "; - OS << stackSlotToString(CondJump.Condition); - OS << "| { <0> Zero | <1> NonZero }}\" FallThrough:"; - OS << CondJump.FallThrough << "];\n"; - OS << "Block" << getBlockId(Block); - OS << "Exit:0 -> Block" << getBlockId(*CondJump.Zero) << ";\n"; - OS << "Block" << getBlockId(Block); - OS << "Exit:1 -> Block" << getBlockId(*CondJump.NonZero) - << ";\n"; - }, - [&](const CFG::BasicBlock::FunctionReturn &Return) { - OS << "Block" << getBlockId(Block) - << "Exit [label=\"FunctionReturn[" << MF.getName() - << "]\"];\n"; - OS << "Return values: " << stackToString(Return.RetValues); - }, - [&](const CFG::BasicBlock::Terminated &) { - OS << "Block" << getBlockId(Block) - << "Exit [label=\"Terminated\"];\n"; - }, - [&](const CFG::BasicBlock::Unreachable &) { - OS << "Block" << getBlockId(Block) - << "Exit [label=\"Unreachable\"];\n"; - }, - [&](const CFG::BasicBlock::InvalidExit &) { - assert(0 && "Invalid basic block exit"); - }}, - Block.Exit); - OS << "\n"; -} - -void StackLayoutPrinter::operator()(CFG::BasicBlock const &Block, - bool IsMainEntry) { - if (IsMainEntry) { - OS << "Entry [label=\"Entry\"];\n"; - OS << "Entry -> Block" << getBlockId(Block) << ";\n"; - } - while (!BlocksToPrint.empty()) { - CFG::BasicBlock const *block = *BlocksToPrint.begin(); - BlocksToPrint.erase(BlocksToPrint.begin()); - printBlock(*block); - } -} - -void StackLayoutPrinter::operator()(CFG::BasicBlock const &EntryBB, - const std::vector &Parameters) { +void StackLayoutPrinter::operator()() { OS << "Function: " << MF.getName() << "("; - for (const StackSlot &ParamSlot : Parameters) { + for (const StackSlot &ParamSlot : StackModel.getFunctionParameters()) { if (const auto *Slot = std::get_if(&ParamSlot)) OS << printReg(Slot->VirtualReg, nullptr, 0, nullptr) << ' '; else if (std::holds_alternative(ParamSlot)) @@ -207,43 +95,29 @@ void StackLayoutPrinter::operator()(CFG::BasicBlock const &EntryBB, } OS << ");\n"; OS << "FunctionEntry " - << " -> Block" << getBlockId(EntryBB) << ";\n"; - (*this)(EntryBB, false); -} + << " -> Block" << getBlockId(MF.front()) << ";\n"; -void StackLayoutPrinter::printBlock(CFG::BasicBlock const &Block) { - OS << "Block" << getBlockId(Block) << " [\n"; - // Verify that the entries of this block exit into this block. - for (auto const &entry : Block.Entries) { - std::visit( - Overload{[&](CFG::BasicBlock::Jump const &Jump) { - assert(Jump.Target == &Block); - }, - [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { - assert(ConditionalJump.Zero == &Block || - ConditionalJump.NonZero == &Block); - }, - [&](auto const &) { - llvm_unreachable("Invalid control flow graph"); - }}, - entry->Exit); + for (const auto &MBB : MF) { + printBlock(MBB); } +} - auto const &BlockInfo = Layout.blockInfos.at(&Block); - OS << stackToString(BlockInfo.entryLayout) << "\n"; - for (auto const &Operation : Block.Operations) { +void StackLayoutPrinter::printBlock(MachineBasicBlock const &Block) { + OS << "Block" << getBlockId(Block) << " [\n"; + OS << stackToString(Layout.getMBBEntryLayout(&Block)) << "\n"; + for (auto const &Operation : StackModel.getOperations(&Block)) { OS << "\n"; - auto EntryLayout = Layout.operationEntryLayout.at(&Operation); + Stack EntryLayout = Layout.getOperationEntryLayout(&Operation); OS << stackToString(EntryLayout) << "\n"; - std::visit(Overload{[&](CFG::FunctionCall const &Call) { + std::visit(Overload{[&](FunctionCall const &Call) { const MachineOperand *Callee = - Call.Call->explicit_uses().begin(); + Call.MI->explicit_uses().begin(); OS << Callee->getGlobal()->getName(); }, - [&](CFG::BuiltinCall const &Call) { - OS << getInstName(Call.Builtin); + [&](BuiltinCall const &Call) { + OS << getInstName(Call.MI); }, - [&](CFG::Assignment const &Assignment) { + [&](Assignment const &Assignment) { OS << "Assignment("; for (const auto &Var : Assignment.Variables) OS << printReg(Var.VirtualReg, nullptr, 0, nullptr) @@ -260,57 +134,48 @@ void StackLayoutPrinter::printBlock(CFG::BasicBlock const &Block) { OS << stackToString(EntryLayout) << "\n"; } OS << "\n"; - OS << stackToString(BlockInfo.exitLayout) << "\n"; + OS << stackToString(Layout.getMBBExitLayout(&Block)) << "\n"; OS << "];\n"; - std::visit( - Overload{[&](CFG::BasicBlock::InvalidExit const &) { - assert(0 && "Invalid basic block exit"); - }, - [&](CFG::BasicBlock::Jump const &Jump) { - OS << "Block" << getBlockId(Block) << "Exit [label=\""; - if (Jump.Backwards) - OS << "Backwards"; - OS << "Jump\"];\n"; - OS << "Block" << getBlockId(Block) << "Exit -> Block" - << getBlockId(*Jump.Target) << ";\n"; - }, - [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { - OS << "Block" << getBlockId(Block) << "Exit [label=\"{ "; - OS << stackSlotToString(ConditionalJump.Condition); - OS << "| { <0> Zero | <1> NonZero }}\"];\n"; - OS << "Block" << getBlockId(Block); - OS << "Exit:0 -> Block" << getBlockId(*ConditionalJump.Zero) - << ";\n"; - OS << "Block" << getBlockId(Block); - OS << "Exit:1 -> Block" << getBlockId(*ConditionalJump.NonZero) - << ";\n"; - }, - [&](CFG::BasicBlock::FunctionReturn const &Return) { - OS << "Block" << getBlockId(Block) - << "Exit [label=\"FunctionReturn[" << MF.getName() - << "]\"];\n"; - }, - [&](CFG::BasicBlock::Terminated const &) { - OS << "Block" << getBlockId(Block) - << "Exit [label=\"Terminated\"];\n"; - }, - [&](CFG::BasicBlock::Unreachable const &) { - OS << "Block" << getBlockId(Block) - << "Exit [label=\"Unreachable\"];\n"; - }}, - Block.Exit); + const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(&Block); + MBBExitType ExitType = TermInfo->getExitType(); + if (ExitType == MBBExitType::UnconditionalBranch) { + auto [BranchInstr, Target, IsLatch] = TermInfo->getUnconditionalBranch(); + OS << "Block" << getBlockId(Block) << "Exit [label=\""; + OS << "Jump\"];\n"; + if (IsLatch) + OS << "Backwards"; + OS << "Block" << getBlockId(Block) << "Exit -> Block" << getBlockId(*Target) + << ";\n"; + } else if (ExitType == MBBExitType::ConditionalBranch) { + auto [CondBr, UncondBr, TrueBB, FalseBB, Condition] = + TermInfo->getConditionalBranch(); + OS << "Block" << getBlockId(Block) << "Exit [label=\"{ "; + OS << stackSlotToString(StackModel.getStackSlot(*Condition)); + OS << "| { <0> Zero | <1> NonZero }}\"];\n"; + OS << "Block" << getBlockId(Block); + OS << "Exit:0 -> Block" << getBlockId(*FalseBB) << ";\n"; + OS << "Block" << getBlockId(Block); + OS << "Exit:1 -> Block" << getBlockId(*TrueBB) << ";\n"; + } else if (ExitType == MBBExitType::FunctionReturn) { + OS << "Block" << getBlockId(Block) << "Exit [label=\"FunctionReturn[" + << MF.getName() << "]\"];\n"; + const MachineInstr &MI = Block.back(); + OS << "Return values: " << stackToString(StackModel.getReturnArguments(MI)) + << ";\n"; + } else if (ExitType == MBBExitType::Terminate) { + OS << "Block" << getBlockId(Block) << "Exit [label=\"Terminated\"];\n"; + } OS << "\n"; } -std::string StackLayoutPrinter::getBlockId(CFG::BasicBlock const &Block) { - std::string Name = std::to_string(Block.MBB->getNumber()) + "." + - std::string(Block.MBB->getName()); +std::string StackLayoutPrinter::getBlockId(MachineBasicBlock const &Block) { + std::string Name = + std::to_string(Block.getNumber()) + "." + std::string(Block.getName()); if (auto It = BlockIds.find(&Block); It != BlockIds.end()) return std::to_string(It->second) + "(" + Name + ")"; size_t Id = BlockIds[&Block] = BlockCount++; - BlocksToPrint.emplace_back(&Block); return std::to_string(Id) + "(" + Name + ")"; } #endif // NDEBUG diff --git a/llvm/lib/Target/EVM/EVMStackDebug.h b/llvm/lib/Target/EVM/EVMStackDebug.h index 7978fe79c729..b4c8737ccae5 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.h +++ b/llvm/lib/Target/EVM/EVMStackDebug.h @@ -14,7 +14,8 @@ #ifndef LLVM_LIB_TARGET_EVM_EVMSTACKDEBUG_H #define LLVM_LIB_TARGET_EVM_EVMSTACKDEBUG_H -#include "EVMControlFlowGraph.h" +#include "EVMMachineCFGInfo.h" +#include "EVMStackModel.h" #include "llvm/CodeGen/MachineFunction.h" #include #include @@ -24,49 +25,34 @@ namespace llvm { -struct StackLayout; +class EVMStackLayout; const Function *getCalledFunction(const MachineInstr &MI); std::string stackSlotToString(const StackSlot &Slot); std::string stackToString(Stack const &S); #ifndef NDEBUG -class ControlFlowGraphPrinter { -public: - ControlFlowGraphPrinter(raw_ostream &OS, const MachineFunction &MF) - : OS(OS), MF(MF) {} - void operator()(const CFG &Cfg); - -private: - void operator()(); - std::string getBlockId(const CFG::BasicBlock &Block); - std::string getBlockId(const MachineBasicBlock &MBB); - void printBlock(const CFG::BasicBlock &Block); - - raw_ostream &OS; - const MachineFunction &MF; -}; - class StackLayoutPrinter { public: - StackLayoutPrinter(raw_ostream &OS, const StackLayout &StackLayout, - const MachineFunction &MF) - : OS(OS), Layout(StackLayout), MF(MF) {} - - void operator()(CFG::BasicBlock const &Block, bool IsMainEntry = true); - void operator()(CFG::BasicBlock const &EntryBB, - const std::vector &Parameters); + StackLayoutPrinter(raw_ostream &OS, const MachineFunction &MF, + const EVMStackLayout &EVMStackLayout, + const EVMMachineCFGInfo &CFGInfo, + const EVMStackModel &StackModel) + : OS(OS), MF(MF), Layout(EVMStackLayout), CFGInfo(CFGInfo), + StackModel(StackModel) {} + void operator()(); private: - void printBlock(CFG::BasicBlock const &Block); - std::string getBlockId(CFG::BasicBlock const &Block); + void printBlock(MachineBasicBlock const &Block); + std::string getBlockId(MachineBasicBlock const &Block); raw_ostream &OS; - const StackLayout &Layout; const MachineFunction &MF; - std::map BlockIds; + const EVMStackLayout &Layout; + const EVMMachineCFGInfo &CFGInfo; + const EVMStackModel &StackModel; size_t BlockCount = 0; - std::list BlocksToPrint; + std::map BlockIds; }; #endif // NDEBUG diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp index c1b58d3f420e..634fbc419a15 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -31,35 +31,13 @@ using namespace llvm; #define DEBUG_TYPE "evm-stack-layout-generator" -StackLayout StackLayoutGenerator::run(const CFG &Cfg) { - StackLayout Layout; - StackLayoutGenerator LayoutGenerator{Layout, Cfg.MF, Cfg.Parameters}; - - auto &EntryBB = Cfg.getBlock(&Cfg.MF.front()); - LayoutGenerator.processEntryPoint(EntryBB); - - LLVM_DEBUG({ - dbgs() << "************* Stack Layout *************\n"; - StackLayoutPrinter P(dbgs(), Layout, Cfg.MF); - P(EntryBB, Cfg.Parameters); - }); - - return Layout; -} - -StackLayoutGenerator::StackLayoutGenerator( - StackLayout &Layout, const MachineFunction &MF, - const std::vector &Parameters) - : Layout(Layout), Parameters(Parameters), MF(MF) {} - namespace { - /// Returns all stack too deep errors that would occur when shuffling \p Source /// to \p Target. -std::vector +std::vector findStackTooDeep(Stack const &Source, Stack const &Target) { Stack CurrentStack = Source; - std::vector Errors; + std::vector Errors; auto getVariableChoices = [](auto &&SlotRange) { std::vector Result; @@ -74,18 +52,18 @@ findStackTooDeep(Stack const &Source, Stack const &Target) { CurrentStack, Target, [&](unsigned I) { if (I > 16) - Errors.emplace_back(StackLayoutGenerator::StackTooDeep{ + Errors.emplace_back(EVMStackLayoutGenerator::StackTooDeep{ I - 16, getVariableChoices(EVMUtils::take_last(CurrentStack, I + 1))}); }, [&](StackSlot const &Slot) { - if (canBeFreelyGenerated(Slot)) + if (isRematerializable(Slot)) return; if (auto depth = EVMUtils::findOffset(EVMUtils::get_reverse(CurrentStack), Slot); depth && *depth >= 16) - Errors.emplace_back(StackLayoutGenerator::StackTooDeep{ + Errors.emplace_back(EVMStackLayoutGenerator::StackTooDeep{ *depth - 15, getVariableChoices( EVMUtils::take_last(CurrentStack, *depth + 1))}); }, @@ -247,17 +225,36 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, } // end anonymous namespace -Stack StackLayoutGenerator::propagateStackThroughOperation( - Stack ExitStack, const CFG::Operation &Operation, +EVMStackLayoutGenerator::EVMStackLayoutGenerator( + const MachineFunction &MF, const EVMStackModel &StackModel, + const EVMMachineCFGInfo &CFGInfo) + : MF(MF), StackModel(StackModel), CFGInfo(CFGInfo) {} + +std::unique_ptr EVMStackLayoutGenerator::run() { + processEntryPoint(&MF.front()); + + auto Layout = std::make_unique( + MBBEntryLayoutMap, MBBExitLayoutMap, OperationEntryLayoutMap); + + LLVM_DEBUG({ + dbgs() << "************* Stack Layout *************\n"; + StackLayoutPrinter P(dbgs(), MF, *Layout, CFGInfo, StackModel); + P(); + }); + return Layout; +} + +Stack EVMStackLayoutGenerator::propagateStackThroughOperation( + Stack ExitStack, const Operation &Operation, bool AggressiveStackCompression) { // Enable aggressive stack compression for recursive calls. - if (std::holds_alternative(Operation.Operation)) + if (std::holds_alternative(Operation.Operation)) // TODO: compress stack for recursive functions. AggressiveStackCompression = false; // This is a huge tradeoff between code size, gas cost and stack size. auto generateSlotOnTheFly = [&](StackSlot const &Slot) { - return AggressiveStackCompression && canBeFreelyGenerated(Slot); + return AggressiveStackCompression && isRematerializable(Slot); }; // Determine the ideal permutation of the slots in ExitLayout that are not @@ -268,11 +265,10 @@ Stack StackLayoutGenerator::propagateStackThroughOperation( // Make sure the resulting previous slots do not overlap with any assignmed // variables. - if (auto const *Assignment = - std::get_if(&Operation.Operation)) + if (auto const *Assign = std::get_if(&Operation.Operation)) for (auto &StackSlot : IdealStack) if (auto const *VarSlot = std::get_if(&StackSlot)) - assert(!EVMUtils::contains(Assignment->Variables, *VarSlot)); + assert(!EVMUtils::contains(Assign->Variables, *VarSlot)); // Since stack+Operation.output can be easily shuffled to ExitLayout, the // desired layout before the operation is stack+Operation.input; @@ -283,12 +279,12 @@ Stack StackLayoutGenerator::propagateStackThroughOperation( // this recreation can produce slots that can be freely generated or are // duplicated, i.e. we can compress the stack afterwards without causing // problems for code generation later. - Layout.operationEntryLayout[&Operation] = IdealStack; + OperationEntryLayoutMap[&Operation] = IdealStack; // Remove anything from the stack top that can be freely generated or dupped // from deeper on the stack. while (!IdealStack.empty()) { - if (canBeFreelyGenerated(IdealStack.back())) + if (isRematerializable(IdealStack.back())) IdealStack.pop_back(); else if (auto Offset = EVMUtils::findOffset( EVMUtils::drop_first(EVMUtils::get_reverse(IdealStack), 1), @@ -304,12 +300,14 @@ Stack StackLayoutGenerator::propagateStackThroughOperation( return IdealStack; } -Stack StackLayoutGenerator::propagateStackThroughBlock( - Stack ExitStack, CFG::BasicBlock const &Block, +Stack EVMStackLayoutGenerator::propagateStackThroughBlock( + Stack ExitStack, const MachineBasicBlock *Block, bool AggressiveStackCompression) { Stack CurrentStack = ExitStack; - for (auto &Operation : EVMUtils::get_reverse(Block.Operations)) { - Stack NewStack = propagateStackThroughOperation(CurrentStack, Operation, + const std::vector &Operations = StackModel.getOperations(Block); + auto I = Operations.crbegin(), E = Operations.crend(); + for (; I != E; ++I) { + Stack NewStack = propagateStackThroughOperation(CurrentStack, *I, AggressiveStackCompression); if (!AggressiveStackCompression && !findStackTooDeep(NewStack, CurrentStack).empty()) @@ -320,33 +318,34 @@ Stack StackLayoutGenerator::propagateStackThroughBlock( return CurrentStack; } -void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const &Entry) { - std::list ToVisit{&Entry}; - std::set Visited; +void EVMStackLayoutGenerator::processEntryPoint( + const MachineBasicBlock *Entry) { + std::list ToVisit{Entry}; + DenseSet Visited; // TODO: check whether visiting only a subset of these in the outer iteration // below is enough. - std::list> + std::list> BackwardsJumps = collectBackwardsJumps(Entry); while (!ToVisit.empty()) { // First calculate stack layouts without walking backwards jumps, i.e. // assuming the current preliminary entry layout of the backwards jump // target as the initial exit layout of the backwards-jumping block. while (!ToVisit.empty()) { - CFG::BasicBlock const *Block = *ToVisit.begin(); + const MachineBasicBlock *Block = *ToVisit.begin(); ToVisit.pop_front(); if (Visited.count(Block)) continue; if (std::optional ExitLayout = - getExitLayoutOrStageDependencies(*Block, Visited, ToVisit)) { - Visited.emplace(Block); - auto &Info = Layout.blockInfos[Block]; - Info.exitLayout = *ExitLayout; - Info.entryLayout = propagateStackThroughBlock(Info.exitLayout, *Block); - for (auto Entry : Block->Entries) - ToVisit.emplace_back(Entry); + getExitLayoutOrStageDependencies(Block, Visited, ToVisit)) { + Visited.insert(Block); + MBBExitLayoutMap[Block] = *ExitLayout; + MBBEntryLayoutMap[Block] = + propagateStackThroughBlock(*ExitLayout, Block); + for (auto Pred : Block->predecessors()) + ToVisit.emplace_back(Pred); } } @@ -356,13 +355,13 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const &Entry) { // This block jumps backwards, but does not provide all slots required by // the jump target on exit. Therefore we need to visit the subgraph // between 'Target' and 'JumpingBlock' again. - auto StartIt = std::begin(Layout.blockInfos[Target].entryLayout); - auto EndIt = std::end(Layout.blockInfos[Target].entryLayout); - if (std::any_of(StartIt, EndIt, - [exitLayout = Layout.blockInfos[JumpingBlock].exitLayout]( - StackSlot const &Slot) { - return !EVMUtils::contains(exitLayout, Slot); - })) { + const Stack &EntryLayout = MBBEntryLayoutMap[Target]; + auto B = EntryLayout.begin(), E = EntryLayout.end(); + const Stack &ExitLayout = MBBExitLayoutMap[JumpingBlock]; + if (std::any_of(B, E, [ExitLayout](const StackSlot &Slot) { + return std::find(ExitLayout.begin(), ExitLayout.end(), Slot) == + ExitLayout.end(); + })) { // In particular we can visit backwards starting from 'JumpingBlock' // and mark all entries to-be-visited again until we hit 'Target'. ToVisit.emplace_front(JumpingBlock); @@ -370,17 +369,17 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const &Entry) { // also visit its entries again. This is not required for correctness, // since the set of stack slots will match, but it may move some // required stack shuffling from the loop condition to outside the loop. - for (CFG::BasicBlock const *Entry : Target->Entries) - Visited.erase(Entry); + for (const MachineBasicBlock *Pred : Target->predecessors()) + Visited.erase(Pred); - EVMUtils::BreadthFirstSearch{{JumpingBlock}} - .run([&Visited, Target = Target](const CFG::BasicBlock *Block, - auto AddChild) { + EVMUtils::BreadthFirstSearch{{JumpingBlock}} + .run([&Visited, Target = Target](const MachineBasicBlock *Block, + auto VisitPred) { Visited.erase(Block); if (Block == Target) return; - for (auto const *Entry : Block->Entries) - AddChild(Entry); + for (auto const *Pred : Block->predecessors()) + VisitPred(Pred); }); // While the shuffled layout for 'Target' will be compatible, it can // be worthwhile propagating it further up once more. This would mean @@ -398,183 +397,153 @@ void StackLayoutGenerator::processEntryPoint(CFG::BasicBlock const &Entry) { // Create function entry layout. Stack EntryStack; - if (!MF.getFunction().hasFnAttribute(Attribute::NoReturn)) + bool IsNoReturn = MF.getFunction().hasFnAttribute(Attribute::NoReturn); + if (!IsNoReturn) EntryStack.emplace_back(FunctionReturnLabelSlot{&MF}); // Calling convention: input arguments are passed in stack such that the // first one specified in the function declaration is passed on the stack TOP. - for (auto const &Param : reverse(Parameters)) - EntryStack.emplace_back(Param); - Layout.blockInfos[&Entry].entryLayout = EntryStack; + EntryStack += StackModel.getFunctionParameters(); + std::reverse(IsNoReturn ? EntryStack.begin() : std::next(EntryStack.begin()), + EntryStack.end()); + MBBEntryLayoutMap[Entry] = std::move(EntryStack); } -std::optional StackLayoutGenerator::getExitLayoutOrStageDependencies( - const CFG::BasicBlock &Block, - const std::set &Visited, - std::list &ToVisit) const { - return std::visit( - Overload{ - [&](CFG::BasicBlock::Jump const &Jump) -> std::optional { - if (Jump.Backwards) { - // Choose the best currently known entry layout of the jump target - // as initial exit. Note that this may not yet be the final - // layout. - auto It = Layout.blockInfos.find(Jump.Target); - if (It == Layout.blockInfos.end()) - return Stack{}; - - return It->second.entryLayout; - } - // If the current iteration has already visited the jump target, - // start from its entry layout. - if (Visited.count(Jump.Target)) - return Layout.blockInfos.at(Jump.Target).entryLayout; - // Otherwise stage the jump target for visit and defer the current - // block. - ToVisit.emplace_front(Jump.Target); - return std::nullopt; - }, - [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) - -> std::optional { - bool ZeroVisited = Visited.count(ConditionalJump.Zero); - bool NonZeroVisited = Visited.count(ConditionalJump.NonZero); - - if (ZeroVisited && NonZeroVisited) { - // If the current iteration has already Visited both jump targets, - // start from its entry layout. - Stack CombonedStack = combineStack( - Layout.blockInfos.at(ConditionalJump.Zero).entryLayout, - Layout.blockInfos.at(ConditionalJump.NonZero).entryLayout); - // Additionally, the jump condition has to be at the stack top at - // exit. - CombonedStack.emplace_back(ConditionalJump.Condition); - return CombonedStack; - } +std::optional EVMStackLayoutGenerator::getExitLayoutOrStageDependencies( + const MachineBasicBlock *Block, + const DenseSet &Visited, + std::list &ToVisit) const { + const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(Block); + MBBExitType ExitType = TermInfo->getExitType(); + if (ExitType == MBBExitType::UnconditionalBranch) { + auto [_, Target, IsLatch] = TermInfo->getUnconditionalBranch(); + if (IsLatch) { + // Choose the best currently known entry layout of the jump target + // as initial exit. Note that this may not yet be the final + // layout. + auto It = MBBEntryLayoutMap.find(Target); + return It == MBBEntryLayoutMap.end() ? Stack{} : It->second; + } + // If the current iteration has already visited the jump target, + // start from its entry layout. + if (Visited.count(Target)) + return MBBEntryLayoutMap.at(Target); + // Otherwise stage the jump target for visit and defer the current + // block. + ToVisit.emplace_front(Target); + return std::nullopt; + } + if (ExitType == MBBExitType::ConditionalBranch) { + auto [CondBr, UncondBr, TrueBB, FalseBB, Condition] = + TermInfo->getConditionalBranch(); + bool FalseBBVisited = Visited.count(FalseBB); + bool TrueBBVisited = Visited.count(TrueBB); + + if (FalseBBVisited && TrueBBVisited) { + // If the current iteration has already Visited both jump targets, + // start from its entry layout. + Stack CombinedStack = combineStack(MBBEntryLayoutMap.at(FalseBB), + MBBEntryLayoutMap.at(TrueBB)); + // Additionally, the jump condition has to be at the stack top at + // exit. + CombinedStack.emplace_back(StackModel.getStackSlot(*Condition)); + return CombinedStack; + } + + // If one of the jump targets has not been visited, stage it for + // visit and defer the current block. + if (!FalseBBVisited) + ToVisit.emplace_front(FalseBB); - // If one of the jump targets has not been visited, stage it for - // visit and defer the current block. - if (!ZeroVisited) - ToVisit.emplace_front(ConditionalJump.Zero); - - if (!NonZeroVisited) - ToVisit.emplace_front(ConditionalJump.NonZero); - - return std::nullopt; - }, - [&](CFG::BasicBlock::FunctionReturn const &FunctionReturn) - -> std::optional { - // A function return needs the return variables and the function - // return label slot on stack. - Stack ReturnStack = FunctionReturn.RetValues; - ReturnStack.emplace_back(FunctionReturnLabelSlot{&MF}); - return ReturnStack; - }, - [&](CFG::BasicBlock::Terminated const &) -> std::optional { - // A terminating block can have an empty stack on exit. - return Stack{}; - }, - [&](CFG::BasicBlock::Unreachable const &) -> std::optional { - // A unreachable block can have an empty stack on exit. - return Stack{}; - }, - [](CFG::BasicBlock::InvalidExit const &) -> std::optional { - llvm_unreachable("Unexpected BB terminator"); - }}, - Block.Exit); + if (!TrueBBVisited) + ToVisit.emplace_front(TrueBB); + + return std::nullopt; + } + if (ExitType == MBBExitType::FunctionReturn) { + const MachineInstr &MI = Block->back(); + return StackModel.getReturnArguments(MI); + } + + return Stack{}; } -std::list> -StackLayoutGenerator::collectBackwardsJumps( - CFG::BasicBlock const &Entry) const { - std::list> +std::list> +EVMStackLayoutGenerator::collectBackwardsJumps( + const MachineBasicBlock *Entry) const { + std::list> BackwardsJumps; - EVMUtils::BreadthFirstSearch{{&Entry}}.run( - [&](CFG::BasicBlock const *Block, auto AddChild) { - std::visit( - Overload{ - [&](CFG::BasicBlock::InvalidExit const &) { - llvm_unreachable("Unexpected BB terminator"); - }, - [&](CFG::BasicBlock::Jump const &Jump) { - if (Jump.Backwards) - BackwardsJumps.emplace_back(Block, Jump.Target); - AddChild(Jump.Target); - }, - [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { - AddChild(ConditionalJump.Zero); - AddChild(ConditionalJump.NonZero); - }, - [&](CFG::BasicBlock::FunctionReturn const &) {}, - [&](CFG::BasicBlock::Terminated const &) {}, - [&](CFG::BasicBlock::Unreachable const &) {}, - }, - Block->Exit); + EVMUtils::BreadthFirstSearch{{Entry}}.run( + [&](const MachineBasicBlock *Block, auto VisitSucc) { + const EVMMBBTerminatorsInfo *TermInfo = + CFGInfo.getTerminatorsInfo(Block); + MBBExitType ExitType = TermInfo->getExitType(); + if (ExitType == MBBExitType::UnconditionalBranch) { + auto [_, Target, IsLatch] = TermInfo->getUnconditionalBranch(); + if (IsLatch) + BackwardsJumps.emplace_back(Block, Target); + } + for (const MachineBasicBlock *Succ : Block->successors()) + VisitSucc(Succ); }); return BackwardsJumps; } -void StackLayoutGenerator::stitchConditionalJumps( - CFG::BasicBlock const &Block) { - EVMUtils::BreadthFirstSearch BFS{{&Block}}; - BFS.run([&](CFG::BasicBlock const *Block, auto AddChild) { - auto &Info = Layout.blockInfos.at(Block); - std::visit( - Overload{ - [&](CFG::BasicBlock::InvalidExit const &) { - llvm_unreachable("Unexpected BB terminator"); - }, - [&](CFG::BasicBlock::Jump const &Jump) { - if (!Jump.Backwards) - AddChild(Jump.Target); - }, - [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { - auto &ZeroTargetInfo = Layout.blockInfos.at(ConditionalJump.Zero); - auto &NonZeroTargetInfo = - Layout.blockInfos.at(ConditionalJump.NonZero); - Stack ExitLayout = Info.exitLayout; - - // The last block must have produced the condition at the stack - // top. - assert(!ExitLayout.empty()); - assert(ExitLayout.back() == ConditionalJump.Condition); - // The condition is consumed by the jump. - ExitLayout.pop_back(); - - auto FixJumpTargetEntry = - [&](Stack const &OriginalEntryLayout) -> Stack { - Stack NewEntryLayout = ExitLayout; - // Whatever the block being jumped to does not actually require, - // can be marked as junk. - for (auto &Slot : NewEntryLayout) - if (!EVMUtils::contains(OriginalEntryLayout, Slot)) - Slot = JunkSlot{}; +void EVMStackLayoutGenerator::stitchConditionalJumps( + const MachineBasicBlock *Block) { + EVMUtils::BreadthFirstSearch BFS{{Block}}; + BFS.run([&](const MachineBasicBlock *Block, auto VisitSucc) { + const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(Block); + MBBExitType ExitType = TermInfo->getExitType(); + if (ExitType == MBBExitType::UnconditionalBranch) { + auto [_, Target, IsLatch] = TermInfo->getUnconditionalBranch(); + if (!IsLatch) + VisitSucc(Target); + return; + } + if (ExitType == MBBExitType::ConditionalBranch) { + auto [CondBr, UncondBr, TrueBB, FalseBB, Condition] = + TermInfo->getConditionalBranch(); + Stack ExitLayout = MBBExitLayoutMap.at(Block); + // The last block must have produced the condition at the stack + // top. + assert(!ExitLayout.empty()); + assert(ExitLayout.back() == StackModel.getStackSlot(*Condition)); + // The condition is consumed by the jump. + ExitLayout.pop_back(); + + auto FixJumpTargetEntry = [&](const Stack &OriginalEntryLayout) -> Stack { + Stack NewEntryLayout = ExitLayout; + // Whatever the block being jumped to does not actually require, + // can be marked as junk. + for (StackSlot &Slot : NewEntryLayout) { + if (std::find(OriginalEntryLayout.begin(), OriginalEntryLayout.end(), + Slot) == OriginalEntryLayout.end()) + Slot = JunkSlot{}; + } #ifndef NDEBUG - // Make sure everything the block being jumped to requires is - // actually present or can be generated. - for (auto const &Slot : OriginalEntryLayout) - assert(canBeFreelyGenerated(Slot) || - EVMUtils::contains(NewEntryLayout, Slot)); + // Make sure everything the block being jumped to requires is + // actually present or can be generated. + for (StackSlot const &Slot : OriginalEntryLayout) + assert(isRematerializable(Slot) || + std::find(NewEntryLayout.begin(), NewEntryLayout.end(), + Slot) != NewEntryLayout.end()); #endif // NDEBUG - return NewEntryLayout; - }; - - ZeroTargetInfo.entryLayout = - FixJumpTargetEntry(ZeroTargetInfo.entryLayout); - NonZeroTargetInfo.entryLayout = - FixJumpTargetEntry(NonZeroTargetInfo.entryLayout); - AddChild(ConditionalJump.Zero); - AddChild(ConditionalJump.NonZero); - }, - [&](CFG::BasicBlock::FunctionReturn const &) {}, - [&](CFG::BasicBlock::Terminated const &) {}, - [&](CFG::BasicBlock::Unreachable const &) {}, - }, - Block->Exit); + return NewEntryLayout; + }; + + const Stack &ZeroTargetEntryLayout = MBBEntryLayoutMap.at(FalseBB); + MBBEntryLayoutMap[FalseBB] = FixJumpTargetEntry(ZeroTargetEntryLayout); + const Stack &NonZeroTargetEntryLayout = MBBEntryLayoutMap.at(TrueBB); + MBBEntryLayoutMap[TrueBB] = FixJumpTargetEntry(NonZeroTargetEntryLayout); + VisitSucc(FalseBB); + VisitSucc(TrueBB); + } }); } -Stack StackLayoutGenerator::combineStack(Stack const &Stack1, - Stack const &Stack2) { +Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, + Stack const &Stack2) { // TODO: it would be nicer to replace this by a constructive algorithm. // Currently it uses a reduced version of the Heap Algorithm to partly // brute-force, which seems to work decently well. @@ -630,12 +599,11 @@ Stack StackLayoutGenerator::combineStack(Stack const &Stack1, }; auto DupOrPush = [&](StackSlot const &Slot) { - if (canBeFreelyGenerated(Slot)) + if (isRematerializable(Slot)) return; Stack Tmp = CommonPrefix; Tmp += TestStack; - auto Depth = EVMUtils::findOffset(EVMUtils::get_reverse(Tmp), Slot); if (Depth && *Depth >= 16) NumOps += 1000; @@ -679,7 +647,7 @@ Stack StackLayoutGenerator::combineStack(Stack const &Stack1, return CommonPrefix + BestCandidate; } -Stack StackLayoutGenerator::compressStack(Stack CurStack) { +Stack EVMStackLayoutGenerator::compressStack(Stack CurStack) { std::optional FirstDupOffset; do { if (FirstDupOffset) { @@ -692,7 +660,7 @@ Stack StackLayoutGenerator::compressStack(Stack CurStack) { auto I = CurStack.rbegin(), E = CurStack.rend(); for (size_t Depth = 0; I < E; ++I, ++Depth) { StackSlot &Slot = *I; - if (canBeFreelyGenerated(Slot)) { + if (isRematerializable(Slot)) { FirstDupOffset = CurStack.size() - Depth - 1; break; } @@ -722,7 +690,7 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { }; auto DupOrPush = [&](StackSlot const &Slot) { - if (canBeFreelyGenerated(Slot)) + if (isRematerializable(Slot)) OpGas += 3; else { auto Depth = EVMUtils::findOffset(EVMUtils::get_reverse(Source), Slot); @@ -741,50 +709,35 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { return OpGas; } -void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const &Block) { +void EVMStackLayoutGenerator::fillInJunk(const MachineBasicBlock *Block) { /// Recursively adds junk to the subgraph starting on \p Entry. /// Since it is only called on cut-vertices, the full subgraph retains proper /// stack balance. - auto AddJunkRecursive = [&](CFG::BasicBlock const *Entry, size_t NumJunk) { - EVMUtils::BreadthFirstSearch BFS{{Entry}}; + auto AddJunkRecursive = [&](const MachineBasicBlock *Entry, size_t NumJunk) { + EVMUtils::BreadthFirstSearch BFS{{Entry}}; - BFS.run([&](CFG::BasicBlock const *Block, auto AddChild) { - auto &BlockInfo = Layout.blockInfos.at(Block); - BlockInfo.entryLayout = - Stack{NumJunk, JunkSlot{}} + std::move(BlockInfo.entryLayout); + BFS.run([&](const MachineBasicBlock *Block, auto VisitSucc) { + const Stack &EntryLayout = MBBEntryLayoutMap.at(Block); + MBBEntryLayoutMap[Block] = Stack(NumJunk, JunkSlot{}) + EntryLayout; - for (auto const &Operation : Block->Operations) { - auto &OpEntryLayout = Layout.operationEntryLayout.at(&Operation); - OpEntryLayout = Stack{NumJunk, JunkSlot{}} + std::move(OpEntryLayout); + for (const Operation &Operation : StackModel.getOperations(Block)) { + const Stack &OpEntryLayout = OperationEntryLayoutMap.at(&Operation); + OperationEntryLayoutMap[&Operation] = + Stack(NumJunk, JunkSlot{}) + OpEntryLayout; } - BlockInfo.exitLayout = - Stack{NumJunk, JunkSlot{}} + std::move(BlockInfo.exitLayout); + const Stack &ExitLayout = MBBExitLayoutMap.at(Block); + MBBExitLayoutMap[Block] = Stack(NumJunk, JunkSlot{}) + ExitLayout; - std::visit( - Overload{ - [&](CFG::BasicBlock::InvalidExit const &) { - llvm_unreachable("Unexpected BB terminator"); - }, - [&](CFG::BasicBlock::Jump const &Jump) { AddChild(Jump.Target); }, - [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { - AddChild(ConditionalJump.Zero); - AddChild(ConditionalJump.NonZero); - }, - [&](CFG::BasicBlock::FunctionReturn const &) { - llvm_unreachable("FunctionReturn : unexpected BB terminator"); - }, - [&](CFG::BasicBlock::Terminated const &) {}, - [&](CFG::BasicBlock::Unreachable const &) {}, - }, - Block->Exit); + for (const MachineBasicBlock *Succ : Block->successors()) + VisitSucc(Succ); }); }; /// Returns the number of junk slots to be prepended to \p TargetLayout for /// an optimal transition from \p EntryLayout to \p TargetLayout. - auto GetBestNumJunk = [&](Stack const &EntryLayout, - Stack const &TargetLayout) -> size_t { + auto GetBestNumJunk = [&](const Stack &EntryLayout, + const Stack &TargetLayout) -> size_t { size_t BestCost = EvaluateStackTransform(EntryLayout, TargetLayout); size_t BestNumJunk = 0; size_t MaxJunk = EntryLayout.size(); @@ -800,55 +753,36 @@ void StackLayoutGenerator::fillInJunk(CFG::BasicBlock const &Block) { }; if (MF.getFunction().hasFnAttribute(Attribute::NoReturn) && - Block.AllowsJunk()) { - Stack Params; - for (const auto &Param : Parameters) - Params.emplace_back(Param); + (&MF.front() == Block)) { + Stack Params = StackModel.getFunctionParameters(); std::reverse(Params.begin(), Params.end()); - size_t BestNumJunk = - GetBestNumJunk(Params, Layout.blockInfos.at(&Block).entryLayout); + size_t BestNumJunk = GetBestNumJunk(Params, MBBEntryLayoutMap.at(Block)); if (BestNumJunk > 0) - AddJunkRecursive(&Block, BestNumJunk); + AddJunkRecursive(Block, BestNumJunk); } - /// Traverses the CFG and at each block that allows junk, i.e. that is a /// cut-vertex that never leads to a function return, checks if adding junk /// reduces the shuffling cost upon entering and if so recursively adds junk /// to the spanned subgraph. - EVMUtils::BreadthFirstSearch{{&Block}}.run( - [&](CFG::BasicBlock const *Block, auto AddChild) { - if (Block->AllowsJunk()) { - auto &BlockInfo = Layout.blockInfos.at(Block); - Stack EntryLayout = BlockInfo.entryLayout; + EVMUtils::BreadthFirstSearch{{Block}}.run( + [&](MachineBasicBlock const *Block, auto VisitSucc) { + if (CFGInfo.isCutVertex(Block) && + !CFGInfo.isOnPathToFuncReturn(Block)) { + const Stack EntryLayout = MBBEntryLayoutMap.at(Block); + const Stack &ExitLayout = MBBExitLayoutMap.at(Block); + const std::vector &Ops = StackModel.getOperations(Block); Stack const &NextLayout = - Block->Operations.empty() - ? BlockInfo.exitLayout - : Layout.operationEntryLayout.at(&Block->Operations.front()); + Ops.empty() ? ExitLayout + : OperationEntryLayoutMap.at(&Ops.front()); if (EntryLayout != NextLayout) { size_t BestNumJunk = GetBestNumJunk(EntryLayout, NextLayout); if (BestNumJunk > 0) { AddJunkRecursive(Block, BestNumJunk); - BlockInfo.entryLayout = EntryLayout; + MBBEntryLayoutMap[Block] = EntryLayout; } } } - - std::visit( - Overload{ - [&](CFG::BasicBlock::InvalidExit const &) { - llvm_unreachable("Invalid BB terminator"); - }, - [&](CFG::BasicBlock::Jump const &Jump) { - AddChild(Jump.Target); - }, - [&](CFG::BasicBlock::ConditionalJump const &ConditionalJump) { - AddChild(ConditionalJump.Zero); - AddChild(ConditionalJump.NonZero); - }, - [&](CFG::BasicBlock::FunctionReturn const &) {}, - [&](CFG::BasicBlock::Terminated const &) {}, - [&](CFG::BasicBlock::Unreachable const &) {}, - }, - Block->Exit); + for (const MachineBasicBlock *Succ : Block->successors()) + VisitSucc(Succ); }); } diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h index aed7a5b3d7ba..75ddafce9eae 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h @@ -18,9 +18,9 @@ #ifndef LLVM_LIB_TARGET_EVM_EVMSTACKLAYOUTGENERATOR_H #define LLVM_LIB_TARGET_EVM_EVMSTACKLAYOUTGENERATOR_H -#include "EVMControlFlowGraph.h" -#include -#include +#include "EVMMachineCFGInfo.h" +#include "EVMStackModel.h" +#include "llvm/ADT/DenseMap.h" namespace llvm { @@ -28,22 +28,39 @@ namespace llvm { /// \p Target. size_t EvaluateStackTransform(Stack Source, Stack const &Target); -struct StackLayout { - struct BlockInfo { - /// Complete stack layout that is required for entering a block. - Stack entryLayout; - /// The resulting stack layout after executing the block. - Stack exitLayout; - }; - std::map blockInfos; - /// For each operation the complete stack layout that: - /// - has the slots required for the operation at the stack top. - /// - will have the operation result in a layout that makes it easy to achieve - /// the next desired layout. - std::map operationEntryLayout; +class EVMStackLayout { +public: + EVMStackLayout(DenseMap &MBBEntryLayout, + DenseMap &MBBExitLayout, + DenseMap &OpsEntryLayout) + : MBBEntryLayoutMap(MBBEntryLayout), MBBExitLayoutMap(MBBExitLayout), + OperationEntryLayoutMap(OpsEntryLayout) {} + EVMStackLayout(const EVMStackLayout &) = delete; + EVMStackLayout &operator=(const EVMStackLayout &) = delete; + + const Stack &getMBBEntryLayout(const MachineBasicBlock *MBB) const { + return MBBEntryLayoutMap.at(MBB); + } + + const Stack &getMBBExitLayout(const MachineBasicBlock *MBB) const { + return MBBExitLayoutMap.at(MBB); + } + + const Stack &getOperationEntryLayout(const Operation *Op) const { + return OperationEntryLayoutMap.at(Op); + } + +private: + // Complete stack layout required at MBB entry. + DenseMap MBBEntryLayoutMap; + // Complete stack layout required at MBB exit. + DenseMap MBBExitLayoutMap; + // Complete stack layout that + // has the slots required for the operation at the stack top. + DenseMap OperationEntryLayoutMap; }; -class StackLayoutGenerator { +class EVMStackLayoutGenerator { public: struct StackTooDeep { /// Number of slots that need to be saved. @@ -52,45 +69,44 @@ class StackLayoutGenerator { std::vector variableChoices; }; - static StackLayout run(CFG const &Cfg); - /// Returns all stack too deep errors for the given CFG. - static std::vector reportStackTooDeep(CFG const &Cfg); + EVMStackLayoutGenerator(const MachineFunction &MF, + const EVMStackModel &StackModel, + const EVMMachineCFGInfo &CFGInfo); -private: - StackLayoutGenerator(StackLayout &Layout, const MachineFunction &MF, - const std::vector &Parameters); + std::unique_ptr run(); +private: /// Returns the optimal entry stack layout, s.t. \p Operation can be applied /// to it and the result can be transformed to \p ExitStack with minimal stack /// shuffling. Simultaneously stores the entry layout required for executing - /// the operation in Layout. + /// the operation in the map. Stack propagateStackThroughOperation(Stack ExitStack, - CFG::Operation const &Operation, + Operation const &Operation, bool AggressiveStackCompression = false); /// Returns the desired stack layout at the entry of \p Block, assuming the /// layout after executing the block should be \p ExitStack. Stack propagateStackThroughBlock(Stack ExitStack, - CFG::BasicBlock const &block, + const MachineBasicBlock *Block, bool AggressiveStackCompression = false); /// Main algorithm walking the graph from entry to exit and propagating back /// the stack layouts to the entries. Iteratively reruns itself along /// backwards jumps until the layout is stabilized. - void processEntryPoint(CFG::BasicBlock const &Entry); + void processEntryPoint(const MachineBasicBlock *Entry); /// Returns the best known exit layout of \p Block, if all dependencies are /// already \p Visited. If not, adds the dependencies to \p DependencyList and /// returns std::nullopt. std::optional getExitLayoutOrStageDependencies( - CFG::BasicBlock const &Block, - std::set const &Visited, - std::list &DependencyList) const; + const MachineBasicBlock *Block, + const DenseSet &Visited, + std::list &DependencyList) const; /// Returns a pair of '{jumpingBlock, targetBlock}' for each backwards jump - /// in the graph starting at \p Eentry. - std::list> - collectBackwardsJumps(CFG::BasicBlock const &Entry) const; + /// in the graph starting at \p Entry. + std::list> + collectBackwardsJumps(const MachineBasicBlock *Entry) const; /// After the main algorithms, layouts at conditional jumps are merely /// compatible, i.e. the exit layout of the jumping block is a superset of the @@ -98,17 +114,17 @@ class StackLayoutGenerator { /// of conditional jump targets, s.t., the entry layout of target blocks match /// the exit layout of the jumping block exactly, except that slots not /// required after the jump are marked as 'JunkSlot's. - void stitchConditionalJumps(CFG::BasicBlock const &Block); + void stitchConditionalJumps(const MachineBasicBlock *Block); /// Calculates the ideal stack layout, s.t., both \p Stack1 and \p Stack2 can /// be achieved with minimal stack shuffling when starting from the returned /// layout. - static Stack combineStack(Stack const &Stack1, Stack const &Stack2); + static Stack combineStack(const Stack &Stack1, const Stack &Stack2); /// Walks through the CFG and reports any stack too deep errors that would /// occur when generating code for it without countermeasures. std::vector - reportStackTooDeep(CFG::BasicBlock const &Entry) const; + reportStackTooDeep(const MachineBasicBlock &Entry) const; /// Returns a copy of \p Stack stripped of all duplicates and slots that can /// be freely generated. Attempts to create a layout that requires a minimal @@ -117,11 +133,15 @@ class StackLayoutGenerator { /// Fills in junk when entering branches that do not need a clean stack in /// case the result is cheaper. - void fillInJunk(CFG::BasicBlock const &Block); + void fillInJunk(const MachineBasicBlock *Block); - StackLayout &Layout; - const std::vector &Parameters; const MachineFunction &MF; + const EVMStackModel &StackModel; + const EVMMachineCFGInfo &CFGInfo; + + DenseMap MBBEntryLayoutMap; + DenseMap MBBExitLayoutMap; + DenseMap OperationEntryLayoutMap; }; } // end namespace llvm diff --git a/llvm/lib/Target/EVM/EVMStackModel.cpp b/llvm/lib/Target/EVM/EVMStackModel.cpp new file mode 100644 index 000000000000..1decbd2bff5a --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackModel.cpp @@ -0,0 +1,205 @@ +//===----- EVMEVMStackModel.cpp - EVM Stack Model ---------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// +//===----------------------------------------------------------------------===// + +#include "EVMStackModel.h" +#include "EVM.h" +#include "EVMMachineFunctionInfo.h" +#include "EVMSubtarget.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/MachineFunction.h" + +#include +#include + +using namespace llvm; + +EVMStackModel::EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS) + : MF(MF), LIS(LIS) { + for (MachineBasicBlock &MBB : MF) { + std::vector Ops; + for (MachineInstr &MI : MBB) + createOperation(MI, Ops); + OperationsMap[&MBB] = std::move(Ops); + } +} + +Stack EVMStackModel::getFunctionParameters() const { + auto *MFI = MF.getInfo(); + std::vector Parameters(MFI->getNumParams(), JunkSlot{}); + for (const MachineInstr &MI : MF.front()) { + if (MI.getOpcode() == EVM::ARGUMENT) { + int64_t ArgIdx = MI.getOperand(1).getImm(); + Parameters[ArgIdx] = VariableSlot{MI.getOperand(0).getReg()}; + } + } + return Parameters; +} + +StackSlot EVMStackModel::getStackSlot(const MachineOperand &MO) const { + const MachineInstr *MI = MO.getParent(); + StackSlot Slot = VariableSlot{MO.getReg()}; + SlotIndex Idx = LIS.getInstructionIndex(*MI); + const LiveInterval *LI = &LIS.getInterval(MO.getReg()); + LiveQueryResult LRQ = LI->Query(Idx); + const VNInfo *VNI = LRQ.valueIn(); + assert(VNI && "Use of non-existing value"); + // If the virtual register defines a constant and this is the only + // definition, emit the literal slot as MI's input. + if (LI->containsOneValue()) { + assert(!VNI->isPHIDef()); + const MachineInstr *DefMI = LIS.getInstructionFromIndex(VNI->def); + assert(DefMI && "Dead valno in interval"); + if (DefMI->getOpcode() == EVM::CONST_I256) { + const APInt Imm = DefMI->getOperand(1).getCImm()->getValue(); + Slot = LiteralSlot{std::move(Imm)}; + } + } + + return Slot; +} + +Stack EVMStackModel::getInstrInput(const MachineInstr &MI) const { + Stack In; + for (const auto &MO : reverse(MI.explicit_uses())) { + // All the non-register operands are handled in instruction specific + // handlers. + if (!MO.isReg()) + continue; + + // SP is not used anyhow. + if (MO.getReg() == EVM::SP) + continue; + + In.emplace_back(getStackSlot(MO)); + } + return In; +} + +Stack EVMStackModel::getInstrOutput(const MachineInstr &MI) const { + Stack Out; + unsigned ArgNumber = 0; + for (const auto &MO : MI.defs()) + Out.push_back(TemporarySlot{&MI, MO.getReg(), ArgNumber++}); + return Out; +} + +void EVMStackModel::createOperation(MachineInstr &MI, + std::vector &Ops) const { + unsigned Opc = MI.getOpcode(); + switch (Opc) { + case EVM::STACK_LOAD: + case EVM::STACK_STORE: + llvm_unreachable("Unexpected stack memory instruction"); + return; + case EVM::ARGUMENT: + // Is handled above. + return; + case EVM::FCALL: { + Stack Input; + bool IsNoReturn = false; + for (const MachineOperand &MO : MI.operands()) { + if (MO.isGlobal()) { + const auto *Func = dyn_cast(MO.getGlobal()); + assert(Func); + IsNoReturn = Func->hasFnAttribute(Attribute::NoReturn); + if (!IsNoReturn) + Input.push_back(FunctionCallReturnLabelSlot{&MI}); + break; + } + } + const Stack &Tmp = getInstrInput(MI); + Input.insert(Input.end(), Tmp.begin(), Tmp.end()); + size_t NumArgs = Input.size() - (IsNoReturn ? 0 : 1); + Ops.emplace_back(Operation{std::move(Input), getInstrOutput(MI), + FunctionCall{&MI, NumArgs}}); + } break; + case EVM::RET: + case EVM::JUMP: + case EVM::JUMPI: + // These instructions are handled separately. + return; + case EVM::COPY_I256: + case EVM::DATASIZE: + case EVM::DATAOFFSET: + case EVM::LINKERSYMBOL: + // The copy/data instructions just represent an assignment. This case is + // handled below. + break; + case EVM::CONST_I256: { + const LiveInterval *LI = &LIS.getInterval(MI.getOperand(0).getReg()); + // If the virtual register has the only definition, ignore this instruction, + // as we create literal slots from the immediate value at the register uses. + if (LI->containsOneValue()) + return; + } break; + default: { + Ops.emplace_back( + Operation{getInstrInput(MI), getInstrOutput(MI), BuiltinCall{&MI}}); + } break; + } + + // Cretae CFG::Assignment object for the MI. + Stack Input, Output; + std::vector Variables; + switch (MI.getOpcode()) { + case EVM::CONST_I256: { + const Register DefReg = MI.getOperand(0).getReg(); + const APInt Imm = MI.getOperand(1).getCImm()->getValue(); + Input.push_back(LiteralSlot{std::move(Imm)}); + Output.push_back(VariableSlot{DefReg}); + Variables.push_back(VariableSlot{DefReg}); + } break; + case EVM::DATASIZE: + case EVM::DATAOFFSET: + case EVM::LINKERSYMBOL: { + const Register DefReg = MI.getOperand(0).getReg(); + MCSymbol *Sym = MI.getOperand(1).getMCSymbol(); + Input.push_back(SymbolSlot{Sym, &MI}); + Output.push_back(VariableSlot{DefReg}); + Variables.push_back(VariableSlot{DefReg}); + } break; + case EVM::COPY_I256: { + // Copy instruction corresponds to the assignment operator, so + // we do not need to create intermediate TmpSlots. + Input = getInstrInput(MI); + const Register DefReg = MI.getOperand(0).getReg(); + Output.push_back(VariableSlot{DefReg}); + Variables.push_back(VariableSlot{DefReg}); + } break; + default: { + unsigned ArgsNumber = 0; + for (const auto &MO : MI.defs()) { + assert(MO.isReg()); + const Register Reg = MO.getReg(); + Input.push_back(TemporarySlot{&MI, Reg, ArgsNumber++}); + Output.push_back(VariableSlot{Reg}); + Variables.push_back(VariableSlot{Reg}); + } + } break; + } + // We don't need an assignment part of the instructions that do not write + // results. + if (!Input.empty() || !Output.empty()) + Ops.emplace_back(Operation{std::move(Input), std::move(Output), + Assignment{std::move(Variables)}}); +} + +Stack EVMStackModel::getReturnArguments(const MachineInstr &MI) const { + assert(MI.getOpcode() == EVM::RET); + Stack Input = getInstrInput(MI); + // We need to reverse input operands to restore original ordering, + // in the instruction. + // Calling convention: return values are passed in stack such that the + // last one specified in the RET instruction is passed on the stack TOP. + std::reverse(Input.begin(), Input.end()); + Input.emplace_back(FunctionReturnLabelSlot{&MF}); + return Input; +} diff --git a/llvm/lib/Target/EVM/EVMStackModel.h b/llvm/lib/Target/EVM/EVMStackModel.h new file mode 100644 index 000000000000..08ddf2017ed1 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMStackModel.h @@ -0,0 +1,218 @@ +//===------------- EVMStackModel.h - Stack Model ----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a representation used by the backwards propagation +// stackification algorithm. It consists of 'Operation', 'StackSlot' and 'Stack' +// entities. New stack representation is derived from Machine IR as following: +// MachineInstr -> Operation +// MachineOperand -> StackSlot +// MI's defs/uses -> Stack (array of StackSlot) +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMSTACKMODEL_H +#define LLVM_LIB_TARGET_EVM_EVMSTACKMODEL_H + +#include "llvm/ADT/APInt.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/CodeGen/LiveIntervals.h" +#include "llvm/CodeGen/MachineInstr.h" +#include "llvm/CodeGen/MachineLoopInfo.h" +#include "llvm/CodeGen/Register.h" +#include "llvm/MC/MCSymbol.h" + +#include + +namespace llvm { + +class MachineFunction; +class MachineBasicBlock; + +/// The following structs describe different kinds of stack slots. +/// Each stack slot is equality- and less-than-comparable and +/// specifies an attribute 'isRematerializable' that is true, +/// if a slot of this kind always has a known value at compile time and +/// therefore can safely be removed from the stack at any time and then +/// regenerated later. + +/// The label pushed as return label before a function call, i.e. the label the +/// call is supposed to return to. +struct FunctionCallReturnLabelSlot { + const MachineInstr *Call = nullptr; + static constexpr bool isRematerializable = true; + + bool operator==(FunctionCallReturnLabelSlot const &Rhs) const { + return Call == Rhs.Call; + } + + bool operator<(FunctionCallReturnLabelSlot const &Rhs) const { + return Call < Rhs.Call; + } +}; + +/// The return jump target of a function while generating the code of the +/// function body. I.e. the caller of a function pushes a +/// 'FunctionCallReturnLabelSlot' (see above) before jumping to the function +/// and this very slot is viewed as 'FunctionReturnLabelSlot' inside the +/// function body and jumped to when returning from the function. +struct FunctionReturnLabelSlot { + const MachineFunction *MF = nullptr; + static constexpr bool isRematerializable = false; + + bool operator==(FunctionReturnLabelSlot const &Rhs) const { + // There can never be return label slots of different functions on stack + // simultaneously. + assert(MF == Rhs.MF); + return true; + } + + bool operator<(FunctionReturnLabelSlot const &Rhs) const { + // There can never be return label slots of different functions on stack + // simultaneously. + assert(MF == Rhs.MF); + return false; + } +}; + +/// A slot containing the current value of a particular variable. +struct VariableSlot { + Register VirtualReg; + static constexpr bool isRematerializable = false; + + bool operator==(VariableSlot const &Rhs) const { + return VirtualReg == Rhs.VirtualReg; + } + + bool operator<(VariableSlot const &Rhs) const { + return VirtualReg < Rhs.VirtualReg; + } +}; + +/// A slot containing a literal value. +struct LiteralSlot { + APInt Value; + static constexpr bool isRematerializable = true; + + bool operator==(LiteralSlot const &Rhs) const { return Value == Rhs.Value; } + + bool operator<(LiteralSlot const &Rhs) const { return Value.ult(Rhs.Value); } +}; + +/// A slot containing a MCSymbol. +struct SymbolSlot { + MCSymbol *Symbol; + const MachineInstr *MI = nullptr; + static constexpr bool isRematerializable = true; + + bool operator==(SymbolSlot const &Rhs) const { + return Symbol == Rhs.Symbol && MI->getOpcode() == Rhs.MI->getOpcode(); + } + + bool operator<(SymbolSlot const &Rhs) const { + return std::make_pair(Symbol, MI->getOpcode()) < + std::make_pair(Rhs.Symbol, Rhs.MI->getOpcode()); + } +}; + +/// A slot containing the index-th return value of a previous call. +struct TemporarySlot { + /// The call that returned this slot. + const MachineInstr *MI = nullptr; + + Register VirtualReg; + /// Specifies to which of the values returned by the call this slot refers. + /// index == 0 refers to the slot deepest in the stack after the call. + size_t Index = 0; + static constexpr bool isRematerializable = false; + + bool operator==(TemporarySlot const &Rhs) const { + return MI == Rhs.MI && Index == Rhs.Index; + } + + bool operator<(TemporarySlot const &Rhs) const { + return std::make_pair(MI, Index) < std::make_pair(Rhs.MI, Rhs.Index); + } +}; + +/// A slot containing an arbitrary value that is always eventually popped and +/// never used. Used to maintain stack balance on control flow joins. +struct JunkSlot { + static constexpr bool isRematerializable = true; + + bool operator==(JunkSlot const &) const { return true; } + + bool operator<(JunkSlot const &) const { return false; } +}; + +using StackSlot = + std::variant; + +/// The stack top is the last element of the vector. +using Stack = std::vector; + +/// Returns true if Slot can be materialized on the stack at any time. +inline bool isRematerializable(const StackSlot &Slot) { + return std::visit( + [](auto const &TypedSlot) { + return std::decay_t::isRematerializable; + }, + Slot); +} + +struct BuiltinCall { + MachineInstr *MI = nullptr; +}; + +struct FunctionCall { + const MachineInstr *MI; + size_t NumArguments = 0; +}; + +struct Assignment { + /// The variables being assigned to also occur as 'Output' in the + /// 'Operation' containing the assignment, but are also stored here for + /// convenience. + std::vector Variables; +}; + +struct Operation { + /// Stack slots this operation expects at the top of the stack and consumes. + Stack Input; + /// Stack slots this operation leaves on the stack as output. + Stack Output; + std::variant Operation; +}; + +class EVMStackModel { +public: + EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS); + Stack getFunctionParameters() const; + StackSlot getStackSlot(const MachineOperand &MO) const; + Stack getInstrInput(const MachineInstr &MI) const; + Stack getInstrOutput(const MachineInstr &MI) const; + Stack getReturnArguments(const MachineInstr &MI) const; + const std::vector & + getOperations(const MachineBasicBlock *MBB) const { + return OperationsMap.at(MBB); + } + +private: + void createOperation(MachineInstr &MI, std::vector &Ops) const; + + MachineFunction &MF; + const LiveIntervals &LIS; + std::map> OperationsMap; +}; + +} // namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H diff --git a/llvm/lib/Target/EVM/EVMStackShuffler.h b/llvm/lib/Target/EVM/EVMStackShuffler.h index 52432682a32d..170426407974 100644 --- a/llvm/lib/Target/EVM/EVMStackShuffler.h +++ b/llvm/lib/Target/EVM/EVMStackShuffler.h @@ -16,8 +16,8 @@ #ifndef LLVM_LIB_TARGET_EVM_EVMSTACKSHUFFLER_H #define LLVM_LIB_TARGET_EVM_EVMSTACKSHUFFLER_H -#include "EVMControlFlowGraph.h" #include "EVMHelperUtilities.h" +#include "EVMStackModel.h" #include "llvm/CodeGen/MachineFunction.h" #include #include diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index 9c2a06d32f60..ba3ae0151460 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -11,6 +11,7 @@ //===----------------------------------------------------------------------===// #include "EVMStackifyCodeEmitter.h" +#include "EVMHelperUtilities.h" #include "EVMMachineFunctionInfo.h" #include "EVMStackDebug.h" #include "EVMStackShuffler.h" @@ -21,16 +22,6 @@ using namespace llvm; #define DEBUG_TYPE "evm-stackify-code-emitter" -// Return whether the function of the call instruction will return. -static bool callWillReturn(const MachineInstr *Call) { - assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); - const MachineOperand *FuncOp = Call->explicit_uses().begin(); - assert(FuncOp->isGlobal() && "Expected a global value"); - const auto *Func = dyn_cast(FuncOp->getGlobal()); - assert(Func && "Expected a function"); - return !Func->hasFnAttribute(Attribute::NoReturn); -} - // Return the number of input arguments of the call instruction. static size_t getCallArgCount(const MachineInstr *Call) { assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); @@ -42,7 +33,7 @@ static size_t getCallArgCount(const MachineInstr *Call) { // The first operand is a function, so don't count it. If function // will return, we need to account for the return label. constexpr size_t NumFuncOp = 1; - return NumExplicitInputs - NumFuncOp + callWillReturn(Call); + return NumExplicitInputs - NumFuncOp + EVMUtils::callWillReturn(Call); } size_t EVMStackifyCodeEmitter::CodeEmitter::stackHeight() const { @@ -158,7 +149,7 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitFuncCall(const MachineInstr *MI) { // If this function returns, add a return label so we can emit it together // with JUMPDEST. This is taken care in the AsmPrinter. - if (callWillReturn(MI)) + if (EVMUtils::callWillReturn(MI)) NewMI.addSym(CallReturnSyms.at(MI)); verify(NewMI); } @@ -234,39 +225,39 @@ void EVMStackifyCodeEmitter::adjustStackForInst(const MachineInstr *MI, assert(Emitter.stackHeight() == CurrentStack.size()); } -void EVMStackifyCodeEmitter::visitCall(const CFG::FunctionCall &Call) { - size_t NumArgs = getCallArgCount(Call.Call); +void EVMStackifyCodeEmitter::visitCall(const FunctionCall &Call) { + size_t NumArgs = getCallArgCount(Call.MI); // Validate stack. assert(Emitter.stackHeight() == CurrentStack.size()); assert(CurrentStack.size() >= NumArgs); // Assert that we got the correct return label on stack. - if (callWillReturn(Call.Call)) { + if (EVMUtils::callWillReturn(Call.MI)) { [[maybe_unused]] const auto *returnLabelSlot = std::get_if( &CurrentStack.at(CurrentStack.size() - NumArgs)); - assert(returnLabelSlot && returnLabelSlot->Call == Call.Call); + assert(returnLabelSlot && returnLabelSlot->Call == Call.MI); } // Emit call. - Emitter.emitFuncCall(Call.Call); - adjustStackForInst(Call.Call, NumArgs); + Emitter.emitFuncCall(Call.MI); + adjustStackForInst(Call.MI, NumArgs); } -void EVMStackifyCodeEmitter::visitInst(const CFG::BuiltinCall &Call) { - size_t NumArgs = Call.Builtin->getNumExplicitOperands() - - Call.Builtin->getNumExplicitDefs(); +void EVMStackifyCodeEmitter::visitInst(const BuiltinCall &Call) { + size_t NumArgs = + Call.MI->getNumExplicitOperands() - Call.MI->getNumExplicitDefs(); // Validate stack. assert(Emitter.stackHeight() == CurrentStack.size()); assert(CurrentStack.size() >= NumArgs); // TODO: assert that we got a correct stack for the call. // Emit instruction. - Emitter.emitInst(Call.Builtin); - adjustStackForInst(Call.Builtin, NumArgs); + Emitter.emitInst(Call.MI); + adjustStackForInst(Call.MI, NumArgs); } -void EVMStackifyCodeEmitter::visitAssign(const CFG::Assignment &Assignment) { +void EVMStackifyCodeEmitter::visitAssign(const Assignment &Assignment) { assert(Emitter.stackHeight() == CurrentStack.size()); // Invalidate occurrences of the assigned variables. @@ -353,7 +344,7 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { Emitter.emitDUP(static_cast(Depth + 1)); return; } - if (!canBeFreelyGenerated(Slot)) { + if (!isRematerializable(Slot)) { std::string VarName = SlotVariableName(Slot); std::string Msg = ((VarName.empty() ? "slot " + stackSlotToString(Slot) @@ -404,15 +395,15 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { assert(Emitter.stackHeight() == CurrentStack.size()); } -void EVMStackifyCodeEmitter::createOperationLayout(const CFG::Operation &Op) { +void EVMStackifyCodeEmitter::createOperationLayout(const Operation &Op) { // Create required layout for entering the Operation. // Check if we can choose cheaper stack shuffling if the Operation is an // instruction with commutable arguments. bool SwapCommutable = false; - if (const auto *Inst = std::get_if(&Op.Operation); - Inst && Inst->Builtin->isCommutable()) { + if (const auto *Inst = std::get_if(&Op.Operation); + Inst && Inst->MI->isCommutable()) { // Get the stack layout before the instruction. - const Stack &DefaultTargetStack = Layout.operationEntryLayout.at(&Op); + const Stack &DefaultTargetStack = Layout.getOperationEntryLayout(&Op); size_t DefaultCost = EvaluateStackTransform(CurrentStack, DefaultTargetStack); @@ -432,7 +423,7 @@ void EVMStackifyCodeEmitter::createOperationLayout(const CFG::Operation &Op) { createStackLayout(SwapCommutable ? CommutedTargetStack : DefaultTargetStack); } else { - createStackLayout(Layout.operationEntryLayout.at(&Op)); + createStackLayout(Layout.getOperationEntryLayout(&Op)); } // Assert that we have the inputs of the Operation on stack top. @@ -447,36 +438,33 @@ void EVMStackifyCodeEmitter::createOperationLayout(const CFG::Operation &Op) { assert(areLayoutsCompatible(StackInput, Op.Input)); } -void EVMStackifyCodeEmitter::run(CFG::BasicBlock &EntryBB) { +void EVMStackifyCodeEmitter::run() { assert(CurrentStack.empty() && Emitter.stackHeight() == 0); - SmallPtrSet Visited; - SmallVector WorkList{&EntryBB}; + SmallPtrSet Visited; + SmallVector WorkList{&MF.front()}; while (!WorkList.empty()) { auto *Block = WorkList.pop_back_val(); if (!Visited.insert(Block).second) continue; - const auto &BlockInfo = Layout.blockInfos.at(Block); - // Might set some slots to junk, if not required by the block. - CurrentStack = BlockInfo.entryLayout; - Emitter.enterMBB(Block->MBB, CurrentStack.size()); + CurrentStack = Layout.getMBBEntryLayout(Block); + Emitter.enterMBB(Block, CurrentStack.size()); - for (const auto &Operation : Block->Operations) { + for (const auto &Operation : StackModel.getOperations(Block)) { createOperationLayout(Operation); [[maybe_unused]] size_t BaseHeight = CurrentStack.size() - Operation.Input.size(); // Perform the Operation. - std::visit( - Overload{[this](const CFG::FunctionCall &Call) { visitCall(Call); }, - [this](const CFG::BuiltinCall &Call) { visitInst(Call); }, - [this](const CFG::Assignment &Assignment) { - visitAssign(Assignment); - }}, - Operation.Operation); + std::visit(Overload{[this](const FunctionCall &Call) { visitCall(Call); }, + [this](const BuiltinCall &Call) { visitInst(Call); }, + [this](const Assignment &Assignment) { + visitAssign(Assignment); + }}, + Operation.Operation); // Assert that the Operation produced its proclaimed output. assert(CurrentStack.size() == Emitter.stackHeight()); @@ -489,83 +477,57 @@ void EVMStackifyCodeEmitter::run(CFG::BasicBlock &EntryBB) { } // Exit the block. - std::visit( - Overload{ - [&](const CFG::BasicBlock::InvalidExit &) { - llvm_unreachable("Unexpected BB terminator"); - }, - [&](const CFG::BasicBlock::Jump &Jump) { - // Create the stack expected at the jump target. - createStackLayout(Layout.blockInfos.at(Jump.Target).entryLayout); - - // Assert that we have a valid stack for the target. - assert(areLayoutsCompatible( - CurrentStack, Layout.blockInfos.at(Jump.Target).entryLayout)); - - if (Jump.UncondJump) - Emitter.emitUncondJump(Jump.UncondJump, Jump.Target->MBB); - WorkList.emplace_back(Jump.Target); - }, - [&](CFG::BasicBlock::ConditionalJump const &CondJump) { - // Create the shared entry layout of the jump targets, which is - // stored as exit layout of the current block. - createStackLayout(BlockInfo.exitLayout); - - // Assert that we have the correct condition on stack. - assert(!CurrentStack.empty()); - assert(CurrentStack.back() == CondJump.Condition); - - // Emit the conditional jump to the non-zero label and update the - // stored stack. - assert(CondJump.CondJump); - Emitter.emitCondJump(CondJump.CondJump, CondJump.NonZero->MBB); - CurrentStack.pop_back(); - - // Assert that we have a valid stack for both jump targets. - assert(areLayoutsCompatible( - CurrentStack, - Layout.blockInfos.at(CondJump.NonZero).entryLayout)); - assert(areLayoutsCompatible( - CurrentStack, - Layout.blockInfos.at(CondJump.Zero).entryLayout)); - - // Generate unconditional jump if needed. - if (CondJump.UncondJump) - Emitter.emitUncondJump(CondJump.UncondJump, CondJump.Zero->MBB); - WorkList.emplace_back(CondJump.NonZero); - WorkList.emplace_back(CondJump.Zero); - }, - [&](CFG::BasicBlock::FunctionReturn const &FuncReturn) { - assert(!MF.getFunction().hasFnAttribute(Attribute::NoReturn)); - - // Construct the function return layout, which is fully determined - // by the function signature. - Stack ExitStack = FuncReturn.RetValues; - - ExitStack.emplace_back(FunctionReturnLabelSlot{&MF}); - - // Create the function return layout and jump. - createStackLayout(ExitStack); - Emitter.emitRet(FuncReturn.Ret); - }, - [&](CFG::BasicBlock::Unreachable const &) { - assert(Block->Operations.empty()); - }, - [&](CFG::BasicBlock::Terminated const &) { - assert(!Block->Operations.empty()); - if (const CFG::BuiltinCall *BuiltinCall = - std::get_if( - &Block->Operations.back().Operation)) - assert(BuiltinCall->TerminatesOrReverts); - else if (CFG::FunctionCall const *FunctionCall = - std::get_if( - &Block->Operations.back().Operation)) - assert(!callWillReturn(FunctionCall->Call)); - else - llvm_unreachable("Unexpected BB terminator"); - }}, - Block->Exit); + const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(Block); + MBBExitType ExitType = TermInfo->getExitType(); + if (ExitType == MBBExitType::UnconditionalBranch) { + auto [UncondBr, Target, _] = TermInfo->getUnconditionalBranch(); + // Create the stack expected at the jump target. + createStackLayout(Layout.getMBBEntryLayout(Target)); + + // Assert that we have a valid stack for the target. + assert( + areLayoutsCompatible(CurrentStack, Layout.getMBBEntryLayout(Target))); + + if (UncondBr) + Emitter.emitUncondJump(UncondBr, Target); + WorkList.emplace_back(Target); + } else if (ExitType == MBBExitType::ConditionalBranch) { + auto [CondBr, UncondBr, TrueBB, FalseBB, Condition] = + TermInfo->getConditionalBranch(); + // Create the shared entry layout of the jump targets, which is + // stored as exit layout of the current block. + createStackLayout(Layout.getMBBExitLayout(Block)); + + // Assert that we have the correct condition on stack. + assert(!CurrentStack.empty()); + assert(CurrentStack.back() == StackModel.getStackSlot(*Condition)); + + // Emit the conditional jump to the non-zero label and update the + // stored stack. + assert(CondBr); + Emitter.emitCondJump(CondBr, TrueBB); + CurrentStack.pop_back(); + + // Assert that we have a valid stack for both jump targets. + assert( + areLayoutsCompatible(CurrentStack, Layout.getMBBEntryLayout(TrueBB))); + assert(areLayoutsCompatible(CurrentStack, + Layout.getMBBEntryLayout(FalseBB))); + + // Generate unconditional jump if needed. + if (UncondBr) + Emitter.emitUncondJump(UncondBr, FalseBB); + WorkList.emplace_back(TrueBB); + WorkList.emplace_back(FalseBB); + } else if (ExitType == MBBExitType::FunctionReturn) { + assert(!MF.getFunction().hasFnAttribute(Attribute::NoReturn)); + // Create the function return layout and jump. + const MachineInstr *MI = TermInfo->getFunctionReturn(); + assert(StackModel.getReturnArguments(*MI) == + Layout.getMBBExitLayout(Block)); + createStackLayout(Layout.getMBBExitLayout(Block)); + Emitter.emitRet(MI); + } } - Emitter.finalize(); } diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h index 15223de52637..bfacc58200c3 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h @@ -23,11 +23,14 @@ class MCSymbol; class EVMStackifyCodeEmitter { public: - EVMStackifyCodeEmitter(const StackLayout &Layout, MachineFunction &MF) - : Emitter(MF), Layout(Layout), MF(MF) {} + EVMStackifyCodeEmitter(const EVMStackLayout &Layout, + const EVMStackModel &StackModel, + const EVMMachineCFGInfo &CFGInfo, MachineFunction &MF) + : Emitter(MF), Layout(Layout), StackModel(StackModel), CFGInfo(CFGInfo), + MF(MF) {} - /// Stackify instructions, starting from the \p EntryBB. - void run(CFG::BasicBlock &EntryBB); + /// Stackify instructions, starting from the first MF's MBB. + void run(); private: class CodeEmitter { @@ -64,8 +67,10 @@ class EVMStackifyCodeEmitter { }; CodeEmitter Emitter; - const StackLayout &Layout; - const MachineFunction &MF; + const EVMStackLayout &Layout; + const EVMStackModel &StackModel; + const EVMMachineCFGInfo &CFGInfo; + MachineFunction &MF; Stack CurrentStack; /// Checks if it's valid to transition from \p SourceStack to \p @@ -78,17 +83,17 @@ class EVMStackifyCodeEmitter { /// Creates the Op.Input stack layout from the 'CurrentStack' taking into /// account commutative property of the operation. - void createOperationLayout(const CFG::Operation &Op); + void createOperationLayout(const Operation &Op); /// Remove the arguments from the stack and push the return values. void adjustStackForInst(const MachineInstr *MI, size_t NumArgs); /// Generate code for the function call \p Call. - void visitCall(const CFG::FunctionCall &Call); + void visitCall(const FunctionCall &Call); /// Generate code for the builtin call \p Call. - void visitInst(const CFG::BuiltinCall &Call); + void visitInst(const BuiltinCall &Call); /// Generate code for the assignment \p Assignment. - void visitAssign(const CFG::Assignment &Assignment); + void visitAssign(const Assignment &Assignment); }; } // namespace llvm diff --git a/llvm/unittests/Target/EVM/StackShuffler.cpp b/llvm/unittests/Target/EVM/StackShuffler.cpp index 82bfde1b5e6a..8324d2730723 100644 --- a/llvm/unittests/Target/EVM/StackShuffler.cpp +++ b/llvm/unittests/Target/EVM/StackShuffler.cpp @@ -6,9 +6,9 @@ // //===----------------------------------------------------------------------===// -#include "EVMControlFlowGraph.h" #include "EVMRegisterInfo.h" #include "EVMStackDebug.h" +#include "EVMStackModel.h" #include "EVMStackShuffler.h" #include "EVMTargetMachine.h" #include "MCTargetDesc/EVMMCTargetDesc.h" @@ -172,7 +172,7 @@ PUSH JUNK\n\ }, [&](StackSlot const &Slot) { // dupOrPush Output << stackToString(SourceStack) << '\n'; - if (canBeFreelyGenerated(Slot)) + if (isRematerializable(Slot)) Output << "PUSH " << stackSlotToString(Slot) << '\n'; else { Stack TmpStack = SourceStack; From bcfffd148d4fbd68a320e561b3ccd4b09c3cef49 Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 20 Dec 2024 15:43:09 +0100 Subject: [PATCH 16/42] [EVM] Replace std::containers/algorithms with the llvm's counterparts --- llvm/lib/Target/EVM/EVMHelperUtilities.h | 105 +++---------- llvm/lib/Target/EVM/EVMStackDebug.cpp | 2 +- .../Target/EVM/EVMStackLayoutGenerator.cpp | 144 +++++++++--------- llvm/lib/Target/EVM/EVMStackLayoutGenerator.h | 4 +- llvm/lib/Target/EVM/EVMStackModel.cpp | 8 +- llvm/lib/Target/EVM/EVMStackModel.h | 10 +- llvm/lib/Target/EVM/EVMStackShuffler.h | 40 ++--- .../lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 5 +- 8 files changed, 122 insertions(+), 196 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMHelperUtilities.h b/llvm/lib/Target/EVM/EVMHelperUtilities.h index 2ff5849f493a..27c9ff54999d 100644 --- a/llvm/lib/Target/EVM/EVMHelperUtilities.h +++ b/llvm/lib/Target/EVM/EVMHelperUtilities.h @@ -15,14 +15,13 @@ #ifndef LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H #define LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H +#include "llvm/ADT/DenseSet.h" +#include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/iterator_range.h" -#include -#include -#include +#include #include #include -#include namespace llvm { @@ -33,90 +32,32 @@ template struct Overload : Ts... { }; template Overload(Ts...) -> Overload; -template static inline SmallVector iota17(T begin, T end) { - SmallVector R(end - begin); - std::iota(R.begin(), R.end(), begin); - return R; -} - -/// Concatenate the contents of a container into a vector -template -std::vector &operator+=(std::vector &lhs, U &rhs) { - for (auto const &elm : rhs) - lhs.push_back(T(elm)); - return lhs; -} - -template -std::vector &operator+=(std::vector &lhs, U &&rhs) { - std::move(rhs.begin(), rhs.end(), std::back_inserter(lhs)); - return lhs; -} - -/// Concatenate two vectors of elements. -template -inline std::vector operator+(std::vector const &lhs, - std::vector const &rhs) { - std::vector result(lhs); - result += rhs; - return result; -} - -/// Concatenate two vectors of elements, moving them. -template -inline std::vector operator+(std::vector &&lhs, std::vector &&rhs) { - std::vector result(std::move(lhs)); - assert(&lhs != &rhs); - result += std::move(rhs); - return result; -} - namespace EVMUtils { - bool callWillReturn(const MachineInstr *Call); -template bool contains(const T &t, const V &v) { - return std::end(t) != std::find(std::begin(t), std::end(t), v); +/// Return a vector, containing sequentially increasing values from \p Begin +/// to \p End. +template static inline SmallVector iota(T Begin, T End) { + SmallVector R(End - Begin); + std::iota(R.begin(), R.end(), Begin); + return R; } +/// Return the number of hops from the beginning of the \p RangeOrContainer +/// to the \p Item. If no \p Item is found in the \p RangeOrContainer, +/// std::nullopt is returned. template -std::optional findOffset(T &&t, V &&v) { - auto begin = std::begin(t); - auto end = std::end(t); - auto it = std::find(begin, end, std::forward(v)); - if (it == end) - return std::nullopt; - return std::distance(begin, it); -} - -template -auto take_last(T &&t, size_t N) -> iterator_range { - auto it = t.end(); - std::advance(it, -N); - return make_range(it, t.end()); -} - -template -auto drop_first(T &&t, size_t N) -> iterator_range { - auto it = t.begin(); - std::advance(it, N); - return make_range(it, t.end()); -} - -template -iterator_range get_reverse(const T &t) { - return llvm::make_range(t.rbegin(), t.rend()); -} - -template auto to_vector(R &&r) { - std::vector v; - v.assign(r.begin(), r.end()); - return v; +std::optional offset(T &&RangeOrContainer, V &&Item) { + auto It = find(RangeOrContainer, Item); + return (It == adl_end(RangeOrContainer)) + ? std::nullopt + : std::optional(std::distance(adl_begin(RangeOrContainer), It)); } -template void emplace_back_unique(T &t, V &&v) { - if (t.end() == std::find(t.begin(), t.end(), v)) - t.emplace_back(v); +/// Return a range covering the last N elements of \p RangeOrContainer. +template auto take_back(T &&RangeOrContainer, size_t N = 1) { + return make_range(std::prev(adl_end(RangeOrContainer), N), + adl_end(RangeOrContainer)); } /// Generic breadth first search. @@ -159,8 +100,8 @@ template struct BreadthFirstSearch { void abort() { verticesToTraverse.clear(); } - std::list verticesToTraverse; - std::set visited{}; + std::deque verticesToTraverse; + DenseSet visited{}; }; } // namespace EVMUtils diff --git a/llvm/lib/Target/EVM/EVMStackDebug.cpp b/llvm/lib/Target/EVM/EVMStackDebug.cpp index d91a04627580..0752c768c9df 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.cpp +++ b/llvm/lib/Target/EVM/EVMStackDebug.cpp @@ -130,7 +130,7 @@ void StackLayoutPrinter::printBlock(MachineBasicBlock const &Block) { assert(Operation.Input.size() <= EntryLayout.size()); for (size_t i = 0; i < Operation.Input.size(); ++i) EntryLayout.pop_back(); - EntryLayout += Operation.Output; + EntryLayout.append(Operation.Output); OS << stackToString(EntryLayout) << "\n"; } OS << "\n"; diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp index 634fbc419a15..b5e5961e65da 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -34,16 +34,16 @@ using namespace llvm; namespace { /// Returns all stack too deep errors that would occur when shuffling \p Source /// to \p Target. -std::vector +SmallVector findStackTooDeep(Stack const &Source, Stack const &Target) { Stack CurrentStack = Source; - std::vector Errors; + SmallVector Errors; auto getVariableChoices = [](auto &&SlotRange) { - std::vector Result; + SmallVector Result; for (auto const &Slot : SlotRange) if (auto const *VarSlot = std::get_if(&Slot)) - if (!EVMUtils::contains(Result, VarSlot->VirtualReg)) + if (!is_contained(Result, VarSlot->VirtualReg)) Result.push_back(VarSlot->VirtualReg); return Result; }; @@ -54,18 +54,17 @@ findStackTooDeep(Stack const &Source, Stack const &Target) { if (I > 16) Errors.emplace_back(EVMStackLayoutGenerator::StackTooDeep{ I - 16, - getVariableChoices(EVMUtils::take_last(CurrentStack, I + 1))}); + getVariableChoices(EVMUtils::take_back(CurrentStack, I + 1))}); }, [&](StackSlot const &Slot) { if (isRematerializable(Slot)) return; - if (auto depth = - EVMUtils::findOffset(EVMUtils::get_reverse(CurrentStack), Slot); - depth && *depth >= 16) + if (auto Depth = EVMUtils::offset(reverse(CurrentStack), Slot); + Depth && *Depth >= 16) Errors.emplace_back(EVMStackLayoutGenerator::StackTooDeep{ - *depth - 15, getVariableChoices( - EVMUtils::take_last(CurrentStack, *depth + 1))}); + *Depth - 15, getVariableChoices( + EVMUtils::take_back(CurrentStack, *Depth + 1))}); }, [&]() {}); return Errors; @@ -82,7 +81,7 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, struct PreviousSlot { size_t slot; }; - using LayoutT = std::vector>; + using LayoutT = SmallVector>; // Determine the number of slots that have to be on stack before executing the // operation (excluding the inputs of the operation itself). That is slots @@ -90,7 +89,7 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, // operation. size_t PreOperationLayoutSize = Post.size(); for (auto const &Slot : Post) - if (EVMUtils::contains(OperationOutput, Slot) || GenerateSlotOnTheFly(Slot)) + if (is_contained(OperationOutput, Slot) || GenerateSlotOnTheFly(Slot)) --PreOperationLayoutSize; // The symbolic layout directly after the operation has the form @@ -98,7 +97,7 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, LayoutT Layout; for (size_t Index = 0; Index < PreOperationLayoutSize; ++Index) Layout.emplace_back(PreviousSlot{Index}); - Layout += OperationOutput; + Layout.append(OperationOutput.begin(), OperationOutput.end()); // Shortcut for trivial case. if (Layout.empty()) @@ -131,16 +130,15 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, bool isCompatible(size_t Source, size_t Target) { return Source < Layout.size() && Target < Post.size() && - (std::holds_alternative(Post.at(Target)) || + (std::holds_alternative(Post[Target]) || std::visit(Overload{[&](const PreviousSlot &) { - return !Outputs.count(Post.at(Target)) && - !GenerateSlotOnTheFly( - Post.at(Target)); + return !Outputs.count(Post[Target]) && + !GenerateSlotOnTheFly(Post[Target]); }, [&](const StackSlot &S) { - return S == Post.at(Target); + return S == Post[Target]; }}, - Layout.at(Source))); + Layout[Source])); } bool sourceIsSame(size_t Lhs, size_t Rhs) { @@ -151,33 +149,32 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, return Lhs == Rhs; }, [&](auto const &, auto const &) { return false; }}, - Layout.at(Lhs), Layout.at(Rhs)); + Layout[Lhs], Layout[Rhs]); } int sourceMultiplicity(size_t Offset) { return std::visit( Overload{[&](PreviousSlot const &) { return 0; }, [&](StackSlot const &S) { return Mult.at(S); }}, - Layout.at(Offset)); + Layout[Offset]); } int targetMultiplicity(size_t Offset) { - if (!Outputs.count(Post.at(Offset)) && - !GenerateSlotOnTheFly(Post.at(Offset))) + if (!Outputs.count(Post[Offset]) && !GenerateSlotOnTheFly(Post[Offset])) return 0; - return Mult.at(Post.at(Offset)); + return Mult.at(Post[Offset]); } bool targetIsArbitrary(size_t Offset) { return Offset < Post.size() && - std::holds_alternative(Post.at(Offset)); + std::holds_alternative(Post[Offset]); } void swap(size_t I) { assert(!std::holds_alternative( - Layout.at(Layout.size() - I - 1)) || + Layout[Layout.size() - I - 1]) || !std::holds_alternative(Layout.back())); - std::swap(Layout.at(Layout.size() - I - 1), Layout.back()); + std::swap(Layout[Layout.size() - I - 1], Layout.back()); } size_t sourceSize() { return Layout.size(); } @@ -186,7 +183,7 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, void pop() { Layout.pop_back(); } - void pushOrDupTarget(size_t Offset) { Layout.push_back(Post.at(Offset)); } + void pushOrDupTarget(size_t Offset) { Layout.push_back(Post[Offset]); } }; Shuffler::shuffle(Layout, Post, GenerateSlotOnTheFly); @@ -199,12 +196,12 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, // VariableSlot{"tmp"}, then we want the variable tmp in the slot at offset 2 // in the layout before the operation. assert(Layout.size() == Post.size()); - std::vector> IdealLayout(Post.size(), std::nullopt); + SmallVector> IdealLayout(Post.size(), std::nullopt); for (unsigned Idx = 0; Idx < std::min(Layout.size(), Post.size()); ++Idx) { auto &Slot = Post[Idx]; auto &IdealPosition = Layout[Idx]; if (PreviousSlot *PrevSlot = std::get_if(&IdealPosition)) - IdealLayout.at(PrevSlot->slot) = Slot; + IdealLayout[PrevSlot->slot] = Slot; } // The tail of layout must have contained the operation outputs and will not @@ -268,11 +265,11 @@ Stack EVMStackLayoutGenerator::propagateStackThroughOperation( if (auto const *Assign = std::get_if(&Operation.Operation)) for (auto &StackSlot : IdealStack) if (auto const *VarSlot = std::get_if(&StackSlot)) - assert(!EVMUtils::contains(Assign->Variables, *VarSlot)); + assert(!is_contained(Assign->Variables, *VarSlot)); // Since stack+Operation.output can be easily shuffled to ExitLayout, the // desired layout before the operation is stack+Operation.input; - IdealStack += Operation.Input; + IdealStack.append(Operation.Input); // Store the exact desired operation entry layout. The stored layout will be // recreated by the code transform before executing the operation. However, @@ -286,9 +283,8 @@ Stack EVMStackLayoutGenerator::propagateStackThroughOperation( while (!IdealStack.empty()) { if (isRematerializable(IdealStack.back())) IdealStack.pop_back(); - else if (auto Offset = EVMUtils::findOffset( - EVMUtils::drop_first(EVMUtils::get_reverse(IdealStack), 1), - IdealStack.back())) { + else if (auto Offset = EVMUtils::offset(drop_begin(reverse(IdealStack), 1), + IdealStack.back())) { if (*Offset + 2 < 16) IdealStack.pop_back(); else @@ -304,10 +300,8 @@ Stack EVMStackLayoutGenerator::propagateStackThroughBlock( Stack ExitStack, const MachineBasicBlock *Block, bool AggressiveStackCompression) { Stack CurrentStack = ExitStack; - const std::vector &Operations = StackModel.getOperations(Block); - auto I = Operations.crbegin(), E = Operations.crend(); - for (; I != E; ++I) { - Stack NewStack = propagateStackThroughOperation(CurrentStack, *I, + for (const Operation &Op : reverse(StackModel.getOperations(Block))) { + Stack NewStack = propagateStackThroughOperation(CurrentStack, Op, AggressiveStackCompression); if (!AggressiveStackCompression && !findStackTooDeep(NewStack, CurrentStack).empty()) @@ -359,8 +353,7 @@ void EVMStackLayoutGenerator::processEntryPoint( auto B = EntryLayout.begin(), E = EntryLayout.end(); const Stack &ExitLayout = MBBExitLayoutMap[JumpingBlock]; if (std::any_of(B, E, [ExitLayout](const StackSlot &Slot) { - return std::find(ExitLayout.begin(), ExitLayout.end(), Slot) == - ExitLayout.end(); + return find(ExitLayout, Slot) == ExitLayout.end(); })) { // In particular we can visit backwards starting from 'JumpingBlock' // and mark all entries to-be-visited again until we hit 'Target'. @@ -403,7 +396,7 @@ void EVMStackLayoutGenerator::processEntryPoint( // Calling convention: input arguments are passed in stack such that the // first one specified in the function declaration is passed on the stack TOP. - EntryStack += StackModel.getFunctionParameters(); + EntryStack.append(StackModel.getFunctionParameters()); std::reverse(IsNoReturn ? EntryStack.begin() : std::next(EntryStack.begin()), EntryStack.end()); MBBEntryLayoutMap[Entry] = std::move(EntryStack); @@ -517,8 +510,7 @@ void EVMStackLayoutGenerator::stitchConditionalJumps( // Whatever the block being jumped to does not actually require, // can be marked as junk. for (StackSlot &Slot : NewEntryLayout) { - if (std::find(OriginalEntryLayout.begin(), OriginalEntryLayout.end(), - Slot) == OriginalEntryLayout.end()) + if (find(OriginalEntryLayout, Slot) == OriginalEntryLayout.end()) Slot = JunkSlot{}; } #ifndef NDEBUG @@ -526,8 +518,7 @@ void EVMStackLayoutGenerator::stitchConditionalJumps( // actually present or can be generated. for (StackSlot const &Slot : OriginalEntryLayout) assert(isRematerializable(Slot) || - std::find(NewEntryLayout.begin(), NewEntryLayout.end(), - Slot) != NewEntryLayout.end()); + find(NewEntryLayout, Slot) != NewEntryLayout.end()); #endif // NDEBUG return NewEntryLayout; }; @@ -558,25 +549,29 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, } Stack Stack1Tail, Stack2Tail; - for (auto Slot : EVMUtils::drop_first(Stack1, CommonPrefix.size())) + for (const auto &Slot : drop_begin(Stack1, CommonPrefix.size())) Stack1Tail.emplace_back(Slot); - for (auto Slot : EVMUtils::drop_first(Stack2, CommonPrefix.size())) + for (const auto &Slot : drop_begin(Stack2, CommonPrefix.size())) Stack2Tail.emplace_back(Slot); - if (Stack1Tail.empty()) - return CommonPrefix + compressStack(Stack2Tail); + if (Stack1Tail.empty()) { + CommonPrefix.append(compressStack(Stack2Tail)); + return CommonPrefix; + } - if (Stack2Tail.empty()) - return CommonPrefix + compressStack(Stack1Tail); + if (Stack2Tail.empty()) { + CommonPrefix.append(compressStack(Stack1Tail)); + return CommonPrefix; + } Stack Candidate; for (auto Slot : Stack1Tail) - if (!EVMUtils::contains(Candidate, Slot)) + if (!is_contained(Candidate, Slot)) Candidate.emplace_back(Slot); for (auto Slot : Stack2Tail) - if (!EVMUtils::contains(Candidate, Slot)) + if (!is_contained(Candidate, Slot)) Candidate.emplace_back(Slot); { @@ -603,8 +598,8 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, return; Stack Tmp = CommonPrefix; - Tmp += TestStack; - auto Depth = EVMUtils::findOffset(EVMUtils::get_reverse(Tmp), Slot); + Tmp.append(TestStack); + auto Depth = EVMUtils::offset(reverse(Tmp), Slot); if (Depth && *Depth >= 16) NumOps += 1000; }; @@ -618,7 +613,7 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, size_t N = Candidate.size(); Stack BestCandidate = Candidate; size_t BestCost = evaluate(Candidate); - std::vector C(N, 0); + SmallVector C(N, 0); size_t I = 1; while (I < N) { if (C[I] < I) { @@ -644,7 +639,8 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, } } - return CommonPrefix + BestCandidate; + CommonPrefix.append(BestCandidate); + return CommonPrefix; } Stack EVMStackLayoutGenerator::compressStack(Stack CurStack) { @@ -652,7 +648,7 @@ Stack EVMStackLayoutGenerator::compressStack(Stack CurStack) { do { if (FirstDupOffset) { if (*FirstDupOffset != (CurStack.size() - 1)) - std::swap(CurStack.at(*FirstDupOffset), CurStack.back()); + std::swap(CurStack[*FirstDupOffset], CurStack.back()); CurStack.pop_back(); FirstDupOffset.reset(); } @@ -665,9 +661,8 @@ Stack EVMStackLayoutGenerator::compressStack(Stack CurStack) { break; } - if (auto DupDepth = EVMUtils::findOffset( - EVMUtils::drop_first(EVMUtils::get_reverse(CurStack), Depth + 1), - Slot)) { + if (auto DupDepth = EVMUtils::offset( + drop_begin(reverse(CurStack), Depth + 1), Slot)) { if (Depth + *DupDepth <= 16) { FirstDupOffset = CurStack.size() - Depth - 1; break; @@ -693,7 +688,7 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { if (isRematerializable(Slot)) OpGas += 3; else { - auto Depth = EVMUtils::findOffset(EVMUtils::get_reverse(Source), Slot); + auto Depth = EVMUtils::offset(reverse(Source), Slot); if (!Depth) llvm_unreachable("No slot in the stack"); @@ -717,17 +712,19 @@ void EVMStackLayoutGenerator::fillInJunk(const MachineBasicBlock *Block) { EVMUtils::BreadthFirstSearch BFS{{Entry}}; BFS.run([&](const MachineBasicBlock *Block, auto VisitSucc) { - const Stack &EntryLayout = MBBEntryLayoutMap.at(Block); - MBBEntryLayoutMap[Block] = Stack(NumJunk, JunkSlot{}) + EntryLayout; + Stack EntryTmp(NumJunk, JunkSlot{}); + EntryTmp.append(MBBEntryLayoutMap.at(Block)); + MBBEntryLayoutMap[Block] = std::move(EntryTmp); for (const Operation &Operation : StackModel.getOperations(Block)) { - const Stack &OpEntryLayout = OperationEntryLayoutMap.at(&Operation); - OperationEntryLayoutMap[&Operation] = - Stack(NumJunk, JunkSlot{}) + OpEntryLayout; + Stack OpEntryTmp(NumJunk, JunkSlot{}); + OpEntryTmp.append(OperationEntryLayoutMap.at(&Operation)); + OperationEntryLayoutMap[&Operation] = std::move(OpEntryTmp); } - const Stack &ExitLayout = MBBExitLayoutMap.at(Block); - MBBExitLayoutMap[Block] = Stack(NumJunk, JunkSlot{}) + ExitLayout; + Stack ExitTmp(NumJunk, JunkSlot{}); + ExitTmp.append(MBBExitLayoutMap.at(Block)); + MBBExitLayoutMap[Block] = std::move(ExitTmp); for (const MachineBasicBlock *Succ : Block->successors()) VisitSucc(Succ); @@ -742,8 +739,9 @@ void EVMStackLayoutGenerator::fillInJunk(const MachineBasicBlock *Block) { size_t BestNumJunk = 0; size_t MaxJunk = EntryLayout.size(); for (size_t NumJunk = 1; NumJunk <= MaxJunk; ++NumJunk) { - size_t Cost = EvaluateStackTransform( - EntryLayout, Stack{NumJunk, JunkSlot{}} + TargetLayout); + Stack JunkedTarget(NumJunk, JunkSlot{}); + JunkedTarget.append(TargetLayout); + size_t Cost = EvaluateStackTransform(EntryLayout, JunkedTarget); if (Cost < BestCost) { BestCost = Cost; BestNumJunk = NumJunk; @@ -770,7 +768,7 @@ void EVMStackLayoutGenerator::fillInJunk(const MachineBasicBlock *Block) { !CFGInfo.isOnPathToFuncReturn(Block)) { const Stack EntryLayout = MBBEntryLayoutMap.at(Block); const Stack &ExitLayout = MBBExitLayoutMap.at(Block); - const std::vector &Ops = StackModel.getOperations(Block); + const SmallVector &Ops = StackModel.getOperations(Block); Stack const &NextLayout = Ops.empty() ? ExitLayout : OperationEntryLayoutMap.at(&Ops.front()); diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h index 75ddafce9eae..8230e2f1bb16 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h @@ -66,7 +66,7 @@ class EVMStackLayoutGenerator { /// Number of slots that need to be saved. size_t deficit = 0; /// Set of variables, eliminating which would decrease the stack deficit. - std::vector variableChoices; + SmallVector variableChoices; }; EVMStackLayoutGenerator(const MachineFunction &MF, @@ -123,7 +123,7 @@ class EVMStackLayoutGenerator { /// Walks through the CFG and reports any stack too deep errors that would /// occur when generating code for it without countermeasures. - std::vector + SmallVector reportStackTooDeep(const MachineBasicBlock &Entry) const; /// Returns a copy of \p Stack stripped of all duplicates and slots that can diff --git a/llvm/lib/Target/EVM/EVMStackModel.cpp b/llvm/lib/Target/EVM/EVMStackModel.cpp index 1decbd2bff5a..e69d11db925d 100644 --- a/llvm/lib/Target/EVM/EVMStackModel.cpp +++ b/llvm/lib/Target/EVM/EVMStackModel.cpp @@ -24,7 +24,7 @@ using namespace llvm; EVMStackModel::EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS) : MF(MF), LIS(LIS) { for (MachineBasicBlock &MBB : MF) { - std::vector Ops; + SmallVector Ops; for (MachineInstr &MI : MBB) createOperation(MI, Ops); OperationsMap[&MBB] = std::move(Ops); @@ -33,7 +33,7 @@ EVMStackModel::EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS) Stack EVMStackModel::getFunctionParameters() const { auto *MFI = MF.getInfo(); - std::vector Parameters(MFI->getNumParams(), JunkSlot{}); + SmallVector Parameters(MFI->getNumParams(), JunkSlot{}); for (const MachineInstr &MI : MF.front()) { if (MI.getOpcode() == EVM::ARGUMENT) { int64_t ArgIdx = MI.getOperand(1).getImm(); @@ -92,7 +92,7 @@ Stack EVMStackModel::getInstrOutput(const MachineInstr &MI) const { } void EVMStackModel::createOperation(MachineInstr &MI, - std::vector &Ops) const { + SmallVector &Ops) const { unsigned Opc = MI.getOpcode(); switch (Opc) { case EVM::STACK_LOAD: @@ -148,7 +148,7 @@ void EVMStackModel::createOperation(MachineInstr &MI, // Cretae CFG::Assignment object for the MI. Stack Input, Output; - std::vector Variables; + SmallVector Variables; switch (MI.getOpcode()) { case EVM::CONST_I256: { const Register DefReg = MI.getOperand(0).getReg(); diff --git a/llvm/lib/Target/EVM/EVMStackModel.h b/llvm/lib/Target/EVM/EVMStackModel.h index 08ddf2017ed1..1f5972821ff2 100644 --- a/llvm/lib/Target/EVM/EVMStackModel.h +++ b/llvm/lib/Target/EVM/EVMStackModel.h @@ -157,7 +157,7 @@ using StackSlot = JunkSlot>; /// The stack top is the last element of the vector. -using Stack = std::vector; +using Stack = SmallVector; /// Returns true if Slot can be materialized on the stack at any time. inline bool isRematerializable(const StackSlot &Slot) { @@ -181,7 +181,7 @@ struct Assignment { /// The variables being assigned to also occur as 'Output' in the /// 'Operation' containing the assignment, but are also stored here for /// convenience. - std::vector Variables; + SmallVector Variables; }; struct Operation { @@ -200,17 +200,17 @@ class EVMStackModel { Stack getInstrInput(const MachineInstr &MI) const; Stack getInstrOutput(const MachineInstr &MI) const; Stack getReturnArguments(const MachineInstr &MI) const; - const std::vector & + const SmallVector & getOperations(const MachineBasicBlock *MBB) const { return OperationsMap.at(MBB); } private: - void createOperation(MachineInstr &MI, std::vector &Ops) const; + void createOperation(MachineInstr &MI, SmallVector &Ops) const; MachineFunction &MF; const LiveIntervals &LIS; - std::map> OperationsMap; + DenseMap> OperationsMap; }; } // namespace llvm diff --git a/llvm/lib/Target/EVM/EVMStackShuffler.h b/llvm/lib/Target/EVM/EVMStackShuffler.h index 170426407974..6db0184bce9a 100644 --- a/llvm/lib/Target/EVM/EVMStackShuffler.h +++ b/llvm/lib/Target/EVM/EVMStackShuffler.h @@ -18,7 +18,6 @@ #include "EVMHelperUtilities.h" #include "EVMStackModel.h" -#include "llvm/CodeGen/MachineFunction.h" #include #include #include @@ -26,18 +25,6 @@ namespace llvm { -/* -template struct Overload : Ts... { -using Ts::operator()...; -}; -template Overload(Ts...) -> Overload; - -template static inline SmallVector iota17(T begin, T end) { -SmallVector R(end - begin); -std::iota(R.begin(), R.end(), begin); -return R; -} -*/ // Abstraction of stack shuffling operations. Can be defined as actual concept // once we switch to C++20. Used as an interface for the stack shuffler below. // The shuffle operation class is expected to internally keep track of a current @@ -164,7 +151,8 @@ class Shuffler { else if (Ops.sourceMultiplicity(SourceOffset) > 0) { // If this slot occurs again later, we skip this occurrence. // TODO: use C++ 20 ranges::views::iota - if (const auto &R = iota17(SourceOffset + 1, Ops.sourceSize()); + if (const auto &R = + EVMUtils::iota(SourceOffset + 1, Ops.sourceSize()); std::any_of(R.begin(), R.end(), [&](size_t Offset) { return Ops.sourceIsSame(SourceOffset, Offset); })) @@ -226,7 +214,7 @@ class Shuffler { ShuffleOperations Ops{std::forward(args)...}; // All source slots are final. - if (const auto &R = iota17(0u, Ops.sourceSize()); + if (const auto &R = EVMUtils::iota(0u, Ops.sourceSize()); std::all_of(R.begin(), R.end(), [&](size_t Index) { return Ops.isCompatible(Index, Index); })) { @@ -346,7 +334,8 @@ class Shuffler { (Ops.targetIsArbitrary(I) || Ops.targetMultiplicity(I) == 0)); assert(Ops.isCompatible(SourceTop, SourceTop)); - const auto &SwappableOffsets = iota17(Size > 17 ? Size - 17 : 0u, Size); + const auto &SwappableOffsets = + EVMUtils::iota(Size > 17 ? Size - 17 : 0u, Size); // If we find a lower slot that is out of position, but also compatible with // the top, swap that up. @@ -486,7 +475,7 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, auto &Slot = targetStack[Offset]; if (std::holds_alternative(Slot) && Offset < currentStack.size()) - ++multiplicity[currentStack.at(Offset)]; + ++multiplicity[currentStack[Offset]]; else ++multiplicity[Slot]; } @@ -494,31 +483,30 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, bool isCompatible(size_t Source, size_t Target) { return Source < currentStack.size() && Target < targetStack.size() && - (std::holds_alternative(targetStack.at(Target)) || - currentStack.at(Source) == targetStack.at(Target)); + (std::holds_alternative(targetStack[Target]) || + currentStack[Source] == targetStack[Target]); } bool sourceIsSame(size_t Lhs, size_t Rhs) { - return currentStack.at(Lhs) == currentStack.at(Rhs); + return currentStack[Lhs] == currentStack[Rhs]; } int sourceMultiplicity(size_t Offset) { - return multiplicity.at(currentStack.at(Offset)); + return multiplicity.at(currentStack[Offset]); } int targetMultiplicity(size_t Offset) { - return multiplicity.at(targetStack.at(Offset)); + return multiplicity.at(targetStack[Offset]); } bool targetIsArbitrary(size_t Offset) { return Offset < targetStack.size() && - std::holds_alternative(targetStack.at(Offset)); + std::holds_alternative(targetStack[Offset]); } void swap(size_t I) { swapCallback(static_cast(I)); - std::swap(currentStack.at(currentStack.size() - I - 1), - currentStack.back()); + std::swap(currentStack[currentStack.size() - I - 1], currentStack.back()); } size_t sourceSize() { return currentStack.size(); } @@ -531,7 +519,7 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, } void pushOrDupTarget(size_t Offset) { - auto const &targetSlot = targetStack.at(Offset); + auto const &targetSlot = targetStack[Offset]; pushOrDupCallback(targetSlot); currentStack.push_back(targetSlot); } diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index ba3ae0151460..94f265587924 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -235,7 +235,7 @@ void EVMStackifyCodeEmitter::visitCall(const FunctionCall &Call) { if (EVMUtils::callWillReturn(Call.MI)) { [[maybe_unused]] const auto *returnLabelSlot = std::get_if( - &CurrentStack.at(CurrentStack.size() - NumArgs)); + &CurrentStack[CurrentStack.size() - NumArgs]); assert(returnLabelSlot && returnLabelSlot->Call == Call.MI); } @@ -313,8 +313,7 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { Emitter.emitSWAP(I); } else { int Deficit = static_cast(I) - 16; - const StackSlot &DeepSlot = - CurrentStack.at(CurrentStack.size() - I - 1); + const StackSlot &DeepSlot = CurrentStack[CurrentStack.size() - I - 1]; std::string VarNameDeep = SlotVariableName(DeepSlot); std::string VarNameTop = SlotVariableName(CurrentStack.back()); std::string Msg = From 313b5f266d0574a65c538c59e1e92a7bd2d65324 Mon Sep 17 00:00:00 2001 From: Pavel Kopyl Date: Fri, 17 Jan 2025 00:57:36 +0100 Subject: [PATCH 17/42] [EVM] Refactor EVMStackLayoutGenerator to use LLVM API more broadly --- llvm/lib/Target/EVM/CMakeLists.txt | 1 - .../EVMBackwardPropagationStackification.cpp | 2 +- llvm/lib/Target/EVM/EVMHelperUtilities.cpp | 27 -- llvm/lib/Target/EVM/EVMHelperUtilities.h | 110 ----- llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp | 8 +- llvm/lib/Target/EVM/EVMMachineCFGInfo.h | 6 +- llvm/lib/Target/EVM/EVMStackDebug.cpp | 10 +- .../Target/EVM/EVMStackLayoutGenerator.cpp | 416 +++++++++--------- llvm/lib/Target/EVM/EVMStackLayoutGenerator.h | 30 +- llvm/lib/Target/EVM/EVMStackShuffler.h | 7 +- .../lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 24 +- 11 files changed, 242 insertions(+), 399 deletions(-) delete mode 100644 llvm/lib/Target/EVM/EVMHelperUtilities.cpp delete mode 100644 llvm/lib/Target/EVM/EVMHelperUtilities.h diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index 7925a6fd9385..18c208f185c1 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -23,7 +23,6 @@ add_llvm_target(EVMCodeGen EVMBackwardPropagationStackification.cpp EVMCodegenPrepare.cpp EVMFrameLowering.cpp - EVMHelperUtilities.cpp EVMISelDAGToDAG.cpp EVMISelLowering.cpp EVMInstrInfo.cpp diff --git a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp index 3c26c080b2db..265cc0a5eecd 100644 --- a/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp +++ b/llvm/lib/Target/EVM/EVMBackwardPropagationStackification.cpp @@ -94,7 +94,7 @@ bool EVMBPStackification::runOnMachineFunction(MachineFunction &MF) { EVMMachineCFGInfo CFGInfo(MF, MLI); EVMStackModel StackModel(MF, LIS); std::unique_ptr Layout = - EVMStackLayoutGenerator(MF, StackModel, CFGInfo).run(); + EVMStackLayoutGenerator(MF, MLI, StackModel, CFGInfo).run(); EVMStackifyCodeEmitter(*Layout, StackModel, CFGInfo, MF).run(); return true; } diff --git a/llvm/lib/Target/EVM/EVMHelperUtilities.cpp b/llvm/lib/Target/EVM/EVMHelperUtilities.cpp deleted file mode 100644 index 46cfac9b5588..000000000000 --- a/llvm/lib/Target/EVM/EVMHelperUtilities.cpp +++ /dev/null @@ -1,27 +0,0 @@ -//===----------- EVMHelperUtilities.cpp - Helper utilities ------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// -//===----------------------------------------------------------------------===// - -#include "EVMHelperUtilities.h" -#include "MCTargetDesc/EVMMCTargetDesc.h" -#include "llvm/CodeGen/MachineFunction.h" -#include "llvm/IR/Function.h" - -using namespace llvm; - -// Return whether the function of the call instruction will return. -bool EVMUtils::callWillReturn(const MachineInstr *Call) { - assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); - const MachineOperand *FuncOp = Call->explicit_uses().begin(); - assert(FuncOp->isGlobal() && "Expected a global value"); - const auto *Func = dyn_cast(FuncOp->getGlobal()); - assert(Func && "Expected a function"); - return !Func->hasFnAttribute(Attribute::NoReturn); -} diff --git a/llvm/lib/Target/EVM/EVMHelperUtilities.h b/llvm/lib/Target/EVM/EVMHelperUtilities.h deleted file mode 100644 index 27c9ff54999d..000000000000 --- a/llvm/lib/Target/EVM/EVMHelperUtilities.h +++ /dev/null @@ -1,110 +0,0 @@ -//===----------- EVMHelperUtilities.h - Helper utilities --------*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// This file defines different utility templates. In particular, for a partial -// emulation of the C++ range-V3 library functionality; BFS graph traversal; -// the overload pattern support. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H -#define LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H - -#include "llvm/ADT/DenseSet.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/iterator_range.h" -#include -#include -#include - -namespace llvm { - -class MachineInstr; - -template struct Overload : Ts... { - using Ts::operator()...; -}; -template Overload(Ts...) -> Overload; - -namespace EVMUtils { -bool callWillReturn(const MachineInstr *Call); - -/// Return a vector, containing sequentially increasing values from \p Begin -/// to \p End. -template static inline SmallVector iota(T Begin, T End) { - SmallVector R(End - Begin); - std::iota(R.begin(), R.end(), Begin); - return R; -} - -/// Return the number of hops from the beginning of the \p RangeOrContainer -/// to the \p Item. If no \p Item is found in the \p RangeOrContainer, -/// std::nullopt is returned. -template -std::optional offset(T &&RangeOrContainer, V &&Item) { - auto It = find(RangeOrContainer, Item); - return (It == adl_end(RangeOrContainer)) - ? std::nullopt - : std::optional(std::distance(adl_begin(RangeOrContainer), It)); -} - -/// Return a range covering the last N elements of \p RangeOrContainer. -template auto take_back(T &&RangeOrContainer, size_t N = 1) { - return make_range(std::prev(adl_end(RangeOrContainer), N), - adl_end(RangeOrContainer)); -} - -/// Generic breadth first search. -/// -/// Note that V needs to be a comparable value type or a pointer. -/// -/// Example: Gather all (recursive) children in a graph starting at (and -/// including) ``root``: -/// -/// Node const* root = ...; -/// std::set allNodes = BreadthFirstSearch{{root}}.run([](Node const* _node, auto&& _addChild) { -/// // Potentially process ``_node``. -/// for (Node const& _child: _node->children()) -/// // Potentially filter the children to be visited. -/// _addChild(&_child); -/// }).visited; -/// -template struct BreadthFirstSearch { - /// Runs the breadth first search. The verticesToTraverse member of the struct - /// needs to be initialized. - /// @param _forEachChild is a callable of the form [...](V const& _node, - /// auto&& _addChild) { ... } that is called for each visited node and is - /// supposed to call _addChild(childNode) for every child node of _node. - template - BreadthFirstSearch &run(ForEachChild &&forEachChild) { - while (!verticesToTraverse.empty()) { - V v = std::move(verticesToTraverse.front()); - verticesToTraverse.pop_front(); - - if (!visited.insert(v).second) - continue; - - forEachChild(v, [this](V vertex) { - verticesToTraverse.emplace_back(std::move(vertex)); - }); - } - return *this; - } - - void abort() { verticesToTraverse.clear(); } - - std::deque verticesToTraverse; - DenseSet visited{}; -}; - -} // namespace EVMUtils -} // namespace llvm - -#endif // LLVM_LIB_TARGET_EVM_EVMHELPERUTILITIES_H diff --git a/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp b/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp index c6f45aa65909..8a2ad98da818 100644 --- a/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp +++ b/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// #include "EVMMachineCFGInfo.h" -#include "EVMHelperUtilities.h" #include "EVMMachineFunctionInfo.h" #include "EVMSubtarget.h" #include "MCTargetDesc/EVMMCTargetDesc.h" @@ -98,9 +97,6 @@ void EVMMachineCFGInfo::collectTerminatorsInfo(const TargetInstrInfo *TII, return; } - bool IsLatch = false; - if (MachineLoop *ML = MLI->getLoopFor(&MBB)) - IsLatch = ML->isLoopLatch(&MBB); auto [CondBr, UncondBr] = getBranchInstructions(MBB); if (!TBB || (TBB && Cond.empty())) { // Fall through, or unconditional jump. @@ -111,10 +107,10 @@ void EVMMachineCFGInfo::collectTerminatorsInfo(const TargetInstrInfo *TII, TBB = MBB.getFallThrough(); assert(TBB); } + Info->ExitType = MBBExitType::UnconditionalBranch; - Info->BranchInfo.Unconditional = {TBB, UncondBr, IsLatch}; + Info->BranchInfo.Unconditional = {TBB, UncondBr}; } else if (TBB && !Cond.empty()) { - assert(!IsLatch); // Conditional jump + fallthrough, or // conditional jump followed by unconditional jump). if (!FBB) { diff --git a/llvm/lib/Target/EVM/EVMMachineCFGInfo.h b/llvm/lib/Target/EVM/EVMMachineCFGInfo.h index 6792765ee8ff..fdb97ae1785e 100644 --- a/llvm/lib/Target/EVM/EVMMachineCFGInfo.h +++ b/llvm/lib/Target/EVM/EVMMachineCFGInfo.h @@ -51,7 +51,6 @@ class EVMMBBTerminatorsInfo { struct { MachineBasicBlock *TargetBB; MachineInstr *Br; - bool IsLatch; } Unconditional; } BranchInfo; @@ -73,11 +72,10 @@ class EVMMBBTerminatorsInfo { BranchInfo.Conditional.Condition}; } - std::tuple + std::pair getUnconditionalBranch() const { assert(ExitType == MBBExitType::UnconditionalBranch); - return {BranchInfo.Unconditional.Br, BranchInfo.Unconditional.TargetBB, - BranchInfo.Unconditional.IsLatch}; + return {BranchInfo.Unconditional.Br, BranchInfo.Unconditional.TargetBB}; } MachineInstr *getFunctionReturn() const { diff --git a/llvm/lib/Target/EVM/EVMStackDebug.cpp b/llvm/lib/Target/EVM/EVMStackDebug.cpp index 0752c768c9df..6e13e145fb52 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.cpp +++ b/llvm/lib/Target/EVM/EVMStackDebug.cpp @@ -12,7 +12,6 @@ //===----------------------------------------------------------------------===// #include "EVMStackDebug.h" -#include "EVMHelperUtilities.h" #include "EVMStackLayoutGenerator.h" #include "EVMSubtarget.h" #include "MCTargetDesc/EVMMCTargetDesc.h" @@ -20,6 +19,11 @@ using namespace llvm; +template struct Overload : Ts... { + using Ts::operator()...; +}; +template Overload(Ts...) -> Overload; + static std::string getInstName(const MachineInstr *MI) { const MachineFunction *MF = MI->getParent()->getParent(); const TargetInstrInfo *TII = MF->getSubtarget().getInstrInfo(); @@ -140,11 +144,9 @@ void StackLayoutPrinter::printBlock(MachineBasicBlock const &Block) { const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(&Block); MBBExitType ExitType = TermInfo->getExitType(); if (ExitType == MBBExitType::UnconditionalBranch) { - auto [BranchInstr, Target, IsLatch] = TermInfo->getUnconditionalBranch(); + auto [BranchInstr, Target] = TermInfo->getUnconditionalBranch(); OS << "Block" << getBlockId(Block) << "Exit [label=\""; OS << "Jump\"];\n"; - if (IsLatch) - OS << "Backwards"; OS << "Block" << getBlockId(Block) << "Exit -> Block" << getBlockId(*Target) << ";\n"; } else if (ExitType == MBBExitType::ConditionalBranch) { diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp index b5e5961e65da..26afd5114633 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -16,11 +16,11 @@ //===----------------------------------------------------------------------===// #include "EVMStackLayoutGenerator.h" -#include "EVMHelperUtilities.h" #include "EVMRegisterInfo.h" #include "EVMStackDebug.h" #include "EVMStackShuffler.h" #include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/ADT/DepthFirstIterator.h" #include "llvm/CodeGen/MachineFunction.h" #include "llvm/CodeGen/TargetInstrInfo.h" #include "llvm/IR/DebugLoc.h" @@ -32,6 +32,28 @@ using namespace llvm; #define DEBUG_TYPE "evm-stack-layout-generator" namespace { +template struct Overload : Ts... { + using Ts::operator()...; +}; +template Overload(Ts...) -> Overload; + +/// Return the number of hops from the beginning of the \p RangeOrContainer +/// to the \p Item. If no \p Item is found in the \p RangeOrContainer, +/// std::nullopt is returned. +template +std::optional offset(T &&RangeOrContainer, V &&Item) { + auto It = find(RangeOrContainer, Item); + return (It == adl_end(RangeOrContainer)) + ? std::nullopt + : std::optional(std::distance(adl_begin(RangeOrContainer), It)); +} + +/// Return a range covering the last N elements of \p RangeOrContainer. +template auto take_back(T &&RangeOrContainer, size_t N = 1) { + return make_range(std::prev(adl_end(RangeOrContainer), N), + adl_end(RangeOrContainer)); +} + /// Returns all stack too deep errors that would occur when shuffling \p Source /// to \p Target. SmallVector @@ -53,18 +75,17 @@ findStackTooDeep(Stack const &Source, Stack const &Target) { [&](unsigned I) { if (I > 16) Errors.emplace_back(EVMStackLayoutGenerator::StackTooDeep{ - I - 16, - getVariableChoices(EVMUtils::take_back(CurrentStack, I + 1))}); + I - 16, getVariableChoices(take_back(CurrentStack, I + 1))}); }, [&](StackSlot const &Slot) { if (isRematerializable(Slot)) return; - if (auto Depth = EVMUtils::offset(reverse(CurrentStack), Slot); + if (auto Depth = offset(reverse(CurrentStack), Slot); Depth && *Depth >= 16) Errors.emplace_back(EVMStackLayoutGenerator::StackTooDeep{ - *Depth - 15, getVariableChoices( - EVMUtils::take_back(CurrentStack, *Depth + 1))}); + *Depth - 15, + getVariableChoices(take_back(CurrentStack, *Depth + 1))}); }, [&]() {}); return Errors; @@ -223,12 +244,12 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, } // end anonymous namespace EVMStackLayoutGenerator::EVMStackLayoutGenerator( - const MachineFunction &MF, const EVMStackModel &StackModel, - const EVMMachineCFGInfo &CFGInfo) - : MF(MF), StackModel(StackModel), CFGInfo(CFGInfo) {} + const MachineFunction &MF, const MachineLoopInfo *MLI, + const EVMStackModel &StackModel, const EVMMachineCFGInfo &CFGInfo) + : MF(MF), MLI(MLI), StackModel(StackModel), CFGInfo(CFGInfo) {} std::unique_ptr EVMStackLayoutGenerator::run() { - processEntryPoint(&MF.front()); + runPropagation(); auto Layout = std::make_unique( MBBEntryLayoutMap, MBBExitLayoutMap, OperationEntryLayoutMap); @@ -283,8 +304,8 @@ Stack EVMStackLayoutGenerator::propagateStackThroughOperation( while (!IdealStack.empty()) { if (isRematerializable(IdealStack.back())) IdealStack.pop_back(); - else if (auto Offset = EVMUtils::offset(drop_begin(reverse(IdealStack), 1), - IdealStack.back())) { + else if (auto Offset = offset(drop_begin(reverse(IdealStack), 1), + IdealStack.back())) { if (*Offset + 2 < 16) IdealStack.pop_back(); else @@ -312,15 +333,49 @@ Stack EVMStackLayoutGenerator::propagateStackThroughBlock( return CurrentStack; } -void EVMStackLayoutGenerator::processEntryPoint( - const MachineBasicBlock *Entry) { - std::list ToVisit{Entry}; +// Returns the number of junk slots to be prepended to \p TargetLayout for +// an optimal transition from \p EntryLayout to \p TargetLayout. +static size_t getOptimalNumberOfJunks(const Stack &EntryLayout, + const Stack &TargetLayout) { + size_t BestCost = EvaluateStackTransform(EntryLayout, TargetLayout); + size_t BestNumJunk = 0; + size_t MaxJunk = EntryLayout.size(); + for (size_t NumJunk = 1; NumJunk <= MaxJunk; ++NumJunk) { + Stack JunkedTarget(NumJunk, JunkSlot{}); + JunkedTarget.append(TargetLayout); + size_t Cost = EvaluateStackTransform(EntryLayout, JunkedTarget); + if (Cost < BestCost) { + BestCost = Cost; + BestNumJunk = NumJunk; + } + } + return BestNumJunk; +} + +void EVMStackLayoutGenerator::runPropagation() { + std::deque ToVisit{&MF.front()}; DenseSet Visited; - // TODO: check whether visiting only a subset of these in the outer iteration - // below is enough. - std::list> - BackwardsJumps = collectBackwardsJumps(Entry); + // Collect all the backedges in the MF. + // TODO: CPR-1847. Consider changing CFG before the stackification such that + // every loop has only one backedge. + SmallVector, + 64> + Backedges; + for (const MachineLoop *TopLevelLoop : *MLI) { + // TODO: CPR-1847. Investigate in which order it's better to traverse + // loops. + for (const MachineLoop *L : depth_first(TopLevelLoop)) { + SmallVector Latches; + L->getLoopLatches(Latches); + const MachineBasicBlock *Header = L->getHeader(); + transform(Latches, std::back_inserter(Backedges), + [Header](const MachineBasicBlock *MBB) { + return std::make_pair(MBB, Header); + }); + } + } + while (!ToVisit.empty()) { // First calculate stack layouts without walking backwards jumps, i.e. // assuming the current preliminary entry layout of the backwards jump @@ -328,7 +383,6 @@ void EVMStackLayoutGenerator::processEntryPoint( while (!ToVisit.empty()) { const MachineBasicBlock *Block = *ToVisit.begin(); ToVisit.pop_front(); - if (Visited.count(Block)) continue; @@ -343,52 +397,90 @@ void EVMStackLayoutGenerator::processEntryPoint( } } - // Determine which backwards jumps still require fixing and stage revisits - // of appropriate nodes. - for (auto [JumpingBlock, Target] : BackwardsJumps) { - // This block jumps backwards, but does not provide all slots required by - // the jump target on exit. Therefore we need to visit the subgraph - // between 'Target' and 'JumpingBlock' again. - const Stack &EntryLayout = MBBEntryLayoutMap[Target]; - auto B = EntryLayout.begin(), E = EntryLayout.end(); - const Stack &ExitLayout = MBBExitLayoutMap[JumpingBlock]; - if (std::any_of(B, E, [ExitLayout](const StackSlot &Slot) { - return find(ExitLayout, Slot) == ExitLayout.end(); - })) { - // In particular we can visit backwards starting from 'JumpingBlock' - // and mark all entries to-be-visited again until we hit 'Target'. - ToVisit.emplace_front(JumpingBlock); - // Since we are likely to permute the entry layout of 'Target', we - // also visit its entries again. This is not required for correctness, - // since the set of stack slots will match, but it may move some - // required stack shuffling from the loop condition to outside the loop. - for (const MachineBasicBlock *Pred : Target->predecessors()) - Visited.erase(Pred); - - EVMUtils::BreadthFirstSearch{{JumpingBlock}} - .run([&Visited, Target = Target](const MachineBasicBlock *Block, - auto VisitPred) { - Visited.erase(Block); - if (Block == Target) - return; - for (auto const *Pred : Block->predecessors()) - VisitPred(Pred); - }); - // While the shuffled layout for 'Target' will be compatible, it can - // be worthwhile propagating it further up once more. This would mean - // not stopping at Block == Target above, resp. even doing - // Visited.clear() here, revisiting the entire graph. This is a tradeoff - // between the runtime of this process and the optimality of the result. - // Also note that while visiting the entire graph again *can* be - // helpful, it can also be detrimental. + // Check which latch blocks still require fixing of their exit layouts. + // Revisit these blocks again. + for (auto [Latch, Header] : Backedges) { + const Stack &HeaderEntryLayout = MBBEntryLayoutMap[Header]; + const Stack &LatchExitLayout = MBBExitLayoutMap[Latch]; + if (all_of(HeaderEntryLayout, [LatchExitLayout](const StackSlot &Slot) { + return is_contained(LatchExitLayout, Slot); + })) + continue; + + // The latch block does not provide all slots required by the loop + // header. Therefore we need to visit the subgraph between the latch + // and header again. We will visit blocks backwards starting from latch + // and mark all MBBs to-be-visited again until we reach the header. + + ToVisit.emplace_back(Latch); + + // Since we are likely to permute the entry layout of 'Header', we + // also visit its entries again. This is not required for correctness, + // since the set of stack slots will match, but it may move some + // required stack shuffling from the loop condition to outside the loop. + for (const MachineBasicBlock *Pred : Header->predecessors()) + Visited.erase(Pred); + + // DFS upwards traversal from latch to the header. + for (auto I = idf_begin(Latch), E = idf_end(Latch); I != E;) { + const MachineBasicBlock *MBB = *I; + Visited.erase(MBB); + if (MBB == Header) { + I.skipChildren(); + continue; + } + ++I; } + // TODO: Consider revisiting the entire graph to propagate the optimal + // layout above the loop. } } - stitchConditionalJumps(Entry); - fillInJunk(Entry); + // At this point layouts at conditional jumps are merely + // compatible, i.e. the exit layout of the jumping block is a superset of the + // entry layout of the target block. We need to modify the entry layouts + // of conditional jump targets, s.t., the entry layout of target blocks match + // the exit layout of the jumping block exactly, except that slots not + // required after the jump are marked as 'JunkSlot'. + for (const MachineBasicBlock &MBB : MF) { + const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(&MBB); + MBBExitType ExitType = TermInfo->getExitType(); + if (ExitType != MBBExitType::ConditionalBranch) + continue; + + Stack ExitLayout = MBBExitLayoutMap.at(&MBB); + +#ifndef NDEBUG + // The last block must have produced the condition at the stack top. + auto [CondBr, UncondBr, TrueBB, FalseBB, Condition] = + TermInfo->getConditionalBranch(); + assert(ExitLayout.back() == StackModel.getStackSlot(*Condition)); +#endif // NDEBUG + + // The condition is consumed by the conditional jump. + ExitLayout.pop_back(); + for (const MachineBasicBlock *Succ : MBB.successors()) { + const Stack &SuccEntryLayout = MBBEntryLayoutMap.at(Succ); + Stack NewSuccEntryLayout = ExitLayout; + // Whatever the block being jumped to does not actually require, + // can be marked as junk. + for (StackSlot &Slot : NewSuccEntryLayout) + if (!is_contained(SuccEntryLayout, Slot)) + Slot = JunkSlot{}; + +#ifndef NDEBUG + // Make sure everything the block being jumped to requires is + // actually present or can be generated. + for (const StackSlot &Slot : SuccEntryLayout) + assert(isRematerializable(Slot) || + is_contained(NewSuccEntryLayout, Slot)); +#endif // NDEBUG + + MBBEntryLayoutMap[Succ] = NewSuccEntryLayout; + } + } - // Create function entry layout. + // Create the function entry layout. Stack EntryStack; bool IsNoReturn = MF.getFunction().hasFnAttribute(Attribute::NoReturn); if (!IsNoReturn) @@ -399,18 +491,42 @@ void EVMStackLayoutGenerator::processEntryPoint( EntryStack.append(StackModel.getFunctionParameters()); std::reverse(IsNoReturn ? EntryStack.begin() : std::next(EntryStack.begin()), EntryStack.end()); - MBBEntryLayoutMap[Entry] = std::move(EntryStack); + MBBEntryLayoutMap[&MF.front()] = std::move(EntryStack); + + // Traverse the CFG and at each block that allows junk, i.e. that is a + // cut-vertex that never leads to a function return, checks if adding junk + // reduces the shuffling cost upon entering and if so recursively adds junk + // to the spanned subgraph. This is needed only for optimization purposes, + // not for correctness. + for (const MachineBasicBlock &MBB : MF) { + if (!CFGInfo.isCutVertex(&MBB) || CFGInfo.isOnPathToFuncReturn(&MBB)) + continue; + + const Stack EntryLayout = MBBEntryLayoutMap.at(&MBB); + const Stack &ExitLayout = MBBExitLayoutMap.at(&MBB); + const SmallVector &Ops = StackModel.getOperations(&MBB); + Stack const &NextLayout = + Ops.empty() ? ExitLayout : OperationEntryLayoutMap.at(&Ops.front()); + if (EntryLayout != NextLayout) { + size_t OptimalNumJunks = getOptimalNumberOfJunks(EntryLayout, NextLayout); + if (OptimalNumJunks > 0) { + addJunksToStackBottom(&MBB, OptimalNumJunks); + MBBEntryLayoutMap[&MBB] = EntryLayout; + } + } + } } std::optional EVMStackLayoutGenerator::getExitLayoutOrStageDependencies( const MachineBasicBlock *Block, const DenseSet &Visited, - std::list &ToVisit) const { + std::deque &ToVisit) const { const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(Block); MBBExitType ExitType = TermInfo->getExitType(); if (ExitType == MBBExitType::UnconditionalBranch) { - auto [_, Target, IsLatch] = TermInfo->getUnconditionalBranch(); - if (IsLatch) { + auto [_, Target] = TermInfo->getUnconditionalBranch(); + if (MachineLoop *ML = MLI->getLoopFor(Block); + ML && ML->isLoopLatch(Block)) { // Choose the best currently known entry layout of the jump target // as initial exit. Note that this may not yet be the final // layout. @@ -461,78 +577,6 @@ std::optional EVMStackLayoutGenerator::getExitLayoutOrStageDependencies( return Stack{}; } -std::list> -EVMStackLayoutGenerator::collectBackwardsJumps( - const MachineBasicBlock *Entry) const { - std::list> - BackwardsJumps; - EVMUtils::BreadthFirstSearch{{Entry}}.run( - [&](const MachineBasicBlock *Block, auto VisitSucc) { - const EVMMBBTerminatorsInfo *TermInfo = - CFGInfo.getTerminatorsInfo(Block); - MBBExitType ExitType = TermInfo->getExitType(); - if (ExitType == MBBExitType::UnconditionalBranch) { - auto [_, Target, IsLatch] = TermInfo->getUnconditionalBranch(); - if (IsLatch) - BackwardsJumps.emplace_back(Block, Target); - } - for (const MachineBasicBlock *Succ : Block->successors()) - VisitSucc(Succ); - }); - return BackwardsJumps; -} - -void EVMStackLayoutGenerator::stitchConditionalJumps( - const MachineBasicBlock *Block) { - EVMUtils::BreadthFirstSearch BFS{{Block}}; - BFS.run([&](const MachineBasicBlock *Block, auto VisitSucc) { - const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(Block); - MBBExitType ExitType = TermInfo->getExitType(); - if (ExitType == MBBExitType::UnconditionalBranch) { - auto [_, Target, IsLatch] = TermInfo->getUnconditionalBranch(); - if (!IsLatch) - VisitSucc(Target); - return; - } - if (ExitType == MBBExitType::ConditionalBranch) { - auto [CondBr, UncondBr, TrueBB, FalseBB, Condition] = - TermInfo->getConditionalBranch(); - Stack ExitLayout = MBBExitLayoutMap.at(Block); - // The last block must have produced the condition at the stack - // top. - assert(!ExitLayout.empty()); - assert(ExitLayout.back() == StackModel.getStackSlot(*Condition)); - // The condition is consumed by the jump. - ExitLayout.pop_back(); - - auto FixJumpTargetEntry = [&](const Stack &OriginalEntryLayout) -> Stack { - Stack NewEntryLayout = ExitLayout; - // Whatever the block being jumped to does not actually require, - // can be marked as junk. - for (StackSlot &Slot : NewEntryLayout) { - if (find(OriginalEntryLayout, Slot) == OriginalEntryLayout.end()) - Slot = JunkSlot{}; - } -#ifndef NDEBUG - // Make sure everything the block being jumped to requires is - // actually present or can be generated. - for (StackSlot const &Slot : OriginalEntryLayout) - assert(isRematerializable(Slot) || - find(NewEntryLayout, Slot) != NewEntryLayout.end()); -#endif // NDEBUG - return NewEntryLayout; - }; - - const Stack &ZeroTargetEntryLayout = MBBEntryLayoutMap.at(FalseBB); - MBBEntryLayoutMap[FalseBB] = FixJumpTargetEntry(ZeroTargetEntryLayout); - const Stack &NonZeroTargetEntryLayout = MBBEntryLayoutMap.at(TrueBB); - MBBEntryLayoutMap[TrueBB] = FixJumpTargetEntry(NonZeroTargetEntryLayout); - VisitSucc(FalseBB); - VisitSucc(TrueBB); - } - }); -} - Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, Stack const &Stack2) { // TODO: it would be nicer to replace this by a constructive algorithm. @@ -599,7 +643,7 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, Stack Tmp = CommonPrefix; Tmp.append(TestStack); - auto Depth = EVMUtils::offset(reverse(Tmp), Slot); + auto Depth = offset(reverse(Tmp), Slot); if (Depth && *Depth >= 16) NumOps += 1000; }; @@ -661,8 +705,8 @@ Stack EVMStackLayoutGenerator::compressStack(Stack CurStack) { break; } - if (auto DupDepth = EVMUtils::offset( - drop_begin(reverse(CurStack), Depth + 1), Slot)) { + if (auto DupDepth = + offset(drop_begin(reverse(CurStack), Depth + 1), Slot)) { if (Depth + *DupDepth <= 16) { FirstDupOffset = CurStack.size() - Depth - 1; break; @@ -688,7 +732,7 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { if (isRematerializable(Slot)) OpGas += 3; else { - auto Depth = EVMUtils::offset(reverse(Source), Slot); + auto Depth = offset(reverse(Source), Slot); if (!Depth) llvm_unreachable("No slot in the stack"); @@ -704,83 +748,21 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { return OpGas; } -void EVMStackLayoutGenerator::fillInJunk(const MachineBasicBlock *Block) { - /// Recursively adds junk to the subgraph starting on \p Entry. - /// Since it is only called on cut-vertices, the full subgraph retains proper - /// stack balance. - auto AddJunkRecursive = [&](const MachineBasicBlock *Entry, size_t NumJunk) { - EVMUtils::BreadthFirstSearch BFS{{Entry}}; - - BFS.run([&](const MachineBasicBlock *Block, auto VisitSucc) { - Stack EntryTmp(NumJunk, JunkSlot{}); - EntryTmp.append(MBBEntryLayoutMap.at(Block)); - MBBEntryLayoutMap[Block] = std::move(EntryTmp); - - for (const Operation &Operation : StackModel.getOperations(Block)) { - Stack OpEntryTmp(NumJunk, JunkSlot{}); - OpEntryTmp.append(OperationEntryLayoutMap.at(&Operation)); - OperationEntryLayoutMap[&Operation] = std::move(OpEntryTmp); - } - - Stack ExitTmp(NumJunk, JunkSlot{}); - ExitTmp.append(MBBExitLayoutMap.at(Block)); - MBBExitLayoutMap[Block] = std::move(ExitTmp); - - for (const MachineBasicBlock *Succ : Block->successors()) - VisitSucc(Succ); - }); - }; - - /// Returns the number of junk slots to be prepended to \p TargetLayout for - /// an optimal transition from \p EntryLayout to \p TargetLayout. - auto GetBestNumJunk = [&](const Stack &EntryLayout, - const Stack &TargetLayout) -> size_t { - size_t BestCost = EvaluateStackTransform(EntryLayout, TargetLayout); - size_t BestNumJunk = 0; - size_t MaxJunk = EntryLayout.size(); - for (size_t NumJunk = 1; NumJunk <= MaxJunk; ++NumJunk) { - Stack JunkedTarget(NumJunk, JunkSlot{}); - JunkedTarget.append(TargetLayout); - size_t Cost = EvaluateStackTransform(EntryLayout, JunkedTarget); - if (Cost < BestCost) { - BestCost = Cost; - BestNumJunk = NumJunk; - } +void EVMStackLayoutGenerator::addJunksToStackBottom( + const MachineBasicBlock *Entry, size_t NumJunk) { + for (const MachineBasicBlock *MBB : depth_first(Entry)) { + Stack EntryTmp(NumJunk, JunkSlot{}); + EntryTmp.append(MBBEntryLayoutMap.at(MBB)); + MBBEntryLayoutMap[MBB] = std::move(EntryTmp); + + for (const Operation &Operation : StackModel.getOperations(MBB)) { + Stack OpEntryTmp(NumJunk, JunkSlot{}); + OpEntryTmp.append(OperationEntryLayoutMap.at(&Operation)); + OperationEntryLayoutMap[&Operation] = std::move(OpEntryTmp); } - return BestNumJunk; - }; - if (MF.getFunction().hasFnAttribute(Attribute::NoReturn) && - (&MF.front() == Block)) { - Stack Params = StackModel.getFunctionParameters(); - std::reverse(Params.begin(), Params.end()); - size_t BestNumJunk = GetBestNumJunk(Params, MBBEntryLayoutMap.at(Block)); - if (BestNumJunk > 0) - AddJunkRecursive(Block, BestNumJunk); + Stack ExitTmp(NumJunk, JunkSlot{}); + ExitTmp.append(MBBExitLayoutMap.at(MBB)); + MBBExitLayoutMap[MBB] = std::move(ExitTmp); } - /// Traverses the CFG and at each block that allows junk, i.e. that is a - /// cut-vertex that never leads to a function return, checks if adding junk - /// reduces the shuffling cost upon entering and if so recursively adds junk - /// to the spanned subgraph. - EVMUtils::BreadthFirstSearch{{Block}}.run( - [&](MachineBasicBlock const *Block, auto VisitSucc) { - if (CFGInfo.isCutVertex(Block) && - !CFGInfo.isOnPathToFuncReturn(Block)) { - const Stack EntryLayout = MBBEntryLayoutMap.at(Block); - const Stack &ExitLayout = MBBExitLayoutMap.at(Block); - const SmallVector &Ops = StackModel.getOperations(Block); - Stack const &NextLayout = - Ops.empty() ? ExitLayout - : OperationEntryLayoutMap.at(&Ops.front()); - if (EntryLayout != NextLayout) { - size_t BestNumJunk = GetBestNumJunk(EntryLayout, NextLayout); - if (BestNumJunk > 0) { - AddJunkRecursive(Block, BestNumJunk); - MBBEntryLayoutMap[Block] = EntryLayout; - } - } - } - for (const MachineBasicBlock *Succ : Block->successors()) - VisitSucc(Succ); - }); } diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h index 8230e2f1bb16..280d7c1e0fe3 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.h @@ -22,6 +22,8 @@ #include "EVMStackModel.h" #include "llvm/ADT/DenseMap.h" +#include + namespace llvm { /// Returns the number of operations required to transform stack \p Source to @@ -69,7 +71,7 @@ class EVMStackLayoutGenerator { SmallVector variableChoices; }; - EVMStackLayoutGenerator(const MachineFunction &MF, + EVMStackLayoutGenerator(const MachineFunction &MF, const MachineLoopInfo *MLI, const EVMStackModel &StackModel, const EVMMachineCFGInfo &CFGInfo); @@ -93,7 +95,11 @@ class EVMStackLayoutGenerator { /// Main algorithm walking the graph from entry to exit and propagating back /// the stack layouts to the entries. Iteratively reruns itself along /// backwards jumps until the layout is stabilized. - void processEntryPoint(const MachineBasicBlock *Entry); + void runPropagation(); + + /// Adds junks to the subgraph starting at \p Entry. It should only be + /// called on cut-vertices, so the full subgraph retains proper stack balance. + void addJunksToStackBottom(const MachineBasicBlock *Entry, size_t NumJunk); /// Returns the best known exit layout of \p Block, if all dependencies are /// already \p Visited. If not, adds the dependencies to \p DependencyList and @@ -101,20 +107,7 @@ class EVMStackLayoutGenerator { std::optional getExitLayoutOrStageDependencies( const MachineBasicBlock *Block, const DenseSet &Visited, - std::list &DependencyList) const; - - /// Returns a pair of '{jumpingBlock, targetBlock}' for each backwards jump - /// in the graph starting at \p Entry. - std::list> - collectBackwardsJumps(const MachineBasicBlock *Entry) const; - - /// After the main algorithms, layouts at conditional jumps are merely - /// compatible, i.e. the exit layout of the jumping block is a superset of the - /// entry layout of the target block. This function modifies the entry layouts - /// of conditional jump targets, s.t., the entry layout of target blocks match - /// the exit layout of the jumping block exactly, except that slots not - /// required after the jump are marked as 'JunkSlot's. - void stitchConditionalJumps(const MachineBasicBlock *Block); + std::deque &DependencyList) const; /// Calculates the ideal stack layout, s.t., both \p Stack1 and \p Stack2 can /// be achieved with minimal stack shuffling when starting from the returned @@ -131,11 +124,8 @@ class EVMStackLayoutGenerator { /// amount of operations to reconstruct the original stack \p Stack. static Stack compressStack(Stack Stack); - /// Fills in junk when entering branches that do not need a clean stack in - /// case the result is cheaper. - void fillInJunk(const MachineBasicBlock *Block); - const MachineFunction &MF; + const MachineLoopInfo *MLI; const EVMStackModel &StackModel; const EVMMachineCFGInfo &CFGInfo; diff --git a/llvm/lib/Target/EVM/EVMStackShuffler.h b/llvm/lib/Target/EVM/EVMStackShuffler.h index 6db0184bce9a..e91a640727b5 100644 --- a/llvm/lib/Target/EVM/EVMStackShuffler.h +++ b/llvm/lib/Target/EVM/EVMStackShuffler.h @@ -16,7 +16,6 @@ #ifndef LLVM_LIB_TARGET_EVM_EVMSTACKSHUFFLER_H #define LLVM_LIB_TARGET_EVM_EVMSTACKSHUFFLER_H -#include "EVMHelperUtilities.h" #include "EVMStackModel.h" #include #include @@ -152,7 +151,7 @@ class Shuffler { // If this slot occurs again later, we skip this occurrence. // TODO: use C++ 20 ranges::views::iota if (const auto &R = - EVMUtils::iota(SourceOffset + 1, Ops.sourceSize()); + llvm::seq(SourceOffset + 1, Ops.sourceSize()); std::any_of(R.begin(), R.end(), [&](size_t Offset) { return Ops.sourceIsSame(SourceOffset, Offset); })) @@ -214,7 +213,7 @@ class Shuffler { ShuffleOperations Ops{std::forward(args)...}; // All source slots are final. - if (const auto &R = EVMUtils::iota(0u, Ops.sourceSize()); + if (const auto &R = llvm::seq(0u, Ops.sourceSize()); std::all_of(R.begin(), R.end(), [&](size_t Index) { return Ops.isCompatible(Index, Index); })) { @@ -335,7 +334,7 @@ class Shuffler { assert(Ops.isCompatible(SourceTop, SourceTop)); const auto &SwappableOffsets = - EVMUtils::iota(Size > 17 ? Size - 17 : 0u, Size); + llvm::seq(Size > 17 ? Size - 17 : 0u, Size); // If we find a lower slot that is out of position, but also compatible with // the top, swap that up. diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index 94f265587924..bbf6d822fb95 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -11,7 +11,6 @@ //===----------------------------------------------------------------------===// #include "EVMStackifyCodeEmitter.h" -#include "EVMHelperUtilities.h" #include "EVMMachineFunctionInfo.h" #include "EVMStackDebug.h" #include "EVMStackShuffler.h" @@ -22,6 +21,21 @@ using namespace llvm; #define DEBUG_TYPE "evm-stackify-code-emitter" +template struct Overload : Ts... { + using Ts::operator()...; +}; +template Overload(Ts...) -> Overload; + +// Return whether the function of the call instruction will return. +bool callWillReturn(const MachineInstr *Call) { + assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); + const MachineOperand *FuncOp = Call->explicit_uses().begin(); + assert(FuncOp->isGlobal() && "Expected a global value"); + const auto *Func = cast(FuncOp->getGlobal()); + assert(Func && "Expected a function"); + return !Func->hasFnAttribute(Attribute::NoReturn); +} + // Return the number of input arguments of the call instruction. static size_t getCallArgCount(const MachineInstr *Call) { assert(Call->getOpcode() == EVM::FCALL && "Unexpected call instruction"); @@ -33,7 +47,7 @@ static size_t getCallArgCount(const MachineInstr *Call) { // The first operand is a function, so don't count it. If function // will return, we need to account for the return label. constexpr size_t NumFuncOp = 1; - return NumExplicitInputs - NumFuncOp + EVMUtils::callWillReturn(Call); + return NumExplicitInputs - NumFuncOp + callWillReturn(Call); } size_t EVMStackifyCodeEmitter::CodeEmitter::stackHeight() const { @@ -149,7 +163,7 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitFuncCall(const MachineInstr *MI) { // If this function returns, add a return label so we can emit it together // with JUMPDEST. This is taken care in the AsmPrinter. - if (EVMUtils::callWillReturn(MI)) + if (callWillReturn(MI)) NewMI.addSym(CallReturnSyms.at(MI)); verify(NewMI); } @@ -232,7 +246,7 @@ void EVMStackifyCodeEmitter::visitCall(const FunctionCall &Call) { assert(CurrentStack.size() >= NumArgs); // Assert that we got the correct return label on stack. - if (EVMUtils::callWillReturn(Call.MI)) { + if (callWillReturn(Call.MI)) { [[maybe_unused]] const auto *returnLabelSlot = std::get_if( &CurrentStack[CurrentStack.size() - NumArgs]); @@ -479,7 +493,7 @@ void EVMStackifyCodeEmitter::run() { const EVMMBBTerminatorsInfo *TermInfo = CFGInfo.getTerminatorsInfo(Block); MBBExitType ExitType = TermInfo->getExitType(); if (ExitType == MBBExitType::UnconditionalBranch) { - auto [UncondBr, Target, _] = TermInfo->getUnconditionalBranch(); + auto [UncondBr, Target] = TermInfo->getUnconditionalBranch(); // Create the stack expected at the jump target. createStackLayout(Layout.getMBBEntryLayout(Target)); From 026ee9af12d16944fa1a72d244cc99d8fe6a7871 Mon Sep 17 00:00:00 2001 From: Dmitry Borisenkov Date: Mon, 13 Jan 2025 19:55:43 +0200 Subject: [PATCH 18/42] [EVM][CMake] Fix unittests shared libs build --- llvm/unittests/Target/EVM/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/llvm/unittests/Target/EVM/CMakeLists.txt b/llvm/unittests/Target/EVM/CMakeLists.txt index e259d70e745d..f5aee1e08f97 100644 --- a/llvm/unittests/Target/EVM/CMakeLists.txt +++ b/llvm/unittests/Target/EVM/CMakeLists.txt @@ -13,6 +13,7 @@ set(LLVM_LINK_COMPONENTS MIRParser SelectionDAG Support + Target TargetParser ) From 2b78f1525d594f05851ca09c5ac770d285452cd9 Mon Sep 17 00:00:00 2001 From: Dmitry Borisenkov Date: Thu, 16 Jan 2025 21:59:05 +0200 Subject: [PATCH 19/42] [EVM] Refactor StackSlot std::variant -> LLVM style RTTI --- llvm/lib/Target/EVM/EVMStackDebug.cpp | 74 +---- llvm/lib/Target/EVM/EVMStackDebug.h | 6 - .../Target/EVM/EVMStackLayoutGenerator.cpp | 134 ++++----- llvm/lib/Target/EVM/EVMStackModel.cpp | 90 ++++-- llvm/lib/Target/EVM/EVMStackModel.h | 282 ++++++++++++------ llvm/lib/Target/EVM/EVMStackShuffler.h | 86 +++--- .../lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 117 +++----- llvm/unittests/Target/EVM/CMakeLists.txt | 1 + llvm/unittests/Target/EVM/StackModel.cpp | 157 ++++++++++ llvm/unittests/Target/EVM/StackShuffler.cpp | 90 +++--- 10 files changed, 612 insertions(+), 425 deletions(-) create mode 100644 llvm/unittests/Target/EVM/StackModel.cpp diff --git a/llvm/lib/Target/EVM/EVMStackDebug.cpp b/llvm/lib/Target/EVM/EVMStackDebug.cpp index 6e13e145fb52..98702de4fcee 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.cpp +++ b/llvm/lib/Target/EVM/EVMStackDebug.cpp @@ -24,82 +24,28 @@ template struct Overload : Ts... { }; template Overload(Ts...) -> Overload; -static std::string getInstName(const MachineInstr *MI) { - const MachineFunction *MF = MI->getParent()->getParent(); - const TargetInstrInfo *TII = MF->getSubtarget().getInstrInfo(); - return TII->getName(MI->getOpcode()).str(); -} - -const Function *llvm::getCalledFunction(const MachineInstr &MI) { - for (const MachineOperand &MO : MI.operands()) { - if (!MO.isGlobal()) - continue; - const Function *Func = dyn_cast(MO.getGlobal()); - if (Func != nullptr) - return Func; - } - return nullptr; -} - std::string llvm::stackToString(const Stack &S) { std::string Result("[ "); - for (auto const &Slot : S) - Result += stackSlotToString(Slot) + ' '; + for (const auto *Slot : S) + Result += Slot->toString() + ' '; Result += ']'; return Result; } -std::string llvm::stackSlotToString(const StackSlot &Slot) { - return std::visit( - Overload{ - [](const FunctionCallReturnLabelSlot &Ret) -> std::string { - return "RET[" + - std::string(getCalledFunction(*Ret.Call)->getName()) + "]"; - }, - [](const FunctionReturnLabelSlot &) -> std::string { return "RET"; }, - [](const VariableSlot &Var) -> std::string { - SmallString<64> S; - raw_svector_ostream OS(S); - OS << printReg(Var.VirtualReg, nullptr, 0, nullptr); - return std::string(S); - ; - }, - [](const LiteralSlot &Literal) -> std::string { - SmallString<64> S; - Literal.Value.toStringSigned(S); - return std::string(S); - }, - [](const SymbolSlot &Symbol) -> std::string { - return getInstName(Symbol.MI) + ":" + - std::string(Symbol.Symbol->getName()); - }, - [](const TemporarySlot &Tmp) -> std::string { - SmallString<128> S; - raw_svector_ostream OS(S); - OS << "TMP[" << getInstName(Tmp.MI) << ", "; - OS << std::to_string(Tmp.Index) + "]"; - return std::string(S); - }, - [](const JunkSlot &Junk) -> std::string { return "JUNK"; }}, - Slot); - ; -} - #ifndef NDEBUG void StackLayoutPrinter::operator()() { OS << "Function: " << MF.getName() << "("; - for (const StackSlot &ParamSlot : StackModel.getFunctionParameters()) { - if (const auto *Slot = std::get_if(&ParamSlot)) - OS << printReg(Slot->VirtualReg, nullptr, 0, nullptr) << ' '; - else if (std::holds_alternative(ParamSlot)) + for (const StackSlot *ParamSlot : StackModel.getFunctionParameters()) { + if (const auto *Slot = dyn_cast(ParamSlot)) + OS << printReg(Slot->getReg(), nullptr, 0, nullptr) << ' '; + else if (isa(ParamSlot)) OS << "[unused param] "; else llvm_unreachable("Unexpected stack slot"); } OS << ");\n"; - OS << "FunctionEntry " - << " -> Block" << getBlockId(MF.front()) << ";\n"; + OS << "FunctionEntry " << " -> Block" << getBlockId(MF.front()) << ";\n"; for (const auto &MBB : MF) { printBlock(MBB); @@ -123,8 +69,8 @@ void StackLayoutPrinter::printBlock(MachineBasicBlock const &Block) { }, [&](Assignment const &Assignment) { OS << "Assignment("; - for (const auto &Var : Assignment.Variables) - OS << printReg(Var.VirtualReg, nullptr, 0, nullptr) + for (const auto *Var : Assignment.Variables) + OS << printReg(Var->getReg(), nullptr, 0, nullptr) << ", "; OS << ")"; }}, @@ -153,7 +99,7 @@ void StackLayoutPrinter::printBlock(MachineBasicBlock const &Block) { auto [CondBr, UncondBr, TrueBB, FalseBB, Condition] = TermInfo->getConditionalBranch(); OS << "Block" << getBlockId(Block) << "Exit [label=\"{ "; - OS << stackSlotToString(StackModel.getStackSlot(*Condition)); + OS << StackModel.getStackSlot(*Condition)->toString(); OS << "| { <0> Zero | <1> NonZero }}\"];\n"; OS << "Block" << getBlockId(Block); OS << "Exit:0 -> Block" << getBlockId(*FalseBB) << ";\n"; diff --git a/llvm/lib/Target/EVM/EVMStackDebug.h b/llvm/lib/Target/EVM/EVMStackDebug.h index b4c8737ccae5..615ffd9279c7 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.h +++ b/llvm/lib/Target/EVM/EVMStackDebug.h @@ -17,18 +17,12 @@ #include "EVMMachineCFGInfo.h" #include "EVMStackModel.h" #include "llvm/CodeGen/MachineFunction.h" -#include #include -#include -#include -#include namespace llvm { class EVMStackLayout; -const Function *getCalledFunction(const MachineInstr &MI); -std::string stackSlotToString(const StackSlot &Slot); std::string stackToString(Stack const &S); #ifndef NDEBUG diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp index 26afd5114633..29c3ce825870 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -63,10 +63,10 @@ findStackTooDeep(Stack const &Source, Stack const &Target) { auto getVariableChoices = [](auto &&SlotRange) { SmallVector Result; - for (auto const &Slot : SlotRange) - if (auto const *VarSlot = std::get_if(&Slot)) - if (!is_contained(Result, VarSlot->VirtualReg)) - Result.push_back(VarSlot->VirtualReg); + for (auto const *Slot : SlotRange) + if (auto const *VarSlot = dyn_cast(Slot)) + if (!is_contained(Result, VarSlot->getReg())) + Result.push_back(VarSlot->getReg()); return Result; }; @@ -77,8 +77,8 @@ findStackTooDeep(Stack const &Source, Stack const &Target) { Errors.emplace_back(EVMStackLayoutGenerator::StackTooDeep{ I - 16, getVariableChoices(take_back(CurrentStack, I + 1))}); }, - [&](StackSlot const &Slot) { - if (isRematerializable(Slot)) + [&](const StackSlot *Slot) { + if (Slot->isRematerializable()) return; if (auto Depth = offset(reverse(CurrentStack), Slot); @@ -102,14 +102,14 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, struct PreviousSlot { size_t slot; }; - using LayoutT = SmallVector>; + using LayoutT = SmallVector>; // Determine the number of slots that have to be on stack before executing the // operation (excluding the inputs of the operation itself). That is slots // that should not be generated on the fly and are not outputs of the // operation. size_t PreOperationLayoutSize = Post.size(); - for (auto const &Slot : Post) + for (const auto *Slot : Post) if (is_contained(OperationOutput, Slot) || GenerateSlotOnTheFly(Slot)) --PreOperationLayoutSize; @@ -129,54 +129,53 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, struct ShuffleOperations { LayoutT &Layout; const Stack &Post; - std::set Outputs; + std::set Outputs; Multiplicity Mult; Callable GenerateSlotOnTheFly; ShuffleOperations(LayoutT &Layout, Stack const &Post, Callable GenerateSlotOnTheFly) : Layout(Layout), Post(Post), GenerateSlotOnTheFly(GenerateSlotOnTheFly) { - for (auto const &LayoutSlot : Layout) - if (const StackSlot *Slot = std::get_if(&LayoutSlot)) + for (const auto &LayoutSlot : Layout) + if (auto Slot = std::get_if(&LayoutSlot)) Outputs.insert(*Slot); - for (auto const &LayoutSlot : Layout) - if (const StackSlot *Slot = std::get_if(&LayoutSlot)) + for (const auto &LayoutSlot : Layout) + if (auto Slot = std::get_if(&LayoutSlot)) --Mult[*Slot]; - for (auto &&Slot : Post) + for (auto *Slot : Post) if (Outputs.count(Slot) || GenerateSlotOnTheFly(Slot)) ++Mult[Slot]; } bool isCompatible(size_t Source, size_t Target) { return Source < Layout.size() && Target < Post.size() && - (std::holds_alternative(Post[Target]) || + (isa(Post[Target]) || std::visit(Overload{[&](const PreviousSlot &) { return !Outputs.count(Post[Target]) && !GenerateSlotOnTheFly(Post[Target]); }, - [&](const StackSlot &S) { + [&](const StackSlot *S) { return S == Post[Target]; }}, Layout[Source])); } bool sourceIsSame(size_t Lhs, size_t Rhs) { - return std::visit( - Overload{ - [&](PreviousSlot const &, PreviousSlot const &) { return true; }, - [&](StackSlot const &Lhs, StackSlot const &Rhs) { - return Lhs == Rhs; - }, - [&](auto const &, auto const &) { return false; }}, - Layout[Lhs], Layout[Rhs]); + if (std::holds_alternative(Layout[Lhs]) && + std::holds_alternative(Layout[Rhs])) + return true; + + auto SlotLHS = std::get_if(&Layout[Lhs]); + auto SlotRHS = std::get_if(&Layout[Rhs]); + return SlotLHS && SlotRHS && *SlotLHS == *SlotRHS; } int sourceMultiplicity(size_t Offset) { return std::visit( Overload{[&](PreviousSlot const &) { return 0; }, - [&](StackSlot const &S) { return Mult.at(S); }}, + [&](const StackSlot *S) { return Mult.at(S); }}, Layout[Offset]); } @@ -187,8 +186,7 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, } bool targetIsArbitrary(size_t Offset) { - return Offset < Post.size() && - std::holds_alternative(Post[Offset]); + return Offset < Post.size() && isa(Post[Offset]); } void swap(size_t I) { @@ -217,9 +215,9 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, // VariableSlot{"tmp"}, then we want the variable tmp in the slot at offset 2 // in the layout before the operation. assert(Layout.size() == Post.size()); - SmallVector> IdealLayout(Post.size(), std::nullopt); + SmallVector IdealLayout(Post.size(), nullptr); for (unsigned Idx = 0; Idx < std::min(Layout.size(), Post.size()); ++Idx) { - auto &Slot = Post[Idx]; + auto *Slot = Post[Idx]; auto &IdealPosition = Layout[Idx]; if (PreviousSlot *PrevSlot = std::get_if(&IdealPosition)) IdealLayout[PrevSlot->slot] = Slot; @@ -233,9 +231,9 @@ Stack createIdealLayout(const Stack &OperationOutput, const Stack &Post, assert(IdealLayout.size() == PreOperationLayoutSize); Stack Result; - for (const auto &Item : IdealLayout) { + for (auto *Item : IdealLayout) { assert(Item); - Result.emplace_back(*Item); + Result.push_back(Item); } return Result; @@ -271,8 +269,8 @@ Stack EVMStackLayoutGenerator::propagateStackThroughOperation( AggressiveStackCompression = false; // This is a huge tradeoff between code size, gas cost and stack size. - auto generateSlotOnTheFly = [&](StackSlot const &Slot) { - return AggressiveStackCompression && isRematerializable(Slot); + auto generateSlotOnTheFly = [&](const StackSlot *Slot) { + return AggressiveStackCompression && Slot->isRematerializable(); }; // Determine the ideal permutation of the slots in ExitLayout that are not @@ -284,9 +282,9 @@ Stack EVMStackLayoutGenerator::propagateStackThroughOperation( // Make sure the resulting previous slots do not overlap with any assignmed // variables. if (auto const *Assign = std::get_if(&Operation.Operation)) - for (auto &StackSlot : IdealStack) - if (auto const *VarSlot = std::get_if(&StackSlot)) - assert(!is_contained(Assign->Variables, *VarSlot)); + for (auto *StackSlot : IdealStack) + if (const auto *VarSlot = dyn_cast(StackSlot)) + assert(!is_contained(Assign->Variables, VarSlot)); // Since stack+Operation.output can be easily shuffled to ExitLayout, the // desired layout before the operation is stack+Operation.input; @@ -302,7 +300,7 @@ Stack EVMStackLayoutGenerator::propagateStackThroughOperation( // Remove anything from the stack top that can be freely generated or dupped // from deeper on the stack. while (!IdealStack.empty()) { - if (isRematerializable(IdealStack.back())) + if (IdealStack.back()->isRematerializable()) IdealStack.pop_back(); else if (auto Offset = offset(drop_begin(reverse(IdealStack), 1), IdealStack.back())) { @@ -341,7 +339,7 @@ static size_t getOptimalNumberOfJunks(const Stack &EntryLayout, size_t BestNumJunk = 0; size_t MaxJunk = EntryLayout.size(); for (size_t NumJunk = 1; NumJunk <= MaxJunk; ++NumJunk) { - Stack JunkedTarget(NumJunk, JunkSlot{}); + Stack JunkedTarget(NumJunk, EVMStackModel::getJunkSlot()); JunkedTarget.append(TargetLayout); size_t Cost = EvaluateStackTransform(EntryLayout, JunkedTarget); if (Cost < BestCost) { @@ -397,12 +395,11 @@ void EVMStackLayoutGenerator::runPropagation() { } } - // Check which latch blocks still require fixing of their exit layouts. // Revisit these blocks again. for (auto [Latch, Header] : Backedges) { const Stack &HeaderEntryLayout = MBBEntryLayoutMap[Header]; const Stack &LatchExitLayout = MBBExitLayoutMap[Latch]; - if (all_of(HeaderEntryLayout, [LatchExitLayout](const StackSlot &Slot) { + if (all_of(HeaderEntryLayout, [LatchExitLayout](const StackSlot *Slot) { return is_contained(LatchExitLayout, Slot); })) continue; @@ -464,15 +461,15 @@ void EVMStackLayoutGenerator::runPropagation() { Stack NewSuccEntryLayout = ExitLayout; // Whatever the block being jumped to does not actually require, // can be marked as junk. - for (StackSlot &Slot : NewSuccEntryLayout) + for (StackSlot *&Slot : NewSuccEntryLayout) if (!is_contained(SuccEntryLayout, Slot)) - Slot = JunkSlot{}; + Slot = EVMStackModel::getJunkSlot(); #ifndef NDEBUG // Make sure everything the block being jumped to requires is // actually present or can be generated. - for (const StackSlot &Slot : SuccEntryLayout) - assert(isRematerializable(Slot) || + for (const StackSlot *Slot : SuccEntryLayout) + assert(Slot->isRematerializable() || is_contained(NewSuccEntryLayout, Slot)); #endif // NDEBUG @@ -484,7 +481,7 @@ void EVMStackLayoutGenerator::runPropagation() { Stack EntryStack; bool IsNoReturn = MF.getFunction().hasFnAttribute(Attribute::NoReturn); if (!IsNoReturn) - EntryStack.emplace_back(FunctionReturnLabelSlot{&MF}); + EntryStack.push_back(StackModel.getFunctionReturnLabelSlot(&MF)); // Calling convention: input arguments are passed in stack such that the // first one specified in the function declaration is passed on the stack TOP. @@ -585,19 +582,19 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, Stack CommonPrefix; for (unsigned Idx = 0; Idx < std::min(Stack1.size(), Stack2.size()); ++Idx) { - const StackSlot &Slot1 = Stack1[Idx]; - const StackSlot &Slot2 = Stack2[Idx]; + StackSlot *Slot1 = Stack1[Idx]; + const StackSlot *Slot2 = Stack2[Idx]; if (!(Slot1 == Slot2)) break; - CommonPrefix.emplace_back(Slot1); + CommonPrefix.push_back(Slot1); } Stack Stack1Tail, Stack2Tail; - for (const auto &Slot : drop_begin(Stack1, CommonPrefix.size())) - Stack1Tail.emplace_back(Slot); + for (auto *Slot : drop_begin(Stack1, CommonPrefix.size())) + Stack1Tail.push_back(Slot); - for (const auto &Slot : drop_begin(Stack2, CommonPrefix.size())) - Stack2Tail.emplace_back(Slot); + for (auto *Slot : drop_begin(Stack2, CommonPrefix.size())) + Stack2Tail.push_back(Slot); if (Stack1Tail.empty()) { CommonPrefix.append(compressStack(Stack2Tail)); @@ -610,20 +607,19 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, } Stack Candidate; - for (auto Slot : Stack1Tail) + for (auto *Slot : Stack1Tail) if (!is_contained(Candidate, Slot)) - Candidate.emplace_back(Slot); + Candidate.push_back(Slot); - for (auto Slot : Stack2Tail) + for (auto *Slot : Stack2Tail) if (!is_contained(Candidate, Slot)) - Candidate.emplace_back(Slot); + Candidate.push_back(Slot); { auto RemIt = std::remove_if( - Candidate.begin(), Candidate.end(), [](StackSlot const &Slot) { - return std::holds_alternative(Slot) || - std::holds_alternative(Slot) || - std::holds_alternative(Slot); + Candidate.begin(), Candidate.end(), [](const StackSlot *Slot) { + return isa(Slot) || isa(Slot) || + isa(Slot); }); Candidate.erase(RemIt, Candidate.end()); } @@ -637,8 +633,8 @@ Stack EVMStackLayoutGenerator::combineStack(Stack const &Stack1, NumOps += 1000; }; - auto DupOrPush = [&](StackSlot const &Slot) { - if (isRematerializable(Slot)) + auto DupOrPush = [&](const StackSlot *Slot) { + if (Slot->isRematerializable()) return; Stack Tmp = CommonPrefix; @@ -699,8 +695,8 @@ Stack EVMStackLayoutGenerator::compressStack(Stack CurStack) { auto I = CurStack.rbegin(), E = CurStack.rend(); for (size_t Depth = 0; I < E; ++I, ++Depth) { - StackSlot &Slot = *I; - if (isRematerializable(Slot)) { + StackSlot *Slot = *I; + if (Slot->isRematerializable()) { FirstDupOffset = CurStack.size() - Depth - 1; break; } @@ -728,8 +724,8 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { OpGas += 3; // SWAP* gas price; }; - auto DupOrPush = [&](StackSlot const &Slot) { - if (isRematerializable(Slot)) + auto DupOrPush = [&](const StackSlot *Slot) { + if (Slot->isRematerializable()) OpGas += 3; else { auto Depth = offset(reverse(Source), Slot); @@ -751,17 +747,17 @@ size_t llvm::EvaluateStackTransform(Stack Source, Stack const &Target) { void EVMStackLayoutGenerator::addJunksToStackBottom( const MachineBasicBlock *Entry, size_t NumJunk) { for (const MachineBasicBlock *MBB : depth_first(Entry)) { - Stack EntryTmp(NumJunk, JunkSlot{}); + Stack EntryTmp(NumJunk, EVMStackModel::getJunkSlot()); EntryTmp.append(MBBEntryLayoutMap.at(MBB)); MBBEntryLayoutMap[MBB] = std::move(EntryTmp); for (const Operation &Operation : StackModel.getOperations(MBB)) { - Stack OpEntryTmp(NumJunk, JunkSlot{}); + Stack OpEntryTmp(NumJunk, EVMStackModel::getJunkSlot()); OpEntryTmp.append(OperationEntryLayoutMap.at(&Operation)); OperationEntryLayoutMap[&Operation] = std::move(OpEntryTmp); } - Stack ExitTmp(NumJunk, JunkSlot{}); + Stack ExitTmp(NumJunk, EVMStackModel::getJunkSlot()); ExitTmp.append(MBBExitLayoutMap.at(MBB)); MBBExitLayoutMap[MBB] = std::move(ExitTmp); } diff --git a/llvm/lib/Target/EVM/EVMStackModel.cpp b/llvm/lib/Target/EVM/EVMStackModel.cpp index e69d11db925d..ca67872d8b30 100644 --- a/llvm/lib/Target/EVM/EVMStackModel.cpp +++ b/llvm/lib/Target/EVM/EVMStackModel.cpp @@ -17,10 +17,38 @@ #include "llvm/CodeGen/MachineFunction.h" #include -#include using namespace llvm; +static const Function *getCalledFunction(const MachineInstr &MI) { + for (const MachineOperand &MO : MI.operands()) { + if (!MO.isGlobal()) + continue; + if (const auto *Func = dyn_cast(MO.getGlobal())) + return Func; + } + return nullptr; +} +// TODO: make it static once Operation gets rid of std::variant. +std::string llvm::getInstName(const MachineInstr *MI) { + const MachineFunction *MF = MI->getParent()->getParent(); + const TargetInstrInfo *TII = MF->getSubtarget().getInstrInfo(); + return TII->getName(MI->getOpcode()).str(); +} + +std::string SymbolSlot::toString() const { + return getInstName(MI) + ":" + std::string(Symbol->getName()); +} +std::string FunctionCallReturnLabelSlot::toString() const { + return "RET[" + std::string(getCalledFunction(*Call)->getName()) + "]"; +} +std::string TemporarySlot::toString() const { + SmallString<128> S; + raw_svector_ostream OS(S); + OS << "TMP[" << getInstName(MI) << ", " << std::to_string(Index) + "]"; + return std::string(S.str()); +} + EVMStackModel::EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS) : MF(MF), LIS(LIS) { for (MachineBasicBlock &MBB : MF) { @@ -33,37 +61,34 @@ EVMStackModel::EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS) Stack EVMStackModel::getFunctionParameters() const { auto *MFI = MF.getInfo(); - SmallVector Parameters(MFI->getNumParams(), JunkSlot{}); + SmallVector Parameters(MFI->getNumParams(), + EVMStackModel::getJunkSlot()); for (const MachineInstr &MI : MF.front()) { if (MI.getOpcode() == EVM::ARGUMENT) { int64_t ArgIdx = MI.getOperand(1).getImm(); - Parameters[ArgIdx] = VariableSlot{MI.getOperand(0).getReg()}; + Parameters[ArgIdx] = getVariableSlot(MI.getOperand(0).getReg()); } } return Parameters; } -StackSlot EVMStackModel::getStackSlot(const MachineOperand &MO) const { - const MachineInstr *MI = MO.getParent(); - StackSlot Slot = VariableSlot{MO.getReg()}; - SlotIndex Idx = LIS.getInstructionIndex(*MI); - const LiveInterval *LI = &LIS.getInterval(MO.getReg()); - LiveQueryResult LRQ = LI->Query(Idx); - const VNInfo *VNI = LRQ.valueIn(); - assert(VNI && "Use of non-existing value"); +StackSlot *EVMStackModel::getStackSlot(const MachineOperand &MO) const { // If the virtual register defines a constant and this is the only // definition, emit the literal slot as MI's input. + const LiveInterval *LI = &LIS.getInterval(MO.getReg()); if (LI->containsOneValue()) { + SlotIndex Idx = LIS.getInstructionIndex(*MO.getParent()); + const VNInfo *VNI = LI->Query(Idx).valueIn(); + assert(VNI && "Use of non-existing value"); assert(!VNI->isPHIDef()); const MachineInstr *DefMI = LIS.getInstructionFromIndex(VNI->def); assert(DefMI && "Dead valno in interval"); if (DefMI->getOpcode() == EVM::CONST_I256) { const APInt Imm = DefMI->getOperand(1).getCImm()->getValue(); - Slot = LiteralSlot{std::move(Imm)}; + return getLiteralSlot(std::move(Imm)); } } - - return Slot; + return getVariableSlot(MO.getReg()); } Stack EVMStackModel::getInstrInput(const MachineInstr &MI) const { @@ -78,16 +103,15 @@ Stack EVMStackModel::getInstrInput(const MachineInstr &MI) const { if (MO.getReg() == EVM::SP) continue; - In.emplace_back(getStackSlot(MO)); + In.push_back(getStackSlot(MO)); } return In; } Stack EVMStackModel::getInstrOutput(const MachineInstr &MI) const { Stack Out; - unsigned ArgNumber = 0; - for (const auto &MO : MI.defs()) - Out.push_back(TemporarySlot{&MI, MO.getReg(), ArgNumber++}); + for (unsigned I = 0, E = MI.getNumExplicitDefs(); I < E; ++I) + Out.push_back(getTemporarySlot(&MI, I)); return Out; } @@ -111,7 +135,7 @@ void EVMStackModel::createOperation(MachineInstr &MI, assert(Func); IsNoReturn = Func->hasFnAttribute(Attribute::NoReturn); if (!IsNoReturn) - Input.push_back(FunctionCallReturnLabelSlot{&MI}); + Input.push_back(getFunctionCallReturnLabelSlot(&MI)); break; } } @@ -146,42 +170,42 @@ void EVMStackModel::createOperation(MachineInstr &MI, } break; } - // Cretae CFG::Assignment object for the MI. + // Create CFG::Assignment object for the MI. Stack Input, Output; - SmallVector Variables; + SmallVector Variables; switch (MI.getOpcode()) { case EVM::CONST_I256: { const Register DefReg = MI.getOperand(0).getReg(); const APInt Imm = MI.getOperand(1).getCImm()->getValue(); - Input.push_back(LiteralSlot{std::move(Imm)}); - Output.push_back(VariableSlot{DefReg}); - Variables.push_back(VariableSlot{DefReg}); + Input.push_back(getLiteralSlot(std::move(Imm))); + Output.push_back(getVariableSlot(DefReg)); + Variables.push_back(getVariableSlot(DefReg)); } break; case EVM::DATASIZE: case EVM::DATAOFFSET: case EVM::LINKERSYMBOL: { const Register DefReg = MI.getOperand(0).getReg(); MCSymbol *Sym = MI.getOperand(1).getMCSymbol(); - Input.push_back(SymbolSlot{Sym, &MI}); - Output.push_back(VariableSlot{DefReg}); - Variables.push_back(VariableSlot{DefReg}); + Input.push_back(getSymbolSlot(Sym, &MI)); + Output.push_back(getVariableSlot(DefReg)); + Variables.push_back(getVariableSlot(DefReg)); } break; case EVM::COPY_I256: { // Copy instruction corresponds to the assignment operator, so // we do not need to create intermediate TmpSlots. Input = getInstrInput(MI); const Register DefReg = MI.getOperand(0).getReg(); - Output.push_back(VariableSlot{DefReg}); - Variables.push_back(VariableSlot{DefReg}); + Output.push_back(getVariableSlot(DefReg)); + Variables.push_back(getVariableSlot(DefReg)); } break; default: { unsigned ArgsNumber = 0; for (const auto &MO : MI.defs()) { assert(MO.isReg()); const Register Reg = MO.getReg(); - Input.push_back(TemporarySlot{&MI, Reg, ArgsNumber++}); - Output.push_back(VariableSlot{Reg}); - Variables.push_back(VariableSlot{Reg}); + Input.push_back(getTemporarySlot(&MI, ArgsNumber++)); + Output.push_back(getVariableSlot(Reg)); + Variables.push_back(getVariableSlot(Reg)); } } break; } @@ -200,6 +224,6 @@ Stack EVMStackModel::getReturnArguments(const MachineInstr &MI) const { // Calling convention: return values are passed in stack such that the // last one specified in the RET instruction is passed on the stack TOP. std::reverse(Input.begin(), Input.end()); - Input.emplace_back(FunctionReturnLabelSlot{&MF}); + Input.push_back(getFunctionReturnLabelSlot(&MF)); return Input; } diff --git a/llvm/lib/Target/EVM/EVMStackModel.h b/llvm/lib/Target/EVM/EVMStackModel.h index 1f5972821ff2..d716b2208431 100644 --- a/llvm/lib/Target/EVM/EVMStackModel.h +++ b/llvm/lib/Target/EVM/EVMStackModel.h @@ -26,6 +26,7 @@ #include "llvm/CodeGen/MachineInstr.h" #include "llvm/CodeGen/MachineLoopInfo.h" #include "llvm/CodeGen/Register.h" +#include "llvm/CodeGen/TargetInstrInfo.h" #include "llvm/MC/MCSymbol.h" #include @@ -35,138 +36,172 @@ namespace llvm { class MachineFunction; class MachineBasicBlock; -/// The following structs describe different kinds of stack slots. -/// Each stack slot is equality- and less-than-comparable and -/// specifies an attribute 'isRematerializable' that is true, -/// if a slot of this kind always has a known value at compile time and -/// therefore can safely be removed from the stack at any time and then -/// regenerated later. +std::string getInstName(const MachineInstr *MI); -/// The label pushed as return label before a function call, i.e. the label the -/// call is supposed to return to. -struct FunctionCallReturnLabelSlot { - const MachineInstr *Call = nullptr; - static constexpr bool isRematerializable = true; +class StackSlot { +public: + enum SlotKind { + SK_Literal, + SK_Variable, + SK_Symbol, + SK_FunctionCallReturnLabel, + SK_FunctionReturnLabel, + SK_Temporary, + SK_Junk, + }; - bool operator==(FunctionCallReturnLabelSlot const &Rhs) const { - return Call == Rhs.Call; - } +private: + const SlotKind KindID; - bool operator<(FunctionCallReturnLabelSlot const &Rhs) const { - return Call < Rhs.Call; - } +protected: + StackSlot(SlotKind KindID) : KindID(KindID) {} + +public: + virtual ~StackSlot() = default; + + unsigned getSlotKind() const { return KindID; } + + // 'isRematerializable()' returns true, if a slot always has a known value + // at compile time and therefore can safely be removed from the stack at any + // time and then regenerated later. + virtual bool isRematerializable() const = 0; + virtual std::string toString() const = 0; }; -/// The return jump target of a function while generating the code of the -/// function body. I.e. the caller of a function pushes a -/// 'FunctionCallReturnLabelSlot' (see above) before jumping to the function -/// and this very slot is viewed as 'FunctionReturnLabelSlot' inside the -/// function body and jumped to when returning from the function. -struct FunctionReturnLabelSlot { - const MachineFunction *MF = nullptr; - static constexpr bool isRematerializable = false; +/// A slot containing a literal value. +class LiteralSlot final : public StackSlot { + APInt Value; - bool operator==(FunctionReturnLabelSlot const &Rhs) const { - // There can never be return label slots of different functions on stack - // simultaneously. - assert(MF == Rhs.MF); - return true; +public: + LiteralSlot(const APInt &V) : StackSlot(SK_Literal), Value(V) {} + const APInt &getValue() const { return Value; } + + bool isRematerializable() const override { return true; } + std::string toString() const override { + SmallString<64> S; + Value.toStringSigned(S); + return std::string(S.str()); } - - bool operator<(FunctionReturnLabelSlot const &Rhs) const { - // There can never be return label slots of different functions on stack - // simultaneously. - assert(MF == Rhs.MF); - return false; + static bool classof(const StackSlot *S) { + return S->getSlotKind() == SK_Literal; } }; /// A slot containing the current value of a particular variable. -struct VariableSlot { +class VariableSlot final : public StackSlot { Register VirtualReg; - static constexpr bool isRematerializable = false; - bool operator==(VariableSlot const &Rhs) const { - return VirtualReg == Rhs.VirtualReg; +public: + VariableSlot(const Register &R) : StackSlot(SK_Variable), VirtualReg(R) {} + const Register &getReg() const { return VirtualReg; } + + bool isRematerializable() const override { return false; } + std::string toString() const override { + SmallString<64> S; + raw_svector_ostream OS(S); + OS << printReg(VirtualReg, nullptr, 0, nullptr); + return std::string(S.str()); } - - bool operator<(VariableSlot const &Rhs) const { - return VirtualReg < Rhs.VirtualReg; + static bool classof(const StackSlot *S) { + return S->getSlotKind() == SK_Variable; } }; -/// A slot containing a literal value. -struct LiteralSlot { - APInt Value; - static constexpr bool isRematerializable = true; +/// A slot containing a MCSymbol. +class SymbolSlot final : public StackSlot { + MCSymbol *Symbol; + const MachineInstr *MI = nullptr; + +public: + SymbolSlot(MCSymbol *S, const MachineInstr *MI) + : StackSlot(SK_Symbol), Symbol(S), MI(MI) {} + const MachineInstr *getMachineInstr() const { return MI; } + MCSymbol *getSymbol() const { return Symbol; } - bool operator==(LiteralSlot const &Rhs) const { return Value == Rhs.Value; } + bool isRematerializable() const override { return true; } + std::string toString() const override; - bool operator<(LiteralSlot const &Rhs) const { return Value.ult(Rhs.Value); } + static bool classof(const StackSlot *S) { + return S->getSlotKind() == SK_Symbol; + } }; -/// A slot containing a MCSymbol. -struct SymbolSlot { - MCSymbol *Symbol; - const MachineInstr *MI = nullptr; - static constexpr bool isRematerializable = true; +/// The label pushed as return label before a function call, i.e. the label the +/// call is supposed to return to. +class FunctionCallReturnLabelSlot final : public StackSlot { + const MachineInstr *Call = nullptr; + +public: + FunctionCallReturnLabelSlot(const MachineInstr *Call) + : StackSlot(SK_FunctionCallReturnLabel), Call(Call) {} + const MachineInstr *getCall() const { return Call; } - bool operator==(SymbolSlot const &Rhs) const { - return Symbol == Rhs.Symbol && MI->getOpcode() == Rhs.MI->getOpcode(); + bool isRematerializable() const override { return true; } + std::string toString() const override; + + static bool classof(const StackSlot *S) { + return S->getSlotKind() == SK_FunctionCallReturnLabel; } +}; + +/// The return jump target of a function while generating the code of the +/// function body. I.e. the caller of a function pushes a +/// 'FunctionCallReturnLabelSlot' (see above) before jumping to the function +/// and this very slot is viewed as 'FunctionReturnLabelSlot' inside the +/// function body and jumped to when returning from the function. +class FunctionReturnLabelSlot final : public StackSlot { + const MachineFunction *MF = nullptr; - bool operator<(SymbolSlot const &Rhs) const { - return std::make_pair(Symbol, MI->getOpcode()) < - std::make_pair(Rhs.Symbol, Rhs.MI->getOpcode()); +public: + FunctionReturnLabelSlot(const MachineFunction *MF) + : StackSlot(SK_FunctionReturnLabel), MF(MF) {} + const MachineFunction *getMachineFunction() { return MF; } + + bool isRematerializable() const override { return false; } + std::string toString() const override { return "RET"; } + + static bool classof(const StackSlot *S) { + return S->getSlotKind() == SK_FunctionReturnLabel; } }; /// A slot containing the index-th return value of a previous call. -struct TemporarySlot { +class TemporarySlot final : public StackSlot { /// The call that returned this slot. const MachineInstr *MI = nullptr; - Register VirtualReg; /// Specifies to which of the values returned by the call this slot refers. /// index == 0 refers to the slot deepest in the stack after the call. size_t Index = 0; - static constexpr bool isRematerializable = false; - bool operator==(TemporarySlot const &Rhs) const { - return MI == Rhs.MI && Index == Rhs.Index; - } +public: + TemporarySlot(const MachineInstr *MI, size_t Idx) + : StackSlot(SK_Temporary), MI(MI), Index(Idx) {} - bool operator<(TemporarySlot const &Rhs) const { - return std::make_pair(MI, Index) < std::make_pair(Rhs.MI, Rhs.Index); + bool isRematerializable() const override { return false; } + std::string toString() const override; + + static bool classof(const StackSlot *S) { + return S->getSlotKind() == SK_Temporary; } }; /// A slot containing an arbitrary value that is always eventually popped and /// never used. Used to maintain stack balance on control flow joins. -struct JunkSlot { - static constexpr bool isRematerializable = true; +class JunkSlot final : public StackSlot { +public: + JunkSlot() : StackSlot(SK_Junk) {} - bool operator==(JunkSlot const &) const { return true; } + bool isRematerializable() const override { return true; } + std::string toString() const override { return "JUNK"; } - bool operator<(JunkSlot const &) const { return false; } + static bool classof(const StackSlot *S) { + return S->getSlotKind() == SK_Junk; + } }; -using StackSlot = - std::variant; - /// The stack top is the last element of the vector. -using Stack = SmallVector; - -/// Returns true if Slot can be materialized on the stack at any time. -inline bool isRematerializable(const StackSlot &Slot) { - return std::visit( - [](auto const &TypedSlot) { - return std::decay_t::isRematerializable; - }, - Slot); -} +using Stack = SmallVector; struct BuiltinCall { MachineInstr *MI = nullptr; @@ -181,7 +216,7 @@ struct Assignment { /// The variables being assigned to also occur as 'Output' in the /// 'Operation' containing the assignment, but are also stored here for /// convenience. - SmallVector Variables; + SmallVector Variables; }; struct Operation { @@ -193,10 +228,29 @@ struct Operation { }; class EVMStackModel { + MachineFunction &MF; + const LiveIntervals &LIS; + DenseMap> OperationsMap; + + // Storage for stack slots. + mutable DenseMap> LiteralStorage; + mutable DenseMap> VariableStorage; + mutable DenseMap, + std::unique_ptr> + SymbolStorage; + mutable DenseMap> + FunctionCallReturnLabelStorage; + mutable DenseMap, + std::unique_ptr> + TemporaryStorage; + + // There should be a single FunctionReturnLabelSlot for the MF. + mutable std::unique_ptr TheFunctionReturnLabelSlot; + public: EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS); Stack getFunctionParameters() const; - StackSlot getStackSlot(const MachineOperand &MO) const; Stack getInstrInput(const MachineInstr &MI) const; Stack getInstrOutput(const MachineInstr &MI) const; Stack getReturnArguments(const MachineInstr &MI) const; @@ -205,14 +259,54 @@ class EVMStackModel { return OperationsMap.at(MBB); } + // Get or create a requested stack slot. + StackSlot *getStackSlot(const MachineOperand &MO) const; + LiteralSlot *getLiteralSlot(const APInt &V) const { + if (LiteralStorage.count(V) == 0) + LiteralStorage[V] = std::make_unique(V); + return LiteralStorage[V].get(); + } + VariableSlot *getVariableSlot(const Register &R) const { + if (VariableStorage.count(R) == 0) + VariableStorage[R] = std::make_unique(R); + return VariableStorage[R].get(); + } + SymbolSlot *getSymbolSlot(MCSymbol *S, const MachineInstr *MI) const { + auto Key = std::make_pair(S, MI); + if (SymbolStorage.count(Key) == 0) + SymbolStorage[Key] = std::make_unique(S, MI); + return SymbolStorage[Key].get(); + } + FunctionCallReturnLabelSlot * + getFunctionCallReturnLabelSlot(const MachineInstr *Call) const { + if (FunctionCallReturnLabelStorage.count(Call) == 0) + FunctionCallReturnLabelStorage[Call] = + std::make_unique(Call); + return FunctionCallReturnLabelStorage[Call].get(); + } + FunctionReturnLabelSlot * + getFunctionReturnLabelSlot(const MachineFunction *MF) const { + if (!TheFunctionReturnLabelSlot) + TheFunctionReturnLabelSlot = + std::make_unique(MF); + assert(MF == TheFunctionReturnLabelSlot->getMachineFunction()); + return TheFunctionReturnLabelSlot.get(); + } + TemporarySlot *getTemporarySlot(const MachineInstr *MI, size_t Idx) const { + auto Key = std::make_pair(MI, Idx); + if (TemporaryStorage.count(Key) == 0) + TemporaryStorage[Key] = std::make_unique(MI, Idx); + return TemporaryStorage[Key].get(); + } + // Junk is always the same slot. + static JunkSlot *getJunkSlot() { + static JunkSlot TheJunkSlot; + return &TheJunkSlot; + } + private: void createOperation(MachineInstr &MI, SmallVector &Ops) const; - - MachineFunction &MF; - const LiveIntervals &LIS; - DenseMap> OperationsMap; }; - } // namespace llvm -#endif // LLVM_LIB_TARGET_EVM_EVMCONTROLFLOWGRAPH_H +#endif // LLVM_LIB_TARGET_EVM_EVMSTACKMODEL_H diff --git a/llvm/lib/Target/EVM/EVMStackShuffler.h b/llvm/lib/Target/EVM/EVMStackShuffler.h index e91a640727b5..e95a9554b0de 100644 --- a/llvm/lib/Target/EVM/EVMStackShuffler.h +++ b/llvm/lib/Target/EVM/EVMStackShuffler.h @@ -395,50 +395,50 @@ class Shuffler { /// A simple optimized map for mapping StackSlot to ints. class Multiplicity { public: - int &operator[](StackSlot const &Slot) { - if (auto *p = std::get_if(&Slot)) - return FunctionCallReturnLabelSlotMultiplicity[*p]; - if (std::holds_alternative(Slot)) + int &operator[](const StackSlot *Slot) { + if (auto *p = dyn_cast(Slot)) + return FunctionCallReturnLabelSlotMultiplicity[p]; + if (isa(Slot)) return FunctionReturnLabelSlotMultiplicity; - if (auto *p = std::get_if(&Slot)) - return VariableSlotMultiplicity[*p]; - if (auto *p = std::get_if(&Slot)) - return LiteralSlotMultiplicity[*p]; - if (auto *p = std::get_if(&Slot)) - return SymbolSlotMultiplicity[*p]; - if (auto *p = std::get_if(&Slot)) - return TemporarySlotMultiplicity[*p]; - - assert(std::holds_alternative(Slot)); + if (auto *p = dyn_cast(Slot)) + return VariableSlotMultiplicity[p]; + if (auto *p = dyn_cast(Slot)) + return LiteralSlotMultiplicity[p]; + if (auto *p = dyn_cast(Slot)) + return SymbolSlotMultiplicity[p]; + if (auto *p = dyn_cast(Slot)) + return TemporarySlotMultiplicity[p]; + + assert(isa(Slot)); return JunkSlotMultiplicity; } - int at(StackSlot const &Slot) const { - if (auto *p = std::get_if(&Slot)) - return FunctionCallReturnLabelSlotMultiplicity.at(*p); - if (std::holds_alternative(Slot)) + int at(const StackSlot *Slot) const { + if (auto *p = dyn_cast(Slot)) + return FunctionCallReturnLabelSlotMultiplicity.at(p); + if (isa(Slot)) return FunctionReturnLabelSlotMultiplicity; - if (auto *p = std::get_if(&Slot)) - return VariableSlotMultiplicity.at(*p); - if (auto *p = std::get_if(&Slot)) - return LiteralSlotMultiplicity.at(*p); - if (auto *p = std::get_if(&Slot)) - return SymbolSlotMultiplicity.at(*p); - if (auto *p = std::get_if(&Slot)) - return TemporarySlotMultiplicity.at(*p); - - assert(std::holds_alternative(Slot)); + if (auto *p = dyn_cast(Slot)) + return VariableSlotMultiplicity.at(p); + if (auto *p = dyn_cast(Slot)) + return LiteralSlotMultiplicity.at(p); + if (auto *p = dyn_cast(Slot)) + return SymbolSlotMultiplicity.at(p); + if (auto *p = dyn_cast(Slot)) + return TemporarySlotMultiplicity.at(p); + + assert(isa(Slot)); return JunkSlotMultiplicity; } private: - std::map + std::map FunctionCallReturnLabelSlotMultiplicity; int FunctionReturnLabelSlotMultiplicity = 0; - std::map VariableSlotMultiplicity; - std::map LiteralSlotMultiplicity; - std::map SymbolSlotMultiplicity; - std::map TemporarySlotMultiplicity; + std::map VariableSlotMultiplicity; + std::map LiteralSlotMultiplicity; + std::map SymbolSlotMultiplicity; + std::map TemporarySlotMultiplicity; int JunkSlotMultiplicity = 0; }; @@ -471,9 +471,8 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, --multiplicity[slot]; for (unsigned Offset = 0; Offset < targetStack.size(); ++Offset) { - auto &Slot = targetStack[Offset]; - if (std::holds_alternative(Slot) && - Offset < currentStack.size()) + auto *Slot = targetStack[Offset]; + if (isa(Slot) && Offset < currentStack.size()) ++multiplicity[currentStack[Offset]]; else ++multiplicity[Slot]; @@ -482,7 +481,7 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, bool isCompatible(size_t Source, size_t Target) { return Source < currentStack.size() && Target < targetStack.size() && - (std::holds_alternative(targetStack[Target]) || + (isa(targetStack[Target]) || currentStack[Source] == targetStack[Target]); } @@ -499,8 +498,7 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, } bool targetIsArbitrary(size_t Offset) { - return Offset < targetStack.size() && - std::holds_alternative(targetStack[Offset]); + return Offset < targetStack.size() && isa(targetStack[Offset]); } void swap(size_t I) { @@ -518,7 +516,7 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, } void pushOrDupTarget(size_t Offset) { - auto const &targetSlot = targetStack[Offset]; + auto *targetSlot = targetStack[Offset]; pushOrDupCallback(targetSlot); currentStack.push_back(targetSlot); } @@ -529,10 +527,10 @@ void createStackLayout(Stack &CurrentStack, Stack const &TargetStack, assert(CurrentStack.size() == TargetStack.size()); for (unsigned I = 0; I < CurrentStack.size(); ++I) { - auto &Current = CurrentStack[I]; - auto &Target = TargetStack[I]; - if (std::holds_alternative(Target)) - Current = JunkSlot{}; + StackSlot *&Current = CurrentStack[I]; + auto *Target = TargetStack[I]; + if (isa(Target)) + Current = EVMStackModel::getJunkSlot(); else assert(Current == Target); } diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index bbf6d822fb95..addad590a46c 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -234,7 +234,7 @@ void EVMStackifyCodeEmitter::adjustStackForInst(const MachineInstr *MI, unsigned Idx = 0; for (const auto &MO : MI->defs()) { assert(MO.isReg()); - CurrentStack.emplace_back(TemporarySlot{MI, MO.getReg(), Idx++}); + CurrentStack.push_back(StackModel.getTemporarySlot(MI, Idx++)); } assert(Emitter.stackHeight() == CurrentStack.size()); } @@ -247,10 +247,10 @@ void EVMStackifyCodeEmitter::visitCall(const FunctionCall &Call) { // Assert that we got the correct return label on stack. if (callWillReturn(Call.MI)) { - [[maybe_unused]] const auto *returnLabelSlot = - std::get_if( - &CurrentStack[CurrentStack.size() - NumArgs]); - assert(returnLabelSlot && returnLabelSlot->Call == Call.MI); + [[maybe_unused]] const auto *ReturnLabelSlot = + dyn_cast( + CurrentStack[CurrentStack.size() - NumArgs]); + assert(ReturnLabelSlot && ReturnLabelSlot->getCall() == Call.MI); } // Emit call. @@ -275,10 +275,10 @@ void EVMStackifyCodeEmitter::visitAssign(const Assignment &Assignment) { assert(Emitter.stackHeight() == CurrentStack.size()); // Invalidate occurrences of the assigned variables. - for (auto &CurrentSlot : CurrentStack) - if (const VariableSlot *VarSlot = std::get_if(&CurrentSlot)) - if (is_contained(Assignment.Variables, *VarSlot)) - CurrentSlot = JunkSlot{}; + for (auto *&CurrentSlot : CurrentStack) + if (const auto *VarSlot = dyn_cast(CurrentSlot)) + if (is_contained(Assignment.Variables, VarSlot)) + CurrentSlot = EVMStackModel::getJunkSlot(); // Assign variables to current stack top. assert(CurrentStack.size() >= Assignment.Variables.size()); @@ -290,30 +290,12 @@ bool EVMStackifyCodeEmitter::areLayoutsCompatible(const Stack &SourceStack, const Stack &TargetStack) { return SourceStack.size() == TargetStack.size() && all_of(zip_equal(SourceStack, TargetStack), [](const auto &Pair) { - const auto &[Src, Tgt] = Pair; - return std::holds_alternative(Tgt) || (Src == Tgt); + const auto [Src, Tgt] = Pair; + return isa(Tgt) || (Src == Tgt); }); } void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { - auto SlotVariableName = [](const StackSlot &Slot) { - return std::visit( - Overload{ - [&](const VariableSlot &Var) { - SmallString<1024> StrBuf; - raw_svector_ostream OS(StrBuf); - OS << printReg(Var.VirtualReg, nullptr, 0, nullptr); - return std::string(StrBuf.c_str()); - }, - [&](const FunctionCallReturnLabelSlot &) { return std::string(); }, - [&](const FunctionReturnLabelSlot &) { return std::string(); }, - [&](const LiteralSlot &) { return std::string(); }, - [&](const SymbolSlot &) { return std::string(); }, - [&](const TemporarySlot &) { return std::string(); }, - [&](const JunkSlot &) { return std::string(); }}, - Slot); - }; - assert(Emitter.stackHeight() == CurrentStack.size()); // ::createStackLayout asserts that it has successfully achieved the target // layout. @@ -327,26 +309,23 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { Emitter.emitSWAP(I); } else { int Deficit = static_cast(I) - 16; - const StackSlot &DeepSlot = CurrentStack[CurrentStack.size() - I - 1]; - std::string VarNameDeep = SlotVariableName(DeepSlot); - std::string VarNameTop = SlotVariableName(CurrentStack.back()); + const StackSlot *DeepSlot = CurrentStack[CurrentStack.size() - I - 1]; std::string Msg = (Twine("cannot swap ") + - (VarNameDeep.empty() ? ("slot " + stackSlotToString(DeepSlot)) - : (Twine("variable ") + VarNameDeep)) + - " with " + - (VarNameTop.empty() - ? ("slot " + stackSlotToString(CurrentStack.back())) - : (Twine("variable ") + VarNameTop)) + - ": too deep in the stack by " + std::to_string(Deficit) + - " slots in " + stackToString(CurrentStack)) + (isa(DeepSlot) ? "variable " : "slot ") + + DeepSlot->toString() + " with " + + (isa(CurrentStack.back()) ? "variable " + : "slot ") + + CurrentStack.back()->toString() + ": too deep in the stack by " + + std::to_string(Deficit) + " slots in " + + stackToString(CurrentStack)) .str(); report_fatal_error(MF.getName() + Twine(": ") + Msg); } }, // Push or dup callback. - [&](const StackSlot &Slot) { + [&](const StackSlot *Slot) { assert(CurrentStack.size() == Emitter.stackHeight()); // Dup the slot, if already on stack and reachable. @@ -357,14 +336,11 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { Emitter.emitDUP(static_cast(Depth + 1)); return; } - if (!isRematerializable(Slot)) { - std::string VarName = SlotVariableName(Slot); + if (!Slot->isRematerializable()) { std::string Msg = - ((VarName.empty() ? "slot " + stackSlotToString(Slot) - : Twine("variable ") + VarName) + - " is " + std::to_string(Depth - 15) + - " too deep in the stack " + stackToString(CurrentStack)) - .str(); + (isa(Slot) ? "variable " : "slot ") + + Slot->toString() + " is " + std::to_string(Depth - 15) + + " too deep in the stack " + stackToString(CurrentStack); report_fatal_error(MF.getName() + ": " + Msg); return; @@ -375,32 +351,25 @@ void EVMStackifyCodeEmitter::createStackLayout(const Stack &TargetStack) { // The slot can be freely generated or is an unassigned return variable. // Push it. - std::visit( - Overload{[&](const LiteralSlot &Literal) { - Emitter.emitConstant(Literal.Value); - }, - [&](const SymbolSlot &Symbol) { - Emitter.emitSymbol(Symbol.MI, Symbol.Symbol); - }, - [&](const FunctionReturnLabelSlot &) { - llvm_unreachable("Cannot produce function return label"); - }, - [&](const FunctionCallReturnLabelSlot &ReturnLabel) { - Emitter.emitLabelReference(ReturnLabel.Call); - }, - [&](const VariableSlot &Variable) { - llvm_unreachable("Variable not found on stack"); - }, - [&](const TemporarySlot &) { - llvm_unreachable("Function call result requested, but " - "not found on stack."); - }, - [&](const JunkSlot &) { - // Note: this will always be popped, so we can push - // anything. - Emitter.emitConstant(0); - }}, - Slot); + if (const auto *L = dyn_cast(Slot)) { + Emitter.emitConstant(L->getValue()); + } else if (const auto *S = dyn_cast(Slot)) { + Emitter.emitSymbol(S->getMachineInstr(), S->getSymbol()); + } else if (const auto *CallRet = + dyn_cast(Slot)) { + Emitter.emitLabelReference(CallRet->getCall()); + } else if (isa(Slot)) { + llvm_unreachable("Cannot produce function return label"); + } else if (isa(Slot)) { + llvm_unreachable("Variable not found on stack"); + } else if (isa(Slot)) { + llvm_unreachable("Function call result requested, but " + "not found on stack."); + } else { + assert(isa(Slot)); + // Note: this will always be popped, so we can push anything. + Emitter.emitConstant(0); + } }, // Pop callback. [&]() { Emitter.emitPOP(); }); diff --git a/llvm/unittests/Target/EVM/CMakeLists.txt b/llvm/unittests/Target/EVM/CMakeLists.txt index f5aee1e08f97..315aef1b0650 100644 --- a/llvm/unittests/Target/EVM/CMakeLists.txt +++ b/llvm/unittests/Target/EVM/CMakeLists.txt @@ -19,6 +19,7 @@ set(LLVM_LINK_COMPONENTS add_llvm_target_unittest(EVMTests StackShuffler.cpp + StackModel.cpp ) set_property(TARGET EVMTests PROPERTY FOLDER "Tests/UnitTests/TargetTests") diff --git a/llvm/unittests/Target/EVM/StackModel.cpp b/llvm/unittests/Target/EVM/StackModel.cpp new file mode 100644 index 000000000000..aaf9da9debe7 --- /dev/null +++ b/llvm/unittests/Target/EVM/StackModel.cpp @@ -0,0 +1,157 @@ +//===---------- llvm/unittests/EVM/StackSlotBuilder.cpp -------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "EVMStackModel.h" +#include "EVMTargetMachine.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/MC/MCAsmInfo.h" +#include "llvm/MC/TargetRegistry.h" +#include "llvm/Target/CodeGenCWrappers.h" +#include "llvm/Target/TargetMachine.h" +#include "gtest/gtest.h" + +using namespace llvm; + +#include +static TargetMachine *unwrap(LLVMTargetMachineRef P) { + return reinterpret_cast(P); +} + +class EVMStackModelTest : public testing::Test { + void SetUp() override { + LLVMInitializeEVMTargetInfo(); + LLVMInitializeEVMTarget(); + LLVMInitializeEVMTargetMC(); + + LLVMTargetRef Target = 0; + const char *Triple = "evm"; + char *ErrMsg = 0; + if (LLVMGetTargetFromTriple(Triple, &Target, &ErrMsg)) { + FAIL() << "Failed to create target from the triple (" << Triple + << "): " << ErrMsg; + return; + } + ASSERT_TRUE(Target); + + // Construct a TargetMachine. + TM = + LLVMCreateTargetMachine(Target, Triple, "", "", LLVMCodeGenLevelDefault, + LLVMRelocDefault, LLVMCodeModelDefault); + Context = std::make_unique(); + Mod = std::make_unique("TestModule", *Context); + Mod->setDataLayout(unwrap(TM)->createDataLayout()); + const LLVMTargetMachine &LLVMTM = + static_cast(*unwrap(TM)); + MMIWP = std::make_unique(&LLVMTM); + + Type *const ReturnType = Type::getVoidTy(Mod->getContext()); + FunctionType *FunctionType = FunctionType::get(ReturnType, false); + Function *const F = Function::Create( + FunctionType, GlobalValue::InternalLinkage, "TestFunction", Mod.get()); + MF = &MMIWP->getMMI().getOrCreateMachineFunction(*F); + + LIS = std::make_unique(); + StackModel = std::make_unique(*MF, *LIS.get()); + } + + void TearDown() override { LLVMDisposeTargetMachine(TM); } + +public: + LLVMTargetMachineRef TM; + std::unique_ptr Context; + std::unique_ptr MMIWP; + std::unique_ptr Mod; + std::unique_ptr LIS; + std::unique_ptr StackModel; + MachineFunction *MF = nullptr; +}; + +TEST_F(EVMStackModelTest, LiteralSlot) { + APInt Int0 = APInt(32, 0); + APInt Int42 = APInt(32, 42); + + auto *LiteralSlot0 = StackModel->getLiteralSlot(Int0); + auto *LiteralSlot0Copy = StackModel->getLiteralSlot(Int0); + EXPECT_TRUE(LiteralSlot0 == LiteralSlot0Copy); + + auto *LiteralSlot42 = StackModel->getLiteralSlot(Int42); + EXPECT_TRUE(LiteralSlot0 != LiteralSlot42); + EXPECT_TRUE(LiteralSlot0->getValue() != LiteralSlot42->getValue()); +} + +TEST_F(EVMStackModelTest, VariableSlot) { + MachineRegisterInfo &MRI = MF->getRegInfo(); + Register Reg1 = MRI.createVirtualRegister(&EVM::GPRRegClass); + Register Reg2 = MRI.createVirtualRegister(&EVM::GPRRegClass); + + auto *VarSlot1 = StackModel->getVariableSlot(Reg1); + auto *VarSlot1Copy = StackModel->getVariableSlot(Reg1); + EXPECT_TRUE(VarSlot1 == VarSlot1Copy); + + auto *VarSlot2 = StackModel->getVariableSlot(Reg2); + EXPECT_TRUE(VarSlot1 != VarSlot2); + EXPECT_TRUE(VarSlot1->getReg() != VarSlot2->getReg()); +} + +TEST_F(EVMStackModelTest, SymbolSlot) { + MCInstrDesc MCID = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + auto MI = MF->CreateMachineInstr(MCID, DebugLoc()); + auto MAI = MCAsmInfo(); + auto MC = std::make_unique(Triple("evm"), &MAI, nullptr, nullptr, + nullptr, nullptr, false); + MCSymbol *Sym1 = MC->createTempSymbol("sym1", false); + MCSymbol *Sym2 = MC->createTempSymbol("sym2", false); + + auto *SymSlot1 = StackModel->getSymbolSlot(Sym1, MI); + auto *SymSlot1Copy = StackModel->getSymbolSlot(Sym1, MI); + EXPECT_TRUE(SymSlot1 == SymSlot1Copy); + + auto *SymSlot2 = StackModel->getSymbolSlot(Sym2, MI); + EXPECT_TRUE(SymSlot1 != SymSlot2); + EXPECT_TRUE(SymSlot1->getSymbol() != SymSlot2->getSymbol()); +} + +TEST_F(EVMStackModelTest, FunctionCallReturnLabelSlot) { + const TargetInstrInfo *TII = MF->getSubtarget().getInstrInfo(); + auto Call = MF->CreateMachineInstr(TII->get(EVM::FCALL), DebugLoc()); + auto Call2 = MF->CreateMachineInstr(TII->get(EVM::FCALL), DebugLoc()); + + auto *RetSlot1 = StackModel->getFunctionCallReturnLabelSlot(Call); + auto *RetSlot1Copy = StackModel->getFunctionCallReturnLabelSlot(Call); + EXPECT_TRUE(RetSlot1 == RetSlot1Copy); + + auto *RetSlot2 = StackModel->getFunctionCallReturnLabelSlot(Call2); + EXPECT_TRUE(RetSlot1 != RetSlot2); + EXPECT_TRUE(RetSlot1->getCall() != RetSlot2->getCall()); +} + +TEST_F(EVMStackModelTest, FunctionReturnLabelSlot) { + // Be sure the slot for function return label is a single one. + EXPECT_TRUE(StackModel->getFunctionReturnLabelSlot(MF) == + StackModel->getFunctionReturnLabelSlot(MF)); +} + +TEST_F(EVMStackModelTest, TemporarySlot) { + MCInstrDesc MCID = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + auto MI = MF->CreateMachineInstr(MCID, DebugLoc()); + + auto *TempSlot1 = StackModel->getTemporarySlot(MI, 0); + auto *TempSlot1Copy = StackModel->getTemporarySlot(MI, 0); + EXPECT_TRUE(TempSlot1 == TempSlot1Copy); + + auto *TempSlot2 = StackModel->getTemporarySlot(MI, 1); + EXPECT_TRUE(TempSlot1 != TempSlot2); +} + +TEST_F(EVMStackModelTest, JunkSlot) { + // Be sure the JunkSlot is a single one. + EXPECT_TRUE(EVMStackModel::getJunkSlot() == EVMStackModel::getJunkSlot()); +} diff --git a/llvm/unittests/Target/EVM/StackShuffler.cpp b/llvm/unittests/Target/EVM/StackShuffler.cpp index 8324d2730723..2e99bb685ee8 100644 --- a/llvm/unittests/Target/EVM/StackShuffler.cpp +++ b/llvm/unittests/Target/EVM/StackShuffler.cpp @@ -67,6 +67,9 @@ class LLDCTest : public testing::Test { Function *const F = Function::Create( FunctionType, GlobalValue::InternalLinkage, "TestFunction", Mod.get()); MF = &MMIWP->getMMI().getOrCreateMachineFunction(*F); + + LIS = std::make_unique(); + StackModel = std::make_unique(*MF, *LIS.get()); } void TearDown() override { LLVMDisposeTargetMachine(TM); } @@ -77,6 +80,8 @@ class LLDCTest : public testing::Test { std::unique_ptr Context; std::unique_ptr Mod; std::unique_ptr MMIWP; + std::unique_ptr LIS; + std::unique_ptr StackModel; }; TEST_F(LLDCTest, Basic) { @@ -101,46 +106,49 @@ TEST_F(LLDCTest, Basic) { // Create the source stack layout: // [ %0 %1 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET RET %5 ] - SourceStack.emplace_back(VariableSlot{Instrs[0].second}); - SourceStack.emplace_back(VariableSlot{Instrs[1].second}); - SourceStack.emplace_back(VariableSlot{Instrs[2].second}); - SourceStack.emplace_back(VariableSlot{Instrs[3].second}); - SourceStack.emplace_back(VariableSlot{Instrs[4].second}); - SourceStack.emplace_back(VariableSlot{Instrs[5].second}); - SourceStack.emplace_back(VariableSlot{Instrs[6].second}); - SourceStack.emplace_back(VariableSlot{Instrs[7].second}); - SourceStack.emplace_back(VariableSlot{Instrs[9].second}); - SourceStack.emplace_back(VariableSlot{Instrs[10].second}); - SourceStack.emplace_back(VariableSlot{Instrs[11].second}); - SourceStack.emplace_back(VariableSlot{Instrs[12].second}); - SourceStack.emplace_back(VariableSlot{Instrs[13].second}); - SourceStack.emplace_back(VariableSlot{Instrs[14].second}); - SourceStack.emplace_back(VariableSlot{Instrs[15].second}); - SourceStack.emplace_back(VariableSlot{Instrs[16].second}); - SourceStack.emplace_back(FunctionReturnLabelSlot{MBB->getParent()}); - SourceStack.emplace_back(FunctionReturnLabelSlot{MBB->getParent()}); - SourceStack.emplace_back(VariableSlot{Instrs[5].second}); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[0].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[1].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[2].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[3].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[4].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[5].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[6].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[7].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[9].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[10].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[11].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[12].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[13].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[14].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[15].second)); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[16].second)); + SourceStack.emplace_back( + StackModel->getFunctionReturnLabelSlot(MBB->getParent())); + SourceStack.emplace_back( + StackModel->getFunctionReturnLabelSlot(MBB->getParent())); + SourceStack.emplace_back(StackModel->getVariableSlot(Instrs[5].second)); // [ %1 %0 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET JUNK JUNK ] - TargetStack.emplace_back(VariableSlot{Instrs[1].second}); - TargetStack.emplace_back(VariableSlot{Instrs[0].second}); - TargetStack.emplace_back(VariableSlot{Instrs[2].second}); - TargetStack.emplace_back(VariableSlot{Instrs[3].second}); - TargetStack.emplace_back(VariableSlot{Instrs[4].second}); - TargetStack.emplace_back(VariableSlot{Instrs[5].second}); - TargetStack.emplace_back(VariableSlot{Instrs[6].second}); - TargetStack.emplace_back(VariableSlot{Instrs[7].second}); - TargetStack.emplace_back(VariableSlot{Instrs[9].second}); - TargetStack.emplace_back(VariableSlot{Instrs[10].second}); - TargetStack.emplace_back(VariableSlot{Instrs[11].second}); - TargetStack.emplace_back(VariableSlot{Instrs[12].second}); - TargetStack.emplace_back(VariableSlot{Instrs[13].second}); - TargetStack.emplace_back(VariableSlot{Instrs[14].second}); - TargetStack.emplace_back(VariableSlot{Instrs[15].second}); - TargetStack.emplace_back(VariableSlot{Instrs[16].second}); - TargetStack.emplace_back(FunctionReturnLabelSlot{MBB->getParent()}); - TargetStack.emplace_back(JunkSlot{}); - TargetStack.emplace_back(JunkSlot{}); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[1].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[0].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[2].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[3].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[4].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[5].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[6].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[7].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[9].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[10].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[11].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[12].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[13].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[14].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[15].second)); + TargetStack.emplace_back(StackModel->getVariableSlot(Instrs[16].second)); + TargetStack.emplace_back( + StackModel->getFunctionReturnLabelSlot(MBB->getParent())); + TargetStack.emplace_back(StackModel->getJunkSlot()); + TargetStack.emplace_back(StackModel->getJunkSlot()); StringRef Reference("\ [ %0 %1 %2 %3 %4 %5 %6 %7 %9 %10 %11 %12 %13 %14 %15 %16 RET RET %5 ]\n\ @@ -170,10 +178,10 @@ PUSH JUNK\n\ Output << stackToString(SourceStack) << '\n'; Output << "SWAP" << SwapDepth << '\n'; }, - [&](StackSlot const &Slot) { // dupOrPush + [&](const StackSlot *Slot) { // dupOrPush Output << stackToString(SourceStack) << '\n'; - if (isRematerializable(Slot)) - Output << "PUSH " << stackSlotToString(Slot) << '\n'; + if (Slot->isRematerializable()) + Output << "PUSH " << Slot->toString() << '\n'; else { Stack TmpStack = SourceStack; std::reverse(TmpStack.begin(), TmpStack.end()); From e6d46fb8bff838db4f5025e945178f3606a2d001 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Tue, 28 Jan 2025 13:26:59 +0100 Subject: [PATCH 20/42] [EVM] Update assert in EVMStackifyCodeEmitter::CodeEmitter::emitSymbol Update assert to reflect support for libraries. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index addad590a46c..91a7e8abc9ac 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -120,8 +120,8 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitConstant(uint64_t Val) { void EVMStackifyCodeEmitter::CodeEmitter::emitSymbol(const MachineInstr *MI, MCSymbol *Symbol) { unsigned Opc = MI->getOpcode(); - assert(Opc == EVM::DATASIZE || - Opc == EVM::DATAOFFSET && "Unexpected symbol instruction"); + assert(Opc == EVM::DATASIZE || Opc == EVM::DATAOFFSET || + Opc == EVM::LINKERSYMBOL && "Unexpected symbol instruction"); StackHeight += 1; // This is codegen-only instruction, that will be converted into PUSH4. auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), From 73dcbf09b3c628f0e2bf20a1fd7cf26126e9915b Mon Sep 17 00:00:00 2001 From: Kevin McAfee Date: Fri, 16 Aug 2024 11:48:57 -0700 Subject: [PATCH 21/42] [SDAG] Read-only intrinsics must have WillReturn and !Throws attributes to be treated as loads (#99999) This change avoids deleting `!willReturn` intrinsics for which the return value is unused when building the SDAG. Currently, calls to read-only intrinsics not marked with `IntrWillReturn` cannot be deleted at the LLVM IR level but may be deleted when building the SDAG. These calls are unsafe to remove from the IR because the functions are `!willReturn` and should also be unsafe to remove fromthe SDAG for the same reason. This change aligns the behavior of the SDAG to that of LLVM IR. This change also requires that intrinsics not have the `Throws` attribute to be treated as loads for the same reason. --- llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp index 7388e82c522c..6d650f103bd5 100644 --- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp +++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp @@ -4912,7 +4912,8 @@ void SelectionDAGBuilder::visitTargetIntrinsic(const CallInst &I, // definition. const Function *F = I.getCalledFunction(); bool HasChain = !F->doesNotAccessMemory(); - bool OnlyLoad = HasChain && F->onlyReadsMemory(); + bool OnlyLoad = + HasChain && F->onlyReadsMemory() && F->willReturn() && F->doesNotThrow(); // Build the operand list. SmallVector Ops; From 9edc16c4b731e840552c5a1364568b648e5c64f3 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Wed, 6 Nov 2024 15:19:01 +0100 Subject: [PATCH 22/42] [EVM] Add pre-commit test for Update intrinsic attributes Signed-off-by: Vladimir Radosavljevic --- llvm/test/CodeGen/EVM/cse-intrinsic.ll | 607 +++++++++++++++++++++++++ 1 file changed, 607 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/cse-intrinsic.ll diff --git a/llvm/test/CodeGen/EVM/cse-intrinsic.ll b/llvm/test/CodeGen/EVM/cse-intrinsic.ll new file mode 100644 index 000000000000..7268e4ead9a8 --- /dev/null +++ b/llvm/test/CodeGen/EVM/cse-intrinsic.ll @@ -0,0 +1,607 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 2 +; RUN: opt -O3 -S < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +define i256 @addmod_dce(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { +; CHECK-LABEL: define i256 @addmod_dce +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.addmod(i256 [[RS1]], i256 [[RS2]], i256 [[RS3]]) +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.addmod(i256 %rs1, i256 %rs2, i256 %rs3) + ret i256 0 +} + +define i256 @sha3_dce(ptr addrspace(1) %offset, i256 %size) nounwind { +; CHECK-LABEL: define i256 @sha3_dce +; CHECK-SAME: (ptr addrspace(1) readonly [[OFFSET:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[OFFSET]], i256 [[SIZE]]) +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.sha3(ptr addrspace(1) %offset, i256 %size) + ret i256 0 +} + +define i256 @pc_dce() nounwind { +; CHECK-LABEL: define i256 @pc_dce +; CHECK-SAME: () local_unnamed_addr #[[ATTR2:[0-9]+]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.pc() +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.pc() + ret i256 0 +} + +define i256 @gas_dce() nounwind { +; CHECK-LABEL: define i256 @gas_dce +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.gas() +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.gas() + ret i256 0 +} + +define i256 @msize_dce() nounwind { +; CHECK-LABEL: define i256 @msize_dce +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.msize() +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.msize() + ret i256 0 +} + +define i256 @balance_dce(i256 %rs1) nounwind { +; CHECK-LABEL: define i256 @balance_dce +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.balance(i256 [[RS1]]) +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.balance(i256 %rs1) + ret i256 0 +} + +define i256 @selfbalance_dce() nounwind { +; CHECK-LABEL: define i256 @selfbalance_dce +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.selfbalance() +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.selfbalance() + ret i256 0 +} + +define i256 @returndatasize_dce() nounwind { +; CHECK-LABEL: define i256 @returndatasize_dce +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.returndatasize() +; CHECK-NEXT: ret i256 0 +; + %res = call i256 @llvm.evm.returndatasize() + ret i256 0 +} + +define i256 @addmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { +; CHECK-LABEL: define i256 @addmod +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.addmod(i256 [[RS1]], i256 [[RS2]], i256 [[RS3]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.addmod(i256 %rs1, i256 %rs2, i256 %rs3) + %res2 = call i256 @llvm.evm.addmod(i256 %rs1, i256 %rs2, i256 %rs3) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @mulmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { +; CHECK-LABEL: define i256 @mulmod +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.mulmod(i256 [[RS1]], i256 [[RS2]], i256 [[RS3]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.mulmod(i256 %rs1, i256 %rs2, i256 %rs3) + %res2 = call i256 @llvm.evm.mulmod(i256 %rs1, i256 %rs2, i256 %rs3) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @exp(i256 %rs1, i256 %rs2) nounwind { +; CHECK-LABEL: define i256 @exp +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.exp(i256 [[RS1]], i256 [[RS2]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.exp(i256 %rs1, i256 %rs2) + %res2 = call i256 @llvm.evm.exp(i256 %rs1, i256 %rs2) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @sha3(ptr addrspace(1) %offset, i256 %size) nounwind { +; CHECK-LABEL: define i256 @sha3 +; CHECK-SAME: (ptr addrspace(1) [[OFFSET:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR1]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[OFFSET]], i256 [[SIZE]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.sha3(ptr addrspace(1) %offset, i256 %size) + %res2 = call i256 @llvm.evm.sha3(ptr addrspace(1) %offset, i256 %size) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @signextend(i256 %bytesize, i256 %val) nounwind { +; CHECK-LABEL: define i256 @signextend +; CHECK-SAME: (i256 [[BYTESIZE:%.*]], i256 [[VAL:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.signextend(i256 [[BYTESIZE]], i256 [[VAL]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.signextend(i256 %bytesize, i256 %val) + %res2 = call i256 @llvm.evm.signextend(i256 %bytesize, i256 %val) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @byte(i256 %rs1, i256 %rs2) nounwind { +; CHECK-LABEL: define i256 @byte +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.byte(i256 [[RS1]], i256 [[RS2]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.byte(i256 %rs1, i256 %rs2) + %res2 = call i256 @llvm.evm.byte(i256 %rs1, i256 %rs2) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @pc() nounwind { +; CHECK-LABEL: define i256 @pc +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.pc() +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.pc() +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.pc() + %res2 = call i256 @llvm.evm.pc() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @gas() nounwind { +; CHECK-LABEL: define i256 @gas +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.gas() +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.gas() +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.gas() + %res2 = call i256 @llvm.evm.gas() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @msize() nounwind { +; CHECK-LABEL: define i256 @msize +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.msize() +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.msize() +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.msize() + %res2 = call i256 @llvm.evm.msize() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @address() nounwind { +; CHECK-LABEL: define i256 @address +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.address() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.address() + %res2 = call i256 @llvm.evm.address() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @origin() nounwind { +; CHECK-LABEL: define i256 @origin +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.origin() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.origin() + %res2 = call i256 @llvm.evm.origin() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @caller() nounwind { +; CHECK-LABEL: define i256 @caller +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.caller() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.caller() + %res2 = call i256 @llvm.evm.caller() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @balance(i256 %rs1) nounwind { +; CHECK-LABEL: define i256 @balance +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.balance(i256 [[RS1]]) +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.balance(i256 [[RS1]]) +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.balance(i256 %rs1) + %res2 = call i256 @llvm.evm.balance(i256 %rs1) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @calldatasize() nounwind { +; CHECK-LABEL: define i256 @calldatasize +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.calldatasize() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.calldatasize() + %res2 = call i256 @llvm.evm.calldatasize() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @calldataload(ptr addrspace(2) %rs1) nounwind { +; CHECK-LABEL: define i256 @calldataload +; CHECK-SAME: (ptr addrspace(2) [[RS1:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.calldataload(ptr addrspace(2) [[RS1]]) +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.calldataload(ptr addrspace(2) [[RS1]]) +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.calldataload(ptr addrspace(2) %rs1) + %res2 = call i256 @llvm.evm.calldataload(ptr addrspace(2) %rs1) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @callvalue() nounwind { +; CHECK-LABEL: define i256 @callvalue +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.callvalue() +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.callvalue() +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.callvalue() + %res2 = call i256 @llvm.evm.callvalue() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @codesize() nounwind { +; CHECK-LABEL: define i256 @codesize +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.codesize() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.codesize() + %res2 = call i256 @llvm.evm.codesize() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @gasprice() nounwind { +; CHECK-LABEL: define i256 @gasprice +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.gasprice() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.gasprice() + %res2 = call i256 @llvm.evm.gasprice() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @extcodesize(i256 %rs1) nounwind { +; CHECK-LABEL: define i256 @extcodesize +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.extcodesize(i256 [[RS1]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.extcodesize(i256 %rs1) + %res2 = call i256 @llvm.evm.extcodesize(i256 %rs1) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @extcodehash(i256 %rs1) nounwind { +; CHECK-LABEL: define i256 @extcodehash +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.extcodehash(i256 [[RS1]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.extcodehash(i256 %rs1) + %res2 = call i256 @llvm.evm.extcodehash(i256 %rs1) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @returndatasize() nounwind { +; CHECK-LABEL: define i256 @returndatasize +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.returndatasize() +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.returndatasize() +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.returndatasize() + %res2 = call i256 @llvm.evm.returndatasize() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @blockhash(i256 %rs1) nounwind { +; CHECK-LABEL: define i256 @blockhash +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.blockhash(i256 [[RS1]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.blockhash(i256 %rs1) + %res2 = call i256 @llvm.evm.blockhash(i256 %rs1) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @blobhash(i256 %rs1) nounwind { +; CHECK-LABEL: define i256 @blobhash +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.blobhash(i256 [[RS1]]) +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.blobhash(i256 %rs1) + %res2 = call i256 @llvm.evm.blobhash(i256 %rs1) + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @coinbase() nounwind { +; CHECK-LABEL: define i256 @coinbase +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.coinbase() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.coinbase() + %res2 = call i256 @llvm.evm.coinbase() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @timestamp() nounwind { +; CHECK-LABEL: define i256 @timestamp +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.timestamp() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.timestamp() + %res2 = call i256 @llvm.evm.timestamp() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @number() nounwind { +; CHECK-LABEL: define i256 @number +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.number() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.number() + %res2 = call i256 @llvm.evm.number() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @difficulty() nounwind { +; CHECK-LABEL: define i256 @difficulty +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.difficulty() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.difficulty() + %res2 = call i256 @llvm.evm.difficulty() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @gaslimit() nounwind { +; CHECK-LABEL: define i256 @gaslimit +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.gaslimit() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.gaslimit() + %res2 = call i256 @llvm.evm.gaslimit() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @chainid() nounwind { +; CHECK-LABEL: define i256 @chainid +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.chainid() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.chainid() + %res2 = call i256 @llvm.evm.chainid() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @selfbalance() nounwind { +; CHECK-LABEL: define i256 @selfbalance +; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.selfbalance() +; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.selfbalance() +; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.selfbalance() + %res2 = call i256 @llvm.evm.selfbalance() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @basefee() nounwind { +; CHECK-LABEL: define i256 @basefee +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.basefee() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.basefee() + %res2 = call i256 @llvm.evm.basefee() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define i256 @blobbasefee() nounwind { +; CHECK-LABEL: define i256 @blobbasefee +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.blobbasefee() +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 +; CHECK-NEXT: ret i256 [[RET]] +; + %res1 = call i256 @llvm.evm.blobbasefee() + %res2 = call i256 @llvm.evm.blobbasefee() + %ret = add i256 %res1, %res2 + ret i256 %ret +} + +define void @log0(ptr addrspace(1) %off, i256 %size) nounwind { +; CHECK-LABEL: define void @log0 +; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: tail call void @llvm.evm.log0(ptr addrspace(1) [[OFF]], i256 [[SIZE]]) +; CHECK-NEXT: tail call void @llvm.evm.log0(ptr addrspace(1) [[OFF]], i256 [[SIZE]]) +; CHECK-NEXT: ret void +; + call void @llvm.evm.log0(ptr addrspace(1) %off, i256 %size) + call void @llvm.evm.log0(ptr addrspace(1) %off, i256 %size) + ret void +} + +define void @log1(ptr addrspace(1) %off, i256 %size, i256 %t1) nounwind { +; CHECK-LABEL: define void @log1 +; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: tail call void @llvm.evm.log1(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]]) +; CHECK-NEXT: tail call void @llvm.evm.log1(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]]) +; CHECK-NEXT: ret void +; + call void @llvm.evm.log1(ptr addrspace(1) %off, i256 %size, i256 %t1) + call void @llvm.evm.log1(ptr addrspace(1) %off, i256 %size, i256 %t1) + ret void +} + +define void @log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) nounwind { +; CHECK-LABEL: define void @log2 +; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: tail call void @llvm.evm.log2(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]]) +; CHECK-NEXT: tail call void @llvm.evm.log2(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]]) +; CHECK-NEXT: ret void +; + call void @llvm.evm.log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) + call void @llvm.evm.log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) + ret void +} + +define void @log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3) nounwind { +; CHECK-LABEL: define void @log3 +; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]], i256 [[T3:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: tail call void @llvm.evm.log3(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]]) +; CHECK-NEXT: tail call void @llvm.evm.log3(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]]) +; CHECK-NEXT: ret void +; + call void @llvm.evm.log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3) + call void @llvm.evm.log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3) + ret void +} + +define void @log4(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3, i256 %t4) nounwind { +; CHECK-LABEL: define void @log4 +; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]], i256 [[T3:%.*]], i256 [[T4:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: tail call void @llvm.evm.log4(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]], i256 [[T4]]) +; CHECK-NEXT: tail call void @llvm.evm.log4(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]], i256 [[T4]]) +; CHECK-NEXT: ret void +; + call void @llvm.evm.log4(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3, i256 %t4) + call void @llvm.evm.log4(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3, i256 %t4) + ret void +} + +declare i256 @llvm.evm.addmod(i256, i256, i256) +declare i256 @llvm.evm.mulmod(i256, i256, i256) +declare i256 @llvm.evm.exp(i256, i256) +declare i256 @llvm.evm.sha3(ptr addrspace(1), i256) +declare i256 @llvm.evm.signextend(i256, i256) +declare i256 @llvm.evm.byte(i256, i256) +declare i256 @llvm.evm.pc() +declare i256 @llvm.evm.gas() +declare i256 @llvm.evm.msize() +declare i256 @llvm.evm.address() +declare i256 @llvm.evm.origin() +declare i256 @llvm.evm.caller() +declare i256 @llvm.evm.balance(i256) +declare i256 @llvm.evm.calldatasize() +declare i256 @llvm.evm.calldataload(ptr addrspace(2)) +declare i256 @llvm.evm.callvalue() +declare i256 @llvm.evm.codesize() +declare i256 @llvm.evm.gasprice() +declare i256 @llvm.evm.extcodesize(i256) +declare i256 @llvm.evm.extcodehash(i256) +declare i256 @llvm.evm.blockhash(i256) +declare i256 @llvm.evm.blobhash(i256) +declare i256 @llvm.evm.returndatasize() +declare i256 @llvm.evm.coinbase() +declare i256 @llvm.evm.timestamp() +declare i256 @llvm.evm.number() +declare i256 @llvm.evm.difficulty() +declare i256 @llvm.evm.gaslimit() +declare i256 @llvm.evm.chainid() +declare i256 @llvm.evm.selfbalance() +declare i256 @llvm.evm.basefee() +declare i256 @llvm.evm.blobbasefee() +declare void @llvm.evm.log0(ptr addrspace(1), i256) +declare void @llvm.evm.log1(ptr addrspace(1), i256, i256) +declare void @llvm.evm.log2(ptr addrspace(1), i256, i256, i256) +declare void @llvm.evm.log3(ptr addrspace(1), i256, i256, i256, i256) +declare void @llvm.evm.log4(ptr addrspace(1), i256, i256, i256, i256, i256) From 63f1d86bff063b1f0e408d0178178325eb84b524 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Thu, 31 Oct 2024 16:25:11 +0100 Subject: [PATCH 23/42] [EVM] Update intrinsic attributes Signed-off-by: Vladimir Radosavljevic --- llvm/include/llvm/IR/IntrinsicsEVM.td | 90 +++++++++++------------ llvm/lib/Transforms/Utils/Local.cpp | 14 ++++ llvm/test/CodeGen/EVM/cse-intrinsic.ll | 99 ++++++++++++-------------- 3 files changed, 104 insertions(+), 99 deletions(-) diff --git a/llvm/include/llvm/IR/IntrinsicsEVM.td b/llvm/include/llvm/IR/IntrinsicsEVM.td index 722f12edc201..756c16a94861 100644 --- a/llvm/include/llvm/IR/IntrinsicsEVM.td +++ b/llvm/include/llvm/IR/IntrinsicsEVM.td @@ -25,47 +25,47 @@ let TargetPrefix = "evm" in { // Arithmetical operations. def int_evm_div - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_sdiv - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_mod - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_smod - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_shl - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_shr - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_sar - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_addmod : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty, llvm_i256_ty], - [IntrNoMem]>; + [IntrNoMem, IntrWillReturn]>; def int_evm_mulmod : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty, llvm_i256_ty], - [IntrNoMem]>; + [IntrNoMem, IntrWillReturn]>; def int_evm_exp - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_sha3 : Intrinsic<[llvm_i256_ty], - [LLVMQualPointerType, llvm_i256_ty], [IntrReadMem]>; + [LLVMQualPointerType, llvm_i256_ty], [IntrReadMem, IntrWillReturn]>; def int_evm_signextend - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_byte : Intrinsic<[llvm_i256_ty], [llvm_i256_ty, llvm_i256_ty], - [IntrNoMem]>; + [IntrNoMem, IntrWillReturn]>; // Memory operations. def int_evm_mstore8 @@ -73,86 +73,86 @@ def int_evm_mstore8 // TODO: CPR-1555. // Review system intrinsics attributes taking into account EraVM. -def int_evm_msize : Intrinsic<[llvm_i256_ty], [], []>; +def int_evm_msize : Intrinsic<[llvm_i256_ty], [], [IntrInaccessibleMemOnly, IntrWillReturn]>; -def int_evm_pc : Intrinsic<[llvm_i256_ty], [], []>; +def int_evm_pc : Intrinsic<[llvm_i256_ty], [], [IntrInaccessibleMemOnly, IntrWillReturn]>; -def int_evm_gas : Intrinsic<[llvm_i256_ty], [], []>; +def int_evm_gas : Intrinsic<[llvm_i256_ty], [], [IntrInaccessibleMemOnly, IntrWillReturn]>; // Getting values from the context. -def int_evm_address : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_address : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_origin : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_origin : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_caller : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_caller : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_balance : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], []>; +def int_evm_balance : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrInaccessibleMemOnly, IntrWillReturn]>; -def int_evm_callvalue : Intrinsic<[llvm_i256_ty], [], []>; +def int_evm_callvalue : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_calldatasize : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_calldatasize : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; def int_evm_calldataload : Intrinsic<[llvm_i256_ty], [LLVMQualPointerType], []>; -def int_evm_codesize : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_codesize : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; // TODO: Ensure the gasprice is fixed while a function execution. -def int_evm_gasprice : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_gasprice : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; def int_evm_extcodesize - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; def int_evm_extcodecopy : Intrinsic<[], [llvm_i256_ty, LLVMQualPointerType, LLVMQualPointerType, llvm_i256_ty], [IntrWriteMem]>; -def int_evm_returndatasize : Intrinsic<[llvm_i256_ty], [], []>; +def int_evm_returndatasize : Intrinsic<[llvm_i256_ty], [], [IntrInaccessibleMemOnly, IntrWillReturn]>; def int_evm_extcodehash - : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem]>; + : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; -def int_evm_blockhash : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem]>; +def int_evm_blockhash : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; -def int_evm_blobhash : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem]>; +def int_evm_blobhash : Intrinsic<[llvm_i256_ty], [llvm_i256_ty], [IntrNoMem, IntrWillReturn]>; -def int_evm_coinbase : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_coinbase : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_timestamp : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_timestamp : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_number : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_number : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_difficulty : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_difficulty : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_gaslimit : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_gaslimit : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_chainid : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_chainid : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_selfbalance : Intrinsic<[llvm_i256_ty], [], []>; +def int_evm_selfbalance : Intrinsic<[llvm_i256_ty], [], [IntrInaccessibleMemOnly]>; -def int_evm_basefee : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_basefee : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; -def int_evm_blobbasefee : Intrinsic<[llvm_i256_ty], [], [IntrNoMem]>; +def int_evm_blobbasefee : Intrinsic<[llvm_i256_ty], [], [IntrNoMem, IntrWillReturn]>; // Logging. def int_evm_log0 - : Intrinsic<[], [LLVMQualPointerType, llvm_i256_ty], []>; + : Intrinsic<[], [LLVMQualPointerType, llvm_i256_ty], [IntrReadMem]>; def int_evm_log1 - : Intrinsic<[], [LLVMQualPointerType, llvm_i256_ty, llvm_i256_ty], []>; + : Intrinsic<[], [LLVMQualPointerType, llvm_i256_ty, llvm_i256_ty], [IntrReadMem]>; def int_evm_log2 : Intrinsic<[], [LLVMQualPointerType, llvm_i256_ty, llvm_i256_ty, - llvm_i256_ty], []>; + llvm_i256_ty], [IntrReadMem]>; def int_evm_log3 : Intrinsic<[], [LLVMQualPointerType, llvm_i256_ty, llvm_i256_ty, - llvm_i256_ty, llvm_i256_ty], []>; + llvm_i256_ty, llvm_i256_ty], [IntrReadMem]>; def int_evm_log4 : Intrinsic<[], [LLVMQualPointerType, llvm_i256_ty, llvm_i256_ty, - llvm_i256_ty, llvm_i256_ty, llvm_i256_ty], []>; + llvm_i256_ty, llvm_i256_ty, llvm_i256_ty], [IntrReadMem]>; // System calls def int_evm_create @@ -252,14 +252,14 @@ def int_evm_memcpyas1as4 // passed in the metadata. def int_evm_datasize : DefaultAttrsIntrinsic< [llvm_i256_ty], [llvm_metadata_ty], - [IntrNoMem, IntrSpeculatable] + [IntrNoMem, IntrWillReturn, IntrSpeculatable] >; // Returns a code offset of the Yul object with the name // passed in the metadata. def int_evm_dataoffset : DefaultAttrsIntrinsic< [llvm_i256_ty], [llvm_metadata_ty], - [IntrNoMem, IntrSpeculatable] + [IntrNoMem, IntrWillReturn, IntrSpeculatable] >; diff --git a/llvm/lib/Transforms/Utils/Local.cpp b/llvm/lib/Transforms/Utils/Local.cpp index 6a6587d9e223..3f4e599ef1dd 100644 --- a/llvm/lib/Transforms/Utils/Local.cpp +++ b/llvm/lib/Transforms/Utils/Local.cpp @@ -59,6 +59,9 @@ // EraVM local begin #include "llvm/IR/IntrinsicsEraVM.h" // EraVM local end +// EVM local begin +#include "llvm/IR/IntrinsicsEVM.h" +// EVM local end #include "llvm/IR/IntrinsicsWebAssembly.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/MDBuilder.h" @@ -444,6 +447,17 @@ bool llvm::wouldInstructionBeTriviallyDead(Instruction *I, return true; // EraVM local end + // EVM local begin + if (auto *II = dyn_cast(I)) + if (II->getIntrinsicID() == Intrinsic::evm_msize || + II->getIntrinsicID() == Intrinsic::evm_pc || + II->getIntrinsicID() == Intrinsic::evm_gas || + II->getIntrinsicID() == Intrinsic::evm_balance || + II->getIntrinsicID() == Intrinsic::evm_returndatasize || + II->getIntrinsicID() == Intrinsic::evm_selfbalance) + return true; + // EVM local end + if (auto *CB = dyn_cast(I)) if (isRemovableAlloc(CB, TLI)) return true; diff --git a/llvm/test/CodeGen/EVM/cse-intrinsic.ll b/llvm/test/CodeGen/EVM/cse-intrinsic.ll index 7268e4ead9a8..666f94dab32e 100644 --- a/llvm/test/CodeGen/EVM/cse-intrinsic.ll +++ b/llvm/test/CodeGen/EVM/cse-intrinsic.ll @@ -7,7 +7,6 @@ target triple = "evm" define i256 @addmod_dce(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { ; CHECK-LABEL: define i256 @addmod_dce ; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.addmod(i256 [[RS1]], i256 [[RS2]], i256 [[RS3]]) ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.addmod(i256 %rs1, i256 %rs2, i256 %rs3) @@ -16,8 +15,7 @@ define i256 @addmod_dce(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { define i256 @sha3_dce(ptr addrspace(1) %offset, i256 %size) nounwind { ; CHECK-LABEL: define i256 @sha3_dce -; CHECK-SAME: (ptr addrspace(1) readonly [[OFFSET:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[OFFSET]], i256 [[SIZE]]) +; CHECK-SAME: (ptr addrspace(1) nocapture readnone [[OFFSET:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.sha3(ptr addrspace(1) %offset, i256 %size) @@ -26,8 +24,7 @@ define i256 @sha3_dce(ptr addrspace(1) %offset, i256 %size) nounwind { define i256 @pc_dce() nounwind { ; CHECK-LABEL: define i256 @pc_dce -; CHECK-SAME: () local_unnamed_addr #[[ATTR2:[0-9]+]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.pc() +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.pc() @@ -36,8 +33,7 @@ define i256 @pc_dce() nounwind { define i256 @gas_dce() nounwind { ; CHECK-LABEL: define i256 @gas_dce -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.gas() +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.gas() @@ -46,8 +42,7 @@ define i256 @gas_dce() nounwind { define i256 @msize_dce() nounwind { ; CHECK-LABEL: define i256 @msize_dce -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.msize() +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.msize() @@ -56,8 +51,7 @@ define i256 @msize_dce() nounwind { define i256 @balance_dce(i256 %rs1) nounwind { ; CHECK-LABEL: define i256 @balance_dce -; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR2]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.balance(i256 [[RS1]]) +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.balance(i256 %rs1) @@ -66,8 +60,7 @@ define i256 @balance_dce(i256 %rs1) nounwind { define i256 @selfbalance_dce() nounwind { ; CHECK-LABEL: define i256 @selfbalance_dce -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.selfbalance() +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.selfbalance() @@ -76,8 +69,7 @@ define i256 @selfbalance_dce() nounwind { define i256 @returndatasize_dce() nounwind { ; CHECK-LABEL: define i256 @returndatasize_dce -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { -; CHECK-NEXT: [[RES:%.*]] = tail call i256 @llvm.evm.returndatasize() +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: ret i256 0 ; %res = call i256 @llvm.evm.returndatasize() @@ -86,7 +78,7 @@ define i256 @returndatasize_dce() nounwind { define i256 @addmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { ; CHECK-LABEL: define i256 @addmod -; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR1:[0-9]+]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.addmod(i256 [[RS1]], i256 [[RS2]], i256 [[RS3]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -99,7 +91,7 @@ define i256 @addmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { define i256 @mulmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { ; CHECK-LABEL: define i256 @mulmod -; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]], i256 [[RS3:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.mulmod(i256 [[RS1]], i256 [[RS2]], i256 [[RS3]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -112,7 +104,7 @@ define i256 @mulmod(i256 %rs1, i256 %rs2, i256 %rs3) nounwind { define i256 @exp(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: define i256 @exp -; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.exp(i256 [[RS1]], i256 [[RS2]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -125,7 +117,7 @@ define i256 @exp(i256 %rs1, i256 %rs2) nounwind { define i256 @sha3(ptr addrspace(1) %offset, i256 %size) nounwind { ; CHECK-LABEL: define i256 @sha3 -; CHECK-SAME: (ptr addrspace(1) [[OFFSET:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR1]] { +; CHECK-SAME: (ptr addrspace(1) [[OFFSET:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[OFFSET]], i256 [[SIZE]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -138,7 +130,7 @@ define i256 @sha3(ptr addrspace(1) %offset, i256 %size) nounwind { define i256 @signextend(i256 %bytesize, i256 %val) nounwind { ; CHECK-LABEL: define i256 @signextend -; CHECK-SAME: (i256 [[BYTESIZE:%.*]], i256 [[VAL:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[BYTESIZE:%.*]], i256 [[VAL:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.signextend(i256 [[BYTESIZE]], i256 [[VAL]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -151,7 +143,7 @@ define i256 @signextend(i256 %bytesize, i256 %val) nounwind { define i256 @byte(i256 %rs1, i256 %rs2) nounwind { ; CHECK-LABEL: define i256 @byte -; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]], i256 [[RS2:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.byte(i256 [[RS1]], i256 [[RS2]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -164,7 +156,7 @@ define i256 @byte(i256 %rs1, i256 %rs2) nounwind { define i256 @pc() nounwind { ; CHECK-LABEL: define i256 @pc -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR3:[0-9]+]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.pc() ; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.pc() ; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] @@ -178,7 +170,7 @@ define i256 @pc() nounwind { define i256 @gas() nounwind { ; CHECK-LABEL: define i256 @gas -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR3]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.gas() ; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.gas() ; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] @@ -192,7 +184,7 @@ define i256 @gas() nounwind { define i256 @msize() nounwind { ; CHECK-LABEL: define i256 @msize -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR3]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.msize() ; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.msize() ; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] @@ -206,7 +198,7 @@ define i256 @msize() nounwind { define i256 @address() nounwind { ; CHECK-LABEL: define i256 @address -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.address() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -219,7 +211,7 @@ define i256 @address() nounwind { define i256 @origin() nounwind { ; CHECK-LABEL: define i256 @origin -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.origin() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -232,7 +224,7 @@ define i256 @origin() nounwind { define i256 @caller() nounwind { ; CHECK-LABEL: define i256 @caller -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.caller() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -245,7 +237,7 @@ define i256 @caller() nounwind { define i256 @balance(i256 %rs1) nounwind { ; CHECK-LABEL: define i256 @balance -; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR3]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.balance(i256 [[RS1]]) ; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.balance(i256 [[RS1]]) ; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] @@ -259,7 +251,7 @@ define i256 @balance(i256 %rs1) nounwind { define i256 @calldatasize() nounwind { ; CHECK-LABEL: define i256 @calldatasize -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.calldatasize() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -272,7 +264,7 @@ define i256 @calldatasize() nounwind { define i256 @calldataload(ptr addrspace(2) %rs1) nounwind { ; CHECK-LABEL: define i256 @calldataload -; CHECK-SAME: (ptr addrspace(2) [[RS1:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(2) [[RS1:%.*]]) local_unnamed_addr #[[ATTR4:[0-9]+]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.calldataload(ptr addrspace(2) [[RS1]]) ; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.calldataload(ptr addrspace(2) [[RS1]]) ; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] @@ -286,10 +278,9 @@ define i256 @calldataload(ptr addrspace(2) %rs1) nounwind { define i256 @callvalue() nounwind { ; CHECK-LABEL: define i256 @callvalue -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.callvalue() -; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.callvalue() -; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] +; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] ; %res1 = call i256 @llvm.evm.callvalue() @@ -300,7 +291,7 @@ define i256 @callvalue() nounwind { define i256 @codesize() nounwind { ; CHECK-LABEL: define i256 @codesize -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.codesize() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -313,7 +304,7 @@ define i256 @codesize() nounwind { define i256 @gasprice() nounwind { ; CHECK-LABEL: define i256 @gasprice -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.gasprice() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -326,7 +317,7 @@ define i256 @gasprice() nounwind { define i256 @extcodesize(i256 %rs1) nounwind { ; CHECK-LABEL: define i256 @extcodesize -; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.extcodesize(i256 [[RS1]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -339,7 +330,7 @@ define i256 @extcodesize(i256 %rs1) nounwind { define i256 @extcodehash(i256 %rs1) nounwind { ; CHECK-LABEL: define i256 @extcodehash -; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.extcodehash(i256 [[RS1]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -352,7 +343,7 @@ define i256 @extcodehash(i256 %rs1) nounwind { define i256 @returndatasize() nounwind { ; CHECK-LABEL: define i256 @returndatasize -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR3]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.returndatasize() ; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.returndatasize() ; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] @@ -366,7 +357,7 @@ define i256 @returndatasize() nounwind { define i256 @blockhash(i256 %rs1) nounwind { ; CHECK-LABEL: define i256 @blockhash -; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.blockhash(i256 [[RS1]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -379,7 +370,7 @@ define i256 @blockhash(i256 %rs1) nounwind { define i256 @blobhash(i256 %rs1) nounwind { ; CHECK-LABEL: define i256 @blobhash -; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: (i256 [[RS1:%.*]]) local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.blobhash(i256 [[RS1]]) ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -392,7 +383,7 @@ define i256 @blobhash(i256 %rs1) nounwind { define i256 @coinbase() nounwind { ; CHECK-LABEL: define i256 @coinbase -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.coinbase() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -405,7 +396,7 @@ define i256 @coinbase() nounwind { define i256 @timestamp() nounwind { ; CHECK-LABEL: define i256 @timestamp -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.timestamp() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -418,7 +409,7 @@ define i256 @timestamp() nounwind { define i256 @number() nounwind { ; CHECK-LABEL: define i256 @number -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.number() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -431,7 +422,7 @@ define i256 @number() nounwind { define i256 @difficulty() nounwind { ; CHECK-LABEL: define i256 @difficulty -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.difficulty() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -444,7 +435,7 @@ define i256 @difficulty() nounwind { define i256 @gaslimit() nounwind { ; CHECK-LABEL: define i256 @gaslimit -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.gaslimit() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -457,7 +448,7 @@ define i256 @gaslimit() nounwind { define i256 @chainid() nounwind { ; CHECK-LABEL: define i256 @chainid -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.chainid() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -470,7 +461,7 @@ define i256 @chainid() nounwind { define i256 @selfbalance() nounwind { ; CHECK-LABEL: define i256 @selfbalance -; CHECK-SAME: () local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR5:[0-9]+]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.selfbalance() ; CHECK-NEXT: [[RES2:%.*]] = tail call i256 @llvm.evm.selfbalance() ; CHECK-NEXT: [[RET:%.*]] = add i256 [[RES2]], [[RES1]] @@ -484,7 +475,7 @@ define i256 @selfbalance() nounwind { define i256 @basefee() nounwind { ; CHECK-LABEL: define i256 @basefee -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.basefee() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -497,7 +488,7 @@ define i256 @basefee() nounwind { define i256 @blobbasefee() nounwind { ; CHECK-LABEL: define i256 @blobbasefee -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { ; CHECK-NEXT: [[RES1:%.*]] = tail call i256 @llvm.evm.blobbasefee() ; CHECK-NEXT: [[RET:%.*]] = shl i256 [[RES1]], 1 ; CHECK-NEXT: ret i256 [[RET]] @@ -510,7 +501,7 @@ define i256 @blobbasefee() nounwind { define void @log0(ptr addrspace(1) %off, i256 %size) nounwind { ; CHECK-LABEL: define void @log0 -; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[OFF:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR6:[0-9]+]] { ; CHECK-NEXT: tail call void @llvm.evm.log0(ptr addrspace(1) [[OFF]], i256 [[SIZE]]) ; CHECK-NEXT: tail call void @llvm.evm.log0(ptr addrspace(1) [[OFF]], i256 [[SIZE]]) ; CHECK-NEXT: ret void @@ -522,7 +513,7 @@ define void @log0(ptr addrspace(1) %off, i256 %size) nounwind { define void @log1(ptr addrspace(1) %off, i256 %size, i256 %t1) nounwind { ; CHECK-LABEL: define void @log1 -; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]]) local_unnamed_addr #[[ATTR6]] { ; CHECK-NEXT: tail call void @llvm.evm.log1(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]]) ; CHECK-NEXT: tail call void @llvm.evm.log1(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]]) ; CHECK-NEXT: ret void @@ -534,7 +525,7 @@ define void @log1(ptr addrspace(1) %off, i256 %size, i256 %t1) nounwind { define void @log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) nounwind { ; CHECK-LABEL: define void @log2 -; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]]) local_unnamed_addr #[[ATTR6]] { ; CHECK-NEXT: tail call void @llvm.evm.log2(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]]) ; CHECK-NEXT: tail call void @llvm.evm.log2(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]]) ; CHECK-NEXT: ret void @@ -546,7 +537,7 @@ define void @log2(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2) nounwin define void @log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3) nounwind { ; CHECK-LABEL: define void @log3 -; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]], i256 [[T3:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]], i256 [[T3:%.*]]) local_unnamed_addr #[[ATTR6]] { ; CHECK-NEXT: tail call void @llvm.evm.log3(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]]) ; CHECK-NEXT: tail call void @llvm.evm.log3(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]]) ; CHECK-NEXT: ret void @@ -558,7 +549,7 @@ define void @log3(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t define void @log4(ptr addrspace(1) %off, i256 %size, i256 %t1, i256 %t2, i256 %t3, i256 %t4) nounwind { ; CHECK-LABEL: define void @log4 -; CHECK-SAME: (ptr addrspace(1) [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]], i256 [[T3:%.*]], i256 [[T4:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[OFF:%.*]], i256 [[SIZE:%.*]], i256 [[T1:%.*]], i256 [[T2:%.*]], i256 [[T3:%.*]], i256 [[T4:%.*]]) local_unnamed_addr #[[ATTR6]] { ; CHECK-NEXT: tail call void @llvm.evm.log4(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]], i256 [[T4]]) ; CHECK-NEXT: tail call void @llvm.evm.log4(ptr addrspace(1) [[OFF]], i256 [[SIZE]], i256 [[T1]], i256 [[T2]], i256 [[T3]], i256 [[T4]]) ; CHECK-NEXT: ret void From 3d02e31924612e7130ef3a86d040cb7575d6e79f Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Thu, 31 Oct 2024 14:01:50 +0100 Subject: [PATCH 24/42] Generalize EraVMAAResult and move it to the common part This way, we can share address space AliasAnalysis implementation between the backends. Signed-off-by: Vladimir Radosavljevic --- llvm/include/llvm/Analysis/VMAliasAnalysis.h | 50 ++++++ llvm/lib/Analysis/CMakeLists.txt | 3 + llvm/lib/Analysis/VMAliasAnalysis.cpp | 173 +++++++++++++++++++ llvm/lib/Target/EraVM/EraVMAliasAnalysis.cpp | 167 ++---------------- llvm/lib/Target/EraVM/EraVMAliasAnalysis.h | 46 +---- 5 files changed, 247 insertions(+), 192 deletions(-) create mode 100644 llvm/include/llvm/Analysis/VMAliasAnalysis.h create mode 100644 llvm/lib/Analysis/VMAliasAnalysis.cpp diff --git a/llvm/include/llvm/Analysis/VMAliasAnalysis.h b/llvm/include/llvm/Analysis/VMAliasAnalysis.h new file mode 100644 index 000000000000..86a932afd687 --- /dev/null +++ b/llvm/include/llvm/Analysis/VMAliasAnalysis.h @@ -0,0 +1,50 @@ +//===-- VMAliasAnalysis.h - VM alias analysis -------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is the address space based alias analysis pass, that is shared between +// EraVM and EVM backends. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_ANALYSIS_VM_VMALIASANALYSIS_H +#define LLVM_ANALYSIS_VM_VMALIASANALYSIS_H + +#include "llvm/Analysis/AliasAnalysis.h" + +namespace llvm { + +class DataLayout; +class MemoryLocation; + +/// A simple AA result that uses address space info to answer queries. +class VMAAResult : public AAResultBase { + const DataLayout &DL; + SmallDenseSet StorageAS; + SmallDenseSet HeapAS; + unsigned MaxAS; + +public: + explicit VMAAResult(const DataLayout &DL, + const SmallDenseSet &StorageAS, + const SmallDenseSet &HeapAS, unsigned MaxAS) + : DL(DL), StorageAS(StorageAS), HeapAS(HeapAS), MaxAS(MaxAS) {} + + /// Handle invalidation events from the new pass manager. + /// + /// By definition, this result is stateless and so remains valid. + bool invalidate(Function &, const PreservedAnalyses &, + FunctionAnalysisManager::Invalidator &) { + return false; + } + + AliasResult alias(const MemoryLocation &LocA, const MemoryLocation &LocB, + AAQueryInfo &AAQI, const Instruction *I); +}; +} // end namespace llvm + +#endif // LLVM_ANALYSIS_VM_ERAVMALIASANALYSIS_H diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt index 4a1797c42789..7a15ca8158fc 100644 --- a/llvm/lib/Analysis/CMakeLists.txt +++ b/llvm/lib/Analysis/CMakeLists.txt @@ -140,6 +140,9 @@ add_llvm_component_library(LLVMAnalysis ValueTracking.cpp VectorUtils.cpp VFABIDemangling.cpp + # EraVM local begin + VMAliasAnalysis.cpp + # EraVM local end ${GeneratedMLSources} ADDITIONAL_HEADER_DIRS diff --git a/llvm/lib/Analysis/VMAliasAnalysis.cpp b/llvm/lib/Analysis/VMAliasAnalysis.cpp new file mode 100644 index 000000000000..5d90b654ac58 --- /dev/null +++ b/llvm/lib/Analysis/VMAliasAnalysis.cpp @@ -0,0 +1,173 @@ +//===-- VMAliasAnalysis.cpp - VM alias analysis -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is the address space based alias analysis pass, that is shared between +// EraVM and EVM backends. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Analysis/VMAliasAnalysis.h" +#include "llvm/Analysis/ValueTracking.h" +#include "llvm/IR/Instructions.h" +#include + +using namespace llvm; + +// Get the base pointer and the offset by looking through the +// ptrtoint+arithmetic+inttoptr sequence. +static std::pair +getBaseWithOffset(const Value *V, unsigned BitWidth, unsigned MaxLookup = 6) { + auto Offset = APInt::getZero(BitWidth); + + // Bail out if this is not an inttoptr instruction. + if (!isa(V)) + return {nullptr, Offset}; + + V = cast(V)->getOperand(0); + + for (unsigned I = 0; I < MaxLookup; ++I) { + // If this is a ptrtoint, get the operand and stop the lookup. + if (const auto *PtrToInt = dyn_cast(V)) { + V = PtrToInt->getOperand(0); + break; + } + + // We only handle binary operations. + const auto *BOp = dyn_cast(V); + if (!BOp) + break; + + // With constant operand. + const auto *CI = dyn_cast(BOp->getOperand(1)); + if (!CI) + break; + + auto Val = CI->getValue(); + + // If the value is larger than the current bitwidth, extend the offset + // and remember the new bitwidth. + if (Val.getBitWidth() > BitWidth) { + BitWidth = Val.getBitWidth(); + Offset = Offset.sext(BitWidth); + } else { + // Otherwise, extend the value to the current bitwidth. + Val = Val.sext(BitWidth); + } + + // TODO: CPR-1652 Support more instructions. + if (BOp->getOpcode() == Instruction::Add) + Offset += Val; + else if (BOp->getOpcode() == Instruction::Sub) + Offset -= Val; + else + break; + + V = BOp->getOperand(0); + } + return {V, Offset}; +} + +static std::optional getConstStartLoc(const MemoryLocation &Loc, + unsigned BitWidth) { + if (isa(Loc.Ptr)) + return APInt::getZero(BitWidth); + + if (const auto *CE = dyn_cast(Loc.Ptr)) { + if (CE->getOpcode() == Instruction::IntToPtr) { + if (auto *CI = dyn_cast(CE->getOperand(0))) + return CI->getValue(); + } + } + + if (const auto *IntToPtr = dyn_cast(Loc.Ptr)) { + if (auto *CI = dyn_cast(IntToPtr->getOperand(0))) + return CI->getValue(); + } + + return std::nullopt; +} + +AliasResult VMAAResult::alias(const MemoryLocation &LocA, + const MemoryLocation &LocB, AAQueryInfo &AAQI, + const Instruction *I) { + const unsigned ASA = LocA.Ptr->getType()->getPointerAddressSpace(); + const unsigned ASB = LocB.Ptr->getType()->getPointerAddressSpace(); + + // If we don't know what this is, bail out. + if (ASA > MaxAS || ASB > MaxAS) + return AAResultBase::alias(LocA, LocB, AAQI, I); + + // Pointers can't alias if they are not in the same address space. + if (ASA != ASB) + return AliasResult::NoAlias; + + // Since pointers are in the same address space, handle only cases that are + // interesting to us. + if (!StorageAS.count(ASA) && !HeapAS.count(ASA)) + return AAResultBase::alias(LocA, LocB, AAQI, I); + + // Don't check unknown memory locations. + if (!LocA.Size.isPrecise() || !LocB.Size.isPrecise()) + return AAResultBase::alias(LocA, LocB, AAQI, I); + + // Only 256-bit keys are valid for storage. + if (StorageAS.count(ASA)) { + constexpr unsigned KeyByteWidth = 32; + if (LocA.Size != KeyByteWidth || LocB.Size != KeyByteWidth) + return AAResultBase::alias(LocA, LocB, AAQI, I); + } + + unsigned BitWidth = DL.getPointerSizeInBits(ASA); + auto StartA = getConstStartLoc(LocA, BitWidth); + auto StartB = getConstStartLoc(LocB, BitWidth); + + // If we don't have constant start locations, try to get the base pointer and + // the offset. In case we managed to find them and pointers have the same + // base, we can compare offsets to prove aliasing. Otherwise, forward the + // query to the next alias analysis. + if (!StartA || !StartB) { + auto [BaseA, OffsetA] = getBaseWithOffset(LocA.Ptr, BitWidth); + auto [BaseB, OffsetB] = getBaseWithOffset(LocB.Ptr, BitWidth); + if (!BaseA || !BaseB || BaseA != BaseB) + return AAResultBase::alias(LocA, LocB, AAQI, I); + + StartA = OffsetA; + StartB = OffsetB; + } + + // Extend start locations to the same bitwidth and not less than pointer size. + unsigned MaxBitWidth = std::max(StartA->getBitWidth(), StartB->getBitWidth()); + MaxBitWidth = std::max(MaxBitWidth, BitWidth); + const APInt StartAVal = StartA->sext(MaxBitWidth); + const APInt StartBVal = StartB->sext(MaxBitWidth); + + // Keys in storage can't overlap. + if (StorageAS.count(ASA)) { + if (StartAVal == StartBVal) + return AliasResult::MustAlias; + return AliasResult::NoAlias; + } + + // If heap locations are the same, they either must or partially alias based + // on the size of locations. + if (StartAVal == StartBVal) { + if (LocA.Size == LocB.Size) + return AliasResult::MustAlias; + return AliasResult::PartialAlias; + } + + auto DoesOverlap = [](const APInt &X, const APInt &XEnd, const APInt &Y) { + return Y.sge(X) && Y.slt(XEnd); + }; + + // For heap accesses, if locations don't overlap, they are not aliasing. + if (!DoesOverlap(StartAVal, StartAVal + LocA.Size.getValue(), StartBVal) && + !DoesOverlap(StartBVal, StartBVal + LocB.Size.getValue(), StartAVal)) + return AliasResult::NoAlias; + return AliasResult::PartialAlias; +} diff --git a/llvm/lib/Target/EraVM/EraVMAliasAnalysis.cpp b/llvm/lib/Target/EraVM/EraVMAliasAnalysis.cpp index 631007c8d1e7..f3f6868eace3 100644 --- a/llvm/lib/Target/EraVM/EraVMAliasAnalysis.cpp +++ b/llvm/lib/Target/EraVM/EraVMAliasAnalysis.cpp @@ -12,9 +12,6 @@ #include "EraVMAliasAnalysis.h" #include "EraVM.h" -#include "llvm/Analysis/ValueTracking.h" -#include "llvm/IR/Instructions.h" -#include using namespace llvm; @@ -48,157 +45,19 @@ void EraVMAAWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { AU.setPreservesAll(); } -// Get the base pointer and the offset by looking through the -// ptrtoint+arithmetic+inttoptr sequence. -static std::pair -getBaseWithOffset(const Value *V, unsigned BitWidth, unsigned MaxLookup = 6) { - auto Offset = APInt::getZero(BitWidth); - - // Bail out if this is not an inttoptr instruction. - if (!isa(V)) - return {nullptr, Offset}; - - V = cast(V)->getOperand(0); - - for (unsigned I = 0; I < MaxLookup; ++I) { - // If this is a ptrtoint, get the operand and stop the lookup. - if (const auto *PtrToInt = dyn_cast(V)) { - V = PtrToInt->getOperand(0); - break; - } - - // We only handle binary operations. - const auto *BOp = dyn_cast(V); - if (!BOp) - break; - - // With constant operand. - const auto *CI = dyn_cast(BOp->getOperand(1)); - if (!CI) - break; - - auto Val = CI->getValue(); - - // If the value is larger than the current bitwidth, extend the offset - // and remember the new bitwidth. - if (Val.getBitWidth() > BitWidth) { - BitWidth = Val.getBitWidth(); - Offset = Offset.sext(BitWidth); - } else { - // Otherwise, extend the value to the current bitwidth. - Val = Val.sext(BitWidth); - } - - // TODO: CPR-1652 Support more instructions. - if (BOp->getOpcode() == Instruction::Add) - Offset += Val; - else if (BOp->getOpcode() == Instruction::Sub) - Offset -= Val; - else - break; - - V = BOp->getOperand(0); - } - return {V, Offset}; -} - -static std::optional getConstStartLoc(const MemoryLocation &Loc, - unsigned BitWidth) { - if (isa(Loc.Ptr)) - return APInt::getZero(BitWidth); - - if (const auto *CE = dyn_cast(Loc.Ptr)) { - if (CE->getOpcode() == Instruction::IntToPtr) { - if (auto *CI = dyn_cast(CE->getOperand(0))) - return CI->getValue(); - } - } - - if (const auto *IntToPtr = dyn_cast(Loc.Ptr)) { - if (auto *CI = dyn_cast(IntToPtr->getOperand(0))) - return CI->getValue(); - } - - return std::nullopt; +bool EraVMAAWrapperPass::doInitialization(Module &M) { + SmallDenseSet StorageAS = {EraVMAS::AS_STORAGE, + EraVMAS::AS_TRANSIENT}; + SmallDenseSet HeapAS = {EraVMAS::AS_HEAP, EraVMAS::AS_HEAP_AUX}; + Result = std::make_unique(M.getDataLayout(), StorageAS, HeapAS, + EraVMAS::MAX_ADDRESS); + return false; } -AliasResult EraVMAAResult::alias(const MemoryLocation &LocA, - const MemoryLocation &LocB, AAQueryInfo &AAQI, - const Instruction *I) { - const unsigned ASA = LocA.Ptr->getType()->getPointerAddressSpace(); - const unsigned ASB = LocB.Ptr->getType()->getPointerAddressSpace(); - - // If we don't know what this is, bail out. - if (ASA > EraVMAS::MAX_ADDRESS || ASB > EraVMAS::MAX_ADDRESS) - return AAResultBase::alias(LocA, LocB, AAQI, I); - - // Pointers can't alias if they are not in the same address space. - if (ASA != ASB) - return AliasResult::NoAlias; - - // Since pointers are in the same address space, handle only cases that are - // interesting to us. - if (ASA != EraVMAS::AS_HEAP && ASA != EraVMAS::AS_HEAP_AUX && - ASA != EraVMAS::AS_STORAGE && ASA != EraVMAS::AS_TRANSIENT) - return AAResultBase::alias(LocA, LocB, AAQI, I); - - // Don't check unknown memory locations. - if (!LocA.Size.isPrecise() || !LocB.Size.isPrecise()) - return AAResultBase::alias(LocA, LocB, AAQI, I); - - // Only 256-bit keys are valid for storage. - if (ASA == EraVMAS::AS_STORAGE || ASA == EraVMAS::AS_TRANSIENT) { - constexpr unsigned KeyByteWidth = 32; - if (LocA.Size != KeyByteWidth || LocB.Size != KeyByteWidth) - return AAResultBase::alias(LocA, LocB, AAQI, I); - } - - unsigned BitWidth = DL.getPointerSizeInBits(ASA); - auto StartA = getConstStartLoc(LocA, BitWidth); - auto StartB = getConstStartLoc(LocB, BitWidth); - - // If we don't have constant start locations, try to get the base pointer and - // the offset. In case we managed to find them and pointers have the same - // base, we can compare offsets to prove aliasing. Otherwise, forward the - // query to the next alias analysis. - if (!StartA || !StartB) { - auto [BaseA, OffsetA] = getBaseWithOffset(LocA.Ptr, BitWidth); - auto [BaseB, OffsetB] = getBaseWithOffset(LocB.Ptr, BitWidth); - if (!BaseA || !BaseB || BaseA != BaseB) - return AAResultBase::alias(LocA, LocB, AAQI, I); - - StartA = OffsetA; - StartB = OffsetB; - } - - // Extend start locations to the same bitwidth and not less than pointer size. - unsigned MaxBitWidth = std::max(StartA->getBitWidth(), StartB->getBitWidth()); - MaxBitWidth = std::max(MaxBitWidth, BitWidth); - const APInt StartAVal = StartA->sext(MaxBitWidth); - const APInt StartBVal = StartB->sext(MaxBitWidth); - - // Keys in storage can't overlap. - if (ASA == EraVMAS::AS_STORAGE || ASA == EraVMAS::AS_TRANSIENT) { - if (StartAVal == StartBVal) - return AliasResult::MustAlias; - return AliasResult::NoAlias; - } - - // If heap locations are the same, they either must or partially alias based - // on the size of locations. - if (StartAVal == StartBVal) { - if (LocA.Size == LocB.Size) - return AliasResult::MustAlias; - return AliasResult::PartialAlias; - } - - auto DoesOverlap = [](const APInt &X, const APInt &XEnd, const APInt &Y) { - return Y.sge(X) && Y.slt(XEnd); - }; - - // For heap accesses, if locations don't overlap, they are not aliasing. - if (!DoesOverlap(StartAVal, StartAVal + LocA.Size.getValue(), StartBVal) && - !DoesOverlap(StartBVal, StartBVal + LocB.Size.getValue(), StartAVal)) - return AliasResult::NoAlias; - return AliasResult::PartialAlias; +VMAAResult EraVMAA::run(Function &F, AnalysisManager &AM) { + SmallDenseSet StorageAS = {EraVMAS::AS_STORAGE, + EraVMAS::AS_TRANSIENT}; + SmallDenseSet HeapAS = {EraVMAS::AS_HEAP, EraVMAS::AS_HEAP_AUX}; + return VMAAResult(F.getParent()->getDataLayout(), StorageAS, HeapAS, + EraVMAS::MAX_ADDRESS); } diff --git a/llvm/lib/Target/EraVM/EraVMAliasAnalysis.h b/llvm/lib/Target/EraVM/EraVMAliasAnalysis.h index 9eb6be16720e..3bc1da06575e 100644 --- a/llvm/lib/Target/EraVM/EraVMAliasAnalysis.h +++ b/llvm/lib/Target/EraVM/EraVMAliasAnalysis.h @@ -13,32 +13,9 @@ #ifndef LLVM_LIB_TARGET_ERAVM_ERAVMALIASANALYSIS_H #define LLVM_LIB_TARGET_ERAVM_ERAVMALIASANALYSIS_H -#include "llvm/Analysis/AliasAnalysis.h" +#include "llvm/Analysis/VMAliasAnalysis.h" namespace llvm { - -class DataLayout; -class MemoryLocation; - -/// A simple AA result that uses address space info to answer queries. -class EraVMAAResult : public AAResultBase { - const DataLayout &DL; - -public: - explicit EraVMAAResult(const DataLayout &DL) : DL(DL) {} - - /// Handle invalidation events from the new pass manager. - /// - /// By definition, this result is stateless and so remains valid. - bool invalidate(Function &, const PreservedAnalyses &, - FunctionAnalysisManager::Invalidator &) { - return false; - } - - AliasResult alias(const MemoryLocation &LocA, const MemoryLocation &LocB, - AAQueryInfo &AAQI, const Instruction *I); -}; - /// Analysis pass providing a never-invalidated alias analysis result. class EraVMAA : public AnalysisInfoMixin { friend AnalysisInfoMixin; @@ -46,35 +23,28 @@ class EraVMAA : public AnalysisInfoMixin { static AnalysisKey Key; public: - using Result = EraVMAAResult; - - EraVMAAResult run(Function &F, AnalysisManager &AM) { - return EraVMAAResult(F.getParent()->getDataLayout()); - } + using Result = VMAAResult; + VMAAResult run(Function &F, AnalysisManager &AM); }; -/// Legacy wrapper pass to provide the EraVMAAResult object. +/// Legacy wrapper pass to provide the VMAAResult object. class EraVMAAWrapperPass : public ImmutablePass { - std::unique_ptr Result; + std::unique_ptr Result; public: static char ID; EraVMAAWrapperPass(); - EraVMAAResult &getResult() { return *Result; } - const EraVMAAResult &getResult() const { return *Result; } - - bool doInitialization(Module &M) override { - Result = std::make_unique(M.getDataLayout()); - return false; - } + VMAAResult &getResult() { return *Result; } + const VMAAResult &getResult() const { return *Result; } bool doFinalization(Module &M) override { Result.reset(); return false; } + bool doInitialization(Module &M) override; void getAnalysisUsage(AnalysisUsage &AU) const override; }; From b33c8f8d7aaf17b782f3f402ee1a667f8a149aec Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Wed, 6 Nov 2024 17:38:08 +0100 Subject: [PATCH 25/42] [EVM] Add pre-commit tests for Add implementation of AliasAnalysis Signed-off-by: Vladimir Radosavljevic --- llvm/test/CodeGen/EVM/aa-dse.ll | 20 ++ llvm/test/CodeGen/EVM/aa-eval.ll | 257 +++++++++++++++++++++++++ llvm/test/CodeGen/EVM/aa-memory-opt.ll | 231 ++++++++++++++++++++++ 3 files changed, 508 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/aa-dse.ll create mode 100644 llvm/test/CodeGen/EVM/aa-eval.ll create mode 100644 llvm/test/CodeGen/EVM/aa-memory-opt.ll diff --git a/llvm/test/CodeGen/EVM/aa-dse.ll b/llvm/test/CodeGen/EVM/aa-dse.ll new file mode 100644 index 000000000000..5f0af2ddfdd7 --- /dev/null +++ b/llvm/test/CodeGen/EVM/aa-dse.ll @@ -0,0 +1,20 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 2 +; RUN: opt -passes=dse -S < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +define void @test() { +; CHECK-LABEL: define void @test() { +; CHECK-NEXT: [[PTR1:%.*]] = inttoptr i256 32 to ptr addrspace(1) +; CHECK-NEXT: [[PTR2:%.*]] = inttoptr i256 32 to ptr addrspace(1) +; CHECK-NEXT: store i256 2, ptr addrspace(1) [[PTR1]], align 64 +; CHECK-NEXT: store i256 1, ptr addrspace(1) [[PTR2]], align 64 +; CHECK-NEXT: ret void +; + %ptr1 = inttoptr i256 32 to ptr addrspace(1) + %ptr2 = inttoptr i256 32 to ptr addrspace(1) + store i256 2, ptr addrspace(1) %ptr1, align 64 + store i256 1, ptr addrspace(1) %ptr2, align 64 + ret void +} diff --git a/llvm/test/CodeGen/EVM/aa-eval.ll b/llvm/test/CodeGen/EVM/aa-eval.ll new file mode 100644 index 000000000000..61a51f755ea2 --- /dev/null +++ b/llvm/test/CodeGen/EVM/aa-eval.ll @@ -0,0 +1,257 @@ +; RUN: opt -passes=aa-eval -aa-pipeline=basic-aa -print-all-alias-modref-info -disable-output < %s 2>&1 | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +; CHECK-LABEL: Function: test_offset_i8_noalias +; CHECK: MayAlias: i8 addrspace(1)* %inttoptr1, i8 addrspace(1)* %inttoptr2 +define void @test_offset_i8_noalias(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i8 + %add1 = add i8 %ptrtoint, 32 + %inttoptr1 = inttoptr i8 %add1 to ptr addrspace(1) + store i8 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = add i8 %ptrtoint, 33 + %inttoptr2 = inttoptr i8 %add2 to ptr addrspace(1) + store i8 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_i512_noalias +; CHECK: MayAlias: i512 addrspace(1)* %inttoptr1, i512 addrspace(1)* %inttoptr2 +define void @test_offset_i512_noalias(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i512 + %add1 = add i512 %ptrtoint, 32 + %inttoptr1 = inttoptr i512 %add1 to ptr addrspace(1) + store i512 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = add i512 %ptrtoint, 96 + %inttoptr2 = inttoptr i512 %add2 to ptr addrspace(1) + store i512 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_mustalias +; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +define void @test_offset_mustalias(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %inttoptr1 = inttoptr i256 %ptrtoint to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %inttoptr2 = inttoptr i256 %ptrtoint to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_noalias1 +; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +define void @test_offset_noalias1(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %add1 = add i256 %ptrtoint, 32 + %inttoptr1 = inttoptr i256 %add1 to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = add i256 %ptrtoint, 64 + %inttoptr2 = inttoptr i256 %add2 to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_noalias2 +; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +define void @test_offset_noalias2(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %add1 = add i256 %ptrtoint, -32 + %inttoptr1 = inttoptr i256 %add1 to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = add i256 %ptrtoint, -64 + %inttoptr2 = inttoptr i256 %add2 to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_noalias3 +; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +define void @test_offset_noalias3(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %add1 = sub i256 %ptrtoint, 32 + %inttoptr1 = inttoptr i256 %add1 to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = sub i256 %ptrtoint, 64 + %inttoptr2 = inttoptr i256 %add2 to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_partialalias1 +; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +define void @test_offset_partialalias1(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %add1 = add i256 %ptrtoint, 32 + %inttoptr1 = inttoptr i256 %add1 to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = add i256 %ptrtoint, 48 + %inttoptr2 = inttoptr i256 %add2 to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_partialalias2 +; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +define void @test_offset_partialalias2(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %add1 = add i256 %ptrtoint, -32 + %inttoptr1 = inttoptr i256 %add1 to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = add i256 %ptrtoint, -48 + %inttoptr2 = inttoptr i256 %add2 to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_offset_partialalias3 +; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +define void @test_offset_partialalias3(ptr addrspace(1) %addr) { + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %add1 = sub i256 %ptrtoint, 32 + %inttoptr1 = inttoptr i256 %add1 to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = sub i256 %ptrtoint, 48 + %inttoptr2 = inttoptr i256 %add2 to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + ret void +} + +; CHECK-LABEL: Function: test_as1_i8_noalias +; CHECK: MayAlias: i8 addrspace(1)* %ptr1, i8 addrspace(1)* %ptr2 +define void @test_as1_i8_noalias() { + %ptr1 = inttoptr i8 32 to ptr addrspace(1) + %ptr2 = inttoptr i8 64 to ptr addrspace(1) + store i8 2, ptr addrspace(1) %ptr1, align 64 + store i8 1, ptr addrspace(1) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_unknown_as +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(10)* %ptr2 +define void @test_unknown_as() { + %ptr1 = inttoptr i256 32 to ptr addrspace(1) + %ptr2 = inttoptr i256 64 to ptr addrspace(10) + store i256 2, ptr addrspace(1) %ptr1, align 64 + store i256 1, ptr addrspace(10) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_mayalias +; CHECK: MayAlias: i256* %0, i256* %1 +define void @test_mayalias(ptr %0, ptr %1) { + store i256 2, ptr %0, align 64 + store i256 1, ptr %1, align 64 + ret void +} + +; CHECK-LABEL: Function: test_noalias_fallback +; CHECK: NoAlias: i256* %0, i256* %1 +define void @test_noalias_fallback(ptr noalias %0, ptr noalias %1) { + store i256 2, ptr %0, align 64 + store i256 1, ptr %1, align 64 + ret void +} + +; CHECK-LABEL: Function: test_noalias +; CHECK: MayAlias: i256* %ptr0, i256 addrspace(1)* %ptr1 +; CHECK: MayAlias: i256* %ptr0, i256 addrspace(2)* %ptr2 +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(2)* %ptr2 +; CHECK: MayAlias: i256* %ptr0, i256 addrspace(4)* %ptr4 +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(4)* %ptr4 +; CHECK: MayAlias: i256 addrspace(2)* %ptr2, i256 addrspace(4)* %ptr4 +; CHECK: MayAlias: i256* %ptr0, i256 addrspace(5)* %ptr5 +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(5)* %ptr5 +; CHECK: MayAlias: i256 addrspace(2)* %ptr2, i256 addrspace(5)* %ptr5 +; CHECK: MayAlias: i256 addrspace(4)* %ptr4, i256 addrspace(5)* %ptr5 +; CHECK: MayAlias: i256* %ptr0, i256 addrspace(6)* %ptr6 +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(6)* %ptr6 +; CHECK: MayAlias: i256 addrspace(2)* %ptr2, i256 addrspace(6)* %ptr6 +; CHECK: MayAlias: i256 addrspace(4)* %ptr4, i256 addrspace(6)* %ptr6 +; CHECK: MayAlias: i256 addrspace(5)* %ptr5, i256 addrspace(6)* %ptr6 +define void @test_noalias() { + %ptr0 = inttoptr i256 32 to ptr + %ptr1 = inttoptr i256 32 to ptr addrspace(1) + %ptr2 = inttoptr i256 32 to ptr addrspace(2) + %ptr4 = inttoptr i256 32 to ptr addrspace(4) + %ptr5 = inttoptr i256 32 to ptr addrspace(5) + %ptr6 = inttoptr i256 32 to ptr addrspace(6) + store i256 1, ptr %ptr0, align 64 + store i256 1, ptr addrspace(1) %ptr1, align 64 + store i256 1, ptr addrspace(2) %ptr2, align 64 + store i256 1, ptr addrspace(4) %ptr4, align 64 + store i256 1, ptr addrspace(5) %ptr5, align 64 + store i256 1, ptr addrspace(6) %ptr6, align 64 + ret void +} + +; CHECK-LABEL: Function: test_as1_noalias +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 +define void @test_as1_noalias() { + %ptr1 = inttoptr i256 32 to ptr addrspace(1) + %ptr2 = inttoptr i256 64 to ptr addrspace(1) + store i256 2, ptr addrspace(1) %ptr1, align 64 + store i256 1, ptr addrspace(1) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_as1_mustalias +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 +define void @test_as1_mustalias() { + %ptr1 = inttoptr i256 32 to ptr addrspace(1) + %ptr2 = inttoptr i256 32 to ptr addrspace(1) + store i256 2, ptr addrspace(1) %ptr1, align 64 + store i256 1, ptr addrspace(1) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_as1_partialalias +; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 +define void @test_as1_partialalias() { + %ptr1 = inttoptr i256 32 to ptr addrspace(1) + %ptr2 = inttoptr i256 48 to ptr addrspace(1) + store i256 2, ptr addrspace(1) %ptr1, align 64 + store i256 1, ptr addrspace(1) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_as5_noalias +; CHECK: MayAlias: i256 addrspace(5)* %ptr1, i256 addrspace(5)* %ptr2 +define void @test_as5_noalias() { + %ptr1 = inttoptr i256 0 to ptr addrspace(5) + %ptr2 = inttoptr i256 1 to ptr addrspace(5) + store i256 2, ptr addrspace(5) %ptr1, align 64 + store i256 1, ptr addrspace(5) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_as5_mustalias +; CHECK: MayAlias: i256 addrspace(5)* %ptr1, i256 addrspace(5)* %ptr2 +define void @test_as5_mustalias() { + %ptr1 = inttoptr i256 0 to ptr addrspace(5) + %ptr2 = inttoptr i256 0 to ptr addrspace(5) + store i256 2, ptr addrspace(5) %ptr1, align 64 + store i256 1, ptr addrspace(5) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_as6_noalias +; CHECK: MayAlias: i256 addrspace(6)* %ptr1, i256 addrspace(6)* %ptr2 +define void @test_as6_noalias() { + %ptr1 = inttoptr i256 0 to ptr addrspace(6) + %ptr2 = inttoptr i256 1 to ptr addrspace(6) + store i256 2, ptr addrspace(6) %ptr1, align 64 + store i256 1, ptr addrspace(6) %ptr2, align 64 + ret void +} + +; CHECK-LABEL: Function: test_as6_mustalias +; CHECK: MayAlias: i256 addrspace(6)* %ptr1, i256 addrspace(6)* %ptr2 +define void @test_as6_mustalias() { + %ptr1 = inttoptr i256 0 to ptr addrspace(6) + %ptr2 = inttoptr i256 0 to ptr addrspace(6) + store i256 2, ptr addrspace(6) %ptr1, align 64 + store i256 1, ptr addrspace(6) %ptr2, align 64 + ret void +} diff --git a/llvm/test/CodeGen/EVM/aa-memory-opt.ll b/llvm/test/CodeGen/EVM/aa-memory-opt.ll new file mode 100644 index 000000000000..a6fb53e75f5e --- /dev/null +++ b/llvm/test/CodeGen/EVM/aa-memory-opt.ll @@ -0,0 +1,231 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 2 +; RUN: opt -O3 -S < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +define i256 @test_offset(ptr addrspace(1) %addr) { +; CHECK-LABEL: define i256 @test_offset +; CHECK-SAME: (ptr addrspace(1) [[ADDR:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: [[PTRTOINT:%.*]] = ptrtoint ptr addrspace(1) [[ADDR]] to i256 +; CHECK-NEXT: [[ADD1:%.*]] = add i256 [[PTRTOINT]], 32 +; CHECK-NEXT: [[INTTOPTR1:%.*]] = inttoptr i256 [[ADD1]] to ptr addrspace(1) +; CHECK-NEXT: store i256 3, ptr addrspace(1) [[INTTOPTR1]], align 1 +; CHECK-NEXT: [[ADD2:%.*]] = add i256 [[PTRTOINT]], 64 +; CHECK-NEXT: [[INTTOPTR2:%.*]] = inttoptr i256 [[ADD2]] to ptr addrspace(1) +; CHECK-NEXT: store i256 58, ptr addrspace(1) [[INTTOPTR2]], align 1 +; CHECK-NEXT: [[LOAD:%.*]] = load i256, ptr addrspace(1) [[INTTOPTR1]], align 1 +; CHECK-NEXT: ret i256 [[LOAD]] +; + %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 + %add1 = add i256 %ptrtoint, 32 + %inttoptr1 = inttoptr i256 %add1 to ptr addrspace(1) + store i256 3, ptr addrspace(1) %inttoptr1, align 1 + %add2 = add i256 %ptrtoint, 64 + %inttoptr2 = inttoptr i256 %add2 to ptr addrspace(1) + store i256 58, ptr addrspace(1) %inttoptr2, align 1 + %load = load i256, ptr addrspace(1) %inttoptr1, align 1 + ret i256 %load +} + +define i256 @test_memcpy() { +; CHECK-LABEL: define i256 @test_memcpy +; CHECK-SAME: () local_unnamed_addr #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 +; CHECK-NEXT: tail call void @llvm.memcpy.p1.p1.i256(ptr addrspace(1) noundef nonnull align 32 dereferenceable(53) inttoptr (i256 96 to ptr addrspace(1)), ptr addrspace(1) noundef nonnull align 256 dereferenceable(53) inttoptr (i256 256 to ptr addrspace(1)), i256 53, i1 false) +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 + tail call void @llvm.memcpy.p1.p1.i256(ptr addrspace(1) inttoptr (i256 96 to ptr addrspace(1)), ptr addrspace(1) inttoptr (i256 256 to ptr addrspace(1)), i256 53, i1 false) + %ret = load i256, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 + ret i256 %ret +} + +define i256 @test_different_locsizes() { +; CHECK-LABEL: define i256 @test_different_locsizes +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 +; CHECK-NEXT: store i8 1, ptr addrspace(1) inttoptr (i8 1 to ptr addrspace(1)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 + store i8 1, ptr addrspace(1) inttoptr (i8 1 to ptr addrspace(1)), align 64 + %ret = load i256, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 + ret i256 %ret +} + +define i256 @test_gas() { +; CHECK-LABEL: define i256 @test_gas +; CHECK-SAME: () local_unnamed_addr #[[ATTR2:[0-9]+]] { +; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 +; CHECK-NEXT: [[GAS:%.*]] = tail call i256 @llvm.evm.gas() +; CHECK-NEXT: store i256 [[GAS]], ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + %gas = call i256 @llvm.evm.gas() + store i256 %gas, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 + %ret = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + ret i256 %ret +} + +define i256 @test_log0(ptr addrspace(1) %off, i256 %size) { +; CHECK-LABEL: define i256 @test_log0 +; CHECK-SAME: (ptr addrspace(1) nocapture readonly [[OFF:%.*]], i256 [[SIZE:%.*]]) local_unnamed_addr #[[ATTR3:[0-9]+]] { +; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 +; CHECK-NEXT: tail call void @llvm.evm.log0(ptr addrspace(1) [[OFF]], i256 [[SIZE]]) +; CHECK-NEXT: ret i256 2 +; + store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + call void @llvm.evm.log0(ptr addrspace(1) %off, i256 %size) + %ret = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + ret i256 %ret +} + +define i256 @test_as() { +; CHECK-LABEL: define i256 @test_as +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 +; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 + %ret = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + ret i256 %ret +} + +define i256 @test_as1_overlap() { +; CHECK-LABEL: define i256 @test_as1_overlap +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(1) null, align 4294967296 +; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 31 to ptr addrspace(1)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) null, align 4294967296 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(1) null, align 64 + store i256 1, ptr addrspace(1) inttoptr (i256 31 to ptr addrspace(1)), align 64 + %ret = load i256, ptr addrspace(1) null, align 64 + ret i256 %ret +} + +define i256 @test_as1_null() { +; CHECK-LABEL: define i256 @test_as1_null +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(1) null, align 4294967296 +; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) null, align 4294967296 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(1) null, align 64 + store i256 1, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 64 + %ret = load i256, ptr addrspace(1) null, align 64 + ret i256 %ret +} + +define i256 @test_as1_small() { +; CHECK-LABEL: define i256 @test_as1_small +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 33 to ptr addrspace(1)), align 64 +; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 33 to ptr addrspace(1)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(1) inttoptr (i256 33 to ptr addrspace(1)), align 64 + store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 + %ret = load i256, ptr addrspace(1) inttoptr (i256 33 to ptr addrspace(1)), align 64 + ret i256 %ret +} + +define i256 @test_as1_large() { +; CHECK-LABEL: define i256 @test_as1_large +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533664 to ptr addrspace(1)), align 64 +; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(1)), align 4294967296 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533664 to ptr addrspace(1)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533664 to ptr addrspace(1)), align 64 + store i256 1, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(1)), align 64 + %ret = load i256, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533664 to ptr addrspace(1)), align 64 + ret i256 %ret +} + +define i256 @test_as5_null() { +; CHECK-LABEL: define i256 @test_as5_null +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(5) null, align 4294967296 +; CHECK-NEXT: store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) null, align 4294967296 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(5) null, align 64 + store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 + %ret = load i256, ptr addrspace(5) null, align 64 + ret i256 %ret +} + +define i256 @test_as5_small() { +; CHECK-LABEL: define i256 @test_as5_small +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 +; CHECK-NEXT: store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 + %ret = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 + ret i256 %ret +} + +define i256 @test_as5_large() { +; CHECK-LABEL: define i256 @test_as5_large +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(5)), align 4294967296 +; CHECK-NEXT: store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(5)), align 4294967296 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(5) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(5)), align 64 + store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 + %ret = load i256, ptr addrspace(5) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(5)), align 64 + ret i256 %ret +} + +define i256 @test_as6_small() { +; CHECK-LABEL: define i256 @test_as6_small +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(6) inttoptr (i256 2 to ptr addrspace(6)), align 64 +; CHECK-NEXT: store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(6) inttoptr (i256 2 to ptr addrspace(6)), align 64 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(6) inttoptr (i256 2 to ptr addrspace(6)), align 64 + store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 + %ret = load i256, ptr addrspace(6) inttoptr (i256 2 to ptr addrspace(6)), align 64 + ret i256 %ret +} + +define i256 @test_as6_large() { +; CHECK-LABEL: define i256 @test_as6_large +; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-NEXT: store i256 2, ptr addrspace(6) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(6)), align 4294967296 +; CHECK-NEXT: store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 +; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(6) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(6)), align 4294967296 +; CHECK-NEXT: ret i256 [[RET]] +; + store i256 2, ptr addrspace(6) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(6)), align 64 + store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 + %ret = load i256, ptr addrspace(6) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(6)), align 64 + ret i256 %ret +} + +declare void @llvm.memcpy.p1.p1.i256(ptr addrspace(1), ptr addrspace(1), i256, i1 immarg) +declare i256 @llvm.evm.gas() +declare void @llvm.evm.log0(ptr addrspace(1), i256) + From befcbdc422200d52605f86db821a69a31b56bbfb Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Thu, 31 Oct 2024 15:19:32 +0100 Subject: [PATCH 26/42] [EVM] Add implementation of AliasAnalysis Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/CMakeLists.txt | 1 + llvm/lib/Target/EVM/EVM.h | 9 +++- llvm/lib/Target/EVM/EVMAliasAnalysis.cpp | 59 +++++++++++++++++++++ llvm/lib/Target/EVM/EVMAliasAnalysis.h | 66 ++++++++++++++++++++++++ llvm/lib/Target/EVM/EVMTargetMachine.cpp | 27 ++++++++++ llvm/lib/Target/EVM/EVMTargetMachine.h | 1 + llvm/test/CodeGen/EVM/aa-dse.ll | 2 - llvm/test/CodeGen/EVM/aa-eval.ll | 66 ++++++++++++------------ llvm/test/CodeGen/EVM/aa-memory-opt.ll | 41 +++++---------- 9 files changed, 209 insertions(+), 63 deletions(-) create mode 100644 llvm/lib/Target/EVM/EVMAliasAnalysis.cpp create mode 100644 llvm/lib/Target/EVM/EVMAliasAnalysis.h diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index 18c208f185c1..4ae61c0186d2 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -17,6 +17,7 @@ set (STDLIB_CONTENT "R\"(${STDLIB_CONTENT})\"") file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/EVMStdLib.inc ${STDLIB_CONTENT}) add_llvm_target(EVMCodeGen + EVMAliasAnalysis.cpp EVMAllocaHoisting.cpp EVMArgumentMove.cpp EVMAsmPrinter.cpp diff --git a/llvm/lib/Target/EVM/EVM.h b/llvm/lib/Target/EVM/EVM.h index 15bed82879b1..87fffce6f3d1 100644 --- a/llvm/lib/Target/EVM/EVM.h +++ b/llvm/lib/Target/EVM/EVM.h @@ -16,6 +16,7 @@ #include "llvm/IR/PassManager.h" #include "llvm/MC/TargetRegistry.h" +#include "llvm/Pass.h" namespace llvm { class EVMTargetMachine; @@ -31,13 +32,17 @@ enum AddressSpaces { AS_CALL_DATA = 2, AS_RETURN_DATA = 3, AS_CODE = 4, - AS_STORAGE = 5 + AS_STORAGE = 5, + AS_TSTORAGE = 6, + MAX_ADDRESS = AS_TSTORAGE }; } // namespace EVMAS // LLVM IR passes. ModulePass *createEVMLowerIntrinsicsPass(); FunctionPass *createEVMCodegenPreparePass(); +ImmutablePass *createEVMAAWrapperPass(); +ImmutablePass *createEVMExternalAAWrapperPass(); // ISel and immediate followup passes. FunctionPass *createEVMISelDag(EVMTargetMachine &TM, @@ -66,6 +71,8 @@ void initializeEVMSingleUseExpressionPass(PassRegistry &); void initializeEVMSplitCriticalEdgesPass(PassRegistry &); void initializeEVMStackifyPass(PassRegistry &); void initializeEVMBPStackificationPass(PassRegistry &); +void initializeEVMAAWrapperPassPass(PassRegistry &); +void initializeEVMExternalAAWrapperPass(PassRegistry &); struct EVMLinkRuntimePass : PassInfoMixin { EVMLinkRuntimePass() = default; diff --git a/llvm/lib/Target/EVM/EVMAliasAnalysis.cpp b/llvm/lib/Target/EVM/EVMAliasAnalysis.cpp new file mode 100644 index 000000000000..f087b7e779aa --- /dev/null +++ b/llvm/lib/Target/EVM/EVMAliasAnalysis.cpp @@ -0,0 +1,59 @@ +//===-- EVMAliasAnalysis.cpp - EVM alias analysis ---------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is the EVM address space based alias analysis pass. +// +//===----------------------------------------------------------------------===// + +#include "EVMAliasAnalysis.h" +#include "EVM.h" + +using namespace llvm; + +#define DEBUG_TYPE "evm-aa" + +AnalysisKey EVMAA::Key; + +// Register this pass... +char EVMAAWrapperPass::ID = 0; +char EVMExternalAAWrapper::ID = 0; + +INITIALIZE_PASS(EVMAAWrapperPass, "evm-aa", + "EVM Address space based Alias Analysis", false, true) + +INITIALIZE_PASS(EVMExternalAAWrapper, "evm-aa-wrapper", + "EVM Address space based Alias Analysis Wrapper", false, true) + +ImmutablePass *llvm::createEVMAAWrapperPass() { return new EVMAAWrapperPass(); } + +ImmutablePass *llvm::createEVMExternalAAWrapperPass() { + return new EVMExternalAAWrapper(); +} + +EVMAAWrapperPass::EVMAAWrapperPass() : ImmutablePass(ID) { + initializeEVMAAWrapperPassPass(*PassRegistry::getPassRegistry()); +} + +void EVMAAWrapperPass::getAnalysisUsage(AnalysisUsage &AU) const { + AU.setPreservesAll(); +} + +bool EVMAAWrapperPass::doInitialization(Module &M) { + SmallDenseSet StorageAS = {EVMAS::AS_STORAGE, EVMAS::AS_TSTORAGE}; + SmallDenseSet HeapAS = {EVMAS::AS_HEAP}; + Result = std::make_unique(M.getDataLayout(), StorageAS, HeapAS, + EVMAS::MAX_ADDRESS); + return false; +} + +VMAAResult EVMAA::run(Function &F, AnalysisManager &AM) { + SmallDenseSet StorageAS = {EVMAS::AS_STORAGE, EVMAS::AS_TSTORAGE}; + SmallDenseSet HeapAS = {EVMAS::AS_HEAP}; + return VMAAResult(F.getParent()->getDataLayout(), StorageAS, HeapAS, + EVMAS::MAX_ADDRESS); +} diff --git a/llvm/lib/Target/EVM/EVMAliasAnalysis.h b/llvm/lib/Target/EVM/EVMAliasAnalysis.h new file mode 100644 index 000000000000..1f65703037ad --- /dev/null +++ b/llvm/lib/Target/EVM/EVMAliasAnalysis.h @@ -0,0 +1,66 @@ +//===-- EVMAliasAnalysis.h - EVM alias analysis -----------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is the EVM address space based alias analysis pass. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_LIB_TARGET_EVM_EVMALIASANALYSIS_H +#define LLVM_LIB_TARGET_EVM_EVMALIASANALYSIS_H + +#include "llvm/Analysis/VMAliasAnalysis.h" + +namespace llvm { +/// Analysis pass providing a never-invalidated alias analysis result. +class EVMAA : public AnalysisInfoMixin { + friend AnalysisInfoMixin; + + static AnalysisKey Key; + +public: + using Result = VMAAResult; + VMAAResult run(Function &F, AnalysisManager &AM); +}; + +/// Legacy wrapper pass to provide the VMAAResult object. +class EVMAAWrapperPass : public ImmutablePass { + std::unique_ptr Result; + +public: + static char ID; + + EVMAAWrapperPass(); + + VMAAResult &getResult() { return *Result; } + const VMAAResult &getResult() const { return *Result; } + + bool doFinalization(Module &M) override { + Result.reset(); + return false; + } + + bool doInitialization(Module &M) override; + void getAnalysisUsage(AnalysisUsage &AU) const override; +}; + +// Wrapper around ExternalAAWrapperPass so that the default constructor gets the +// callback. +class EVMExternalAAWrapper : public ExternalAAWrapperPass { +public: + static char ID; + + EVMExternalAAWrapper() + : ExternalAAWrapperPass([](Pass &P, Function &, AAResults &AAR) { + if (auto *WrapperPass = P.getAnalysisIfAvailable()) + AAR.addAAResult(WrapperPass->getResult()); + }) {} +}; + +} // end namespace llvm + +#endif // LLVM_LIB_TARGET_EVM_EVMALIASANALYSIS_H diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.cpp b/llvm/lib/Target/EVM/EVMTargetMachine.cpp index e1991ddfb969..4e5b107ecf0d 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.cpp +++ b/llvm/lib/Target/EVM/EVMTargetMachine.cpp @@ -12,6 +12,7 @@ #include "EVM.h" +#include "EVMAliasAnalysis.h" #include "EVMMachineFunctionInfo.h" #include "EVMTargetMachine.h" #include "EVMTargetObjectFile.h" @@ -58,6 +59,8 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeEVMTarget() { initializeEVMSplitCriticalEdgesPass(PR); initializeEVMStackifyPass(PR); initializeEVMBPStackificationPass(PR); + initializeEVMAAWrapperPassPass(PR); + initializeEVMExternalAAWrapperPass(PR); } static std::string computeDataLayout() { @@ -114,6 +117,10 @@ bool EVMTargetMachine::parseMachineFunctionInfo( return false; } +void EVMTargetMachine::registerDefaultAliasAnalyses(AAManager &AAM) { + AAM.registerFunctionAnalysis(); +} + void EVMTargetMachine::registerPassBuilderCallbacks(PassBuilder &PB) { PB.registerPipelineStartEPCallback( [](ModulePassManager &PM, OptimizationLevel Level) { @@ -126,6 +133,18 @@ void EVMTargetMachine::registerPassBuilderCallbacks(PassBuilder &PB) { if (Level.getSizeLevel() || Level.getSpeedupLevel() > 1) PM.addPass(MergeIdenticalBBPass()); }); + + PB.registerAnalysisRegistrationCallback([](FunctionAnalysisManager &FAM) { + FAM.registerPass([] { return EVMAA(); }); + }); + + PB.registerParseAACallback([](StringRef AAName, AAManager &AAM) { + if (AAName == "evm-aa") { + AAM.registerFunctionAnalysis(); + return true; + } + return false; + }); } namespace { @@ -160,6 +179,14 @@ class EVMPassConfig final : public TargetPassConfig { void EVMPassConfig::addIRPasses() { addPass(createEVMLowerIntrinsicsPass()); + if (TM->getOptLevel() != CodeGenOpt::None) { + addPass(createEVMAAWrapperPass()); + addPass( + createExternalAAWrapperPass([](Pass &P, Function &, AAResults &AAR) { + if (auto *WrapperPass = P.getAnalysisIfAvailable()) + AAR.addAAResult(WrapperPass->getResult()); + })); + } TargetPassConfig::addIRPasses(); } diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.h b/llvm/lib/Target/EVM/EVMTargetMachine.h index 3bb1b542b48e..c45ffce42ef3 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.h +++ b/llvm/lib/Target/EVM/EVMTargetMachine.h @@ -61,6 +61,7 @@ class EVMTargetMachine final : public LLVMTargetMachine { bool usesPhysRegsForValues() const override { return false; } void registerPassBuilderCallbacks(PassBuilder &PB) override; + void registerDefaultAliasAnalyses(AAManager &AAM) override; }; // EVMTargetMachine. } // end namespace llvm diff --git a/llvm/test/CodeGen/EVM/aa-dse.ll b/llvm/test/CodeGen/EVM/aa-dse.ll index 5f0af2ddfdd7..c2b639fd88b4 100644 --- a/llvm/test/CodeGen/EVM/aa-dse.ll +++ b/llvm/test/CodeGen/EVM/aa-dse.ll @@ -6,9 +6,7 @@ target triple = "evm" define void @test() { ; CHECK-LABEL: define void @test() { -; CHECK-NEXT: [[PTR1:%.*]] = inttoptr i256 32 to ptr addrspace(1) ; CHECK-NEXT: [[PTR2:%.*]] = inttoptr i256 32 to ptr addrspace(1) -; CHECK-NEXT: store i256 2, ptr addrspace(1) [[PTR1]], align 64 ; CHECK-NEXT: store i256 1, ptr addrspace(1) [[PTR2]], align 64 ; CHECK-NEXT: ret void ; diff --git a/llvm/test/CodeGen/EVM/aa-eval.ll b/llvm/test/CodeGen/EVM/aa-eval.ll index 61a51f755ea2..a59e89e91306 100644 --- a/llvm/test/CodeGen/EVM/aa-eval.ll +++ b/llvm/test/CodeGen/EVM/aa-eval.ll @@ -1,10 +1,10 @@ -; RUN: opt -passes=aa-eval -aa-pipeline=basic-aa -print-all-alias-modref-info -disable-output < %s 2>&1 | FileCheck %s +; RUN: opt -passes=aa-eval -aa-pipeline=basic-aa,evm-aa -print-all-alias-modref-info -disable-output < %s 2>&1 | FileCheck %s target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" target triple = "evm" ; CHECK-LABEL: Function: test_offset_i8_noalias -; CHECK: MayAlias: i8 addrspace(1)* %inttoptr1, i8 addrspace(1)* %inttoptr2 +; CHECK: NoAlias: i8 addrspace(1)* %inttoptr1, i8 addrspace(1)* %inttoptr2 define void @test_offset_i8_noalias(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i8 %add1 = add i8 %ptrtoint, 32 @@ -17,7 +17,7 @@ define void @test_offset_i8_noalias(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_i512_noalias -; CHECK: MayAlias: i512 addrspace(1)* %inttoptr1, i512 addrspace(1)* %inttoptr2 +; CHECK: NoAlias: i512 addrspace(1)* %inttoptr1, i512 addrspace(1)* %inttoptr2 define void @test_offset_i512_noalias(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i512 %add1 = add i512 %ptrtoint, 32 @@ -30,7 +30,7 @@ define void @test_offset_i512_noalias(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_mustalias -; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +; CHECK: MustAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 define void @test_offset_mustalias(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %inttoptr1 = inttoptr i256 %ptrtoint to ptr addrspace(1) @@ -41,7 +41,7 @@ define void @test_offset_mustalias(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_noalias1 -; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +; CHECK: NoAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 define void @test_offset_noalias1(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %add1 = add i256 %ptrtoint, 32 @@ -54,7 +54,7 @@ define void @test_offset_noalias1(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_noalias2 -; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +; CHECK: NoAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 define void @test_offset_noalias2(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %add1 = add i256 %ptrtoint, -32 @@ -67,7 +67,7 @@ define void @test_offset_noalias2(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_noalias3 -; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +; CHECK: NoAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 define void @test_offset_noalias3(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %add1 = sub i256 %ptrtoint, 32 @@ -80,7 +80,7 @@ define void @test_offset_noalias3(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_partialalias1 -; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +; CHECK: PartialAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 define void @test_offset_partialalias1(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %add1 = add i256 %ptrtoint, 32 @@ -93,7 +93,7 @@ define void @test_offset_partialalias1(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_partialalias2 -; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +; CHECK: PartialAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 define void @test_offset_partialalias2(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %add1 = add i256 %ptrtoint, -32 @@ -106,7 +106,7 @@ define void @test_offset_partialalias2(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_offset_partialalias3 -; CHECK: MayAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 +; CHECK: PartialAlias: i256 addrspace(1)* %inttoptr1, i256 addrspace(1)* %inttoptr2 define void @test_offset_partialalias3(ptr addrspace(1) %addr) { %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %add1 = sub i256 %ptrtoint, 32 @@ -119,7 +119,7 @@ define void @test_offset_partialalias3(ptr addrspace(1) %addr) { } ; CHECK-LABEL: Function: test_as1_i8_noalias -; CHECK: MayAlias: i8 addrspace(1)* %ptr1, i8 addrspace(1)* %ptr2 +; CHECK: NoAlias: i8 addrspace(1)* %ptr1, i8 addrspace(1)* %ptr2 define void @test_as1_i8_noalias() { %ptr1 = inttoptr i8 32 to ptr addrspace(1) %ptr2 = inttoptr i8 64 to ptr addrspace(1) @@ -155,21 +155,21 @@ define void @test_noalias_fallback(ptr noalias %0, ptr noalias %1) { } ; CHECK-LABEL: Function: test_noalias -; CHECK: MayAlias: i256* %ptr0, i256 addrspace(1)* %ptr1 -; CHECK: MayAlias: i256* %ptr0, i256 addrspace(2)* %ptr2 -; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(2)* %ptr2 -; CHECK: MayAlias: i256* %ptr0, i256 addrspace(4)* %ptr4 -; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(4)* %ptr4 -; CHECK: MayAlias: i256 addrspace(2)* %ptr2, i256 addrspace(4)* %ptr4 -; CHECK: MayAlias: i256* %ptr0, i256 addrspace(5)* %ptr5 -; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(5)* %ptr5 -; CHECK: MayAlias: i256 addrspace(2)* %ptr2, i256 addrspace(5)* %ptr5 -; CHECK: MayAlias: i256 addrspace(4)* %ptr4, i256 addrspace(5)* %ptr5 -; CHECK: MayAlias: i256* %ptr0, i256 addrspace(6)* %ptr6 -; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(6)* %ptr6 -; CHECK: MayAlias: i256 addrspace(2)* %ptr2, i256 addrspace(6)* %ptr6 -; CHECK: MayAlias: i256 addrspace(4)* %ptr4, i256 addrspace(6)* %ptr6 -; CHECK: MayAlias: i256 addrspace(5)* %ptr5, i256 addrspace(6)* %ptr6 +; CHECK: NoAlias: i256* %ptr0, i256 addrspace(1)* %ptr1 +; CHECK: NoAlias: i256* %ptr0, i256 addrspace(2)* %ptr2 +; CHECK: NoAlias: i256 addrspace(1)* %ptr1, i256 addrspace(2)* %ptr2 +; CHECK: NoAlias: i256* %ptr0, i256 addrspace(4)* %ptr4 +; CHECK: NoAlias: i256 addrspace(1)* %ptr1, i256 addrspace(4)* %ptr4 +; CHECK: NoAlias: i256 addrspace(2)* %ptr2, i256 addrspace(4)* %ptr4 +; CHECK: NoAlias: i256* %ptr0, i256 addrspace(5)* %ptr5 +; CHECK: NoAlias: i256 addrspace(1)* %ptr1, i256 addrspace(5)* %ptr5 +; CHECK: NoAlias: i256 addrspace(2)* %ptr2, i256 addrspace(5)* %ptr5 +; CHECK: NoAlias: i256 addrspace(4)* %ptr4, i256 addrspace(5)* %ptr5 +; CHECK: NoAlias: i256* %ptr0, i256 addrspace(6)* %ptr6 +; CHECK: NoAlias: i256 addrspace(1)* %ptr1, i256 addrspace(6)* %ptr6 +; CHECK: NoAlias: i256 addrspace(2)* %ptr2, i256 addrspace(6)* %ptr6 +; CHECK: NoAlias: i256 addrspace(4)* %ptr4, i256 addrspace(6)* %ptr6 +; CHECK: NoAlias: i256 addrspace(5)* %ptr5, i256 addrspace(6)* %ptr6 define void @test_noalias() { %ptr0 = inttoptr i256 32 to ptr %ptr1 = inttoptr i256 32 to ptr addrspace(1) @@ -187,7 +187,7 @@ define void @test_noalias() { } ; CHECK-LABEL: Function: test_as1_noalias -; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 +; CHECK: NoAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 define void @test_as1_noalias() { %ptr1 = inttoptr i256 32 to ptr addrspace(1) %ptr2 = inttoptr i256 64 to ptr addrspace(1) @@ -197,7 +197,7 @@ define void @test_as1_noalias() { } ; CHECK-LABEL: Function: test_as1_mustalias -; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 +; CHECK: MustAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 define void @test_as1_mustalias() { %ptr1 = inttoptr i256 32 to ptr addrspace(1) %ptr2 = inttoptr i256 32 to ptr addrspace(1) @@ -207,7 +207,7 @@ define void @test_as1_mustalias() { } ; CHECK-LABEL: Function: test_as1_partialalias -; CHECK: MayAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 +; CHECK: PartialAlias: i256 addrspace(1)* %ptr1, i256 addrspace(1)* %ptr2 define void @test_as1_partialalias() { %ptr1 = inttoptr i256 32 to ptr addrspace(1) %ptr2 = inttoptr i256 48 to ptr addrspace(1) @@ -217,7 +217,7 @@ define void @test_as1_partialalias() { } ; CHECK-LABEL: Function: test_as5_noalias -; CHECK: MayAlias: i256 addrspace(5)* %ptr1, i256 addrspace(5)* %ptr2 +; CHECK: NoAlias: i256 addrspace(5)* %ptr1, i256 addrspace(5)* %ptr2 define void @test_as5_noalias() { %ptr1 = inttoptr i256 0 to ptr addrspace(5) %ptr2 = inttoptr i256 1 to ptr addrspace(5) @@ -227,7 +227,7 @@ define void @test_as5_noalias() { } ; CHECK-LABEL: Function: test_as5_mustalias -; CHECK: MayAlias: i256 addrspace(5)* %ptr1, i256 addrspace(5)* %ptr2 +; CHECK: MustAlias: i256 addrspace(5)* %ptr1, i256 addrspace(5)* %ptr2 define void @test_as5_mustalias() { %ptr1 = inttoptr i256 0 to ptr addrspace(5) %ptr2 = inttoptr i256 0 to ptr addrspace(5) @@ -237,7 +237,7 @@ define void @test_as5_mustalias() { } ; CHECK-LABEL: Function: test_as6_noalias -; CHECK: MayAlias: i256 addrspace(6)* %ptr1, i256 addrspace(6)* %ptr2 +; CHECK: NoAlias: i256 addrspace(6)* %ptr1, i256 addrspace(6)* %ptr2 define void @test_as6_noalias() { %ptr1 = inttoptr i256 0 to ptr addrspace(6) %ptr2 = inttoptr i256 1 to ptr addrspace(6) @@ -247,7 +247,7 @@ define void @test_as6_noalias() { } ; CHECK-LABEL: Function: test_as6_mustalias -; CHECK: MayAlias: i256 addrspace(6)* %ptr1, i256 addrspace(6)* %ptr2 +; CHECK: MustAlias: i256 addrspace(6)* %ptr1, i256 addrspace(6)* %ptr2 define void @test_as6_mustalias() { %ptr1 = inttoptr i256 0 to ptr addrspace(6) %ptr2 = inttoptr i256 0 to ptr addrspace(6) diff --git a/llvm/test/CodeGen/EVM/aa-memory-opt.ll b/llvm/test/CodeGen/EVM/aa-memory-opt.ll index a6fb53e75f5e..610948ac2d1d 100644 --- a/llvm/test/CodeGen/EVM/aa-memory-opt.ll +++ b/llvm/test/CodeGen/EVM/aa-memory-opt.ll @@ -14,8 +14,7 @@ define i256 @test_offset(ptr addrspace(1) %addr) { ; CHECK-NEXT: [[ADD2:%.*]] = add i256 [[PTRTOINT]], 64 ; CHECK-NEXT: [[INTTOPTR2:%.*]] = inttoptr i256 [[ADD2]] to ptr addrspace(1) ; CHECK-NEXT: store i256 58, ptr addrspace(1) [[INTTOPTR2]], align 1 -; CHECK-NEXT: [[LOAD:%.*]] = load i256, ptr addrspace(1) [[INTTOPTR1]], align 1 -; CHECK-NEXT: ret i256 [[LOAD]] +; CHECK-NEXT: ret i256 3 ; %ptrtoint = ptrtoint ptr addrspace(1) %addr to i256 %add1 = add i256 %ptrtoint, 32 @@ -33,8 +32,7 @@ define i256 @test_memcpy() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR1:[0-9]+]] { ; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 ; CHECK-NEXT: tail call void @llvm.memcpy.p1.p1.i256(ptr addrspace(1) noundef nonnull align 32 dereferenceable(53) inttoptr (i256 96 to ptr addrspace(1)), ptr addrspace(1) noundef nonnull align 256 dereferenceable(53) inttoptr (i256 256 to ptr addrspace(1)), i256 53, i1 false) -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 tail call void @llvm.memcpy.p1.p1.i256(ptr addrspace(1) inttoptr (i256 96 to ptr addrspace(1)), ptr addrspace(1) inttoptr (i256 256 to ptr addrspace(1)), i256 53, i1 false) @@ -47,8 +45,7 @@ define i256 @test_different_locsizes() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 ; CHECK-NEXT: store i8 1, ptr addrspace(1) inttoptr (i8 1 to ptr addrspace(1)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(1) inttoptr (i256 2 to ptr addrspace(1)), align 64 store i8 1, ptr addrspace(1) inttoptr (i8 1 to ptr addrspace(1)), align 64 @@ -62,8 +59,7 @@ define i256 @test_gas() { ; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 ; CHECK-NEXT: [[GAS:%.*]] = tail call i256 @llvm.evm.gas() ; CHECK-NEXT: store i256 [[GAS]], ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 %gas = call i256 @llvm.evm.gas() @@ -90,8 +86,7 @@ define i256 @test_as() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 ; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 @@ -101,7 +96,7 @@ define i256 @test_as() { define i256 @test_as1_overlap() { ; CHECK-LABEL: define i256 @test_as1_overlap -; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR4:[0-9]+]] { ; CHECK-NEXT: store i256 2, ptr addrspace(1) null, align 4294967296 ; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 31 to ptr addrspace(1)), align 64 ; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) null, align 4294967296 @@ -118,8 +113,7 @@ define i256 @test_as1_null() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(1) null, align 4294967296 ; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) null, align 4294967296 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(1) null, align 64 store i256 1, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 64 @@ -132,8 +126,7 @@ define i256 @test_as1_small() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 33 to ptr addrspace(1)), align 64 ; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 33 to ptr addrspace(1)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(1) inttoptr (i256 33 to ptr addrspace(1)), align 64 store i256 1, ptr addrspace(1) inttoptr (i256 1 to ptr addrspace(1)), align 64 @@ -146,8 +139,7 @@ define i256 @test_as1_large() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533664 to ptr addrspace(1)), align 64 ; CHECK-NEXT: store i256 1, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(1)), align 4294967296 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533664 to ptr addrspace(1)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533664 to ptr addrspace(1)), align 64 store i256 1, ptr addrspace(1) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(1)), align 64 @@ -160,8 +152,7 @@ define i256 @test_as5_null() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(5) null, align 4294967296 ; CHECK-NEXT: store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) null, align 4294967296 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(5) null, align 64 store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 @@ -174,8 +165,7 @@ define i256 @test_as5_small() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 ; CHECK-NEXT: store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(5) inttoptr (i256 2 to ptr addrspace(5)), align 64 store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 @@ -188,8 +178,7 @@ define i256 @test_as5_large() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(5) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(5)), align 4294967296 ; CHECK-NEXT: store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(5) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(5)), align 4294967296 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(5) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(5)), align 64 store i256 1, ptr addrspace(5) inttoptr (i256 1 to ptr addrspace(5)), align 64 @@ -202,8 +191,7 @@ define i256 @test_as6_small() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(6) inttoptr (i256 2 to ptr addrspace(6)), align 64 ; CHECK-NEXT: store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(6) inttoptr (i256 2 to ptr addrspace(6)), align 64 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(6) inttoptr (i256 2 to ptr addrspace(6)), align 64 store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 @@ -216,8 +204,7 @@ define i256 @test_as6_large() { ; CHECK-SAME: () local_unnamed_addr #[[ATTR0]] { ; CHECK-NEXT: store i256 2, ptr addrspace(6) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(6)), align 4294967296 ; CHECK-NEXT: store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 -; CHECK-NEXT: [[RET:%.*]] = load i256, ptr addrspace(6) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(6)), align 4294967296 -; CHECK-NEXT: ret i256 [[RET]] +; CHECK-NEXT: ret i256 2 ; store i256 2, ptr addrspace(6) inttoptr (i256 53919893334301279589334030174039261352344891250716429051063678533632 to ptr addrspace(6)), align 64 store i256 1, ptr addrspace(6) inttoptr (i256 1 to ptr addrspace(6)), align 64 From ecd0c2f5fe3e1bacc1d81440539faf24db178c73 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Mon, 4 Nov 2024 15:39:27 +0100 Subject: [PATCH 27/42] Generalize EraVMSHA3ConstFolding and move it to the common part This way, we can share SHA3ConstFolding implementation between the backends. Signed-off-by: Vladimir Radosavljevic --- .../llvm/Transforms/Scalar/SHA3ConstFolding.h | 36 + llvm/lib/Target/EraVM/EraVM.h | 2 - .../Target/EraVM/EraVMSHA3ConstFolding.cpp | 812 +----------------- llvm/lib/Target/EraVM/EraVMTargetMachine.cpp | 1 - llvm/lib/Transforms/Scalar/CMakeLists.txt | 3 + .../Transforms/Scalar/SHA3ConstFolding.cpp | 742 ++++++++++++++++ 6 files changed, 799 insertions(+), 797 deletions(-) create mode 100644 llvm/include/llvm/Transforms/Scalar/SHA3ConstFolding.h create mode 100644 llvm/lib/Transforms/Scalar/SHA3ConstFolding.cpp diff --git a/llvm/include/llvm/Transforms/Scalar/SHA3ConstFolding.h b/llvm/include/llvm/Transforms/Scalar/SHA3ConstFolding.h new file mode 100644 index 000000000000..be77e6b2251b --- /dev/null +++ b/llvm/include/llvm/Transforms/Scalar/SHA3ConstFolding.h @@ -0,0 +1,36 @@ +//===-- SHA3ConstFolding.h - Const fold calls to sha3 -----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file provides the interface for the SHA3 const folding pass. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_TRANSFORMS_SCALAR_SHA3CONSTFOLDING_H +#define LLVM_TRANSFORMS_SCALAR_SHA3CONSTFOLDING_H + +#include "llvm/Analysis/AliasAnalysis.h" + +namespace llvm { + +class Function; +class AssumptionCache; +class MemorySSA; +class DominatorTree; +class TargetLibraryInfo; +class LoopInfo; +class Instruction; + +bool runSHA3ConstFolding( + Function &F, AliasAnalysis &AA, AssumptionCache &AC, MemorySSA &MSSA, + DominatorTree &DT, const TargetLibraryInfo &TLI, const LoopInfo &LI, + const std::function &IsSha3Call, + unsigned HeapAS); + +} // end namespace llvm + +#endif // LLVM_TRANSFORMS_SCALAR_SHA3CONSTFOLDING_H diff --git a/llvm/lib/Target/EraVM/EraVM.h b/llvm/lib/Target/EraVM/EraVM.h index ffedfebe053f..110dced21e30 100644 --- a/llvm/lib/Target/EraVM/EraVM.h +++ b/llvm/lib/Target/EraVM/EraVM.h @@ -84,7 +84,6 @@ FunctionPass *createEraVMOptimizeStdLibCallsPass(); ImmutablePass *createEraVMAAWrapperPass(); ImmutablePass *createEraVMExternalAAWrapperPass(); ModulePass *createEraVMAlwaysInlinePass(); -FunctionPass *createEraVMSHA3ConstFoldingPass(); FunctionPass *createEraVMOptimizeSelectPostRAPass(); Pass *createEraVMIndexedMemOpsPreparePass(); FunctionPass *createEraVMTieSelectOperandsPass(); @@ -113,7 +112,6 @@ void initializeEraVMOptimizeStdLibCallsPass(PassRegistry &); void initializeEraVMAAWrapperPassPass(PassRegistry &); void initializeEraVMExternalAAWrapperPass(PassRegistry &); void initializeEraVMAlwaysInlinePass(PassRegistry &); -void initializeEraVMSHA3ConstFoldingPass(PassRegistry &); void initializeEraVMOptimizeSelectPostRAPass(PassRegistry &); void initializeEraVMIndexedMemOpsPreparePass(PassRegistry &); void initializeEraVMTieSelectOperandsPass(PassRegistry &); diff --git a/llvm/lib/Target/EraVM/EraVMSHA3ConstFolding.cpp b/llvm/lib/Target/EraVM/EraVMSHA3ConstFolding.cpp index a8a17a0fe116..f81831672284 100644 --- a/llvm/lib/Target/EraVM/EraVMSHA3ConstFolding.cpp +++ b/llvm/lib/Target/EraVM/EraVMSHA3ConstFolding.cpp @@ -6,811 +6,20 @@ // //===----------------------------------------------------------------------===// // -// The code below tries to replace __sha3 (that calucates keccak256 hash) calls -// with the calculated hash values. -// -// It uses the following general approach: given a __sha3 call (MemoryUse), -// walk upwards to find store instructions (clobbers) with -// constant values that fully define data (in memory) for which we compute hash. -// Only __sha3 calls with the constant 'size' argument are checked. -// -// For example: -// -// store i256 1, ptr addrspace(1) null -// store i256 2, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)) -// %hash = tail call i256 @__sha3(ptr addrspace(1) null, i256 64, i1 true) -// ret i256 %hash -// -// is transformed into: -// -// store i256 1, ptr addrspace(1) null -// store i256 2, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)) -// ret i256 -536754096339594166047489462334966539640... -// -// A bit more concretely: -// -// For all __sha3 calls: -// 1. Collect potentially dominating clobbering MemoryDefs by walking upwards. -// Check that clobbering values are constants, otherwise bail out. -// -// 2. Check that -// 1. Each clobber is withing the __sha3 memory location: -// |--clobber--| -// |------MemUse-------| -// 2. Clobbers are not intersected with each other: -// |--cl1--| -// |cl2| -// |--cl3--| -// |------MemUse-------| -// 3.Collect clobber values -// -// 3. Create a memory array from the collected values and calculate -// the Keccak256 hash. -// -// 4. Run simplification for each instruction in the function, as __sha3 folding -// can provide new opportunities for this. -// -// 5. If the simplification has changed the function, run one more iteration -// of the whole process starting from p.1. +// This is the EraVM SHA3 const folding pass. // //===----------------------------------------------------------------------===// #include "EraVM.h" - -#include "llvm/ADT/PostOrderIterator.h" -#include "llvm/ADT/Statistic.h" -#include "llvm/ADT/StringExtras.h" #include "llvm/Analysis/AssumptionCache.h" -#include "llvm/Analysis/GlobalsModRef.h" -#include "llvm/Analysis/InstructionSimplify.h" #include "llvm/Analysis/LoopInfo.h" -#include "llvm/Analysis/MemoryBuiltins.h" -#include "llvm/Analysis/MemoryLocation.h" #include "llvm/Analysis/MemorySSA.h" -#include "llvm/Analysis/MemorySSAUpdater.h" -#include "llvm/Analysis/MustExecute.h" #include "llvm/Analysis/TargetLibraryInfo.h" -#include "llvm/Analysis/ValueTracking.h" #include "llvm/IR/Dominators.h" -#include "llvm/IR/InstIterator.h" -#include "llvm/IR/Instruction.h" -#include "llvm/IR/Instructions.h" -#include "llvm/IR/Intrinsics.h" -#include "llvm/IR/IntrinsicsEraVM.h" -#include "llvm/Support/DebugCounter.h" -#include "llvm/Support/EndianStream.h" -#include "llvm/Support/KECCAK.h" -#include "llvm/Transforms/Utils/AssumeBundleBuilder.h" -#include "llvm/Transforms/Utils/Local.h" -#include +#include "llvm/Transforms/Scalar/SHA3ConstFolding.h" using namespace llvm; -#define DEBUG_TYPE "eravm-sha3-constant-folding" - -STATISTIC(NumSHA3Folded, "Number of __sha3 calls folded"); - -DEBUG_COUNTER(SHA3Counter, "eravm-sha3-constant-folding", - "Controls which instructions are removed"); - -namespace { - -class EraVMSHA3ConstFolding final : public FunctionPass { -private: - StringRef getPassName() const override { - return "EraVM sha3 constant folding"; - } - - void getAnalysisUsage(AnalysisUsage &AU) const override { - AU.setPreservesCFG(); - AU.addRequired(); - AU.addRequired(); - AU.addRequired(); - AU.addRequired(); - AU.addRequired(); - AU.addRequired(); - AU.addPreserved(); - AU.addPreserved(); - AU.addPreserved(); - FunctionPass::getAnalysisUsage(AU); - } - - bool runOnFunction(Function &function) override; - -public: - static char ID; // Pass ID - EraVMSHA3ConstFolding() : FunctionPass(ID) {} -}; - -/// This structure holds an information about a single memory clobber. -/// While walking upwards starting at a __sha3 call (which is a MemUse), -/// we create a MemClobber instance for each dominating memory clobber -/// (in current implementation - a store instruction) and put it in a -/// list which then gets sorted (by the 'Start' value). As result, we get -/// representation of a continuous memory region the __sha3 computes a hash for. -struct MemClobber { - // Byte offset relatively to the beginning of the __sha3 memory location. - uint64_t Start; - - // Size in bytes of the clobber. - uint64_t Size; - - // Value to be stored in the memory. - APInt Clobber; -}; - -/// This class holds an information required for folding __sha3 calls -/// in the function F. -/// -/// The workflow with the class is as follows: -/// 1. Create an FoldingState instance. -/// The constructor collects the MemorySSA uses corresponding to -/// __sha3 calls in the function F. -/// 2. Iterate through the collected memory uses calling the runFolding(), -/// which, on success, returns a computed keccak256 -/// hash value. -/// Replace __sha3 values with the calculated hash values, -/// removing the calls and invoking removeFromMSSA() to keep up to date -/// the MemorySSA representation. -/// 3. Run simplifyInstructions() to simplify/clean up the F. -/// -class FoldingState { - /// Types of the relative placement of the two memory locations. - enum class OverlapType { - // Loc1 is completely within the Loc2 - // |-Loc1-| - // |-----Loc2----| - OL_Complete, - - // Loc1 is partially with the Loc2 - // and they both refer to the same underlying object - // |-Loc1-| - // |-----Loc2----| - OL_MaybePartial, - - // Memory locations don't intersect - // |--Loc1--| - // |---Loc2--| - OL_None, - - // Nothing can be determined - OL_Unknown - }; - - /// Describes how the memory clobber is placed with respect - /// to the memory use (__sha3 call). - struct OverlapResult { - OverlapType Type; - - // Clobber offset relative to the beginning of the memory use. - // Unless the type is 'OL_Complete' actual offset value doesn't matter. - uint64_t ClobberOffset; - }; - - // Whether the function contains any irreducible control flow, useful for - // being accurately able to detect loops. - const bool ContainsIrreducibleLoops; - - // The __sha3 calls to be analyzed. - SmallVector SHA3MemUses; - - Function &F; - AliasAnalysis &AA; - AssumptionCache &AC; - MemorySSA &MSSA; - std::unique_ptr MSSAUpdater; - const TargetLibraryInfo &TLI; - const DataLayout &DL; - const SimplifyQuery SQ; - const LoopInfo &LI; - -public: - FoldingState(const FoldingState &) = delete; - FoldingState &operator=(const FoldingState &) = delete; - FoldingState(FoldingState &&) = delete; - FoldingState &&operator=(FoldingState &&) = delete; - ~FoldingState() = default; - - FoldingState(Function &F, AliasAnalysis &AA, AssumptionCache &AC, - MemorySSA &MSSA, DominatorTree &DT, const TargetLibraryInfo &TLI, - const LoopInfo &LI); - - /// Collect all the potential clobbering memory accesses for the - /// given __sha3 call (\p Call). - SmallVector collectSHA3Clobbers(const CallInst *Call); - - /// For the given __sha3 call (\p Call), walk through the collected MemorySSA - /// definitions (\p MemDefs) and try to build a continuous memory segment with - /// the data for which we compute the keccak256 hash. On success return - /// the computed hash value. - Value *runFolding(const CallInst *Call, - SmallVectorImpl &MemDefs) const; - - /// Try to simplify instructions in the function F. The candidates for - /// simplification may appear after replacing __sha3 calls with the - /// calculated hash values. - bool simplifyInstructions(); - - /// Exclude the instruction from MSSA if it's going to be removed from a BB. - void removeFromMSSA(Instruction *Inst) { - if (VerifyMemorySSA) - MSSA.verifyMemorySSA(); - - MSSAUpdater->removeMemoryAccess(Inst, true); - } - - /// Return collected MemorySSa uses corresponding to __sha3 calls. - const SmallVectorImpl &getSHA3MemUses() const { - return SHA3MemUses; - } - -private: - /// Return true if the \p I is a call to the library function \p F. - /// Consider moving it to a common code. TODO: CPR-1386. - bool isLibFuncCall(const Instruction *I, LibFunc F) const; - - /// Return true if a dependency between \p Clobber and \p MemUse is - /// guaranteed to be loop invariant for the loops that they are in. - bool isGuaranteedLoopIndependent(const Instruction *Clobber, - const Instruction *MemUse, - const MemoryLocation &ClobberLoc) const; - - /// Return true if \p Ptr is guaranteed to be loop invariant for any possible - /// loop. In particular, this guarantees that it only references a single - /// MemoryLocation during execution of the containing function. - bool isGuaranteedLoopInvariant(const Value *Ptr) const; - - /// Check how the two memory locations (\p MemUseLoc and \p ClobberLoc) - /// are located with respect to each other. - OverlapResult isOverlap(const Instruction *MemUse, const Instruction *Clobber, - const MemoryLocation &MemUseLoc, - const MemoryLocation &ClobberLoc) const; - - /// Try to cast the constant pointer value \p Ptr to the integer offset. - /// Consider moving it to a common code. TODO: CPR-1380. - std::optional tryToCastPtrToInt(const Value *Ptr) const; - - /// Ensure the sorted array of clobbers forms a continuous memory region. - /// On success return the size the memory region. - std::optional - checkMemoryClobbers(const SmallVector &MemClobbers) const; - - /// Compute keccak256 hash for the memory segment, formed by the sorted list - /// of memory clobbers passed in \p MemClobbers. - std::array - computeKeccak256Hash(const SmallVectorImpl &MemClobbers) const; - - /// Check if we can ignore non store clobber instruction that doesn't actually - /// clobber heap memory. For example, a memcpy to AS other than heap. - /// Probably we should check more cases here. TODO: CPR-1370. - bool shouldSkipClobber(const Instruction *MemInst) const { - if (!MemInst->mayWriteToMemory()) - return true; - - if (const auto *Intr = dyn_cast(MemInst)) - return Intr->getDestAddressSpace() != EraVMAS::AS_HEAP; - - return false; - } - - /// Return MemoryLocation corresponding to the pointer argument of - /// __sha3 call. - MemoryLocation getLocForSHA3Call(const CallInst *I) const { - return MemoryLocation::getForArgument(I, 0, TLI); - } -}; // end FoldingState struct - -} // end anonymous namespace - -static uint64_t getPointerSize(const Value *V, const DataLayout &DL, - const TargetLibraryInfo &TLI, - const Function *F) { - uint64_t Size = 0; - ObjectSizeOpts Opts; - Opts.NullIsUnknownSize = NullPointerIsDefined(F); - - if (getObjectSize(V, Size, DL, &TLI, Opts)) - return Size; - return MemoryLocation::UnknownSize; -} - -bool FoldingState::isLibFuncCall(const Instruction *I, LibFunc F) const { - const auto *Call = dyn_cast(I); - if (!Call) - return false; - - Function *Callee = Call->getCalledFunction(); - if (!Callee) - return false; - - LibFunc Func = NotLibFunc; - const StringRef Name = Callee->getName(); - return TLI.getLibFunc(Name, Func) && TLI.has(Func) && Func == F; -} - -FoldingState::FoldingState(Function &F, AliasAnalysis &AA, AssumptionCache &AC, - MemorySSA &MSSA, DominatorTree &DT, - const TargetLibraryInfo &TLI, const LoopInfo &LI) - : ContainsIrreducibleLoops(mayContainIrreducibleControl(F, &LI)), F(F), - AA(AA), AC(AC), MSSA(MSSA), - MSSAUpdater(std::make_unique(&MSSA)), TLI(TLI), - DL(F.getParent()->getDataLayout()), SQ(DL, &TLI, &DT, &AC), LI(LI) { - MSSA.ensureOptimizedUses(); - - const ReversePostOrderTraversal RPOT(&*F.begin()); - for (BasicBlock *BB : RPOT) { - for (Instruction &I : *BB) { - if (!isLibFuncCall(&I, LibFunc_xvm_sha3)) - continue; - - const auto *Call = cast(&I); - auto *MA = MSSA.getMemoryAccess(Call); - auto *MU = dyn_cast_or_null(MA); - // __sha3() is passed a pointer in the first argument - // and the memory size in the second one. It's expected that the - // memory size to be a constant expression. In this case - // the memory location should be precise. - if (MU && getLocForSHA3Call(Call).Size.isPrecise()) - SHA3MemUses.push_back(MU); - } - } -} - -SmallVector -FoldingState::collectSHA3Clobbers(const CallInst *Call) { - SmallVector Clobbers; - const MemoryLocation Loc = getLocForSHA3Call(Call); - MemorySSAWalker *Walker = MSSA.getWalker(); - - // For the given __sha3 call (which is MemoryUse in terms of MemorySSA) we - // need to collect all memory clobbering accesses. Usually these are just - // 'store' instructions. Other cases are not handled by the current - // implementation. For this we employ MemorySSA representation that maps - // memory clobbering Instructions to three access types: - // - live on entry (nothing to do, __sha3 is not clobbered), - // - MemoryDef, - // - MemoryPhi - // - // We start with a nearest to the __sha3 call dominating clobbering access. - // Then we do the same for the just found clobber and so on until we find the - // 'live on entry'. - // For simplicity, in case of a MemoryPhi we also stop the search. This - // constraint can be relaxed. TODO: CPR-1370. - - MemoryAccess *MA = - Walker->getClobberingMemoryAccess(MSSA.getMemoryAccess(Call)); - for (; !MSSA.isLiveOnEntryDef(MA) && !isa(MA);) { - if (auto *Def = dyn_cast(MA)) { - Clobbers.push_back(Def); - MA = Walker->getClobberingMemoryAccess(Def->getDefiningAccess(), Loc); - } - } - - return Clobbers; -} - -bool FoldingState::simplifyInstructions() { - bool Changed = false; - for (auto &Inst : make_early_inc_range(instructions(F))) { - if (Value *V = simplifyInstruction(&Inst, SQ)) { - LLVM_DEBUG(dbgs() << " EraVMSHA3ConstFolding simplify: " << Inst - << " to: " << *V << '\n'); - if (!DebugCounter::shouldExecute(SHA3Counter)) { - LLVM_DEBUG(dbgs() << "Skipping due to debug counter\n"); - continue; - } - - if (!Inst.use_empty()) { - Inst.replaceAllUsesWith(V); - Changed = true; - } - if (isInstructionTriviallyDead(&Inst, &TLI)) { - removeFromMSSA(&Inst); - salvageKnowledge(&Inst, &AC); - Inst.eraseFromParent(); - Changed = true; - } - } - } - return Changed; -} - -bool FoldingState::isGuaranteedLoopIndependent( - const Instruction *Clobber, const Instruction *MemUse, - const MemoryLocation &ClobberLoc) const { - // If the dependency is within the same block or loop level (being careful - // of irreducible loops), we know that AA will return a valid result for the - // memory dependency. - if (MemUse->getParent() == Clobber->getParent()) - return true; - - // Check if both Clobber and MemUse are known to be in the same loop level. - const Loop *CurrentLI = LI.getLoopFor(MemUse->getParent()); - if (!ContainsIrreducibleLoops && CurrentLI && - CurrentLI == LI.getLoopFor(Clobber->getParent())) - return true; - - // Otherwise check the memory location is invariant to any loops. - return isGuaranteedLoopInvariant(ClobberLoc.Ptr); -} - -bool FoldingState::isGuaranteedLoopInvariant(const Value *Ptr) const { - Ptr = Ptr->stripPointerCasts(); - if (const auto *GEP = dyn_cast(Ptr)) - if (GEP->hasAllConstantIndices()) - Ptr = GEP->getPointerOperand()->stripPointerCasts(); - - if (const auto *I = dyn_cast(Ptr)) - return I->getParent()->isEntryBlock(); - - return true; -} - -static std::optional getNullOrInt(const APInt &APVal) { - if (APVal.getActiveBits() <= 64) - return APVal.getZExtValue(); - - return std::nullopt; -} - -std::optional -FoldingState::tryToCastPtrToInt(const Value *Ptr) const { - if (isa(Ptr)) - return UINT64_C(0); - - if (const auto *CE = dyn_cast(Ptr)) { - if (CE->getOpcode() == Instruction::IntToPtr) { - if (auto *CI = dyn_cast(CE->getOperand(0))) { - // Give up in case of a huge offsset, as this shouldn't happen - // in a real life. - return getNullOrInt(CI->getValue()); - } - } - } - - if (const auto *IntToPtr = dyn_cast(Ptr)) { - if (auto *CI = dyn_cast(IntToPtr->getOperand(0))) { - return getNullOrInt(CI->getValue()); - } - } - - return std::nullopt; -} - -FoldingState::OverlapResult -FoldingState::isOverlap(const Instruction *MemUse, const Instruction *Clobber, - const MemoryLocation &MemUseLoc, - const MemoryLocation &ClobberLoc) const { - // AliasAnalysis does not always account for loops. Limit overlap checks - // to dependencies for which we can guarantee they are independent of any - // loops they are in. - if (!isGuaranteedLoopIndependent(Clobber, MemUse, ClobberLoc)) - return {OverlapType::OL_Unknown, 0}; - - const Value *ClobberPtr = ClobberLoc.Ptr->stripPointerCasts(); - const Value *MemUsePtr = MemUseLoc.Ptr->stripPointerCasts(); - const Value *ClobberUndObj = getUnderlyingObject(ClobberPtr); - const Value *MemUseUndObj = getUnderlyingObject(MemUsePtr); - const uint64_t MemUseSize = MemUseLoc.Size.getValue(); - const uint64_t ClobberSize = ClobberLoc.Size.getValue(); - - // If both the Clobber and MemUse pointers are constant we expect two cases: - // - pointer is just a nullptr - // - pointer is a cast of an integer constant - - if (isa(ClobberPtr) && isa(MemUsePtr)) { - if (!tryToCastPtrToInt(ClobberPtr) || !tryToCastPtrToInt(MemUsePtr)) - return {OverlapType::OL_Unknown, 0}; - - const uint64_t ClobberPtrInt = *tryToCastPtrToInt(ClobberPtr); - const uint64_t MemUsePtrInt = *tryToCastPtrToInt(MemUsePtr); - if (MemUsePtrInt <= ClobberPtrInt && - (ClobberPtrInt + ClobberSize) <= (MemUsePtrInt + MemUseSize)) { - return {OverlapType::OL_Complete, ClobberPtrInt - MemUsePtrInt}; - } - } - - // Check whether the Clobber overwrites the MemUse object. - if (ClobberUndObj == MemUseUndObj) { - const uint64_t MemUseUndObjSize = getPointerSize(MemUseUndObj, DL, TLI, &F); - if (MemUseUndObjSize != MemoryLocation::UnknownSize && - MemUseUndObjSize == MemUseSize && MemUseSize == ClobberSize) - return {OverlapType::OL_Complete, 0}; - } - - // Query the alias information - const AliasResult AAR = AA.alias(MemUseLoc, ClobberLoc); - - // If the start pointers are the same, we just have to compare sizes to see - // if the MemUse was larger than the Clobber. - if (AAR == AliasResult::MustAlias) { - // Make sure that the MemUseSize size is >= the ClobberSize size. - if (MemUseSize >= ClobberSize) - return {OverlapType::OL_Complete, 0}; - } - - // If we hit a partial alias we may have a full overwrite - if (AAR == AliasResult::PartialAlias && AAR.hasOffset()) { - const int32_t Offset = AAR.getOffset(); - if (Offset >= 0 && - static_cast(Offset) + ClobberSize <= MemUseSize) { - return {OverlapType::OL_Complete, static_cast(Offset)}; - } - } - - // If we can't resolve the same pointers to the same object, then we can't - // analyze them at all. - if (MemUseUndObj != ClobberUndObj) { - if (AAR == AliasResult::NoAlias) - return {OverlapType::OL_None, 0}; - return {OverlapType::OL_Unknown, 0}; - } - - // Okay, we have stores to two completely different pointers. Try to - // decompose the pointer into a "base + constant_offset" form. If the base - // pointers are equal, then we can reason about the MemUse and the Clobber. - int64_t ClobberOffset = 0, MemUseOffset = 0; - const Value *ClobberBasePtr = - GetPointerBaseWithConstantOffset(ClobberPtr, ClobberOffset, DL); - const Value *MemUseBasePtr = - GetPointerBaseWithConstantOffset(MemUsePtr, MemUseOffset, DL); - - // If the base pointers still differ, we have two completely different - // stores. - if (ClobberBasePtr != MemUseBasePtr) - return {OverlapType::OL_Unknown, 0}; - - // The MemUse completely overlaps the clobber if both the start and the end - // of the Clobber are inside the MemUse: - // | |--Clobber-| | - // |------MemUse------| - // We have to be careful here as *Off is signed while *.Size is unsigned. - - // Check if the Clobber access starts not before the MemUse one. - if (ClobberOffset >= MemUseOffset) { - // If the Clobber access ends not after the MemUse access then the - // clobber one is completely overlapped by the MemUse one. - if (static_cast(ClobberOffset - MemUseOffset) + ClobberSize <= - MemUseSize) - return {OverlapType::OL_Complete, - static_cast(ClobberOffset - MemUseOffset)}; - - // If start of the Clobber access is before end of the MemUse access - // then accesses overlap. - if (static_cast(ClobberOffset - MemUseOffset) < MemUseSize) - return {OverlapType::OL_MaybePartial, 0}; - } - // If start of the MemUse access is before end of the Clobber access then - // accesses overlap. - else if (static_cast(MemUseOffset - ClobberOffset) < ClobberSize) { - return {OverlapType::OL_MaybePartial, 0}; - } - - // Can reach here only if accesses are known not to overlap. - return {OverlapType::OL_None, 0}; -} - -std::optional FoldingState::checkMemoryClobbers( - const SmallVector &MemClobbers) const { - auto Begin = MemClobbers.begin(), End = MemClobbers.end(); - assert(Begin != End); - - uint64_t TotalSize = Begin->Size; - for (const auto *It = std::next(Begin); It != End; ++It) { - TotalSize += It->Size; - const auto *ItPrev = std::prev(It); - if ((ItPrev->Start + ItPrev->Size) != It->Start) { - LLVM_DEBUG(dbgs() << "\tclobbers do alias, or there is a gap: [" - << ItPrev->Start << ", " << ItPrev->Start + ItPrev->Size - << "] -> [" << It->Start << ", " << It->Start + It->Size - << "]" << '\n'); - return std::nullopt; - } - } - - return TotalSize; -} - -std::array FoldingState::computeKeccak256Hash( - const SmallVectorImpl &MemClobbers) const { - SmallVector MemBuf; - raw_svector_ostream OS(MemBuf); - // Put all the clobber values to the buffer in the BE order. - for (const auto &MemClobber : MemClobbers) { - // Words of the APInt are in the LE order, so we need to - // iterate them starting from the end. - const auto *ValRawData = MemClobber.Clobber.getRawData(); - for (unsigned I = 0, E = MemClobber.Clobber.getNumWords(); I != E; ++I) - support::endian::write(OS, ValRawData[E - I - 1], - support::endianness::big); - } - LLVM_DEBUG(dbgs() << "\tinput sha3 data: " << toHex(OS.str()) << '\n'); - - return KECCAK::KECCAK_256(OS.str()); -} - -Value *FoldingState::runFolding(const CallInst *Call, - SmallVectorImpl &MemDefs) const { - uint64_t TotalSizeOfClobbers = 0; - SmallVector MemClobbers; - auto UseMemLoc = getLocForSHA3Call(Call); - const uint64_t UseMemSize = UseMemLoc.Size.getValue(); - - for (MemoryDef *MemDef : MemDefs) { - const auto *MemInstr = MemDef->getMemoryInst(); - if (shouldSkipClobber(MemInstr)) - continue; - - const auto *SI = dyn_cast(MemInstr); - if (!SI) { - LLVM_DEBUG(dbgs() << "\tunknown clobber: " << *MemInstr << '\n'); - return nullptr; - } - - LLVM_DEBUG(dbgs() << "\tfound clobber: " << *SI << '\n'); - - const MemoryLocation ClobberLoc = MemoryLocation::get(SI); - if (!ClobberLoc.Size.isPrecise()) - return nullptr; - - auto OverlapRes = isOverlap(Call, SI, UseMemLoc, ClobberLoc); - - // This clobber doesn't write to the memory __sha3 is reading from. - // Just skip it and come to the next clobber. - if (OverlapRes.Type == OverlapType::OL_None) { - LLVM_DEBUG(dbgs() << "\t\twrites out of sha3 memory, offset: " - << OverlapRes.ClobberOffset << '\n'); - continue; - } - - // We give up in these cases, as it's difficult or impossible - // to determine the full memory data for the __sha3. - // If required, we could try to support some cases. TODO: CPR-1370. - if (OverlapRes.Type == OverlapType::OL_MaybePartial || - OverlapRes.Type == OverlapType::OL_Unknown) { - LLVM_DEBUG(dbgs() << "\t\tpartially or unknow overlap" << '\n'); - return nullptr; - } - - // Handle OL_Complete. Try to add new clobber to the memory clobbers. - - // We cannot perform constant folding, if the stored value is not - // a constant expression. - const auto *ClobberVal = dyn_cast(SI->getValueOperand()); - if (!ClobberVal) { - LLVM_DEBUG(dbgs() << "\t\tstored value isn't constant" << '\n'); - return nullptr; - } - - const uint64_t ClobberSize = ClobberLoc.Size.getValue(); - // If we have already seen a clobber with the same start position - // and the size, we can ignore the current clobber, as it's killed - // by existing one. In fact this is similar to what DSE pass does. - if (std::any_of(MemClobbers.begin(), MemClobbers.end(), - [OverlapRes, ClobberSize](const auto &C) { - return C.Start == OverlapRes.ClobberOffset && - C.Size == ClobberSize; - })) { - LLVM_DEBUG(dbgs() << "\t\tstored value is killed by the" - "consequent clobber" - << '\n'); - continue; - } - - TotalSizeOfClobbers += ClobberSize; - assert(ClobberSize * 8 == ClobberVal->getBitWidth()); - - MemClobbers.push_back( - {OverlapRes.ClobberOffset, ClobberSize, ClobberVal->getValue()}); - - // If we have collected clobbers that fully cover the __sha3 memory - // location, sort them in the ascending order of their starting addresses - // and perform some checks. - if (TotalSizeOfClobbers < UseMemSize) - continue; - - // Sort memory clobbers in the ascending order of their starting - // positions. - std::sort( - MemClobbers.begin(), MemClobbers.end(), - [](const auto &Lhs, const auto &Rhs) { return Lhs.Start < Rhs.Start; }); - - // Ensure the sorted array of clobbers forms a continuous memory region. - auto TotalSize = checkMemoryClobbers(MemClobbers); - if (!TotalSize) - return nullptr; - - assert(TotalSize == UseMemSize); - auto Hash = computeKeccak256Hash(MemClobbers); - - LLVM_DEBUG(dbgs() << "\tkeccak256 hash: " << toHex(Hash) << '\n'); - assert(Call->getType()->getIntegerBitWidth() == 256); - - Value *HashVal = - ConstantInt::get(Call->getType(), APInt(256, toHex(Hash), 16)); - return HashVal; - } - - LLVM_DEBUG(dbgs() << "\tcouldn't find enough clobbers that would fully" - "cover sha3 memory" - << '\n'); - return nullptr; -} - -bool runEraVMSHA3ConstFolding(Function &F, AliasAnalysis &AA, - AssumptionCache &AC, MemorySSA &MSSA, - DominatorTree &DT, const TargetLibraryInfo &TLI, - const LoopInfo &LI) { - LLVM_DEBUG(dbgs() << "********** EraVM sha3 constant folding **********\n" - << "********** Function: " << F.getName() << '\n'); - - FoldingState State(F, AA, AC, MSSA, DT, TLI, LI); - SmallSet RemovedSHA3; - - bool Changed = false, ChangedOnIter = false; - do { - LLVM_DEBUG(dbgs() << "Running new iteration of sha3 constant folding...\n"); - - for (MemoryUse *SHA3MemUse : State.getSHA3MemUses()) { - if (RemovedSHA3.count(SHA3MemUse)) - continue; - - auto *SHA3Call = dyn_cast(SHA3MemUse->getMemoryInst()); - assert(SHA3Call != nullptr); - - LLVM_DEBUG(dbgs() << "Analyzing: " << *SHA3Call << '\n'); - SmallVector Clobbers = - State.collectSHA3Clobbers(SHA3Call); - - if (Value *HashVal = State.runFolding(SHA3Call, Clobbers)) { - if (!DebugCounter::shouldExecute(SHA3Counter)) { - LLVM_DEBUG(dbgs() << "Skipping due to debug counter\n"); - return Changed; - } - - SHA3Call->replaceAllUsesWith(HashVal); - State.removeFromMSSA(SHA3Call); - SHA3Call->eraseFromParent(); - RemovedSHA3.insert(SHA3MemUse); - Changed = ChangedOnIter = true; - NumSHA3Folded++; - - LLVM_DEBUG(dbgs() << "\treplacing with the value: " << *HashVal - << '\n'); - } - } - // If we simplified some instructions after folding __sha3 calls, - // run the folding again, as there may be new opportunities for this. - } while (ChangedOnIter && State.simplifyInstructions()); - - return Changed; -} - -bool EraVMSHA3ConstFolding::runOnFunction(Function &F) { - if (skipFunction(F)) - return false; - - auto &AC = getAnalysis().getAssumptionCache(F); - auto &AA = getAnalysis().getAAResults(); - auto &DT = getAnalysis().getDomTree(); - const auto &TLI = getAnalysis().getTLI(F); - auto &MSSA = getAnalysis().getMSSA(); - auto &LI = getAnalysis().getLoopInfo(); - - return runEraVMSHA3ConstFolding(F, AA, AC, MSSA, DT, TLI, LI); -} - -char EraVMSHA3ConstFolding::ID = 0; - -INITIALIZE_PASS( - EraVMSHA3ConstFolding, "eravm-sha3-constant-folding", - "Replace '__sha3' calls with calculated hash values if arguments are known", - false, false) - -FunctionPass *llvm::createEraVMSHA3ConstFoldingPass() { - return new EraVMSHA3ConstFolding; -} - PreservedAnalyses EraVMSHA3ConstFoldingPass::run(Function &F, FunctionAnalysisManager &AM) { auto &AC = AM.getResult(F); @@ -819,8 +28,23 @@ PreservedAnalyses EraVMSHA3ConstFoldingPass::run(Function &F, auto &DT = AM.getResult(F); auto &MSSA = AM.getResult(F).getMSSA(); auto &LI = AM.getResult(F); + auto IsSha3Call = [&TLI](const Instruction *I) { + const auto *Call = dyn_cast(I); + if (!Call) + return false; + + Function *Callee = Call->getCalledFunction(); + if (!Callee) + return false; + + LibFunc Func = NotLibFunc; + const StringRef Name = Callee->getName(); + return TLI.getLibFunc(Name, Func) && TLI.has(Func) && + Func == LibFunc_xvm_sha3; + }; - return runEraVMSHA3ConstFolding(F, AA, AC, MSSA, DT, TLI, LI) + return llvm::runSHA3ConstFolding(F, AA, AC, MSSA, DT, TLI, LI, IsSha3Call, + EraVMAS::AS_HEAP) ? PreservedAnalyses::none() : PreservedAnalyses::all(); } diff --git a/llvm/lib/Target/EraVM/EraVMTargetMachine.cpp b/llvm/lib/Target/EraVM/EraVMTargetMachine.cpp index d6f48a5c613c..7018de150fec 100644 --- a/llvm/lib/Target/EraVM/EraVMTargetMachine.cpp +++ b/llvm/lib/Target/EraVM/EraVMTargetMachine.cpp @@ -64,7 +64,6 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeEraVMTarget() { initializeEraVMAAWrapperPassPass(PR); initializeEraVMExternalAAWrapperPass(PR); initializeEraVMAlwaysInlinePass(PR); - initializeEraVMSHA3ConstFoldingPass(PR); initializeEraVMOptimizeSelectPostRAPass(PR); initializeEraVMTieSelectOperandsPass(PR); initializeEraVMHoistFlagSettingPass(PR); diff --git a/llvm/lib/Transforms/Scalar/CMakeLists.txt b/llvm/lib/Transforms/Scalar/CMakeLists.txt index b7f2f5217dfe..18389645ffac 100644 --- a/llvm/lib/Transforms/Scalar/CMakeLists.txt +++ b/llvm/lib/Transforms/Scalar/CMakeLists.txt @@ -72,6 +72,9 @@ add_llvm_component_library(LLVMScalarOpts Scalarizer.cpp ScalarizeMaskedMemIntrin.cpp SeparateConstOffsetFromGEP.cpp + # EraVM local begin + SHA3ConstFolding.cpp + # EraVM local end SimpleLoopUnswitch.cpp SimplifyCFGPass.cpp Sink.cpp diff --git a/llvm/lib/Transforms/Scalar/SHA3ConstFolding.cpp b/llvm/lib/Transforms/Scalar/SHA3ConstFolding.cpp new file mode 100644 index 000000000000..aa4d3b0644e3 --- /dev/null +++ b/llvm/lib/Transforms/Scalar/SHA3ConstFolding.cpp @@ -0,0 +1,742 @@ +//===-- SHA3ConstFolding.cpp - Const fold calls to sha3 ---------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// The code below tries to replace sha3 (that calucates keccak256 hash) calls +// with the calculated hash values. +// +// It uses the following general approach: given a sha3 call (MemoryUse), +// walk upwards to find store instructions (clobbers) with +// constant values that fully define data (in memory) for which we compute hash. +// Only sha3 calls with the constant 'size' argument are checked. +// +// For example: +// +// store i256 1, ptr addrspace(1) null +// store i256 2, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)) +// %hash = tail call i256 @sha3(ptr addrspace(1) null, i256 64, i1 true) +// ret i256 %hash +// +// is transformed into: +// +// store i256 1, ptr addrspace(1) null +// store i256 2, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)) +// ret i256 -536754096339594166047489462334966539640... +// +// A bit more concretely: +// +// For all sha3 calls: +// 1. Collect potentially dominating clobbering MemoryDefs by walking upwards. +// Check that clobbering values are constants, otherwise bail out. +// +// 2. Check that +// 1. Each clobber is withing the sha3 memory location: +// |--clobber--| +// |------MemUse-------| +// 2. Clobbers are not intersected with each other: +// |--cl1--| +// |cl2| +// |--cl3--| +// |------MemUse-------| +// 3.Collect clobber values +// +// 3. Create a memory array from the collected values and calculate +// the Keccak256 hash. +// +// 4. Run simplification for each instruction in the function, as sha3 folding +// can provide new opportunities for this. +// +// 5. If the simplification has changed the function, run one more iteration +// of the whole process starting from p.1. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Scalar/SHA3ConstFolding.h" +#include "llvm/ADT/PostOrderIterator.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/Analysis/AssumptionCache.h" +#include "llvm/Analysis/GlobalsModRef.h" +#include "llvm/Analysis/InstructionSimplify.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/Analysis/MemoryBuiltins.h" +#include "llvm/Analysis/MemoryLocation.h" +#include "llvm/Analysis/MemorySSA.h" +#include "llvm/Analysis/MemorySSAUpdater.h" +#include "llvm/Analysis/MustExecute.h" +#include "llvm/Analysis/TargetLibraryInfo.h" +#include "llvm/Analysis/ValueTracking.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/InstIterator.h" +#include "llvm/Support/DebugCounter.h" +#include "llvm/Support/EndianStream.h" +#include "llvm/Support/KECCAK.h" +#include "llvm/Transforms/Utils/AssumeBundleBuilder.h" +#include "llvm/Transforms/Utils/Local.h" +#include + +using namespace llvm; + +#define DEBUG_TYPE "sha3-constant-folding" + +STATISTIC(NumSHA3Folded, "Number of sha3 calls folded"); + +DEBUG_COUNTER(SHA3Counter, "sha3-constant-folding", + "Controls which instructions are removed"); + +namespace { +/// This structure holds an information about a single memory clobber. +/// While walking upwards starting at a sha3 call (which is a MemUse), +/// we create a MemClobber instance for each dominating memory clobber +/// (in current implementation - a store instruction) and put it in a +/// list which then gets sorted (by the 'Start' value). As result, we get +/// representation of a continuous memory region the sha3 computes a hash for. +struct MemClobber { + // Byte offset relatively to the beginning of the sha3 memory location. + uint64_t Start; + + // Size in bytes of the clobber. + uint64_t Size; + + // Value to be stored in the memory. + APInt Clobber; +}; + +/// This class holds an information required for folding sha3 calls +/// in the function F. +/// +/// The workflow with the class is as follows: +/// 1. Create an FoldingState instance. +/// The constructor collects the MemorySSA uses corresponding to +/// sha3 calls in the function F. +/// 2. Iterate through the collected memory uses calling the runFolding(), +/// which, on success, returns a computed keccak256 +/// hash value. +/// Replace sha3 values with the calculated hash values, +/// removing the calls and invoking removeFromMSSA() to keep up to date +/// the MemorySSA representation. +/// 3. Run simplifyInstructions() to simplify/clean up the F. +/// +class FoldingState { + /// Types of the relative placement of the two memory locations. + enum class OverlapType { + // Loc1 is completely within the Loc2 + // |-Loc1-| + // |-----Loc2----| + OL_Complete, + + // Loc1 is partially with the Loc2 + // and they both refer to the same underlying object + // |-Loc1-| + // |-----Loc2----| + OL_MaybePartial, + + // Memory locations don't intersect + // |--Loc1--| + // |---Loc2--| + OL_None, + + // Nothing can be determined + OL_Unknown + }; + + /// Describes how the memory clobber is placed with respect + /// to the memory use (sha3 call). + struct OverlapResult { + OverlapType Type; + + // Clobber offset relative to the beginning of the memory use. + // Unless the type is 'OL_Complete' actual offset value doesn't matter. + uint64_t ClobberOffset; + }; + + // Whether the function contains any irreducible control flow, useful for + // being accurately able to detect loops. + const bool ContainsIrreducibleLoops; + + // The sha3 calls to be analyzed. + SmallVector SHA3MemUses; + + Function &F; + AliasAnalysis &AA; + AssumptionCache &AC; + MemorySSA &MSSA; + std::unique_ptr MSSAUpdater; + const TargetLibraryInfo &TLI; + const DataLayout &DL; + const SimplifyQuery SQ; + const LoopInfo &LI; + unsigned HeapAS; + +public: + FoldingState(const FoldingState &) = delete; + FoldingState &operator=(const FoldingState &) = delete; + FoldingState(FoldingState &&) = delete; + FoldingState &&operator=(FoldingState &&) = delete; + ~FoldingState() = default; + + FoldingState(Function &F, AliasAnalysis &AA, AssumptionCache &AC, + MemorySSA &MSSA, DominatorTree &DT, const TargetLibraryInfo &TLI, + const LoopInfo &LI, + const std::function &IsSha3Call, + unsigned HeapAS); + + /// Collect all the potential clobbering memory accesses for the + /// given sha3 call (\p Call). + SmallVector collectSHA3Clobbers(const CallInst *Call); + + /// For the given sha3 call (\p Call), walk through the collected MemorySSA + /// definitions (\p MemDefs) and try to build a continuous memory segment with + /// the data for which we compute the keccak256 hash. On success return + /// the computed hash value. + Value *runFolding(const CallInst *Call, + SmallVectorImpl &MemDefs) const; + + /// Try to simplify instructions in the function F. The candidates for + /// simplification may appear after replacing sha3 calls with the + /// calculated hash values. + bool simplifyInstructions(); + + /// Exclude the instruction from MSSA if it's going to be removed from a BB. + void removeFromMSSA(Instruction *Inst) { + if (VerifyMemorySSA) + MSSA.verifyMemorySSA(); + + MSSAUpdater->removeMemoryAccess(Inst, true); + } + + /// Return collected MemorySSa uses corresponding to sha3 calls. + const SmallVectorImpl &getSHA3MemUses() const { + return SHA3MemUses; + } + +private: + /// Return true if a dependency between \p Clobber and \p MemUse is + /// guaranteed to be loop invariant for the loops that they are in. + bool isGuaranteedLoopIndependent(const Instruction *Clobber, + const Instruction *MemUse, + const MemoryLocation &ClobberLoc) const; + + /// Return true if \p Ptr is guaranteed to be loop invariant for any possible + /// loop. In particular, this guarantees that it only references a single + /// MemoryLocation during execution of the containing function. + bool isGuaranteedLoopInvariant(const Value *Ptr) const; + + /// Check how the two memory locations (\p MemUseLoc and \p ClobberLoc) + /// are located with respect to each other. + OverlapResult isOverlap(const Instruction *MemUse, const Instruction *Clobber, + const MemoryLocation &MemUseLoc, + const MemoryLocation &ClobberLoc) const; + + /// Try to cast the constant pointer value \p Ptr to the integer offset. + /// Consider moving it to a common code. TODO: CPR-1380. + std::optional tryToCastPtrToInt(const Value *Ptr) const; + + /// Ensure the sorted array of clobbers forms a continuous memory region. + /// On success return the size the memory region. + std::optional + checkMemoryClobbers(const SmallVector &MemClobbers) const; + + /// Compute keccak256 hash for the memory segment, formed by the sorted list + /// of memory clobbers passed in \p MemClobbers. + std::array + computeKeccak256Hash(const SmallVectorImpl &MemClobbers) const; + + /// Check if we can ignore non store clobber instruction that doesn't actually + /// clobber heap memory. For example, a memcpy to AS other than heap. + /// Probably we should check more cases here. TODO: CPR-1370. + bool shouldSkipClobber(const Instruction *MemInst) const { + if (!MemInst->mayWriteToMemory()) + return true; + + if (const auto *Intr = dyn_cast(MemInst)) + return Intr->getDestAddressSpace() != HeapAS; + + return false; + } + + /// Return MemoryLocation corresponding to the pointer argument of + /// sha3 call. + MemoryLocation getLocForSHA3Call(const CallInst *I) const { + return MemoryLocation::getForArgument(I, 0, TLI); + } +}; // end FoldingState struct + +} // end anonymous namespace + +static uint64_t getPointerSize(const Value *V, const DataLayout &DL, + const TargetLibraryInfo &TLI, + const Function *F) { + uint64_t Size = 0; + ObjectSizeOpts Opts; + Opts.NullIsUnknownSize = NullPointerIsDefined(F); + + if (getObjectSize(V, Size, DL, &TLI, Opts)) + return Size; + return MemoryLocation::UnknownSize; +} + +FoldingState::FoldingState( + Function &F, AliasAnalysis &AA, AssumptionCache &AC, MemorySSA &MSSA, + DominatorTree &DT, const TargetLibraryInfo &TLI, const LoopInfo &LI, + const std::function &IsSha3Call, unsigned HeapAS) + : ContainsIrreducibleLoops(mayContainIrreducibleControl(F, &LI)), F(F), + AA(AA), AC(AC), MSSA(MSSA), + MSSAUpdater(std::make_unique(&MSSA)), TLI(TLI), + DL(F.getParent()->getDataLayout()), SQ(DL, &TLI, &DT, &AC), LI(LI), + HeapAS(HeapAS) { + MSSA.ensureOptimizedUses(); + + const ReversePostOrderTraversal RPOT(&*F.begin()); + for (BasicBlock *BB : RPOT) { + for (Instruction &I : *BB) { + if (!IsSha3Call(&I)) + continue; + + const auto *Call = cast(&I); + auto *MA = MSSA.getMemoryAccess(Call); + auto *MU = dyn_cast_or_null(MA); + // sha3 is passed a pointer in the first argument + // and the memory size in the second one. It's expected that the + // memory size to be a constant expression. In this case + // the memory location should be precise. + if (MU && getLocForSHA3Call(Call).Size.isPrecise()) + SHA3MemUses.push_back(MU); + } + } +} + +SmallVector +FoldingState::collectSHA3Clobbers(const CallInst *Call) { + SmallVector Clobbers; + const MemoryLocation Loc = getLocForSHA3Call(Call); + MemorySSAWalker *Walker = MSSA.getWalker(); + + // For the given sha3 call (which is MemoryUse in terms of MemorySSA) we + // need to collect all memory clobbering accesses. Usually these are just + // 'store' instructions. Other cases are not handled by the current + // implementation. For this we employ MemorySSA representation that maps + // memory clobbering Instructions to three access types: + // - live on entry (nothing to do, sha3 is not clobbered), + // - MemoryDef, + // - MemoryPhi + // + // We start with a nearest to the sha3 call dominating clobbering access. + // Then we do the same for the just found clobber and so on until we find the + // 'live on entry'. + // For simplicity, in case of a MemoryPhi we also stop the search. This + // constraint can be relaxed. TODO: CPR-1370. + + MemoryAccess *MA = + Walker->getClobberingMemoryAccess(MSSA.getMemoryAccess(Call)); + for (; !MSSA.isLiveOnEntryDef(MA) && !isa(MA);) { + if (auto *Def = dyn_cast(MA)) { + Clobbers.push_back(Def); + MA = Walker->getClobberingMemoryAccess(Def->getDefiningAccess(), Loc); + } + } + + return Clobbers; +} + +bool FoldingState::simplifyInstructions() { + bool Changed = false; + for (auto &Inst : make_early_inc_range(instructions(F))) { + if (Value *V = simplifyInstruction(&Inst, SQ)) { + LLVM_DEBUG(dbgs() << " SHA3ConstFolding simplify: " << Inst + << " to: " << *V << '\n'); + if (!DebugCounter::shouldExecute(SHA3Counter)) { + LLVM_DEBUG(dbgs() << "Skipping due to debug counter\n"); + continue; + } + + if (!Inst.use_empty()) { + Inst.replaceAllUsesWith(V); + Changed = true; + } + if (isInstructionTriviallyDead(&Inst, &TLI)) { + removeFromMSSA(&Inst); + salvageKnowledge(&Inst, &AC); + Inst.eraseFromParent(); + Changed = true; + } + } + } + return Changed; +} + +bool FoldingState::isGuaranteedLoopIndependent( + const Instruction *Clobber, const Instruction *MemUse, + const MemoryLocation &ClobberLoc) const { + // If the dependency is within the same block or loop level (being careful + // of irreducible loops), we know that AA will return a valid result for the + // memory dependency. + if (MemUse->getParent() == Clobber->getParent()) + return true; + + // Check if both Clobber and MemUse are known to be in the same loop level. + const Loop *CurrentLI = LI.getLoopFor(MemUse->getParent()); + if (!ContainsIrreducibleLoops && CurrentLI && + CurrentLI == LI.getLoopFor(Clobber->getParent())) + return true; + + // Otherwise check the memory location is invariant to any loops. + return isGuaranteedLoopInvariant(ClobberLoc.Ptr); +} + +bool FoldingState::isGuaranteedLoopInvariant(const Value *Ptr) const { + Ptr = Ptr->stripPointerCasts(); + if (const auto *GEP = dyn_cast(Ptr)) + if (GEP->hasAllConstantIndices()) + Ptr = GEP->getPointerOperand()->stripPointerCasts(); + + if (const auto *I = dyn_cast(Ptr)) + return I->getParent()->isEntryBlock(); + + return true; +} + +static std::optional getNullOrInt(const APInt &APVal) { + if (APVal.getActiveBits() <= 64) + return APVal.getZExtValue(); + + return std::nullopt; +} + +std::optional +FoldingState::tryToCastPtrToInt(const Value *Ptr) const { + if (isa(Ptr)) + return UINT64_C(0); + + if (const auto *CE = dyn_cast(Ptr)) { + if (CE->getOpcode() == Instruction::IntToPtr) { + if (auto *CI = dyn_cast(CE->getOperand(0))) { + // Give up in case of a huge offsset, as this shouldn't happen + // in a real life. + return getNullOrInt(CI->getValue()); + } + } + } + + if (const auto *IntToPtr = dyn_cast(Ptr)) { + if (auto *CI = dyn_cast(IntToPtr->getOperand(0))) { + return getNullOrInt(CI->getValue()); + } + } + + return std::nullopt; +} + +FoldingState::OverlapResult +FoldingState::isOverlap(const Instruction *MemUse, const Instruction *Clobber, + const MemoryLocation &MemUseLoc, + const MemoryLocation &ClobberLoc) const { + // AliasAnalysis does not always account for loops. Limit overlap checks + // to dependencies for which we can guarantee they are independent of any + // loops they are in. + if (!isGuaranteedLoopIndependent(Clobber, MemUse, ClobberLoc)) + return {OverlapType::OL_Unknown, 0}; + + const Value *ClobberPtr = ClobberLoc.Ptr->stripPointerCasts(); + const Value *MemUsePtr = MemUseLoc.Ptr->stripPointerCasts(); + const Value *ClobberUndObj = getUnderlyingObject(ClobberPtr); + const Value *MemUseUndObj = getUnderlyingObject(MemUsePtr); + const uint64_t MemUseSize = MemUseLoc.Size.getValue(); + const uint64_t ClobberSize = ClobberLoc.Size.getValue(); + + // If both the Clobber and MemUse pointers are constant we expect two cases: + // - pointer is just a nullptr + // - pointer is a cast of an integer constant + + if (isa(ClobberPtr) && isa(MemUsePtr)) { + if (!tryToCastPtrToInt(ClobberPtr) || !tryToCastPtrToInt(MemUsePtr)) + return {OverlapType::OL_Unknown, 0}; + + const uint64_t ClobberPtrInt = *tryToCastPtrToInt(ClobberPtr); + const uint64_t MemUsePtrInt = *tryToCastPtrToInt(MemUsePtr); + if (MemUsePtrInt <= ClobberPtrInt && + (ClobberPtrInt + ClobberSize) <= (MemUsePtrInt + MemUseSize)) { + return {OverlapType::OL_Complete, ClobberPtrInt - MemUsePtrInt}; + } + } + + // Check whether the Clobber overwrites the MemUse object. + if (ClobberUndObj == MemUseUndObj) { + const uint64_t MemUseUndObjSize = getPointerSize(MemUseUndObj, DL, TLI, &F); + if (MemUseUndObjSize != MemoryLocation::UnknownSize && + MemUseUndObjSize == MemUseSize && MemUseSize == ClobberSize) + return {OverlapType::OL_Complete, 0}; + } + + // Query the alias information + const AliasResult AAR = AA.alias(MemUseLoc, ClobberLoc); + + // If the start pointers are the same, we just have to compare sizes to see + // if the MemUse was larger than the Clobber. + if (AAR == AliasResult::MustAlias) { + // Make sure that the MemUseSize size is >= the ClobberSize size. + if (MemUseSize >= ClobberSize) + return {OverlapType::OL_Complete, 0}; + } + + // If we hit a partial alias we may have a full overwrite + if (AAR == AliasResult::PartialAlias && AAR.hasOffset()) { + const int32_t Offset = AAR.getOffset(); + if (Offset >= 0 && + static_cast(Offset) + ClobberSize <= MemUseSize) { + return {OverlapType::OL_Complete, static_cast(Offset)}; + } + } + + // If we can't resolve the same pointers to the same object, then we can't + // analyze them at all. + if (MemUseUndObj != ClobberUndObj) { + if (AAR == AliasResult::NoAlias) + return {OverlapType::OL_None, 0}; + return {OverlapType::OL_Unknown, 0}; + } + + // Okay, we have stores to two completely different pointers. Try to + // decompose the pointer into a "base + constant_offset" form. If the base + // pointers are equal, then we can reason about the MemUse and the Clobber. + int64_t ClobberOffset = 0, MemUseOffset = 0; + const Value *ClobberBasePtr = + GetPointerBaseWithConstantOffset(ClobberPtr, ClobberOffset, DL); + const Value *MemUseBasePtr = + GetPointerBaseWithConstantOffset(MemUsePtr, MemUseOffset, DL); + + // If the base pointers still differ, we have two completely different + // stores. + if (ClobberBasePtr != MemUseBasePtr) + return {OverlapType::OL_Unknown, 0}; + + // The MemUse completely overlaps the clobber if both the start and the end + // of the Clobber are inside the MemUse: + // | |--Clobber-| | + // |------MemUse------| + // We have to be careful here as *Off is signed while *.Size is unsigned. + + // Check if the Clobber access starts not before the MemUse one. + if (ClobberOffset >= MemUseOffset) { + // If the Clobber access ends not after the MemUse access then the + // clobber one is completely overlapped by the MemUse one. + if (static_cast(ClobberOffset - MemUseOffset) + ClobberSize <= + MemUseSize) + return {OverlapType::OL_Complete, + static_cast(ClobberOffset - MemUseOffset)}; + + // If start of the Clobber access is before end of the MemUse access + // then accesses overlap. + if (static_cast(ClobberOffset - MemUseOffset) < MemUseSize) + return {OverlapType::OL_MaybePartial, 0}; + } + // If start of the MemUse access is before end of the Clobber access then + // accesses overlap. + else if (static_cast(MemUseOffset - ClobberOffset) < ClobberSize) { + return {OverlapType::OL_MaybePartial, 0}; + } + + // Can reach here only if accesses are known not to overlap. + return {OverlapType::OL_None, 0}; +} + +std::optional FoldingState::checkMemoryClobbers( + const SmallVector &MemClobbers) const { + auto Begin = MemClobbers.begin(), End = MemClobbers.end(); + assert(Begin != End); + + uint64_t TotalSize = Begin->Size; + for (const auto *It = std::next(Begin); It != End; ++It) { + TotalSize += It->Size; + const auto *ItPrev = std::prev(It); + if ((ItPrev->Start + ItPrev->Size) != It->Start) { + LLVM_DEBUG(dbgs() << "\tclobbers do alias, or there is a gap: [" + << ItPrev->Start << ", " << ItPrev->Start + ItPrev->Size + << "] -> [" << It->Start << ", " << It->Start + It->Size + << "]" << '\n'); + return std::nullopt; + } + } + + return TotalSize; +} + +std::array FoldingState::computeKeccak256Hash( + const SmallVectorImpl &MemClobbers) const { + SmallVector MemBuf; + raw_svector_ostream OS(MemBuf); + // Put all the clobber values to the buffer in the BE order. + for (const auto &MemClobber : MemClobbers) { + // Words of the APInt are in the LE order, so we need to + // iterate them starting from the end. + const auto *ValRawData = MemClobber.Clobber.getRawData(); + for (unsigned I = 0, E = MemClobber.Clobber.getNumWords(); I != E; ++I) + support::endian::write(OS, ValRawData[E - I - 1], + support::endianness::big); + } + LLVM_DEBUG(dbgs() << "\tinput sha3 data: " << toHex(OS.str()) << '\n'); + + return KECCAK::KECCAK_256(OS.str()); +} + +Value *FoldingState::runFolding(const CallInst *Call, + SmallVectorImpl &MemDefs) const { + uint64_t TotalSizeOfClobbers = 0; + SmallVector MemClobbers; + auto UseMemLoc = getLocForSHA3Call(Call); + const uint64_t UseMemSize = UseMemLoc.Size.getValue(); + + for (MemoryDef *MemDef : MemDefs) { + const auto *MemInstr = MemDef->getMemoryInst(); + if (shouldSkipClobber(MemInstr)) + continue; + + const auto *SI = dyn_cast(MemInstr); + if (!SI) { + LLVM_DEBUG(dbgs() << "\tunknown clobber: " << *MemInstr << '\n'); + return nullptr; + } + + LLVM_DEBUG(dbgs() << "\tfound clobber: " << *SI << '\n'); + + const MemoryLocation ClobberLoc = MemoryLocation::get(SI); + if (!ClobberLoc.Size.isPrecise()) + return nullptr; + + auto OverlapRes = isOverlap(Call, SI, UseMemLoc, ClobberLoc); + + // This clobber doesn't write to the memory sha3 is reading from. + // Just skip it and come to the next clobber. + if (OverlapRes.Type == OverlapType::OL_None) { + LLVM_DEBUG(dbgs() << "\t\twrites out of sha3 memory, offset: " + << OverlapRes.ClobberOffset << '\n'); + continue; + } + + // We give up in these cases, as it's difficult or impossible + // to determine the full memory data for the sha3. + // If required, we could try to support some cases. TODO: CPR-1370. + if (OverlapRes.Type == OverlapType::OL_MaybePartial || + OverlapRes.Type == OverlapType::OL_Unknown) { + LLVM_DEBUG(dbgs() << "\t\tpartially or unknow overlap" << '\n'); + return nullptr; + } + + // Handle OL_Complete. Try to add new clobber to the memory clobbers. + + // We cannot perform constant folding, if the stored value is not + // a constant expression. + const auto *ClobberVal = dyn_cast(SI->getValueOperand()); + if (!ClobberVal) { + LLVM_DEBUG(dbgs() << "\t\tstored value isn't constant" << '\n'); + return nullptr; + } + + const uint64_t ClobberSize = ClobberLoc.Size.getValue(); + // If we have already seen a clobber with the same start position + // and the size, we can ignore the current clobber, as it's killed + // by existing one. In fact this is similar to what DSE pass does. + if (std::any_of(MemClobbers.begin(), MemClobbers.end(), + [OverlapRes, ClobberSize](const auto &C) { + return C.Start == OverlapRes.ClobberOffset && + C.Size == ClobberSize; + })) { + LLVM_DEBUG(dbgs() << "\t\tstored value is killed by the" + "consequent clobber" + << '\n'); + continue; + } + + TotalSizeOfClobbers += ClobberSize; + assert(ClobberSize * 8 == ClobberVal->getBitWidth()); + + MemClobbers.push_back( + {OverlapRes.ClobberOffset, ClobberSize, ClobberVal->getValue()}); + + // If we have collected clobbers that fully cover the sha3 memory + // location, sort them in the ascending order of their starting addresses + // and perform some checks. + if (TotalSizeOfClobbers < UseMemSize) + continue; + + // Sort memory clobbers in the ascending order of their starting + // positions. + std::sort( + MemClobbers.begin(), MemClobbers.end(), + [](const auto &Lhs, const auto &Rhs) { return Lhs.Start < Rhs.Start; }); + + // Ensure the sorted array of clobbers forms a continuous memory region. + auto TotalSize = checkMemoryClobbers(MemClobbers); + if (!TotalSize) + return nullptr; + + assert(TotalSize == UseMemSize); + auto Hash = computeKeccak256Hash(MemClobbers); + + LLVM_DEBUG(dbgs() << "\tkeccak256 hash: " << toHex(Hash) << '\n'); + assert(Call->getType()->getIntegerBitWidth() == 256); + + Value *HashVal = + ConstantInt::get(Call->getType(), APInt(256, toHex(Hash), 16)); + return HashVal; + } + + LLVM_DEBUG(dbgs() << "\tcouldn't find enough clobbers that would fully" + "cover sha3 memory" + << '\n'); + return nullptr; +} + +bool llvm::runSHA3ConstFolding( + Function &F, AliasAnalysis &AA, AssumptionCache &AC, MemorySSA &MSSA, + DominatorTree &DT, const TargetLibraryInfo &TLI, const LoopInfo &LI, + const std::function &IsSha3Call, + unsigned HeapAS) { + LLVM_DEBUG(dbgs() << "********** SHA3 constant folding **********\n" + << "********** Function: " << F.getName() << '\n'); + + FoldingState State(F, AA, AC, MSSA, DT, TLI, LI, IsSha3Call, HeapAS); + SmallSet RemovedSHA3; + + bool Changed = false, ChangedOnIter = false; + do { + LLVM_DEBUG(dbgs() << "Running new iteration of sha3 constant folding...\n"); + + for (MemoryUse *SHA3MemUse : State.getSHA3MemUses()) { + if (RemovedSHA3.count(SHA3MemUse)) + continue; + + auto *SHA3Call = dyn_cast(SHA3MemUse->getMemoryInst()); + assert(SHA3Call != nullptr); + + LLVM_DEBUG(dbgs() << "Analyzing: " << *SHA3Call << '\n'); + SmallVector Clobbers = + State.collectSHA3Clobbers(SHA3Call); + + if (Value *HashVal = State.runFolding(SHA3Call, Clobbers)) { + if (!DebugCounter::shouldExecute(SHA3Counter)) { + LLVM_DEBUG(dbgs() << "Skipping due to debug counter\n"); + return Changed; + } + + SHA3Call->replaceAllUsesWith(HashVal); + State.removeFromMSSA(SHA3Call); + SHA3Call->eraseFromParent(); + RemovedSHA3.insert(SHA3MemUse); + Changed = ChangedOnIter = true; + NumSHA3Folded++; + + LLVM_DEBUG(dbgs() << "\treplacing with the value: " << *HashVal + << '\n'); + } + } + // If we simplified some instructions after folding sha3 calls, + // run the folding again, as there may be new opportunities for this. + } while (ChangedOnIter && State.simplifyInstructions()); + + return Changed; +} From 3687154800458d24a65ba52ca166cac4958c3b94 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 8 Nov 2024 12:47:22 +0100 Subject: [PATCH 28/42] [EVM] Add pre-commit test for Add support for constant folding SHA3 calls Signed-off-by: Vladimir Radosavljevic --- .../test/CodeGen/EVM/sha3-constant-folding.ll | 240 ++++++++++++++++++ 1 file changed, 240 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/sha3-constant-folding.ll diff --git a/llvm/test/CodeGen/EVM/sha3-constant-folding.ll b/llvm/test/CodeGen/EVM/sha3-constant-folding.ll new file mode 100644 index 000000000000..21d0b769e526 --- /dev/null +++ b/llvm/test/CodeGen/EVM/sha3-constant-folding.ll @@ -0,0 +1,240 @@ +; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 2 +; RUN: opt -O3 -S < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +; Check that we don't fold the sha3 call if optimizing for size. +define i256 @sha3_test_optsize() minsize { +; CHECK-LABEL: define i256 @sha3_test_optsize +; CHECK-SAME: () local_unnamed_addr #[[ATTR0:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) null, align 4294967296 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 32 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) null, i256 64) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 304594385234, ptr addrspace(1) null, align 4294967296 + store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 32 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) null, i256 64) + ret i256 %hash +} + + +; Both the store instructions and the sha3 call has constexpr addresses. +define i256 @sha3_test_1() nounwind { +; CHECK-LABEL: define i256 @sha3_test_1 +; CHECK-SAME: () local_unnamed_addr #[[ATTR1:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) null, align 4294967296 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 32 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) null, i256 64) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 304594385234, ptr addrspace(1) null, align 4294967296 + store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 32 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) null, i256 64) + ret i256 %hash +} + +; Both the store instructions and the sha3 call has runtime addresses. +define i256 @sha3_test_2(ptr addrspace(1) nocapture %addr) nounwind { +; CHECK-LABEL: define i256 @sha3_test_2 +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 304594385234, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i256, ptr addrspace(1) %addr, i256 1 + store i256 56457598675863654, ptr addrspace(1) %next_addr, align 1 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 64) + ret i256 %hash +} + +; Store instructions don't cover sha3 memory location, so no constant folding. +define i256 @sha3_test_3(ptr addrspace(1) nocapture %addr) nounwind { +; CHECK-LABEL: define i256 @sha3_test_3 +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 0, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR2:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 3 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR2]], align 1 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 96) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 0, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i256, ptr addrspace(1) %addr, i256 1 + store i256 304594385234, ptr addrspace(1) %next_addr, align 1 + %next_addr2 = getelementptr i256, ptr addrspace(1) %addr, i256 3 + store i256 56457598675863654, ptr addrspace(1) %next_addr2, align 1 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 96) + ret i256 %hash +} + +; The second store partially overlaps sha3 memory location, +; so no constant folding. +define i256 @sha3_test_4(ptr addrspace(1) nocapture %addr) nounwind { +; CHECK-LABEL: define i256 @sha3_test_4 +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 0, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 +; CHECK-NEXT: store i512 304594385234, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 0, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i256, ptr addrspace(1) %addr, i256 1 + store i512 304594385234, ptr addrspace(1) %next_addr, align 1 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 64) + ret i256 %hash +} + +; Store instructions have different store sizes. +define i256 @sha3_test_5(ptr addrspace(1) nocapture %addr) nounwind { +; CHECK-LABEL: define i256 @sha3_test_5 +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i128 0, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i8, ptr addrspace(1) [[ADDR]], i256 16 +; CHECK-NEXT: store i128 304594385234, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR2:%.*]] = getelementptr i8, ptr addrspace(1) [[ADDR]], i256 32 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR2]], align 1 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i128 0, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i8, ptr addrspace(1) %addr, i256 16 + store i128 304594385234, ptr addrspace(1) %next_addr, align 1 + %next_addr2 = getelementptr i8, ptr addrspace(1) %addr, i256 32 + store i256 56457598675863654, ptr addrspace(1) %next_addr2, align 1 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 64) + ret i256 %hash +} + +; Only the first store is used for the constant folding. +define i256 @sha3_test_6(ptr addrspace(1) nocapture %addr) nounwind { +; CHECK-LABEL: define i256 @sha3_test_6 +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 32) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 304594385234, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i256, ptr addrspace(1) %addr, i256 1 + store i256 56457598675863654, ptr addrspace(1) %next_addr, align 1 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 32) + ret i256 %hash +} + +; The second sha3 call gets folded, but not the first one because there is +; non-analyzable clobber. +define i256 @sha3_test_7(ptr addrspace(1) nocapture %addr, ptr addrspace(1) nocapture %addr2) nounwind { +; CHECK-LABEL: define i256 @sha3_test_7 +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]], ptr addrspace(1) nocapture writeonly [[ADDR2:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 +; CHECK-NEXT: store i256 111, ptr addrspace(1) [[ADDR2]], align 1 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[HASH1:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) +; CHECK-NEXT: [[HASH2:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[NEXT_ADDR]], i256 32) +; CHECK-NEXT: [[HASH:%.*]] = add i256 [[HASH2]], [[HASH1]] +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 304594385234, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i256, ptr addrspace(1) %addr, i256 1 + store i256 111, ptr addrspace(1) %addr2, align 1 + store i256 56457598675863654, ptr addrspace(1) %next_addr, align 1 + %hash1 = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 64) + %hash2 = call i256 @llvm.evm.sha3(ptr addrspace(1) %next_addr, i256 32) + %hash = add i256 %hash1, %hash2 + ret i256 %hash +} + +; Memory locations of store instructions do alias with each other, so no +; constant folding. Theoretically we can support this case. TODO: CPR-1370. +define i256 @sha3_test_8(ptr addrspace(1) nocapture %addr) nounwind { +; CHECK-LABEL: define i256 @sha3_test_8 +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 0, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i8, ptr addrspace(1) [[ADDR]], i256 31 +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR2:%.*]] = getelementptr i8, ptr addrspace(1) [[ADDR]], i256 63 +; CHECK-NEXT: store i8 17, ptr addrspace(1) [[NEXT_ADDR2]], align 1 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 0, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i8, ptr addrspace(1) %addr, i256 31 + store i256 304594385234, ptr addrspace(1) %next_addr, align 1 + %next_addr2 = getelementptr i8, ptr addrspace(1) %addr, i256 63 + store i8 17, ptr addrspace(1) %next_addr2, align 1 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 64) + ret i256 %hash +} + +; We have two sha3 calls where the second call gets folded on the second iteration. +define i256 @sha3_test_9(ptr addrspace(1) %addr) nounwind { +; CHECK-LABEL: define i256 @sha3_test_9 +; CHECK-SAME: (ptr addrspace(1) [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) +; CHECK-NEXT: [[SUM:%.*]] = add i256 [[HASH]], 10 +; CHECK-NEXT: store i256 [[SUM]], ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: store i256 111111111111, ptr addrspace(1) [[ADDR]], align 1 +; CHECK-NEXT: [[HASH2:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) +; CHECK-NEXT: ret i256 [[HASH2]] +; +entry: + store i256 304594385234, ptr addrspace(1) %addr, align 1 + %next_addr = getelementptr i256, ptr addrspace(1) %addr, i256 1 + store i256 56457598675863654, ptr addrspace(1) %next_addr, align 1 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 64) + %sum = add i256 %hash, 10 + store i256 %sum, ptr addrspace(1) %next_addr, align 1 + store i256 111111111111, ptr addrspace(1) %addr, align 1 + %hash2 = call i256 @llvm.evm.sha3(ptr addrspace(1) %addr, i256 64) + ret i256 %hash2 +} + +; Offset of the second store is too big (requires > 64 bits), so no constant folding. +define i256 @sha3_test_10() nounwind { +; CHECK-LABEL: define i256 @sha3_test_10 +; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { +; CHECK-NEXT: entry: +; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) null, align 4294967296 +; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 18446744073709551616 to ptr addrspace(1)), align 4294967296 +; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) null, i256 64) +; CHECK-NEXT: ret i256 [[HASH]] +; +entry: + store i256 304594385234, ptr addrspace(1) null, align 4294967296 + store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 18446744073709551616 to ptr addrspace(1)), align 32 + %hash = call i256 @llvm.evm.sha3(ptr addrspace(1) null, i256 64) + ret i256 %hash +} + +declare i256 @llvm.evm.sha3(ptr addrspace(1), i256) From f74de102f5bbc411ad4b4a80cbc17f63ce7f53f2 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Wed, 6 Nov 2024 16:03:52 +0100 Subject: [PATCH 29/42] [EVM] Add support for constant folding SHA3 calls Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Analysis/MemoryLocation.cpp | 9 ++++ llvm/lib/Target/EVM/CMakeLists.txt | 1 + llvm/lib/Target/EVM/EVM.h | 5 ++ llvm/lib/Target/EVM/EVMSHA3ConstFolding.cpp | 48 +++++++++++++++++++ llvm/lib/Target/EVM/EVMTargetMachine.cpp | 12 +++++ .../test/CodeGen/EVM/sha3-constant-folding.ll | 41 +++++++--------- 6 files changed, 91 insertions(+), 25 deletions(-) create mode 100644 llvm/lib/Target/EVM/EVMSHA3ConstFolding.cpp diff --git a/llvm/lib/Analysis/MemoryLocation.cpp b/llvm/lib/Analysis/MemoryLocation.cpp index 5453ee6cd582..6568d093b9f8 100644 --- a/llvm/lib/Analysis/MemoryLocation.cpp +++ b/llvm/lib/Analysis/MemoryLocation.cpp @@ -15,6 +15,7 @@ #include "llvm/IR/Module.h" #include "llvm/IR/Type.h" // EraVM local begin +#include "llvm/IR/IntrinsicsEVM.h" #include "llvm/TargetParser/Triple.h" // EraVM local end #include @@ -190,6 +191,14 @@ MemoryLocation MemoryLocation::getForArgument(const CallBase *Call, case Intrinsic::lifetime_end: // it is okay to have lifetime intrinsic break; + case Intrinsic::evm_sha3: { + assert((ArgIdx == 0) && "Invalid argument index for sha3"); + const auto *LenCI = dyn_cast(Call->getArgOperand(1)); + if (LenCI && LenCI->getValue().getActiveBits() <= 64) + return MemoryLocation( + Arg, LocationSize::precise(LenCI->getZExtValue()), AATags); + return MemoryLocation::getAfter(Arg, AATags); + } default: llvm_unreachable("Unexpected intrinsic for EraVM/EVM target"); break; diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index 4ae61c0186d2..0d7a1da7792c 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -35,6 +35,7 @@ add_llvm_target(EVMCodeGen EVMOptimizeLiveIntervals.cpp EVMRegColoring.cpp EVMRegisterInfo.cpp + EVMSHA3ConstFolding.cpp EVMSingleUseExpression.cpp EVMSplitCriticalEdges.cpp EVMStackDebug.cpp diff --git a/llvm/lib/Target/EVM/EVM.h b/llvm/lib/Target/EVM/EVM.h index 87fffce6f3d1..5c1ea833fe3d 100644 --- a/llvm/lib/Target/EVM/EVM.h +++ b/llvm/lib/Target/EVM/EVM.h @@ -84,5 +84,10 @@ struct EVMAllocaHoistingPass : PassInfoMixin { PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); }; +struct EVMSHA3ConstFoldingPass : PassInfoMixin { + EVMSHA3ConstFoldingPass() = default; + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); +}; + } // namespace llvm #endif // LLVM_LIB_TARGET_EVM_EVM_H diff --git a/llvm/lib/Target/EVM/EVMSHA3ConstFolding.cpp b/llvm/lib/Target/EVM/EVMSHA3ConstFolding.cpp new file mode 100644 index 000000000000..062e527b5d57 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMSHA3ConstFolding.cpp @@ -0,0 +1,48 @@ +//===-- EVMSHA3ConstFolding.cpp - Const fold calls to sha3 ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This is the EVM SHA3 const folding pass. +// +//===----------------------------------------------------------------------===// + +#include "EVM.h" +#include "llvm/Analysis/AssumptionCache.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/Analysis/MemorySSA.h" +#include "llvm/Analysis/TargetLibraryInfo.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/IntrinsicInst.h" +#include "llvm/IR/IntrinsicsEVM.h" +#include "llvm/Transforms/Scalar/SHA3ConstFolding.h" + +using namespace llvm; + +PreservedAnalyses EVMSHA3ConstFoldingPass::run(Function &F, + FunctionAnalysisManager &AM) { + // Don't run this pass if optimizing for size, since result of SHA3 + // calls will be replaced with a 32-byte constant, thus PUSH32 will + // be emitted. This will increase code size. + if (F.hasOptSize()) + return PreservedAnalyses::all(); + + auto &AC = AM.getResult(F); + auto &AA = AM.getResult(F); + const auto &TLI = AM.getResult(F); + auto &DT = AM.getResult(F); + auto &MSSA = AM.getResult(F).getMSSA(); + auto &LI = AM.getResult(F); + auto IsSha3Call = [](const Instruction *I) { + const auto *II = dyn_cast(I); + return II && II->getIntrinsicID() == Intrinsic::evm_sha3; + }; + + return llvm::runSHA3ConstFolding(F, AA, AC, MSSA, DT, TLI, LI, IsSha3Call, + EVMAS::AS_HEAP) + ? PreservedAnalyses::none() + : PreservedAnalyses::all(); +} diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.cpp b/llvm/lib/Target/EVM/EVMTargetMachine.cpp index 4e5b107ecf0d..4d19717d761c 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.cpp +++ b/llvm/lib/Target/EVM/EVMTargetMachine.cpp @@ -132,12 +132,24 @@ void EVMTargetMachine::registerPassBuilderCallbacks(PassBuilder &PB) { [](FunctionPassManager &PM, OptimizationLevel Level) { if (Level.getSizeLevel() || Level.getSpeedupLevel() > 1) PM.addPass(MergeIdenticalBBPass()); + if (Level.isOptimizingForSpeed()) + PM.addPass(EVMSHA3ConstFoldingPass()); }); PB.registerAnalysisRegistrationCallback([](FunctionAnalysisManager &FAM) { FAM.registerPass([] { return EVMAA(); }); }); + PB.registerPipelineParsingCallback( + [](StringRef PassName, FunctionPassManager &PM, + ArrayRef) { + if (PassName == "evm-sha3-constant-folding") { + PM.addPass(EVMSHA3ConstFoldingPass()); + return true; + } + return false; + }); + PB.registerParseAACallback([](StringRef AAName, AAManager &AAM) { if (AAName == "evm-aa") { AAM.registerFunctionAnalysis(); diff --git a/llvm/test/CodeGen/EVM/sha3-constant-folding.ll b/llvm/test/CodeGen/EVM/sha3-constant-folding.ll index 21d0b769e526..45bfe247d27e 100644 --- a/llvm/test/CodeGen/EVM/sha3-constant-folding.ll +++ b/llvm/test/CodeGen/EVM/sha3-constant-folding.ll @@ -29,8 +29,7 @@ define i256 @sha3_test_1() nounwind { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) null, align 4294967296 ; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 32 to ptr addrspace(1)), align 32 -; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) null, i256 64) -; CHECK-NEXT: ret i256 [[HASH]] +; CHECK-NEXT: ret i256 -53675409633959416604748946233496653964072736789863655143901645101595015023086 ; entry: store i256 304594385234, ptr addrspace(1) null, align 4294967296 @@ -42,13 +41,12 @@ entry: ; Both the store instructions and the sha3 call has runtime addresses. define i256 @sha3_test_2(ptr addrspace(1) nocapture %addr) nounwind { ; CHECK-LABEL: define i256 @sha3_test_2 -; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] { +; CHECK-SAME: (ptr addrspace(1) nocapture writeonly [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2:[0-9]+]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 ; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 -; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) -; CHECK-NEXT: ret i256 [[HASH]] +; CHECK-NEXT: ret i256 -53675409633959416604748946233496653964072736789863655143901645101595015023086 ; entry: store i256 304594385234, ptr addrspace(1) %addr, align 1 @@ -61,7 +59,7 @@ entry: ; Store instructions don't cover sha3 memory location, so no constant folding. define i256 @sha3_test_3(ptr addrspace(1) nocapture %addr) nounwind { ; CHECK-LABEL: define i256 @sha3_test_3 -; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR3:[0-9]+]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 0, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 @@ -85,7 +83,7 @@ entry: ; so no constant folding. define i256 @sha3_test_4(ptr addrspace(1) nocapture %addr) nounwind { ; CHECK-LABEL: define i256 @sha3_test_4 -; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR3]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 0, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 @@ -104,15 +102,14 @@ entry: ; Store instructions have different store sizes. define i256 @sha3_test_5(ptr addrspace(1) nocapture %addr) nounwind { ; CHECK-LABEL: define i256 @sha3_test_5 -; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture writeonly [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i128 0, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i8, ptr addrspace(1) [[ADDR]], i256 16 ; CHECK-NEXT: store i128 304594385234, ptr addrspace(1) [[NEXT_ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR2:%.*]] = getelementptr i8, ptr addrspace(1) [[ADDR]], i256 32 ; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR2]], align 1 -; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) -; CHECK-NEXT: ret i256 [[HASH]] +; CHECK-NEXT: ret i256 -53675409633959416604748946233496653964072736789863655143901645101595015023086 ; entry: store i128 0, ptr addrspace(1) %addr, align 1 @@ -127,13 +124,12 @@ entry: ; Only the first store is used for the constant folding. define i256 @sha3_test_6(ptr addrspace(1) nocapture %addr) nounwind { ; CHECK-LABEL: define i256 @sha3_test_6 -; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture writeonly [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 ; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 -; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 32) -; CHECK-NEXT: ret i256 [[HASH]] +; CHECK-NEXT: ret i256 -1651279235167815098054286291856006982035426946965232889084721396369881222887 ; entry: store i256 304594385234, ptr addrspace(1) %addr, align 1 @@ -147,15 +143,14 @@ entry: ; non-analyzable clobber. define i256 @sha3_test_7(ptr addrspace(1) nocapture %addr, ptr addrspace(1) nocapture %addr2) nounwind { ; CHECK-LABEL: define i256 @sha3_test_7 -; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]], ptr addrspace(1) nocapture writeonly [[ADDR2:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]], ptr addrspace(1) nocapture writeonly [[ADDR2:%.*]]) local_unnamed_addr #[[ATTR3]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 ; CHECK-NEXT: store i256 111, ptr addrspace(1) [[ADDR2]], align 1 ; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 ; CHECK-NEXT: [[HASH1:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) -; CHECK-NEXT: [[HASH2:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[NEXT_ADDR]], i256 32) -; CHECK-NEXT: [[HASH:%.*]] = add i256 [[HASH2]], [[HASH1]] +; CHECK-NEXT: [[HASH:%.*]] = add i256 [[HASH1]], 28454950007360609575222453380260700122861180288886985272557645317297017637223 ; CHECK-NEXT: ret i256 [[HASH]] ; entry: @@ -173,7 +168,7 @@ entry: ; constant folding. Theoretically we can support this case. TODO: CPR-1370. define i256 @sha3_test_8(ptr addrspace(1) nocapture %addr) nounwind { ; CHECK-LABEL: define i256 @sha3_test_8 -; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture [[ADDR:%.*]]) local_unnamed_addr #[[ATTR3]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 0, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i8, ptr addrspace(1) [[ADDR]], i256 31 @@ -196,17 +191,13 @@ entry: ; We have two sha3 calls where the second call gets folded on the second iteration. define i256 @sha3_test_9(ptr addrspace(1) %addr) nounwind { ; CHECK-LABEL: define i256 @sha3_test_9 -; CHECK-SAME: (ptr addrspace(1) [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { +; CHECK-SAME: (ptr addrspace(1) nocapture writeonly [[ADDR:%.*]]) local_unnamed_addr #[[ATTR2]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) [[ADDR]], align 1 ; CHECK-NEXT: [[NEXT_ADDR:%.*]] = getelementptr i256, ptr addrspace(1) [[ADDR]], i256 1 -; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) [[NEXT_ADDR]], align 1 -; CHECK-NEXT: [[HASH:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) -; CHECK-NEXT: [[SUM:%.*]] = add i256 [[HASH]], 10 -; CHECK-NEXT: store i256 [[SUM]], ptr addrspace(1) [[NEXT_ADDR]], align 1 +; CHECK-NEXT: store i256 -53675409633959416604748946233496653964072736789863655143901645101595015023076, ptr addrspace(1) [[NEXT_ADDR]], align 1 ; CHECK-NEXT: store i256 111111111111, ptr addrspace(1) [[ADDR]], align 1 -; CHECK-NEXT: [[HASH2:%.*]] = tail call i256 @llvm.evm.sha3(ptr addrspace(1) [[ADDR]], i256 64) -; CHECK-NEXT: ret i256 [[HASH2]] +; CHECK-NEXT: ret i256 -28502626979061174856046376292559402895813043346926066817140289137910599757723 ; entry: store i256 304594385234, ptr addrspace(1) %addr, align 1 @@ -223,7 +214,7 @@ entry: ; Offset of the second store is too big (requires > 64 bits), so no constant folding. define i256 @sha3_test_10() nounwind { ; CHECK-LABEL: define i256 @sha3_test_10 -; CHECK-SAME: () local_unnamed_addr #[[ATTR1]] { +; CHECK-SAME: () local_unnamed_addr #[[ATTR4:[0-9]+]] { ; CHECK-NEXT: entry: ; CHECK-NEXT: store i256 304594385234, ptr addrspace(1) null, align 4294967296 ; CHECK-NEXT: store i256 56457598675863654, ptr addrspace(1) inttoptr (i256 18446744073709551616 to ptr addrspace(1)), align 4294967296 From cf8d80198ae017a4f41f8b9c9519f69b1f5f6ef2 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Wed, 6 Nov 2024 16:08:47 +0100 Subject: [PATCH 30/42] [EVM] Remove assert in EVMCodegenPrepare Currently, it is only allowed to have memmove where src and dst are from address space 1 (HEAP). Since MemCpyOptPass can change memmove to memcpy, allow this case, and don't issue an error. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMCodegenPrepare.cpp | 6 ------ llvm/test/CodeGen/EVM/memintrinsics.ll | 9 +++++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp b/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp index 9a0f636f1f5c..4923d58e231c 100644 --- a/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp +++ b/llvm/lib/Target/EVM/EVMCodegenPrepare.cpp @@ -81,12 +81,6 @@ void EVMCodegenPrepare::processMemTransfer(MemTransferInst *M) { } } - assert((SrcAS == EVMAS::AS_HEAP && isa(M)) || - ((SrcAS == EVMAS::AS_CALL_DATA || SrcAS == EVMAS::AS_RETURN_DATA || - SrcAS == EVMAS::AS_CODE) && - isa(M) || - isa(M))); - Intrinsic::ID IntrID = Intrinsic::not_intrinsic; switch (SrcAS) { default: diff --git a/llvm/test/CodeGen/EVM/memintrinsics.ll b/llvm/test/CodeGen/EVM/memintrinsics.ll index f224ae74343c..af74658b90b2 100644 --- a/llvm/test/CodeGen/EVM/memintrinsics.ll +++ b/llvm/test/CodeGen/EVM/memintrinsics.ll @@ -3,6 +3,7 @@ target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" target triple = "evm" +declare void @llvm.memcpy.p1.p1.i256(ptr addrspace(1) noalias nocapture writeonly, ptr addrspace(1) noalias nocapture readonly, i256, i1 immarg) declare void @llvm.memcpy.p1.p2.i256(ptr addrspace(1) noalias nocapture writeonly, ptr addrspace(2) noalias nocapture readonly, i256, i1 immarg) declare void @llvm.memcpy.p1.p3.i256(ptr addrspace(1) noalias nocapture writeonly, ptr addrspace(3) noalias nocapture readonly, i256, i1 immarg) declare void @llvm.memcpy.p1.p4.i256(ptr addrspace(1) noalias nocapture writeonly, ptr addrspace(4) noalias nocapture readonly, i256, i1 immarg) @@ -69,6 +70,14 @@ define fastcc void @normal-known-size-2(ptr addrspace(1) %dest, ptr addrspace(1) ret void } +define fastcc void @heap_to_heap(ptr addrspace(1) %dest, ptr addrspace(1) %src, i256 %len) { +; CHECK-LABEL: heap_to_heap +; CHECK: MCOPY + + call void @llvm.memcpy.p1.p1.i256(ptr addrspace(1) %dest, ptr addrspace(1) %src, i256 %len, i1 false) + ret void +} + define fastcc void @calldata_to_heap(ptr addrspace(1) %dest, ptr addrspace(2) %src, i256 %len) { ; CHECK-LABEL: calldata_to_heap ; CHECK: CALLDATACOPY From a24684ba7e9f476bd27c83afddbedbddc717ad97 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 17 Jan 2025 15:22:27 +0100 Subject: [PATCH 31/42] [EVM] Add pre-commit test for Don't avoid transformations to shift Signed-off-by: Vladimir Radosavljevic --- .../EVM/dont-avoid-shift-transformations.ll | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll diff --git a/llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll b/llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll new file mode 100644 index 000000000000..fd617c43594b --- /dev/null +++ b/llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll @@ -0,0 +1,235 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc -O3 < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +; Check the following conversion in TargetLowering::SimplifySetCC +; (X & 8) != 0 --> (X & 8) >> 3 +define i256 @test1(i256 %x) { +; CHECK-LABEL: test1: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH1 32 +; CHECK-NEXT: AND +; CHECK-NEXT: EQ +; CHECK-NEXT: ISZERO +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP +entry: + %and = and i256 %x, 32 + %cmp = icmp ne i256 %and, 0 + %conv = zext i1 %cmp to i256 + ret i256 %conv +} + +; Check the following conversion in TargetLowering::SimplifySetCC +; (X & 8) == 8 --> (X & 8) >> 3 +define i256 @test2(i256 %x) { +; CHECK-LABEL: test2: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH1 32 +; CHECK-NEXT: AND +; CHECK-NEXT: EQ +; CHECK-NEXT: ISZERO +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP +entry: + %and = and i256 %x, 32 + %cmp = icmp eq i256 %and, 32 + %conv = zext i1 %cmp to i256 + ret i256 %conv +} + +; Check the following conversion in DAGCombiner::SimplifySelectCC +; (select_cc seteq (and x, y), 0, 0, A) -> (and (shr (shl x)) A) +define i256 @test3(i256 %x, i256 %a) { +; CHECK-LABEL: test3: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH2 2048 +; CHECK-NEXT: AND +; CHECK-NEXT: ISZERO +; CHECK-NEXT: PUSH4 @.BB2_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB2_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB2_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH4 @.BB2_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB2_1: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB2_2: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +entry: + %and = and i256 %x, 2048 + %cmp = icmp eq i256 %and, 0 + %cond = select i1 %cmp, i256 0, i256 %a + ret i256 %cond +} + +; Check the following conversion in DAGCombiner foldExtendedSignBitTest +; sext i1 (setgt iN X, -1) --> sra (not X), (N - 1) +define i256 @test4(i256 %x) { +; CHECK-LABEL: test4: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH1 1 +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SUB +; CHECK-NEXT: DUP1 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SGT +; CHECK-NEXT: PUSH4 @.BB3_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB3_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB3_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.BB3_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB3_1: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB3_2: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +entry: + %cmp = icmp sgt i256 %x, -1 + %cond = sext i1 %cmp to i256 + ret i256 %cond +} + +; Check the following conversion in DAGCombiner foldExtendedSignBitTest +; zext i1 (setgt iN X, -1) --> srl (not X), (N - 1) +define i256 @test5(i256 %x) { +; CHECK-LABEL: test5: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH1 1 +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SUB +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: SGT +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP +entry: + %cmp = icmp sgt i256 %x, -1 + %cond = zext i1 %cmp to i256 + ret i256 %cond +} + +; Check the following conversion in DAGCombiner::foldSelectCCToShiftAnd +; select_cc setlt X, 0, A, 0 -> and (sra X, size(X)-1), A +define i256 @test6(i256 %x, i256 %a) { +; CHECK-LABEL: test6: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: SLT +; CHECK-NEXT: PUSH4 @.BB5_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB5_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB5_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.BB5_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB5_1: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB5_2: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +entry: + %cmp = icmp slt i256 %x, 0 + %cond = select i1 %cmp, i256 %a, i256 0 + ret i256 %cond +} + +; Check the following conversion in DAGCombiner::foldSelectCCToShiftAnd +; select_cc setlt X, 0, A, 0 -> "and (srl X, C2), A" iff A is a single-bit +define i256 @test7(i256 %x) { +; CHECK-LABEL: test7: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: PUSH1 2 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SLT +; CHECK-NEXT: PUSH4 @.BB6_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB6_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB6_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.BB6_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB6_1: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB6_2: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +entry: + %cmp = icmp slt i256 %x, 0 + %cond = select i1 %cmp, i256 2, i256 0 + ret i256 %cond +} + +; Check the following conversion in DAGCombiner::SimplifySelectCC +; select C, 16, 0 -> shl C, 4 +define i256 @test8(i256 %a, i256 %b) { +; CHECK-LABEL: test8: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH1 32 +; CHECK-NEXT: SWAP2 +; CHECK-NEXT: SGT +; CHECK-NEXT: PUSH4 @.BB7_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB7_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB7_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.BB7_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB7_1: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB7_2: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +entry: + %cmp = icmp sgt i256 %a, %b + %cond = select i1 %cmp, i256 32, i256 0 + ret i256 %cond +} From e9c578543a763de042c168bcbce270ed2c4025b7 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 17 Jan 2025 15:20:04 +0100 Subject: [PATCH 32/42] [EVM] Don't avoid transformations to shift For EVM, transformations to shift are preferable. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMISelLowering.h | 6 - .../EVM/dont-avoid-shift-transformations.ll | 129 +++--------------- 2 files changed, 22 insertions(+), 113 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMISelLowering.h b/llvm/lib/Target/EVM/EVMISelLowering.h index c47b593917d2..6367ced2a1a6 100644 --- a/llvm/lib/Target/EVM/EVMISelLowering.h +++ b/llvm/lib/Target/EVM/EVMISelLowering.h @@ -72,12 +72,6 @@ class EVMTargetLowering final : public TargetLowering { return true; } - /// Return true if creating a shift of the type by the given - /// amount is not profitable. - bool shouldAvoidTransformToShift(EVT VT, unsigned Amount) const override { - return true; - } - /// Return true if it is profitable to fold a pair of shifts into a mask. /// This is usually true on most targets. But some targets, like Thumb1, /// have immediate shift instructions, but no immediate "and" instruction; diff --git a/llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll b/llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll index fd617c43594b..aa0514a49970 100644 --- a/llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll +++ b/llvm/test/CodeGen/EVM/dont-avoid-shift-transformations.ll @@ -10,12 +10,10 @@ define i256 @test1(i256 %x) { ; CHECK-LABEL: test1: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH1 32 ; CHECK-NEXT: AND -; CHECK-NEXT: EQ -; CHECK-NEXT: ISZERO +; CHECK-NEXT: PUSH1 5 +; CHECK-NEXT: SHR ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP entry: @@ -31,12 +29,10 @@ define i256 @test2(i256 %x) { ; CHECK-LABEL: test2: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH1 32 ; CHECK-NEXT: AND -; CHECK-NEXT: EQ -; CHECK-NEXT: ISZERO +; CHECK-NEXT: PUSH1 5 +; CHECK-NEXT: SHR ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP entry: @@ -52,28 +48,12 @@ define i256 @test3(i256 %x, i256 %a) { ; CHECK-LABEL: test3: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH2 2048 +; CHECK-NEXT: PUSH1 244 +; CHECK-NEXT: SHL +; CHECK-NEXT: PUSH1 255 +; CHECK-NEXT: SAR ; CHECK-NEXT: AND -; CHECK-NEXT: ISZERO -; CHECK-NEXT: PUSH4 @.BB2_3 -; CHECK-NEXT: JUMPI -; CHECK-NEXT: PUSH4 @.BB2_1 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB2_3: -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP2 ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: POP -; CHECK-NEXT: PUSH4 @.BB2_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB2_1: ; %entry -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB2_2: ; %entry -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP entry: %and = and i256 %x, 2048 @@ -88,28 +68,10 @@ define i256 @test4(i256 %x) { ; CHECK-LABEL: test4: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH1 1 -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SUB -; CHECK-NEXT: DUP1 -; CHECK-NEXT: SWAP2 -; CHECK-NEXT: SGT -; CHECK-NEXT: PUSH4 @.BB3_3 -; CHECK-NEXT: JUMPI -; CHECK-NEXT: PUSH4 @.BB3_1 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB3_3: -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH4 @.BB3_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB3_1: ; %entry -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP -; CHECK-NEXT: PUSH0 +; CHECK-NEXT: NOT +; CHECK-NEXT: PUSH1 255 +; CHECK-NEXT: SAR ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB3_2: ; %entry -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP entry: %cmp = icmp sgt i256 %x, -1 @@ -123,11 +85,9 @@ define i256 @test5(i256 %x) { ; CHECK-LABEL: test5: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH1 1 -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SUB -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: SGT +; CHECK-NEXT: NOT +; CHECK-NEXT: PUSH1 255 +; CHECK-NEXT: SHR ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP entry: @@ -142,25 +102,10 @@ define i256 @test6(i256 %x, i256 %a) { ; CHECK-LABEL: test6: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: SLT -; CHECK-NEXT: PUSH4 @.BB5_3 -; CHECK-NEXT: JUMPI -; CHECK-NEXT: PUSH4 @.BB5_1 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB5_3: -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH4 @.BB5_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB5_1: ; %entry -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP -; CHECK-NEXT: PUSH0 +; CHECK-NEXT: PUSH1 255 +; CHECK-NEXT: SAR +; CHECK-NEXT: AND ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB5_2: ; %entry -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP entry: %cmp = icmp slt i256 %x, 0 @@ -174,26 +119,12 @@ define i256 @test7(i256 %x) { ; CHECK-LABEL: test7: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH0 ; CHECK-NEXT: PUSH1 2 -; CHECK-NEXT: SWAP2 -; CHECK-NEXT: SLT -; CHECK-NEXT: PUSH4 @.BB6_3 -; CHECK-NEXT: JUMPI -; CHECK-NEXT: PUSH4 @.BB6_1 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB6_3: -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH4 @.BB6_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB6_1: ; %entry -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP -; CHECK-NEXT: PUSH0 +; CHECK-NEXT: PUSH1 254 +; CHECK-NEXT: SHR +; CHECK-NEXT: AND ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB6_2: ; %entry -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP entry: %cmp = icmp slt i256 %x, 0 @@ -207,26 +138,10 @@ define i256 @test8(i256 %a, i256 %b) { ; CHECK-LABEL: test8: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH1 32 -; CHECK-NEXT: SWAP2 ; CHECK-NEXT: SGT -; CHECK-NEXT: PUSH4 @.BB7_3 -; CHECK-NEXT: JUMPI -; CHECK-NEXT: PUSH4 @.BB7_1 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB7_3: -; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH1 5 +; CHECK-NEXT: SHL ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH4 @.BB7_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB7_1: ; %entry -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB7_2: ; %entry -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP entry: %cmp = icmp sgt i256 %a, %b From 0d9bbc7c73ab978a0fc9334b52d4e3c4026eb2f7 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 17 Jan 2025 15:36:13 +0100 Subject: [PATCH 33/42] [EVM] Add pre-commit test for Set that jumps are expensive Signed-off-by: Vladimir Radosavljevic --- llvm/test/CodeGen/EVM/jumps-are-expensive.ll | 59 ++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/jumps-are-expensive.ll diff --git a/llvm/test/CodeGen/EVM/jumps-are-expensive.ll b/llvm/test/CodeGen/EVM/jumps-are-expensive.ll new file mode 100644 index 000000000000..50d71d9a1291 --- /dev/null +++ b/llvm/test/CodeGen/EVM/jumps-are-expensive.ll @@ -0,0 +1,59 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc -O3 < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +declare i256 @bar() + +define i256 @test(i256 %a, i256 %b) { +; CHECK-LABEL: test: +; CHECK: ; %bb.0: ; %bb1 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ISZERO +; CHECK-NEXT: PUSH4 @.BB0_4 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB0_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_4: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.BB0_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_1: ; %bb1 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: EQ +; CHECK-NEXT: ISZERO +; CHECK-NEXT: PUSH4 @.BB0_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: ; %bb.5: +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB0_2: ; %bb3 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_3: ; %bb4 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH4 @.FUNC_RET0 +; CHECK-NEXT: PUSH4 @bar +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: JUMP +bb1: + %0 = icmp eq i256 %a, 0 + %1 = icmp eq i256 %b, 0 + %or = or i1 %0, %1 + br i1 %or, label %bb3, label %bb4 + +bb3: + ret i256 0 + +bb4: + %2 = call i256 @bar() + ret i256 %2 +} From 4a9cda2e8e28d03fbc04e4901884f6cbc667e949 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 17 Jan 2025 15:30:11 +0100 Subject: [PATCH 34/42] [EVM] Set that jumps are expensive Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMISelLowering.cpp | 2 +- llvm/test/CodeGen/EVM/jumps-are-expensive.ll | 24 ++++++-------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMISelLowering.cpp b/llvm/lib/Target/EVM/EVMISelLowering.cpp index 4ea3ea26464b..8bcd1a7cd987 100644 --- a/llvm/lib/Target/EVM/EVMISelLowering.cpp +++ b/llvm/lib/Target/EVM/EVMISelLowering.cpp @@ -86,7 +86,7 @@ EVMTargetLowering::EVMTargetLowering(const TargetMachine &TM, setOperationAction(ISD::INTRINSIC_VOID, MVT::Other, Custom); setOperationAction(ISD::INTRINSIC_WO_CHAIN, MVT::Other, Custom); - setJumpIsExpensive(false); + setJumpIsExpensive(true); setMaximumJumpTableSize(0); } diff --git a/llvm/test/CodeGen/EVM/jumps-are-expensive.ll b/llvm/test/CodeGen/EVM/jumps-are-expensive.ll index 50d71d9a1291..b873258b3af5 100644 --- a/llvm/test/CodeGen/EVM/jumps-are-expensive.ll +++ b/llvm/test/CodeGen/EVM/jumps-are-expensive.ll @@ -10,32 +10,22 @@ define i256 @test(i256 %a, i256 %b) { ; CHECK-LABEL: test: ; CHECK: ; %bb.0: ; %bb1 ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: ISZERO -; CHECK-NEXT: PUSH4 @.BB0_4 -; CHECK-NEXT: JUMPI -; CHECK-NEXT: PUSH4 @.BB0_1 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_4: -; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH0 +; CHECK-NEXT: EQ +; CHECK-NEXT: ISZERO ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH4 @.BB0_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_1: ; %bb1 -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: EQ ; CHECK-NEXT: ISZERO -; CHECK-NEXT: PUSH4 @.BB0_3 +; CHECK-NEXT: AND +; CHECK-NEXT: PUSH4 @.BB0_2 ; CHECK-NEXT: JUMPI -; CHECK-NEXT: ; %bb.5: +; CHECK-NEXT: ; %bb.1: ; %bb3 ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB0_2: ; %bb3 -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_3: ; %bb4 +; CHECK-NEXT: .BB0_2: ; %bb4 ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: PUSH4 @.FUNC_RET0 ; CHECK-NEXT: PUSH4 @bar From acc82b50c43dbd09a74fb5a070596dc39ffeb3fa Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 17 Jan 2025 16:21:15 +0100 Subject: [PATCH 35/42] [EVM] Add pre-commit test for Allow rematerialization of some of the context instructions Signed-off-by: Vladimir Radosavljevic --- llvm/test/CodeGen/EVM/context-remat.ll | 293 +++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/context-remat.ll diff --git a/llvm/test/CodeGen/EVM/context-remat.ll b/llvm/test/CodeGen/EVM/context-remat.ll new file mode 100644 index 000000000000..ad9406674385 --- /dev/null +++ b/llvm/test/CodeGen/EVM/context-remat.ll @@ -0,0 +1,293 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc -O3 < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +declare void @use(i256) + +define i256 @test_address() { +; CHECK-LABEL: test_address: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ADDRESS +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET0 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.address() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_origin() { +; CHECK-LABEL: test_origin: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ORIGIN +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET1 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET1: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.origin() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_caller() { +; CHECK-LABEL: test_caller: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CALLER +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET2 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET2: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.caller() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_callvalue() { +; CHECK-LABEL: test_callvalue: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CALLVALUE +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET3 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.callvalue() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_calldatasize() { +; CHECK-LABEL: test_calldatasize: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CALLDATASIZE +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET4 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET4: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.calldatasize() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_codesize() { +; CHECK-LABEL: test_codesize: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CODESIZE +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET5 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET5: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.codesize() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_gasprice() { +; CHECK-LABEL: test_gasprice: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: GASPRICE +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET6 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET6: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.gasprice() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_coinbase() { +; CHECK-LABEL: test_coinbase: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: COINBASE +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET7 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET7: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.coinbase() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_timestamp() { +; CHECK-LABEL: test_timestamp: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: TIMESTAMP +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET8 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET8: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.timestamp() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_number() { +; CHECK-LABEL: test_number: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: NUMBER +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET9 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET9: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.number() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_difficulty() { +; CHECK-LABEL: test_difficulty: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DIFFICULTY +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET10 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET10: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.difficulty() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_gaslimit() { +; CHECK-LABEL: test_gaslimit: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: GASLIMIT +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET11 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET11: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.gaslimit() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_chainid() { +; CHECK-LABEL: test_chainid: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CHAINID +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET12 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET12: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.chainid() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_basefee() { +; CHECK-LABEL: test_basefee: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: BASEFEE +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET13 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET13: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.basefee() + call void @use(i256 %ret) + ret i256 %ret +} + +define i256 @test_blobbasefee() { +; CHECK-LABEL: test_blobbasefee: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: BLOBBASEFEE +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.FUNC_RET14 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: PUSH4 @use +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET14: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %ret = call i256 @llvm.evm.blobbasefee() + call void @use(i256 %ret) + ret i256 %ret +} + +declare i256 @llvm.evm.address() +declare i256 @llvm.evm.origin() +declare i256 @llvm.evm.caller() +declare i256 @llvm.evm.callvalue() +declare i256 @llvm.evm.calldatasize() +declare i256 @llvm.evm.codesize() +declare i256 @llvm.evm.gasprice() +declare i256 @llvm.evm.coinbase() +declare i256 @llvm.evm.timestamp() +declare i256 @llvm.evm.number() +declare i256 @llvm.evm.difficulty() +declare i256 @llvm.evm.gaslimit() +declare i256 @llvm.evm.chainid() +declare i256 @llvm.evm.basefee() +declare i256 @llvm.evm.blobbasefee() From 6734faa17cbf92e31a29202a6178f659ffcb1ea2 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Fri, 17 Jan 2025 15:49:30 +0100 Subject: [PATCH 36/42] [EVM] Allow rematerialization of some of the context instructions Since these instructions are cheaper than move, it is beneficial to rematerialize them. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMInstrInfo.cpp | 15 ++++ llvm/lib/Target/EVM/EVMInstrInfo.td | 101 +++++++++++++------------ llvm/test/CodeGen/EVM/context-remat.ll | 90 +++++++++++----------- 3 files changed, 111 insertions(+), 95 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.cpp b/llvm/lib/Target/EVM/EVMInstrInfo.cpp index 7e66f0a9e481..9ba3d73438bf 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.cpp +++ b/llvm/lib/Target/EVM/EVMInstrInfo.cpp @@ -26,6 +26,21 @@ EVMInstrInfo::EVMInstrInfo() bool EVMInstrInfo::isReallyTriviallyReMaterializable( const MachineInstr &MI) const { switch (MI.getOpcode()) { + case EVM::ADDRESS: + case EVM::ORIGIN: + case EVM::CALLER: + case EVM::CALLVALUE: + case EVM::CALLDATASIZE: + case EVM::CODESIZE: + case EVM::GASPRICE: + case EVM::COINBASE: + case EVM::TIMESTAMP: + case EVM::NUMBER: + case EVM::DIFFICULTY: + case EVM::GASLIMIT: + case EVM::CHAINID: + case EVM::BASEFEE: + case EVM::BLOBBASEFEE: case EVM::CONST_I256: return true; default: diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index 2da934ab2070..f4af90a9081c 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -536,15 +536,11 @@ def : Pat<(truncstorei8 GPR:$val, GPR:$off), (STACK_STORE GPR:$off, 0, GPR:$val) // EVM instructions for retrieval values from context. //===----------------------------------------------------------------------===// +let isAsCheapAsAMove = 1, isReMaterializable = 1 in { defm ADDRESS : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_address))], "ADDRESS", " $dst", 0x30, 2>; -defm BALANCE - : I<(outs GPR:$dst), (ins GPR:$addr), - [(set GPR:$dst, (int_evm_balance GPR:$addr))], - "BALANCE", " $dst, $addr", 0x31, 100>; - defm ORIGIN : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_origin))], "ORIGIN", " $dst", 0x32, 2>; @@ -557,6 +553,56 @@ defm CALLVALUE : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_callvalue))], "CALLVALUE", " $dst", 0x34, 2>; +defm CALLDATASIZE + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_calldatasize))], + "CALLDATASIZE", " $dst", 0x36, 2>; + +defm CODESIZE + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_codesize))], + "CODESIZE", " $dst", 0x38, 2>; + +defm GASPRICE + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_gasprice))], + "GASPRICE", " $dst", 0x3A, 2>; + +defm COINBASE + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_coinbase))], + "COINBASE", " $dst", 0x41, 2>; + +defm TIMESTAMP + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_timestamp))], + "TIMESTAMP", " $dst", 0x42, 2>; + +defm NUMBER + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_number))], + "NUMBER", " $dst", 0x43, 2>; + +defm DIFFICULTY + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_difficulty))], + "DIFFICULTY", " $dst", 0x44, 2>; + +defm GASLIMIT + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_gaslimit))], + "GASLIMIT", " $dst", 0x45, 2>; + +defm CHAINID + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_chainid))], + "CHAINID", " $dst", 0x46, 2>; + +defm BASEFEE + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_basefee))], + "BASEFEE", " $dst", 0x48, 2>; + +defm BLOBBASEFEE + : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_blobbasefee))], + "BLOBBASEFEE", " $dst", 0x4A, 2>; +} + +defm BALANCE + : I<(outs GPR:$dst), (ins GPR:$addr), + [(set GPR:$dst, (int_evm_balance GPR:$addr))], + "BALANCE", " $dst, $addr", 0x31, 100>; + let mayLoad = 1 in defm CALLDATALOAD : I<(outs GPR:$dst), (ins GPR:$off), @@ -565,10 +611,6 @@ defm CALLDATALOAD def : Pat<(load_call_data GPR:$off), (CALLDATALOAD GPR:$off)>; -defm CALLDATASIZE - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_calldatasize))], - "CALLDATASIZE", " $dst", 0x36, 2>; - let mayStore = 1 in defm CALLDATACOPY : I<(outs), (ins GPR:$dst_off, GPR:$src_off, GPR:$size), [], @@ -577,10 +619,6 @@ defm CALLDATACOPY def : Pat<(EVMMemcpy_call_data GPR:$dst, GPR:$src, GPR:$size), (CALLDATACOPY GPR:$dst, GPR:$src, GPR:$size)>; -defm CODESIZE - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_codesize))], - "CODESIZE", " $dst", 0x38, 2>; - let mayStore = 1 in defm CODECOPY : I<(outs), (ins GPR:$dst_off, GPR:$src_off, GPR:$size), [], @@ -589,10 +627,6 @@ defm CODECOPY def : Pat<(EVMMemcpy_code GPR:$dst, GPR:$src, GPR:$size), (CODECOPY GPR:$dst, GPR:$src, GPR:$size)>; -defm GASPRICE - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_gasprice))], - "GASPRICE", " $dst", 0x3A, 2>; - defm EXTCODESIZE : I<(outs GPR:$dst), (ins GPR:$addr), [(set GPR:$dst, (int_evm_extcodesize GPR:$addr))], @@ -626,49 +660,16 @@ defm BLOCKHASH [(set GPR:$dst, (int_evm_blockhash GPR:$addr))], "BLOCKHASH", " $dst, $addr", 0x40, 20>; -defm COINBASE - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_coinbase))], - "COINBASE", " $dst", 0x41, 2>; - -defm TIMESTAMP - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_timestamp))], - "TIMESTAMP", " $dst", 0x42, 2>; - -defm NUMBER - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_number))], - "NUMBER", " $dst", 0x43, 2>; - -defm DIFFICULTY - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_difficulty))], - "DIFFICULTY", " $dst", 0x44, 2>; - -defm GASLIMIT - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_gaslimit))], - "GASLIMIT", " $dst", 0x45, 2>; - -defm CHAINID - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_chainid))], - "CHAINID", " $dst", 0x46, 2>; - let hasSideEffects = 1 in defm SELFBALANCE : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_selfbalance))], "SELFBALANCE", " $dst", 0x47, 5>; -defm BASEFEE - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_basefee))], - "BASEFEE", " $dst", 0x48, 2>; - defm BLOBHASH : I<(outs GPR:$dst), (ins GPR:$index), [(set GPR:$dst, (int_evm_blobhash GPR:$index))], "BLOBHASH", " $dst, $index", 0x49, 3>; -defm BLOBBASEFEE - : I<(outs GPR:$dst), (ins), [(set GPR:$dst, (int_evm_blobbasefee))], - "BLOBBASEFEE", " $dst", 0x4A, 2>; - - //===----------------------------------------------------------------------===// // EVM instructions for logging. //===----------------------------------------------------------------------===// diff --git a/llvm/test/CodeGen/EVM/context-remat.ll b/llvm/test/CodeGen/EVM/context-remat.ll index ad9406674385..4a58830498a1 100644 --- a/llvm/test/CodeGen/EVM/context-remat.ll +++ b/llvm/test/CodeGen/EVM/context-remat.ll @@ -10,14 +10,14 @@ define i256 @test_address() { ; CHECK-LABEL: test_address: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: ADDRESS -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET0 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: ADDRESS ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET0: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ADDRESS +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.address() call void @use(i256 %ret) @@ -28,14 +28,14 @@ define i256 @test_origin() { ; CHECK-LABEL: test_origin: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: ORIGIN -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET1 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: ORIGIN ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET1: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ORIGIN +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.origin() call void @use(i256 %ret) @@ -46,14 +46,14 @@ define i256 @test_caller() { ; CHECK-LABEL: test_caller: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: CALLER -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET2 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: CALLER ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET2: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CALLER +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.caller() call void @use(i256 %ret) @@ -64,14 +64,14 @@ define i256 @test_callvalue() { ; CHECK-LABEL: test_callvalue: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: CALLVALUE -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET3 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: CALLVALUE ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET3: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CALLVALUE +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.callvalue() call void @use(i256 %ret) @@ -82,14 +82,14 @@ define i256 @test_calldatasize() { ; CHECK-LABEL: test_calldatasize: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: CALLDATASIZE -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET4 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: CALLDATASIZE ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET4: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CALLDATASIZE +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.calldatasize() call void @use(i256 %ret) @@ -100,14 +100,14 @@ define i256 @test_codesize() { ; CHECK-LABEL: test_codesize: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: CODESIZE -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET5 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: CODESIZE ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET5: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CODESIZE +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.codesize() call void @use(i256 %ret) @@ -118,14 +118,14 @@ define i256 @test_gasprice() { ; CHECK-LABEL: test_gasprice: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: GASPRICE -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET6 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: GASPRICE ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET6: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: GASPRICE +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.gasprice() call void @use(i256 %ret) @@ -136,14 +136,14 @@ define i256 @test_coinbase() { ; CHECK-LABEL: test_coinbase: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: COINBASE -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET7 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: COINBASE ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET7: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: COINBASE +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.coinbase() call void @use(i256 %ret) @@ -154,14 +154,14 @@ define i256 @test_timestamp() { ; CHECK-LABEL: test_timestamp: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: TIMESTAMP -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET8 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: TIMESTAMP ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET8: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: TIMESTAMP +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.timestamp() call void @use(i256 %ret) @@ -172,14 +172,14 @@ define i256 @test_number() { ; CHECK-LABEL: test_number: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: NUMBER -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET9 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: NUMBER ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET9: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: NUMBER +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.number() call void @use(i256 %ret) @@ -190,14 +190,14 @@ define i256 @test_difficulty() { ; CHECK-LABEL: test_difficulty: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: DIFFICULTY -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET10 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: DIFFICULTY ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET10: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: DIFFICULTY +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.difficulty() call void @use(i256 %ret) @@ -208,14 +208,14 @@ define i256 @test_gaslimit() { ; CHECK-LABEL: test_gaslimit: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: GASLIMIT -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET11 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: GASLIMIT ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET11: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: GASLIMIT +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.gaslimit() call void @use(i256 %ret) @@ -226,14 +226,14 @@ define i256 @test_chainid() { ; CHECK-LABEL: test_chainid: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: CHAINID -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET12 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: CHAINID ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET12: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: CHAINID +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.chainid() call void @use(i256 %ret) @@ -244,14 +244,14 @@ define i256 @test_basefee() { ; CHECK-LABEL: test_basefee: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: BASEFEE -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET13 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: BASEFEE ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET13: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: BASEFEE +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.basefee() call void @use(i256 %ret) @@ -262,14 +262,14 @@ define i256 @test_blobbasefee() { ; CHECK-LABEL: test_blobbasefee: ; CHECK: ; %bb.0: ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: BLOBBASEFEE -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH4 @.FUNC_RET14 -; CHECK-NEXT: DUP3 +; CHECK-NEXT: BLOBBASEFEE ; CHECK-NEXT: PUSH4 @use ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET14: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: BLOBBASEFEE +; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP %ret = call i256 @llvm.evm.blobbasefee() call void @use(i256 %ret) From a328155c66afcab28908a73efe9fdc254432bff0 Mon Sep 17 00:00:00 2001 From: Philip Reames Date: Mon, 25 Nov 2024 18:59:31 -0800 Subject: [PATCH 37/42] [TTI][RISCV] Unconditionally break critical edges to sink ADDI (#108889) This looks like a rather weird change, so let me explain why this isn't as unreasonable as it looks. Let's start with the problem it's solving. ``` define signext i32 @overlap_live_ranges(ptr %arg, i32 signext %arg1) { bb: %i = icmp eq i32 %arg1, 1 br i1 %i, label %bb2, label %bb5 bb2: ; preds = %bb %i3 = getelementptr inbounds nuw i8, ptr %arg, i64 4 %i4 = load i32, ptr %i3, align 4 br label %bb5 bb5: ; preds = %bb2, %bb %i6 = phi i32 [ %i4, %bb2 ], [ 13, %bb ] ret i32 %i6 } ``` Right now, we codegen this as: ``` li a3, 1 li a2, 13 bne a1, a3, .LBB0_2 lw a2, 4(a0) .LBB0_2: mv a0, a2 ret ``` In this example, we have two values which must be assigned to a0 per the ABI (%arg, and the return value). SelectionDAG ensures that all values used in a successor phi are defined before exit the predecessor block. This creates an ADDI to materialize the immediate in the entry block. Currently, this ADDI is not sunk into the tail block because we'd have to split a critical edges to do so. Note that if our immediate was anything large enough to require two instructions we *would* split this critical edge. Looking at other targets, we notice that they don't seem to have this problem. They perform the sinking, and tail duplication that we don't. Why? Well, it turns out for AArch64 that this is entirely an accident of the existance of the gpr32all register class. The immediate is materialized into the gpr32 class, and then copied into the gpr32all register class. The existance of that copy puts us right back into the two instruction case noted above. This change essentially just bypasses this emergent behavior aspect of the aarch64 behavior, and implements the same "always sink immediates" behavior for RISCV as well. --- llvm/include/llvm/CodeGen/TargetInstrInfo.h | 6 ++++++ llvm/lib/CodeGen/MachineSink.cpp | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/llvm/include/llvm/CodeGen/TargetInstrInfo.h b/llvm/include/llvm/CodeGen/TargetInstrInfo.h index 3d4c44e41aba..0236993081b1 100644 --- a/llvm/include/llvm/CodeGen/TargetInstrInfo.h +++ b/llvm/include/llvm/CodeGen/TargetInstrInfo.h @@ -144,6 +144,12 @@ class TargetInstrInfo : public MCInstrInfo { return false; } + /// For a "cheap" instruction which doesn't enable additional sinking, + /// should MachineSink break a critical edge to sink it anyways? + virtual bool shouldBreakCriticalEdgeToSink(MachineInstr &MI) const { + return false; + } + protected: /// For instructions with opcodes for which the M_REMATERIALIZABLE flag is /// set, this hook lets the target specify whether the instruction is actually diff --git a/llvm/lib/CodeGen/MachineSink.cpp b/llvm/lib/CodeGen/MachineSink.cpp index 8da97dc7e742..a2cf6e3ea65c 100644 --- a/llvm/lib/CodeGen/MachineSink.cpp +++ b/llvm/lib/CodeGen/MachineSink.cpp @@ -664,7 +664,9 @@ bool MachineSinking::isWorthBreakingCriticalEdge(MachineInstr &MI, } } - return false; + // Let the target decide if it's worth breaking this + // critical edge for a "cheap" instruction. + return TII->shouldBreakCriticalEdgeToSink(MI); } bool MachineSinking::PostponeSplitCriticalEdge(MachineInstr &MI, From 1a479ca09807b634ca8fd5c483da1356710e6257 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Mon, 20 Jan 2025 13:11:06 +0100 Subject: [PATCH 38/42] [EVM] Add pre-commit test for Enable MachineSink to break critical edges for cheap instructions Signed-off-by: Vladimir Radosavljevic --- .../EVM/machine-sink-cheap-instructions.ll | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll diff --git a/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll b/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll new file mode 100644 index 000000000000..d7dc7799a6f2 --- /dev/null +++ b/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll @@ -0,0 +1,96 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc -O3 < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +declare i256 @bar() + +define i256 @test1(i256 %arg) { +; CHECK-LABEL: test1: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH1 10 +; CHECK-NEXT: EQ +; CHECK-NEXT: ISZERO +; CHECK-NEXT: PUSH4 @.BB0_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB0_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.BB0_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_1: ; %bb1 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH4 @.FUNC_RET0 +; CHECK-NEXT: PUSH4 @bar +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB0_2: ; %bb2 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +entry: + %cmp = icmp eq i256 %arg, 10 + br i1 %cmp, label %bb1, label %bb2 + +bb1: + %call = tail call i256 @bar() + br label %bb2 + +bb2: + %phi = phi i256 [ %call, %bb1 ], [ 0, %entry ] + ret i256 %phi +} + +define i256 @test2(i256 %arg) { +; CHECK-LABEL: test2: +; CHECK: ; %bb.0: ; %entry +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ADDRESS +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH1 10 +; CHECK-NEXT: EQ +; CHECK-NEXT: ISZERO +; CHECK-NEXT: PUSH4 @.BB1_3 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB1_1 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB1_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH4 @.BB1_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB1_1: ; %bb1 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH4 @.FUNC_RET1 +; CHECK-NEXT: PUSH4 @bar +; CHECK-NEXT: JUMP +; CHECK-NEXT: .FUNC_RET1: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB1_2: ; %bb2 +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP +entry: + %address = call i256 @llvm.evm.address() + %cmp = icmp eq i256 %arg, 10 + br i1 %cmp, label %bb1, label %bb2 + +bb1: + %call = tail call i256 @bar() + br label %bb2 + +bb2: + %phi = phi i256 [ %call, %bb1 ], [ %address, %entry ] + ret i256 %phi +} + +declare i256 @llvm.evm.address() From 9a8fa98edea0cf18c2d23a048569be9856788106 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Mon, 20 Jan 2025 13:08:32 +0100 Subject: [PATCH 39/42] [EVM] Enable MachineSink to break critical edges for cheap instructions Break critical edges in MachineSink optimizations for instructions that are marked with isAsCheapAsAMove in tablegen. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMInstrInfo.h | 4 +++ .../EVM/machine-sink-cheap-instructions.ll | 30 +++++++------------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.h b/llvm/lib/Target/EVM/EVMInstrInfo.h index 216de0cdebe3..21e6d6c9eae6 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.h +++ b/llvm/lib/Target/EVM/EVMInstrInfo.h @@ -42,6 +42,10 @@ class EVMInstrInfo final : public EVMGenInstrInfo { bool isReallyTriviallyReMaterializable(const MachineInstr &MI) const override; + bool shouldBreakCriticalEdgeToSink(MachineInstr &MI) const override { + return true; + } + void copyPhysReg(MachineBasicBlock &MBB, MachineBasicBlock::iterator MI, const DebugLoc &DL, MCRegister DestReg, MCRegister SrcReg, bool KillSrc, bool RenamableDest = false, diff --git a/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll b/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll index d7dc7799a6f2..e8fd44212c47 100644 --- a/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll +++ b/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll @@ -10,31 +10,26 @@ define i256 @test1(i256 %arg) { ; CHECK-LABEL: test1: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: PUSH0 -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH1 10 ; CHECK-NEXT: EQ ; CHECK-NEXT: ISZERO -; CHECK-NEXT: PUSH4 @.BB0_3 -; CHECK-NEXT: JUMPI ; CHECK-NEXT: PUSH4 @.BB0_1 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB0_2 ; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_3: +; CHECK-NEXT: .BB0_1: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH4 @.BB0_2 ; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_1: ; %bb1 +; CHECK-NEXT: .BB0_2: ; %bb1 ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP ; CHECK-NEXT: PUSH4 @.FUNC_RET0 ; CHECK-NEXT: PUSH4 @bar ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET0: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB0_2: ; %bb2 -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP entry: %cmp = icmp eq i256 %arg, 10 @@ -53,31 +48,26 @@ define i256 @test2(i256 %arg) { ; CHECK-LABEL: test2: ; CHECK: ; %bb.0: ; %entry ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: ADDRESS -; CHECK-NEXT: SWAP1 ; CHECK-NEXT: PUSH1 10 ; CHECK-NEXT: EQ ; CHECK-NEXT: ISZERO -; CHECK-NEXT: PUSH4 @.BB1_3 -; CHECK-NEXT: JUMPI ; CHECK-NEXT: PUSH4 @.BB1_1 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB1_2 ; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB1_3: +; CHECK-NEXT: .BB1_1: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: ADDRESS ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: PUSH4 @.BB1_2 ; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB1_1: ; %bb1 +; CHECK-NEXT: .BB1_2: ; %bb1 ; CHECK-NEXT: JUMPDEST -; CHECK-NEXT: POP ; CHECK-NEXT: PUSH4 @.FUNC_RET1 ; CHECK-NEXT: PUSH4 @bar ; CHECK-NEXT: JUMP ; CHECK-NEXT: .FUNC_RET1: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: SWAP1 -; CHECK-NEXT: .BB1_2: ; %bb2 -; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: JUMP entry: %address = call i256 @llvm.evm.address() From c9bd4acc4126ff6e8f79572df866091c0c0c7cc5 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Tue, 28 Jan 2025 12:18:38 +0100 Subject: [PATCH 40/42] [EVM] Add pre-commit test for Implement reverseBranchCondition and run BranchFolder pass after stackification Signed-off-by: Vladimir Radosavljevic --- .../EVM/branch-folder-after-stackification.ll | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll diff --git a/llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll b/llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll new file mode 100644 index 000000000000..91bfcfe76481 --- /dev/null +++ b/llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll @@ -0,0 +1,53 @@ +; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 2 +; RUN: llc -O3 < %s | FileCheck %s + +target datalayout = "E-p:256:256-i256:256:256-S256-a:256:256" +target triple = "evm" + +define i256 @test(i256 %arg) { +; CHECK-LABEL: test: +; CHECK: ; %bb.0: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: DUP3 +; CHECK-NEXT: SLT +; CHECK-NEXT: PUSH4 @.BB0_1 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: PUSH4 @.BB0_2 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_1: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: PUSH1 10 +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: PUSH4 @.BB0_3 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_2: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: PUSH0 +; CHECK-NEXT: PUSH1 20 +; CHECK-NEXT: SWAP3 +; CHECK-NEXT: .BB0_3: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SGT +; CHECK-NEXT: PUSH4 @.BB0_4 +; CHECK-NEXT: JUMPI +; CHECK-NEXT: ; %bb.6: +; CHECK-NEXT: PUSH4 @.BB0_5 +; CHECK-NEXT: JUMP +; CHECK-NEXT: .BB0_4: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: POP +; CHECK-NEXT: PUSH1 5 +; CHECK-NEXT: SWAP1 +; CHECK-NEXT: .BB0_5: +; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMP + %cmp1 = icmp sgt i256 %arg, 0 + %cmp2 = icmp slt i256 %arg, 0 + %select1 = select i1 %cmp2, i256 10, i256 20 + %select2 = select i1 %cmp1, i256 5, i256 %select1 + ret i256 %select2 +} From c9bdf05213a76a5b85c11d6d25f56bfc38ef787e Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Wed, 22 Jan 2025 12:47:59 +0100 Subject: [PATCH 41/42] [EVM] Implement reverseBranchCondition and run BranchFolder pass after stackification This patch adds jump_unless instructions that allows to implement reverseBranchCondition. This is the same trick that is used for WebAssembly https://reviews.llvm.org/D14995. Also it adds one more run of BranchFolder pass after stackification to optimize branches. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/CMakeLists.txt | 1 + llvm/lib/Target/EVM/EVM.h | 2 + llvm/lib/Target/EVM/EVMInstrInfo.cpp | 86 +++++++------- llvm/lib/Target/EVM/EVMInstrInfo.td | 15 +++ llvm/lib/Target/EVM/EVMLowerJumpUnless.cpp | 107 ++++++++++++++++++ llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp | 3 +- llvm/lib/Target/EVM/EVMStackModel.cpp | 1 + llvm/lib/Target/EVM/EVMStackify.cpp | 17 ++- .../lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 20 ++-- llvm/lib/Target/EVM/EVMTargetMachine.cpp | 10 ++ .../EVM/branch-folder-after-stackification.ll | 20 ++-- .../EVM/machine-sink-cheap-instructions.ll | 16 +-- 12 files changed, 212 insertions(+), 86 deletions(-) create mode 100644 llvm/lib/Target/EVM/EVMLowerJumpUnless.cpp diff --git a/llvm/lib/Target/EVM/CMakeLists.txt b/llvm/lib/Target/EVM/CMakeLists.txt index 0d7a1da7792c..fd4efc22f449 100644 --- a/llvm/lib/Target/EVM/CMakeLists.txt +++ b/llvm/lib/Target/EVM/CMakeLists.txt @@ -29,6 +29,7 @@ add_llvm_target(EVMCodeGen EVMInstrInfo.cpp EVMLinkRuntime.cpp EVMLowerIntrinsics.cpp + EVMLowerJumpUnless.cpp EVMMachineCFGInfo.cpp EVMMachineFunctionInfo.cpp EVMMCInstLower.cpp diff --git a/llvm/lib/Target/EVM/EVM.h b/llvm/lib/Target/EVM/EVM.h index 5c1ea833fe3d..3e7bcd5e3df6 100644 --- a/llvm/lib/Target/EVM/EVM.h +++ b/llvm/lib/Target/EVM/EVM.h @@ -58,6 +58,7 @@ FunctionPass *createEVMSingleUseExpression(); FunctionPass *createEVMSplitCriticalEdges(); FunctionPass *createEVMStackify(); FunctionPass *createEVMBPStackification(); +FunctionPass *createEVMLowerJumpUnless(); // PassRegistry initialization declarations. void initializeEVMCodegenPreparePass(PassRegistry &); @@ -73,6 +74,7 @@ void initializeEVMStackifyPass(PassRegistry &); void initializeEVMBPStackificationPass(PassRegistry &); void initializeEVMAAWrapperPassPass(PassRegistry &); void initializeEVMExternalAAWrapperPass(PassRegistry &); +void initializeEVMLowerJumpUnlessPass(PassRegistry &); struct EVMLinkRuntimePass : PassInfoMixin { EVMLinkRuntimePass() = default; diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.cpp b/llvm/lib/Target/EVM/EVMInstrInfo.cpp index 9ba3d73438bf..9a4d14e5d61d 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.cpp +++ b/llvm/lib/Target/EVM/EVMInstrInfo.cpp @@ -81,12 +81,8 @@ bool EVMInstrInfo::analyzeBranch(MachineBasicBlock &MBB, TBB = nullptr; FBB = nullptr; Cond.clear(); - - const auto *MFI = MBB.getParent()->getInfo(); - if (MFI->getIsStackified()) { - LLVM_DEBUG(dbgs() << "Can't analyze terminators in stackified code"); - return true; - } + const bool IsStackified = + MBB.getParent()->getInfo()->getIsStackified(); // Iterate backwards and analyze all terminators. MachineBasicBlock::reverse_iterator I = MBB.rbegin(), E = MBB.rend(); @@ -119,23 +115,14 @@ bool EVMInstrInfo::analyzeBranch(MachineBasicBlock &MBB, FBB = TBB; TBB = I->getOperand(0).getMBB(); - // Put the "use" of the condition into Cond[0]. - const MachineOperand &UseMO = I->getOperand(1); - Cond.push_back(UseMO); - - // reverseBranch needs the instruction which feeds the branch, but only - // supports comparisons. See if we can find one. - for (MachineBasicBlock::reverse_iterator CI = I; CI != E; ++CI) { - // If it is the right comparison, put its result into Cond[1]. - // TODO: This info is required for branch reversing, but this - // is not yet implemented. - if (CI->isCompare()) { - const MachineOperand &DefMO = CI->getOperand(0); - if (DefMO.getReg() == UseMO.getReg()) - Cond.push_back(DefMO); - // Only give it one shot, this should be enough. - break; - } + // Set from which instruction this condition comes from. It is needed for + // reversing and inserting of branches. + Cond.push_back(MachineOperand::CreateImm( + I->getOpcode() == EVM::JUMPI || I->getOpcode() == EVM::PseudoJUMPI)); + if (!IsStackified) { + // Put the "use" of the condition into Cond[1]. + const MachineOperand &UseMO = I->getOperand(1); + Cond.push_back(UseMO); } } else if (I->isTerminator()) { // Return, indirect branch, fall-through, or some other unrecognized @@ -193,27 +180,39 @@ unsigned EVMInstrInfo::insertBranch(MachineBasicBlock &MBB, // The number of instructions inserted. unsigned InstrCount = 0; - - const bool IsUncondBranch = Cond.empty(); - const bool IsCondBranch = - (Cond.size() == 1 && Cond[0].isReg()) || - (Cond.size() == 2 && Cond[0].isReg() && Cond[1].isReg()); + const bool IsStackified = + MBB.getParent()->getInfo()->getIsStackified(); + unsigned UncondOpc = !IsStackified ? EVM::JUMP : EVM::PseudoJUMP; // Insert a branch to the "true" destination. assert(TBB && "A branch must have a destination"); - if (IsUncondBranch) - BuildMI(&MBB, DL, get(EVM::JUMP)).addMBB(TBB); - else if (IsCondBranch) - BuildMI(&MBB, DL, get(EVM::JUMPI)).addMBB(TBB).add(Cond[0]); - else - llvm_unreachable("Unexpected branching condition"); + if (Cond.empty()) { + BuildMI(&MBB, DL, get(UncondOpc)).addMBB(TBB); + } else { + // Destinguish between stackified and non-stackified instructions. + unsigned CondOpc = 0; + if (!IsStackified) + CondOpc = Cond[0].getImm() ? EVM::JUMPI : EVM::JUMP_UNLESS; + else + CondOpc = Cond[0].getImm() ? EVM::PseudoJUMPI : EVM::PseudoJUMP_UNLESS; + + auto NewMI = BuildMI(&MBB, DL, get(CondOpc)).addMBB(TBB); + + // Add a condition operand, if we are not in stackified form. + if (!IsStackified) { + assert( + Cond.size() == 2 && + "Unexpected number of conditional operands in non-stackified code"); + NewMI.add(Cond[1]); + } + } ++InstrCount; // If there is also a "false" destination, insert another branch. if (FBB) { assert(!Cond.empty() && "Unconditional branch can't have two destinations"); - BuildMI(&MBB, DL, get(EVM::JUMP)).addMBB(FBB); + BuildMI(&MBB, DL, get(UncondOpc)).addMBB(FBB); ++InstrCount; } @@ -222,16 +221,9 @@ unsigned EVMInstrInfo::insertBranch(MachineBasicBlock &MBB, bool EVMInstrInfo::reverseBranchCondition( SmallVectorImpl &Cond) const { - // TODO: CPR-1557. Try to add support for branch reversing. The main problem - // is that it may require insertion of additional instructions in the BB. - // For example, - // - // NE $3, $2, $1 - // - // should be transformed into - // - // EQ $3, $2, $1 - // ISZERO $4, $3 - - return true; + assert((Cond.size() == 1 || Cond.size() == 2) && + "Unexpected number of conditional operands"); + assert(Cond[0].isImm() && "Unexpected condition type"); + Cond.front() = MachineOperand::CreateImm(!Cond.front().getImm()); + return false; } diff --git a/llvm/lib/Target/EVM/EVMInstrInfo.td b/llvm/lib/Target/EVM/EVMInstrInfo.td index f4af90a9081c..9a82984f5ba2 100644 --- a/llvm/lib/Target/EVM/EVMInstrInfo.td +++ b/llvm/lib/Target/EVM/EVMInstrInfo.td @@ -407,7 +407,9 @@ let isBranch = 1, isTerminator = 1 in { defm JUMPI : I<(outs), (ins jmptarget:$dst, GPR:$cond), [(brcond GPR:$cond, bb:$dst)], "JUMPI", " $dst, $cond", 0x57, 10>; +def JUMP_UNLESS : EVMPseudo<(outs), (ins jmptarget:$dst, GPR:$cond), []>; def PseudoJUMPI : EVMPseudo<(outs), (ins jmptarget:$dst), [], true>; +def PseudoJUMP_UNLESS : EVMPseudo<(outs), (ins jmptarget:$dst), [], true>; let isBarrier = 1 in { defm JUMP @@ -416,6 +418,19 @@ def PseudoJUMP : EVMPseudo<(outs), (ins jmptarget:$dst), [], true>; } // isBarrier = 1 } // isBranch = 1, isTerminator = 1 +def : Pat<(brcond (setcc GPR:$src, 0, SETEQ), bb:$dst), + (JUMP_UNLESS bb:$dst, GPR:$src)>; +def : Pat<(brcond (setcc GPR:$rs0, GPR:$rs1, SETNE), bb:$dst), + (JUMP_UNLESS bb:$dst, (EQ GPR:$rs0, GPR:$rs1))>; +def : Pat<(brcond (setcc GPR:$rs0, GPR:$rs1, SETGE), bb:$dst), + (JUMP_UNLESS bb:$dst, (LT GPR:$rs0, GPR:$rs1))>; +def : Pat<(brcond (setcc GPR:$rs0, GPR:$rs1, SETLE), bb:$dst), + (JUMP_UNLESS bb:$dst, (GT GPR:$rs0, GPR:$rs1))>; +def : Pat<(brcond (setcc GPR:$rs0, GPR:$rs1, SETULE), bb:$dst), + (JUMP_UNLESS bb:$dst, (UGT GPR:$rs0, GPR:$rs1))>; +def : Pat<(brcond (setcc GPR:$rs0, GPR:$rs1, SETUGE), bb:$dst), + (JUMP_UNLESS bb:$dst, (ULT GPR:$rs0, GPR:$rs1))>; + // This isn't really a control flow instruction, but it should be used to mark // destination of jump instructions. defm JUMPDEST : I<(outs), (ins), [], "JUMPDEST", "", 0x5B, 1>; diff --git a/llvm/lib/Target/EVM/EVMLowerJumpUnless.cpp b/llvm/lib/Target/EVM/EVMLowerJumpUnless.cpp new file mode 100644 index 000000000000..2e85a5157e70 --- /dev/null +++ b/llvm/lib/Target/EVM/EVMLowerJumpUnless.cpp @@ -0,0 +1,107 @@ +//===----- EVMLowerJumpUnless.cpp - Lower jump_unless ----------*- C++ -*--===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This pass lowers jump_unless into iszero and jumpi instructions. +// +//===----------------------------------------------------------------------===// + +#include "EVM.h" +#include "EVMMachineFunctionInfo.h" +#include "EVMSubtarget.h" +#include "MCTargetDesc/EVMMCTargetDesc.h" +#include "llvm/CodeGen/MachineFunctionPass.h" +#include "llvm/CodeGen/MachineInstrBuilder.h" + +using namespace llvm; + +#define DEBUG_TYPE "evm-lower-jump-unless" +#define EVM_LOWER_JUMP_UNLESS_NAME "EVM Lower jump_unless" + +namespace { +class EVMLowerJumpUnless final : public MachineFunctionPass { +public: + static char ID; + + EVMLowerJumpUnless() : MachineFunctionPass(ID) { + initializeEVMLowerJumpUnlessPass(*PassRegistry::getPassRegistry()); + } + + StringRef getPassName() const override { return EVM_LOWER_JUMP_UNLESS_NAME; } + + void getAnalysisUsage(AnalysisUsage &AU) const override { + AU.setPreservesCFG(); + MachineFunctionPass::getAnalysisUsage(AU); + } + + bool runOnMachineFunction(MachineFunction &MF) override; +}; +} // end anonymous namespace + +char EVMLowerJumpUnless::ID = 0; + +INITIALIZE_PASS(EVMLowerJumpUnless, DEBUG_TYPE, EVM_LOWER_JUMP_UNLESS_NAME, + false, false) + +FunctionPass *llvm::createEVMLowerJumpUnless() { + return new EVMLowerJumpUnless(); +} + +// Lower jump_unless into iszero and jumpi instructions. This instruction +// can only be present in non-stackified functions. +static void lowerJumpUnless(MachineInstr &MI, const EVMInstrInfo *TII, + const bool IsStackified, MachineRegisterInfo &MRI) { + assert(!IsStackified && "Found jump_unless in stackified function"); + assert(MI.getNumExplicitOperands() == 2 && + "Unexpected number of operands in jump_unless"); + auto NewReg = MRI.createVirtualRegister(&EVM::GPRRegClass); + BuildMI(*MI.getParent(), MI, MI.getDebugLoc(), TII->get(EVM::ISZERO), NewReg) + .add(MI.getOperand(1)); + BuildMI(*MI.getParent(), MI, MI.getDebugLoc(), TII->get(EVM::JUMPI)) + .add(MI.getOperand(0)) + .addReg(NewReg); +} + +// Lower pseudo jump_unless into iszero and jumpi instructions. This pseudo +// instruction can only be present in stackified functions. +static void lowerPseudoJumpUnless(MachineInstr &MI, const EVMInstrInfo *TII, + const bool IsStackified) { + assert(IsStackified && "Found pseudo jump_unless in non-stackified function"); + assert(MI.getNumExplicitOperands() == 1 && + "Unexpected number of operands in pseudo jump_unless"); + BuildMI(*MI.getParent(), MI, MI.getDebugLoc(), TII->get(EVM::ISZERO_S)); + BuildMI(*MI.getParent(), MI, MI.getDebugLoc(), TII->get(EVM::PseudoJUMPI)) + .add(MI.getOperand(0)); +} + +bool EVMLowerJumpUnless::runOnMachineFunction(MachineFunction &MF) { + LLVM_DEBUG({ + dbgs() << "********** Lower jump_unless instructions **********\n" + << "********** Function: " << MF.getName() << '\n'; + }); + + MachineRegisterInfo &MRI = MF.getRegInfo(); + const auto *TII = MF.getSubtarget().getInstrInfo(); + const bool IsStackified = + MF.getInfo()->getIsStackified(); + + bool Changed = false; + for (MachineBasicBlock &MBB : MF) { + for (auto &MI : make_early_inc_range(MBB)) { + if (MI.getOpcode() == EVM::PseudoJUMP_UNLESS) + lowerPseudoJumpUnless(MI, TII, IsStackified); + else if (MI.getOpcode() == EVM::JUMP_UNLESS) + lowerJumpUnless(MI, TII, IsStackified, MRI); + else + continue; + + MI.eraseFromParent(); + Changed = true; + } + } + return Changed; +} diff --git a/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp b/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp index 8a2ad98da818..72b3cb2f0033 100644 --- a/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp +++ b/llvm/lib/Target/EVM/EVMMachineCFGInfo.cpp @@ -118,7 +118,8 @@ void EVMMachineCFGInfo::collectTerminatorsInfo(const TargetInstrInfo *TII, assert(FBB); } Info->ExitType = MBBExitType::ConditionalBranch; - assert(Cond[0].isIdenticalTo(CondBr->getOperand(1))); + assert(Cond.size() == 2 && "Unexpected number of conditional operands"); + assert(Cond[1].isIdenticalTo(CondBr->getOperand(1))); Info->BranchInfo.Conditional = {&CondBr->getOperand(1), TBB, FBB, CondBr, UncondBr}; } diff --git a/llvm/lib/Target/EVM/EVMStackModel.cpp b/llvm/lib/Target/EVM/EVMStackModel.cpp index ca67872d8b30..f39444403fda 100644 --- a/llvm/lib/Target/EVM/EVMStackModel.cpp +++ b/llvm/lib/Target/EVM/EVMStackModel.cpp @@ -148,6 +148,7 @@ void EVMStackModel::createOperation(MachineInstr &MI, case EVM::RET: case EVM::JUMP: case EVM::JUMPI: + case EVM::JUMP_UNLESS: // These instructions are handled separately. return; case EVM::COPY_I256: diff --git a/llvm/lib/Target/EVM/EVMStackify.cpp b/llvm/lib/Target/EVM/EVMStackify.cpp index d5ff664dc712..65605891ae83 100644 --- a/llvm/lib/Target/EVM/EVMStackify.cpp +++ b/llvm/lib/Target/EVM/EVMStackify.cpp @@ -366,6 +366,7 @@ void StackModel::handleInstruction(MachineInstr *MI) { handleJump(MI); return; case EVM::JUMPI: + case EVM::JUMP_UNLESS: handleCondJump(MI); return; case EVM::ARGUMENT: @@ -720,7 +721,15 @@ void StackModel::handleArgument(MachineInstr *MI) { void StackModel::handleLStackAtJump(MachineBasicBlock *MBB, MachineInstr *MI, const Register &Reg) { - assert(MI->getOpcode() == EVM::JUMP || MI->getOpcode() == EVM::JUMPI); + unsigned PseudoJumpOpc = 0; + if (MI->getOpcode() == EVM::JUMP) + PseudoJumpOpc = EVM::PseudoJUMP; + else if (MI->getOpcode() == EVM::JUMPI) + PseudoJumpOpc = EVM::PseudoJUMPI; + else if (MI->getOpcode() == EVM::JUMP_UNLESS) + PseudoJumpOpc = EVM::PseudoJUMP_UNLESS; + else + llvm_unreachable("Unexpected jump instruction"); // If the condition register is in the L-stack, we need to move it to // the bottom of the L-stack. After that we should clean clean the L-stack. @@ -731,8 +740,6 @@ void StackModel::handleLStackAtJump(MachineBasicBlock *MBB, MachineInstr *MI, // Insert pseudo jump instruciton that will be replaced with PUSH and JUMP // instructions in AsmPrinter. ToErase.push_back(MI); - unsigned PseudoJumpOpc = - MI->getOpcode() == EVM::JUMP ? EVM::PseudoJUMP : EVM::PseudoJUMPI; BuildMI(*MI->getParent(), MI, DebugLoc(), TII->get(PseudoJumpOpc)) .addMBB(MBB); } @@ -988,8 +995,8 @@ void StackModel::stackifyInstruction(MachineInstr *MI) { unsigned RegOpcode = MI->getOpcode(); if (RegOpcode == EVM::PUSH_LABEL || RegOpcode == EVM::PseudoJUMP || - RegOpcode == EVM::PseudoJUMPI || RegOpcode == EVM::PseudoCALL || - RegOpcode == EVM::PseudoRET) + RegOpcode == EVM::PseudoJUMPI || RegOpcode == EVM::PseudoJUMP_UNLESS || + RegOpcode == EVM::PseudoCALL || RegOpcode == EVM::PseudoRET) return; // Remove register operands. diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index 91a7e8abc9ac..7f50d3ca829a 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -66,9 +66,10 @@ void EVMStackifyCodeEmitter::CodeEmitter::enterMBB(MachineBasicBlock *MBB, void EVMStackifyCodeEmitter::CodeEmitter::emitInst(const MachineInstr *MI) { unsigned Opc = MI->getOpcode(); - assert(Opc != EVM::JUMP && Opc != EVM::JUMPI && Opc != EVM::ARGUMENT && - Opc != EVM::RET && Opc != EVM::CONST_I256 && Opc != EVM::COPY_I256 && - Opc != EVM::FCALL && "Unexpected instruction"); + assert(Opc != EVM::JUMP && Opc != EVM::JUMPI && Opc != EVM::JUMP_UNLESS && + Opc != EVM::ARGUMENT && Opc != EVM::RET && Opc != EVM::CONST_I256 && + Opc != EVM::COPY_I256 && Opc != EVM::FCALL && + "Unexpected instruction"); size_t NumInputs = MI->getNumExplicitOperands() - MI->getNumExplicitDefs(); assert(StackHeight >= NumInputs && "Not enough operands on the stack"); @@ -187,13 +188,16 @@ void EVMStackifyCodeEmitter::CodeEmitter::emitUncondJump( void EVMStackifyCodeEmitter::CodeEmitter::emitCondJump( const MachineInstr *MI, MachineBasicBlock *Target) { - assert(MI->getOpcode() == EVM::JUMPI && - "Unexpected conditional jump instruction"); + assert(MI->getOpcode() == EVM::JUMPI || + MI->getOpcode() == EVM::JUMP_UNLESS && + "Unexpected conditional jump instruction"); assert(StackHeight > 0 && "Expected at least one operand on the stack"); StackHeight -= 1; - auto NewMI = BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), - TII->get(EVM::PseudoJUMPI)) - .addMBB(Target); + auto NewMI = + BuildMI(*CurMBB, CurMBB->end(), MI->getDebugLoc(), + TII->get(MI->getOpcode() == EVM::JUMPI ? EVM::PseudoJUMPI + : EVM::PseudoJUMP_UNLESS)) + .addMBB(Target); verify(NewMI); } diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.cpp b/llvm/lib/Target/EVM/EVMTargetMachine.cpp index 4d19717d761c..e3d1fe15bbd2 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.cpp +++ b/llvm/lib/Target/EVM/EVMTargetMachine.cpp @@ -61,6 +61,7 @@ extern "C" LLVM_EXTERNAL_VISIBILITY void LLVMInitializeEVMTarget() { initializeEVMBPStackificationPass(PR); initializeEVMAAWrapperPassPass(PR); initializeEVMExternalAAWrapperPass(PR); + initializeEVMLowerJumpUnlessPass(PR); } static std::string computeDataLayout() { @@ -186,6 +187,7 @@ class EVMPassConfig final : public TargetPassConfig { bool addInstSelector() override; void addPostRegAlloc() override; void addPreEmitPass() override; + void addPreEmitPass2() override; }; } // namespace @@ -255,9 +257,17 @@ void EVMPassConfig::addPreEmitPass() { } else { addPass(createEVMBPStackification()); } + + // Optimize branch instructions after stackification. This is done again + // here, since EVMSplitCriticalEdges may introduce new BBs that could + // contain only branches after stackification. + if (getOptLevel() != CodeGenOpt::None) + addPass(&BranchFolderPassID); } } +void EVMPassConfig::addPreEmitPass2() { addPass(createEVMLowerJumpUnless()); } + TargetPassConfig *EVMTargetMachine::createPassConfig(PassManagerBase &PM) { return new EVMPassConfig(*this, PM); } diff --git a/llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll b/llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll index 91bfcfe76481..e3af7a77578f 100644 --- a/llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll +++ b/llvm/test/CodeGen/EVM/branch-folder-after-stackification.ll @@ -12,32 +12,26 @@ define i256 @test(i256 %arg) { ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: DUP3 ; CHECK-NEXT: SLT -; CHECK-NEXT: PUSH4 @.BB0_1 -; CHECK-NEXT: JUMPI +; CHECK-NEXT: ISZERO ; CHECK-NEXT: PUSH4 @.BB0_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_1: -; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMPI +; CHECK-NEXT: ; %bb.1: ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: PUSH1 10 -; CHECK-NEXT: SWAP3 ; CHECK-NEXT: PUSH4 @.BB0_3 ; CHECK-NEXT: JUMP ; CHECK-NEXT: .BB0_2: ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: PUSH1 20 -; CHECK-NEXT: SWAP3 ; CHECK-NEXT: .BB0_3: ; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: SWAP3 ; CHECK-NEXT: SGT -; CHECK-NEXT: PUSH4 @.BB0_4 -; CHECK-NEXT: JUMPI -; CHECK-NEXT: ; %bb.6: +; CHECK-NEXT: ISZERO ; CHECK-NEXT: PUSH4 @.BB0_5 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_4: -; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMPI +; CHECK-NEXT: ; %bb.4: ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: POP ; CHECK-NEXT: PUSH1 5 diff --git a/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll b/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll index e8fd44212c47..22bd43eae755 100644 --- a/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll +++ b/llvm/test/CodeGen/EVM/machine-sink-cheap-instructions.ll @@ -12,13 +12,9 @@ define i256 @test1(i256 %arg) { ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: PUSH1 10 ; CHECK-NEXT: EQ -; CHECK-NEXT: ISZERO -; CHECK-NEXT: PUSH4 @.BB0_1 -; CHECK-NEXT: JUMPI ; CHECK-NEXT: PUSH4 @.BB0_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB0_1: -; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMPI +; CHECK-NEXT: ; %bb.1: ; CHECK-NEXT: PUSH0 ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP @@ -50,13 +46,9 @@ define i256 @test2(i256 %arg) { ; CHECK-NEXT: JUMPDEST ; CHECK-NEXT: PUSH1 10 ; CHECK-NEXT: EQ -; CHECK-NEXT: ISZERO -; CHECK-NEXT: PUSH4 @.BB1_1 -; CHECK-NEXT: JUMPI ; CHECK-NEXT: PUSH4 @.BB1_2 -; CHECK-NEXT: JUMP -; CHECK-NEXT: .BB1_1: -; CHECK-NEXT: JUMPDEST +; CHECK-NEXT: JUMPI +; CHECK-NEXT: ; %bb.1: ; CHECK-NEXT: ADDRESS ; CHECK-NEXT: SWAP1 ; CHECK-NEXT: JUMP From 8f52e952965888d1255a66d4e65fed61d845f938 Mon Sep 17 00:00:00 2001 From: Vladimir Radosavljevic Date: Tue, 28 Jan 2025 12:31:48 +0100 Subject: [PATCH 42/42] [EVM] Remove MachineBlockPlacement run Since we disabled MachineBlockPlacement in EVMPassConfig::addPostRegAlloc, adding this pass after that has no effect, since it won't be run. Signed-off-by: Vladimir Radosavljevic --- llvm/lib/Target/EVM/EVMTargetMachine.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/llvm/lib/Target/EVM/EVMTargetMachine.cpp b/llvm/lib/Target/EVM/EVMTargetMachine.cpp index e3d1fe15bbd2..7e1271de6f22 100644 --- a/llvm/lib/Target/EVM/EVMTargetMachine.cpp +++ b/llvm/lib/Target/EVM/EVMTargetMachine.cpp @@ -247,7 +247,6 @@ void EVMPassConfig::addPreEmitPass() { // FIXME: enable all the passes below, but the Stackify with EVMKeepRegisters. if (!EVMKeepRegisters) { addPass(createEVMSplitCriticalEdges()); - addPass(&MachineBlockPlacementID); addPass(createEVMOptimizeLiveIntervals()); addPass(createEVMSingleUseExpression()); if (EVMUseLocalStakify) {