From 9d3038bd56a93a3445c9c5e5060830d9e0695ee1 Mon Sep 17 00:00:00 2001 From: Dmitry Borisenkov Date: Tue, 21 Jan 2025 16:26:21 +0200 Subject: [PATCH] [EVM] Improve Operation implementation --- llvm/lib/Target/EVM/EVMStackDebug.cpp | 35 ++------ .../Target/EVM/EVMStackLayoutGenerator.cpp | 21 +++-- llvm/lib/Target/EVM/EVMStackModel.cpp | 46 ++++++----- llvm/lib/Target/EVM/EVMStackModel.h | 43 +++++----- .../lib/Target/EVM/EVMStackifyCodeEmitter.cpp | 80 +++++++++---------- llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h | 6 +- 6 files changed, 109 insertions(+), 122 deletions(-) diff --git a/llvm/lib/Target/EVM/EVMStackDebug.cpp b/llvm/lib/Target/EVM/EVMStackDebug.cpp index 98702de4fcee..de7a20d351af 100644 --- a/llvm/lib/Target/EVM/EVMStackDebug.cpp +++ b/llvm/lib/Target/EVM/EVMStackDebug.cpp @@ -19,11 +19,6 @@ using namespace llvm; -template struct Overload : Ts... { - using Ts::operator()...; -}; -template Overload(Ts...) -> Overload; - std::string llvm::stackToString(const Stack &S) { std::string Result("[ "); for (const auto *Slot : S) @@ -55,32 +50,14 @@ void StackLayoutPrinter::operator()() { void StackLayoutPrinter::printBlock(MachineBasicBlock const &Block) { OS << "Block" << getBlockId(Block) << " [\n"; OS << stackToString(Layout.getMBBEntryLayout(&Block)) << "\n"; - for (auto const &Operation : StackModel.getOperations(&Block)) { + for (auto const &Op : StackModel.getOperations(&Block)) { OS << "\n"; - Stack EntryLayout = Layout.getOperationEntryLayout(&Operation); + Stack EntryLayout = Layout.getOperationEntryLayout(&Op); OS << stackToString(EntryLayout) << "\n"; - std::visit(Overload{[&](FunctionCall const &Call) { - const MachineOperand *Callee = - Call.MI->explicit_uses().begin(); - OS << Callee->getGlobal()->getName(); - }, - [&](BuiltinCall const &Call) { - OS << getInstName(Call.MI); - }, - [&](Assignment const &Assignment) { - OS << "Assignment("; - for (const auto *Var : Assignment.Variables) - OS << printReg(Var->getReg(), 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.append(Operation.Output); + OS << Op.toString() << "\n"; + assert(Op.getInput().size() <= EntryLayout.size()); + EntryLayout.resize(EntryLayout.size() - Op.getInput().size()); + EntryLayout.append(Op.getOutput()); OS << stackToString(EntryLayout) << "\n"; } OS << "\n"; diff --git a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp index 29c3ce825870..70aef612ba9f 100644 --- a/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp +++ b/llvm/lib/Target/EVM/EVMStackLayoutGenerator.cpp @@ -261,10 +261,9 @@ std::unique_ptr EVMStackLayoutGenerator::run() { } Stack EVMStackLayoutGenerator::propagateStackThroughOperation( - Stack ExitStack, const Operation &Operation, - bool AggressiveStackCompression) { + Stack ExitStack, const Operation &Op, bool AggressiveStackCompression) { // Enable aggressive stack compression for recursive calls. - if (std::holds_alternative(Operation.Operation)) + if (Op.isFunctionCall()) // TODO: compress stack for recursive functions. AggressiveStackCompression = false; @@ -277,25 +276,25 @@ Stack EVMStackLayoutGenerator::propagateStackThroughOperation( // 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); + createIdealLayout(Op.getOutput(), ExitStack, generateSlotOnTheFly); // Make sure the resulting previous slots do not overlap with any assignmed // variables. - if (auto const *Assign = std::get_if(&Operation.Operation)) + if (Op.isAssignment()) for (auto *StackSlot : IdealStack) if (const auto *VarSlot = dyn_cast(StackSlot)) - assert(!is_contained(Assign->Variables, VarSlot)); + assert(!is_contained(Op.getOutput(), VarSlot)); // Since stack+Operation.output can be easily shuffled to ExitLayout, the // desired layout before the operation is stack+Operation.input; - IdealStack.append(Operation.Input); + IdealStack.append(Op.getInput()); // 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. - OperationEntryLayoutMap[&Operation] = IdealStack; + OperationEntryLayoutMap[&Op] = IdealStack; // Remove anything from the stack top that can be freely generated or dupped // from deeper on the stack. @@ -751,10 +750,10 @@ void EVMStackLayoutGenerator::addJunksToStackBottom( EntryTmp.append(MBBEntryLayoutMap.at(MBB)); MBBEntryLayoutMap[MBB] = std::move(EntryTmp); - for (const Operation &Operation : StackModel.getOperations(MBB)) { + for (const Operation &Op : StackModel.getOperations(MBB)) { Stack OpEntryTmp(NumJunk, EVMStackModel::getJunkSlot()); - OpEntryTmp.append(OperationEntryLayoutMap.at(&Operation)); - OperationEntryLayoutMap[&Operation] = std::move(OpEntryTmp); + OpEntryTmp.append(OperationEntryLayoutMap.at(&Op)); + OperationEntryLayoutMap[&Op] = std::move(OpEntryTmp); } Stack ExitTmp(NumJunk, EVMStackModel::getJunkSlot()); diff --git a/llvm/lib/Target/EVM/EVMStackModel.cpp b/llvm/lib/Target/EVM/EVMStackModel.cpp index ca67872d8b30..1a479859bec8 100644 --- a/llvm/lib/Target/EVM/EVMStackModel.cpp +++ b/llvm/lib/Target/EVM/EVMStackModel.cpp @@ -29,8 +29,7 @@ static const Function *getCalledFunction(const MachineInstr &MI) { } return nullptr; } -// TODO: make it static once Operation gets rid of std::variant. -std::string llvm::getInstName(const MachineInstr *MI) { +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(); @@ -48,6 +47,24 @@ std::string TemporarySlot::toString() const { OS << "TMP[" << getInstName(MI) << ", " << std::to_string(Index) + "]"; return std::string(S.str()); } +std::string Operation::toString() const { + if (isFunctionCall()) { + const MachineOperand *Callee = MI->explicit_uses().begin(); + return Callee->getGlobal()->getName().str(); + } + if (isBuiltinCall()) + return getInstName(MI); + + assert(isAssignment()); + SmallString<128> S; + raw_svector_ostream OS(S); + OS << "Assignment("; + for (const auto *S : Output) + OS << printReg(cast(S)->getReg(), nullptr, 0, nullptr) + << ", "; + OS << ")"; + return std::string(S); +} EVMStackModel::EVMStackModel(MachineFunction &MF, const LiveIntervals &LIS) : MF(MF), LIS(LIS) { @@ -128,22 +145,18 @@ void EVMStackModel::createOperation(MachineInstr &MI, 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) + const auto *Func = cast(MO.getGlobal()); + if (!Func->hasFnAttribute(Attribute::NoReturn)) Input.push_back(getFunctionCallReturnLabelSlot(&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}}); + Ops.emplace_back(Operation::FunctionCall, std::move(Input), + getInstrOutput(MI), &MI); } break; case EVM::RET: case EVM::JUMP: @@ -165,21 +178,19 @@ void EVMStackModel::createOperation(MachineInstr &MI, return; } break; default: { - Ops.emplace_back( - Operation{getInstrInput(MI), getInstrOutput(MI), BuiltinCall{&MI}}); + Ops.emplace_back(Operation::BuiltinCall, getInstrInput(MI), + getInstrOutput(MI), &MI); } break; } // Create CFG::Assignment object for the MI. Stack Input, Output; - 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(getLiteralSlot(std::move(Imm))); Output.push_back(getVariableSlot(DefReg)); - Variables.push_back(getVariableSlot(DefReg)); } break; case EVM::DATASIZE: case EVM::DATAOFFSET: @@ -188,7 +199,6 @@ void EVMStackModel::createOperation(MachineInstr &MI, MCSymbol *Sym = MI.getOperand(1).getMCSymbol(); 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 @@ -196,7 +206,6 @@ void EVMStackModel::createOperation(MachineInstr &MI, Input = getInstrInput(MI); const Register DefReg = MI.getOperand(0).getReg(); Output.push_back(getVariableSlot(DefReg)); - Variables.push_back(getVariableSlot(DefReg)); } break; default: { unsigned ArgsNumber = 0; @@ -205,15 +214,14 @@ void EVMStackModel::createOperation(MachineInstr &MI, const Register Reg = MO.getReg(); Input.push_back(getTemporarySlot(&MI, ArgsNumber++)); Output.push_back(getVariableSlot(Reg)); - Variables.push_back(getVariableSlot(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)}}); + Ops.emplace_back(Operation::Assignment, std::move(Input), std::move(Output), + &MI); } Stack EVMStackModel::getReturnArguments(const MachineInstr &MI) const { diff --git a/llvm/lib/Target/EVM/EVMStackModel.h b/llvm/lib/Target/EVM/EVMStackModel.h index d716b2208431..dce7765bcaf2 100644 --- a/llvm/lib/Target/EVM/EVMStackModel.h +++ b/llvm/lib/Target/EVM/EVMStackModel.h @@ -36,8 +36,6 @@ namespace llvm { class MachineFunction; class MachineBasicBlock; -std::string getInstName(const MachineInstr *MI); - class StackSlot { public: enum SlotKind { @@ -203,28 +201,33 @@ class JunkSlot final : public StackSlot { /// The stack top is the last element of the vector. using Stack = SmallVector; -struct BuiltinCall { +class Operation { +public: + enum OpType { BuiltinCall, FunctionCall, Assignment }; + +private: + OpType Type; + // 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; + // The emulated machine instruction. MachineInstr *MI = nullptr; -}; -struct FunctionCall { - const MachineInstr *MI; - size_t NumArguments = 0; -}; +public: + Operation(OpType Type, Stack Input, Stack Output, MachineInstr *MI) + : Type(Type), Input(std::move(Input)), Output(std::move(Output)), MI(MI) { + } -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; -}; + const Stack &getInput() const { return Input; } + const Stack &getOutput() const { return Output; } + MachineInstr *getMachineInstr() const { return MI; } -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; + bool isBuiltinCall() const { return Type == BuiltinCall; } + bool isFunctionCall() const { return Type == FunctionCall; } + bool isAssignment() const { return Type == Assignment; } + + std::string toString() const; }; class EVMStackModel { diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp index 91a7e8abc9ac..2b34e4107185 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.cpp @@ -21,11 +21,6 @@ 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"); @@ -239,51 +234,55 @@ void EVMStackifyCodeEmitter::adjustStackForInst(const MachineInstr *MI, assert(Emitter.stackHeight() == CurrentStack.size()); } -void EVMStackifyCodeEmitter::visitCall(const FunctionCall &Call) { - size_t NumArgs = getCallArgCount(Call.MI); +void EVMStackifyCodeEmitter::processCall(const Operation &Call) { + assert(Call.isFunctionCall()); + auto *MI = Call.getMachineInstr(); + size_t NumArgs = getCallArgCount(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.MI)) { + if (callWillReturn(MI)) { [[maybe_unused]] const auto *ReturnLabelSlot = dyn_cast( CurrentStack[CurrentStack.size() - NumArgs]); - assert(ReturnLabelSlot && ReturnLabelSlot->getCall() == Call.MI); + assert(ReturnLabelSlot && ReturnLabelSlot->getCall() == MI); } // Emit call. - Emitter.emitFuncCall(Call.MI); - adjustStackForInst(Call.MI, NumArgs); + Emitter.emitFuncCall(MI); + adjustStackForInst(MI, NumArgs); } -void EVMStackifyCodeEmitter::visitInst(const BuiltinCall &Call) { - size_t NumArgs = - Call.MI->getNumExplicitOperands() - Call.MI->getNumExplicitDefs(); +void EVMStackifyCodeEmitter::processInst(const Operation &Call) { + assert(Call.isBuiltinCall()); + auto *MI = Call.getMachineInstr(); + size_t NumArgs = MI->getNumExplicitOperands() - 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.MI); - adjustStackForInst(Call.MI, NumArgs); + Emitter.emitInst(MI); + adjustStackForInst(MI, NumArgs); } -void EVMStackifyCodeEmitter::visitAssign(const Assignment &Assignment) { +void EVMStackifyCodeEmitter::processAssign(const Operation &Assignment) { + assert(Assignment.isAssignment()); assert(Emitter.stackHeight() == CurrentStack.size()); // Invalidate occurrences of the assigned variables. for (auto *&CurrentSlot : CurrentStack) if (const auto *VarSlot = dyn_cast(CurrentSlot)) - if (is_contained(Assignment.Variables, VarSlot)) + if (is_contained(Assignment.getOutput(), VarSlot)) CurrentSlot = EVMStackModel::getJunkSlot(); // Assign variables to current stack top. - assert(CurrentStack.size() >= Assignment.Variables.size()); - llvm::copy(Assignment.Variables, - CurrentStack.end() - Assignment.Variables.size()); + assert(CurrentStack.size() >= Assignment.getOutput().size()); + llvm::copy(Assignment.getOutput(), + CurrentStack.end() - Assignment.getOutput().size()); } bool EVMStackifyCodeEmitter::areLayoutsCompatible(const Stack &SourceStack, @@ -382,8 +381,7 @@ void EVMStackifyCodeEmitter::createOperationLayout(const Operation &Op) { // 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->MI->isCommutable()) { + if (Op.isBuiltinCall() && Op.getMachineInstr()->isCommutable()) { // Get the stack layout before the instruction. const Stack &DefaultTargetStack = Layout.getOperationEntryLayout(&Op); size_t DefaultCost = @@ -410,14 +408,15 @@ void EVMStackifyCodeEmitter::createOperationLayout(const Operation &Op) { // Assert that we have the inputs of the Operation on stack top. assert(CurrentStack.size() == Emitter.stackHeight()); - assert(CurrentStack.size() >= Op.Input.size()); - Stack StackInput(CurrentStack.end() - Op.Input.size(), CurrentStack.end()); + assert(CurrentStack.size() >= Op.getInput().size()); + Stack StackInput(CurrentStack.end() - Op.getInput().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)); + assert(areLayoutsCompatible(StackInput, Op.getInput())); } void EVMStackifyCodeEmitter::run() { @@ -434,28 +433,29 @@ void EVMStackifyCodeEmitter::run() { CurrentStack = Layout.getMBBEntryLayout(Block); Emitter.enterMBB(Block, CurrentStack.size()); - for (const auto &Operation : StackModel.getOperations(Block)) { - createOperationLayout(Operation); + for (const auto &Op : StackModel.getOperations(Block)) { + createOperationLayout(Op); [[maybe_unused]] size_t BaseHeight = - CurrentStack.size() - Operation.Input.size(); + CurrentStack.size() - Op.getInput().size(); // Perform the Operation. - std::visit(Overload{[this](const FunctionCall &Call) { visitCall(Call); }, - [this](const BuiltinCall &Call) { visitInst(Call); }, - [this](const Assignment &Assignment) { - visitAssign(Assignment); - }}, - Operation.Operation); + if (Op.isFunctionCall()) + processCall(Op); + else if (Op.isBuiltinCall()) + processInst(Op); + else if (Op.isAssignment()) + processAssign(Op); + else + llvm_unreachable("Unexpected operation type."); // Assert that the Operation produced its proclaimed output. assert(CurrentStack.size() == Emitter.stackHeight()); - assert(CurrentStack.size() == BaseHeight + Operation.Output.size()); - assert(CurrentStack.size() >= Operation.Output.size()); + assert(CurrentStack.size() == BaseHeight + Op.getOutput().size()); + assert(CurrentStack.size() >= Op.getOutput().size()); assert(areLayoutsCompatible( - Stack(CurrentStack.end() - Operation.Output.size(), - CurrentStack.end()), - Operation.Output)); + Stack(CurrentStack.end() - Op.getOutput().size(), CurrentStack.end()), + Op.getOutput())); } // Exit the block. diff --git a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h index bfacc58200c3..0b612e9dd5a7 100644 --- a/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h +++ b/llvm/lib/Target/EVM/EVMStackifyCodeEmitter.h @@ -89,11 +89,11 @@ class EVMStackifyCodeEmitter { void adjustStackForInst(const MachineInstr *MI, size_t NumArgs); /// Generate code for the function call \p Call. - void visitCall(const FunctionCall &Call); + void processCall(const Operation &Call); /// Generate code for the builtin call \p Call. - void visitInst(const BuiltinCall &Call); + void processInst(const Operation &Call); /// Generate code for the assignment \p Assignment. - void visitAssign(const Assignment &Assignment); + void processAssign(const Operation &Assignment); }; } // namespace llvm